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-dispatcher.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-contact-dialogs.h"
50 #include "empathy-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
91 } EmpathyIndividualViewPriv;
95 EmpathyIndividualView *view;
102 EmpathyIndividualView *view;
103 FolksIndividual *individual;
112 PROP_INDIVIDUAL_FEATURES,
117 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
118 * specific EmpathyContacts (between/in/out of Individuals) */
121 DND_DRAG_TYPE_INDIVIDUAL_ID,
122 DND_DRAG_TYPE_PERSONA_ID,
123 DND_DRAG_TYPE_URI_LIST,
124 DND_DRAG_TYPE_STRING,
127 #define DRAG_TYPE(T,I) \
128 { (gchar *) T, 0, I }
130 static const GtkTargetEntry drag_types_dest[] = {
131 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
132 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
133 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
135 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
136 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
139 static const GtkTargetEntry drag_types_source[] = {
140 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
145 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
146 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
150 DRAG_INDIVIDUAL_RECEIVED,
151 DRAG_PERSONA_RECEIVED,
155 static guint signals[LAST_SIGNAL];
157 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
161 individual_view_tooltip_destroy_cb (GtkWidget *widget,
162 EmpathyIndividualView *view)
164 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
166 tp_clear_object (&priv->tooltip_widget);
170 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
173 gboolean keyboard_mode,
177 EmpathyIndividualViewPriv *priv;
178 FolksIndividual *individual;
182 static gint running = 0;
183 gboolean ret = FALSE;
185 priv = GET_PRIV (view);
187 /* Avoid an infinite loop. See GNOME bug #574377 */
193 /* Don't show the tooltip if there's already a popup menu */
194 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
197 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
198 keyboard_mode, &model, &path, &iter))
201 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
202 gtk_tree_path_free (path);
204 gtk_tree_model_get (model, &iter,
205 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
207 if (individual == NULL)
210 if (priv->tooltip_widget == NULL)
212 priv->tooltip_widget = empathy_individual_widget_new (individual,
213 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
214 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
215 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
216 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
217 g_object_ref (priv->tooltip_widget);
218 g_signal_connect (priv->tooltip_widget, "destroy",
219 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
220 gtk_widget_show (priv->tooltip_widget);
224 empathy_individual_widget_set_individual (
225 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
228 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
231 g_object_unref (individual);
239 groups_change_group_cb (GObject *source,
240 GAsyncResult *result,
243 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
244 GError *error = NULL;
246 folks_group_details_change_group_finish (group_details, result, &error);
249 g_warning ("failed to change group: %s", error->message);
250 g_clear_error (&error);
255 group_can_be_modified (const gchar *name,
256 gboolean is_fake_group,
259 /* Real groups can always be modified */
263 /* The favorite fake group can be modified so users can
264 * add/remove favorites using DnD */
265 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
268 /* We can remove contacts from the 'ungrouped' fake group */
269 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
276 individual_view_individual_drag_received (GtkWidget *self,
277 GdkDragContext *context,
280 GtkSelectionData *selection)
282 EmpathyIndividualViewPriv *priv;
283 EmpathyIndividualManager *manager = NULL;
284 FolksIndividual *individual;
285 GtkTreePath *source_path;
286 const gchar *sel_data;
287 gchar *new_group = NULL;
288 gchar *old_group = NULL;
289 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
291 priv = GET_PRIV (self);
293 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
294 new_group = empathy_individual_store_get_parent_group (model, path,
295 NULL, &new_group_is_fake);
297 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
300 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
301 * feature. Otherwise, we just add the dropped contact to whichever group
302 * they were dropped in, and don't remove them from their old group. This
303 * allows for Individual views which shouldn't allow Individuals to have
304 * their groups changed, and also for dragging Individuals between Individual
306 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
307 priv->drag_row != NULL)
309 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
313 empathy_individual_store_get_parent_group (model, source_path,
314 NULL, &old_group_is_fake);
315 gtk_tree_path_free (source_path);
318 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
321 if (!tp_strdiff (old_group, new_group))
324 else if (priv->drag_row != NULL)
326 /* We don't allow changing Individuals' groups, and this Individual was
327 * dragged from another group in *this* Individual view, so we disallow
332 /* XXX: for contacts, we used to ensure the account, create the contact
333 * factory, and then wait on the contacts. But they should already be
334 * created by this point */
336 manager = empathy_individual_manager_dup_singleton ();
337 individual = empathy_individual_manager_lookup_member (manager, sel_data);
339 if (individual == NULL)
341 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
345 /* FIXME: We should probably wait for the cb before calling
348 /* Emit a signal notifying of the drag. We change the Individual's groups in
349 * the default signal handler. */
350 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
351 gdk_drag_context_get_selected_action (context), individual, new_group,
357 tp_clear_object (&manager);
365 real_drag_individual_received_cb (EmpathyIndividualView *self,
366 GdkDragAction action,
367 FolksIndividual *individual,
368 const gchar *new_group,
369 const gchar *old_group)
371 DEBUG ("individual %s dragged from '%s' to '%s'",
372 folks_individual_get_id (individual), old_group, new_group);
374 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
376 /* Mark contact as favourite */
377 folks_favourite_details_set_is_favourite (
378 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
382 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
384 /* Remove contact as favourite */
385 folks_favourite_details_set_is_favourite (
386 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
388 /* Don't try to remove it */
392 if (new_group != NULL)
394 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
395 new_group, TRUE, groups_change_group_cb, NULL);
398 if (old_group != NULL && action == GDK_ACTION_MOVE)
400 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
401 old_group, FALSE, groups_change_group_cb, NULL);
406 individual_view_persona_drag_received (GtkWidget *self,
407 GdkDragContext *context,
410 GtkSelectionData *selection)
412 EmpathyIndividualViewPriv *priv;
413 EmpathyIndividualManager *manager = NULL;
414 FolksIndividual *individual = NULL;
415 FolksPersona *persona = NULL;
416 const gchar *persona_uid;
417 GList *individuals, *l;
418 gboolean retval = FALSE;
420 priv = GET_PRIV (self);
422 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
424 /* FIXME: This is slow, but the only way to find the Persona we're having
426 manager = empathy_individual_manager_dup_singleton ();
427 individuals = empathy_individual_manager_get_members (manager);
429 for (l = individuals; l != NULL; l = l->next)
433 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435 for (p = personas; p != NULL; p = p->next)
437 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
440 persona = g_object_ref (p->data);
441 individual = g_object_ref (l->data);
448 g_list_free (individuals);
450 if (persona == NULL || individual == NULL)
452 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
456 /* Emit a signal notifying of the drag. We change the Individual's groups in
457 * the default signal handler. */
458 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
459 gdk_drag_context_get_selected_action (context), persona, individual,
463 tp_clear_object (&manager);
464 tp_clear_object (&persona);
465 tp_clear_object (&individual);
471 individual_view_file_drag_received (GtkWidget *view,
472 GdkDragContext *context,
475 GtkSelectionData *selection)
478 const gchar *sel_data;
479 FolksIndividual *individual;
480 EmpathyContact *contact;
482 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
484 gtk_tree_model_get_iter (model, &iter, path);
485 gtk_tree_model_get (model, &iter,
486 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
487 if (individual == NULL)
490 contact = empathy_contact_dup_from_folks_individual (individual);
491 empathy_send_file_from_uri_list (contact, sel_data);
493 g_object_unref (individual);
494 tp_clear_object (&contact);
500 individual_view_drag_data_received (GtkWidget *view,
501 GdkDragContext *context,
504 GtkSelectionData *selection,
510 GtkTreeViewDropPosition position;
512 gboolean success = TRUE;
514 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
516 /* Get destination group information. */
517 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
518 x, y, &path, &position);
523 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
525 success = individual_view_individual_drag_received (view,
526 context, model, path, selection);
528 else if (info == DND_DRAG_TYPE_PERSONA_ID)
530 success = individual_view_persona_drag_received (view, context, model,
533 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
535 success = individual_view_file_drag_received (view,
536 context, model, path, selection);
539 gtk_tree_path_free (path);
540 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
544 individual_view_drag_motion_cb (DragMotionData *data)
546 if (data->view != NULL)
548 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
549 g_object_remove_weak_pointer (G_OBJECT (data->view),
550 (gpointer *) &data->view);
553 data->timeout_id = 0;
558 /* Minimum distance between the mouse pointer and a horizontal border when we
559 start auto scrolling. */
560 #define AUTO_SCROLL_MARGIN_SIZE 20
561 /* How far to scroll per one tick. */
562 #define AUTO_SCROLL_PITCH 10
565 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
567 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
571 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
573 if (priv->distance < 0)
574 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
576 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
578 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
579 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
581 gtk_adjustment_set_value (adj, new_value);
587 individual_view_drag_motion (GtkWidget *widget,
588 GdkDragContext *context,
593 EmpathyIndividualViewPriv *priv;
597 static DragMotionData *dm = NULL;
600 gboolean is_different = FALSE;
601 gboolean cleanup = TRUE;
602 gboolean retval = TRUE;
603 GtkAllocation allocation;
605 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
606 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
609 if (priv->auto_scroll_timeout_id != 0)
611 g_source_remove (priv->auto_scroll_timeout_id);
612 priv->auto_scroll_timeout_id = 0;
615 gtk_widget_get_allocation (widget, &allocation);
617 if (y < AUTO_SCROLL_MARGIN_SIZE ||
618 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
620 if (y < AUTO_SCROLL_MARGIN_SIZE)
621 priv->distance = MIN (-y, -1);
623 priv->distance = MAX (allocation.height - y, 1);
625 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
626 (GSourceFunc) individual_view_auto_scroll_cb, widget);
629 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
630 x, y, &path, NULL, NULL, NULL);
632 cleanup &= (dm == NULL);
636 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
637 is_different = ((dm == NULL) || ((dm != NULL)
638 && gtk_tree_path_compare (dm->path, path) != 0));
645 /* Coordinates don't point to an actual row, so make sure the pointer
646 and highlighting don't indicate that a drag is possible.
648 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
649 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
652 target = gtk_drag_dest_find_target (widget, context, NULL);
653 gtk_tree_model_get_iter (model, &iter, path);
655 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
656 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
658 /* This is a file drag, and it can only be dropped on contacts,
660 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
661 * even if we have a valid target. */
662 FolksIndividual *individual = NULL;
663 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
665 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
667 gtk_tree_model_get (model, &iter,
668 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
672 if (individual != NULL)
674 EmpathyContact *contact = NULL;
676 contact = empathy_contact_dup_from_folks_individual (individual);
677 caps = empathy_contact_get_capabilities (contact);
679 tp_clear_object (&contact);
682 if (individual != NULL &&
683 folks_presence_owner_is_online (FOLKS_PRESENCE_OWNER (individual)) &&
684 (caps & EMPATHY_CAPABILITIES_FT))
686 gdk_drag_status (context, GDK_ACTION_COPY, time_);
687 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
688 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
692 gdk_drag_status (context, 0, time_);
693 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
697 if (individual != NULL)
698 g_object_unref (individual);
700 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
701 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
702 priv->drag_row == NULL)) ||
703 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
704 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
706 /* If target != GDK_NONE, then we have a contact (individual or persona)
707 drag. If we're pointing to a group, highlight it. Otherwise, if the
708 contact we're pointing to is in a group, highlight that. Otherwise,
709 set the drag position to before the first row for a drag into
710 the "non-group" at the top.
711 If it's an Individual:
712 We only highlight things if the contact is from a different
713 Individual view, or if this Individual view has
714 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
715 which don't have FEATURE_GROUPS_CHANGE, but do have
716 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
718 We only highlight things if we have FEATURE_PERSONA_DROP.
720 GtkTreeIter group_iter;
722 GtkTreePath *group_path;
723 gtk_tree_model_get (model, &iter,
724 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
731 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
732 gtk_tree_model_get (model, &group_iter,
733 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
737 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
738 group_path = gtk_tree_model_get_path (model, &group_iter);
739 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
740 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
741 gtk_tree_path_free (group_path);
745 group_path = gtk_tree_path_new_first ();
746 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
747 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
748 group_path, GTK_TREE_VIEW_DROP_BEFORE);
752 if (!is_different && !cleanup)
757 gtk_tree_path_free (dm->path);
760 g_source_remove (dm->timeout_id);
768 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
770 dm = g_new0 (DragMotionData, 1);
772 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
773 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
774 dm->path = gtk_tree_path_copy (path);
776 dm->timeout_id = g_timeout_add_seconds (1,
777 (GSourceFunc) individual_view_drag_motion_cb, dm);
784 individual_view_drag_begin (GtkWidget *widget,
785 GdkDragContext *context)
787 EmpathyIndividualViewPriv *priv;
788 GtkTreeSelection *selection;
793 priv = GET_PRIV (widget);
795 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
798 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
799 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
802 path = gtk_tree_model_get_path (model, &iter);
803 priv->drag_row = gtk_tree_row_reference_new (model, path);
804 gtk_tree_path_free (path);
808 individual_view_drag_data_get (GtkWidget *widget,
809 GdkDragContext *context,
810 GtkSelectionData *selection,
814 EmpathyIndividualViewPriv *priv;
815 GtkTreePath *src_path;
818 FolksIndividual *individual;
819 const gchar *individual_id;
821 priv = GET_PRIV (widget);
823 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
824 if (priv->drag_row == NULL)
827 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
828 if (src_path == NULL)
831 if (!gtk_tree_model_get_iter (model, &iter, src_path))
833 gtk_tree_path_free (src_path);
837 gtk_tree_path_free (src_path);
840 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
841 if (individual == NULL)
844 individual_id = folks_individual_get_id (individual);
846 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
848 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
849 (guchar *) individual_id, strlen (individual_id) + 1);
852 g_object_unref (individual);
856 individual_view_drag_end (GtkWidget *widget,
857 GdkDragContext *context)
859 EmpathyIndividualViewPriv *priv;
861 priv = GET_PRIV (widget);
863 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
868 gtk_tree_row_reference_free (priv->drag_row);
869 priv->drag_row = NULL;
874 individual_view_drag_drop (GtkWidget *widget,
875 GdkDragContext *drag_context,
885 EmpathyIndividualView *view;
891 menu_deactivate_cb (GtkMenuShell *menushell,
894 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
895 g_signal_handlers_disconnect_by_func (menushell,
896 menu_deactivate_cb, user_data);
898 gtk_menu_detach (GTK_MENU (menushell));
902 individual_view_popup_menu_idle_cb (gpointer user_data)
904 MenuPopupData *data = user_data;
907 menu = empathy_individual_view_get_individual_menu (data->view);
909 menu = empathy_individual_view_get_group_menu (data->view);
913 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
915 gtk_widget_show (menu);
916 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
919 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
920 * floating ref. We can either wait that the treeview releases its ref
921 * when it will be destroyed (when leaving Empathy) or explicitely
922 * detach the menu when it's not displayed any more.
923 * We go for the latter as we don't want to keep useless menus in memory
924 * during the whole lifetime of Empathy. */
925 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
929 g_slice_free (MenuPopupData, data);
935 individual_view_button_press_event_cb (EmpathyIndividualView *view,
936 GdkEventButton *event,
939 if (event->button == 3)
943 data = g_slice_new (MenuPopupData);
945 data->button = event->button;
946 data->time = event->time;
947 g_idle_add (individual_view_popup_menu_idle_cb, data);
954 individual_view_key_press_event_cb (EmpathyIndividualView *view,
958 if (event->keyval == GDK_KEY_Menu)
962 data = g_slice_new (MenuPopupData);
965 data->time = event->time;
966 g_idle_add (individual_view_popup_menu_idle_cb, data);
967 } else if (event->keyval == GDK_KEY_F2) {
968 FolksIndividual *individual;
969 EmpathyContact *contact;
971 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
973 individual = empathy_individual_view_dup_selected (view);
974 if (individual == NULL)
977 contact = empathy_contact_dup_from_folks_individual (individual);
978 if (contact == NULL) {
979 g_object_unref (individual);
982 empathy_contact_edit_dialog_show (contact, NULL);
984 g_object_unref (individual);
985 g_object_unref (contact);
992 individual_view_row_activated (GtkTreeView *view,
994 GtkTreeViewColumn *column)
996 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
997 FolksIndividual *individual;
998 EmpathyContact *contact;
1002 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1005 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1006 gtk_tree_model_get_iter (model, &iter, path);
1007 gtk_tree_model_get (model, &iter,
1008 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1010 if (individual == NULL)
1013 /* Determine which Persona to chat to, by choosing the most available one. */
1014 contact = empathy_contact_dup_best_for_action (individual,
1015 EMPATHY_ACTION_CHAT);
1017 if (contact != NULL)
1019 DEBUG ("Starting a chat");
1021 empathy_dispatcher_chat_with_contact (contact,
1022 gtk_get_current_event_time ());
1025 g_object_unref (individual);
1026 tp_clear_object (&contact);
1030 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1031 const gchar *path_string,
1032 EmpathyIndividualView *view)
1034 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1036 GtkTreeModel *model;
1038 FolksIndividual *individual;
1039 GdkEventButton *event;
1040 GtkMenuShell *shell;
1043 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1046 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1047 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1050 gtk_tree_model_get (model, &iter,
1051 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1052 if (individual == NULL)
1055 event = (GdkEventButton *) gtk_get_current_event ();
1057 menu = empathy_context_menu_new (GTK_WIDGET (view));
1058 shell = GTK_MENU_SHELL (menu);
1061 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1062 gtk_menu_shell_append (shell, item);
1063 gtk_widget_show (item);
1066 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1067 gtk_menu_shell_append (shell, item);
1068 gtk_widget_show (item);
1070 gtk_widget_show (menu);
1071 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1072 event->button, event->time);
1074 g_object_unref (individual);
1078 individual_view_cell_set_background (EmpathyIndividualView *view,
1079 GtkCellRenderer *cell,
1083 if (!is_group && is_active)
1085 GtkStyleContext *style;
1088 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1090 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1093 /* Here we take the current theme colour and add it to
1094 * the colour for white and average the two. This
1095 * gives a colour which is inline with the theme but
1098 empathy_make_color_whiter (&color);
1100 g_object_set (cell, "cell-background-rgba", &color, NULL);
1103 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1107 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1108 GtkCellRenderer *cell,
1109 GtkTreeModel *model,
1111 EmpathyIndividualView *view)
1117 gtk_tree_model_get (model, iter,
1118 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1119 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1120 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1123 "visible", !is_group,
1127 tp_clear_object (&pixbuf);
1129 individual_view_cell_set_background (view, cell, is_group, is_active);
1133 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1134 GtkCellRenderer *cell,
1135 GtkTreeModel *model,
1137 EmpathyIndividualView *view)
1139 GdkPixbuf *pixbuf = NULL;
1143 gtk_tree_model_get (model, iter,
1144 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1145 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1150 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1152 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1153 GTK_ICON_SIZE_MENU);
1155 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1157 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1158 GTK_ICON_SIZE_MENU);
1163 "visible", pixbuf != NULL,
1167 tp_clear_object (&pixbuf);
1173 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1174 GtkCellRenderer *cell,
1175 GtkTreeModel *model,
1177 EmpathyIndividualView *view)
1181 gboolean can_audio, can_video;
1183 gtk_tree_model_get (model, iter,
1184 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1185 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1186 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1187 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1190 "visible", !is_group && (can_audio || can_video),
1191 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1194 individual_view_cell_set_background (view, cell, is_group, is_active);
1198 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1199 GtkCellRenderer *cell,
1200 GtkTreeModel *model,
1202 EmpathyIndividualView *view)
1205 gboolean show_avatar;
1209 gtk_tree_model_get (model, iter,
1210 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1211 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1212 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1213 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1216 "visible", !is_group && show_avatar,
1220 tp_clear_object (&pixbuf);
1222 individual_view_cell_set_background (view, cell, is_group, is_active);
1226 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1227 GtkCellRenderer *cell,
1228 GtkTreeModel *model,
1230 EmpathyIndividualView *view)
1235 gtk_tree_model_get (model, iter,
1236 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1237 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1239 individual_view_cell_set_background (view, cell, is_group, is_active);
1243 individual_view_expander_cell_data_func (GtkTreeViewColumn *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 if (gtk_tree_model_iter_has_child (model, iter))
1259 gboolean row_expanded;
1261 path = gtk_tree_model_get_path (model, iter);
1263 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1264 (gtk_tree_view_column_get_tree_view (column)), path);
1265 gtk_tree_path_free (path);
1270 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1274 g_object_set (cell, "visible", FALSE, NULL);
1276 individual_view_cell_set_background (view, cell, is_group, is_active);
1280 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1285 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1286 GtkTreeModel *model;
1290 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1293 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1295 gtk_tree_model_get (model, iter,
1296 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1298 expanded = GPOINTER_TO_INT (user_data);
1299 empathy_contact_group_set_expanded (name, expanded);
1305 individual_view_start_search_cb (EmpathyIndividualView *view,
1308 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1310 if (priv->search_widget == NULL)
1313 empathy_individual_view_start_search (view);
1319 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1321 EmpathyIndividualView *view)
1323 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1325 GtkTreeViewColumn *focus_column;
1326 GtkTreeModel *model;
1328 gboolean set_cursor = FALSE;
1330 gtk_tree_model_filter_refilter (priv->filter);
1332 /* Set cursor on the first contact. If it is already set on a group,
1333 * set it on its first child contact. Note that first child of a group
1334 * is its separator, that's why we actually set to the 2nd
1337 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1338 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1342 path = gtk_tree_path_new_from_string ("0:1");
1345 else if (gtk_tree_path_get_depth (path) < 2)
1349 gtk_tree_model_get_iter (model, &iter, path);
1350 gtk_tree_model_get (model, &iter,
1351 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1356 gtk_tree_path_down (path);
1357 gtk_tree_path_next (path);
1364 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1366 if (gtk_tree_model_get_iter (model, &iter, path))
1368 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1373 gtk_tree_path_free (path);
1377 individual_view_search_activate_cb (GtkWidget *search,
1378 EmpathyIndividualView *view)
1381 GtkTreeViewColumn *focus_column;
1383 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1386 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1387 gtk_tree_path_free (path);
1389 gtk_widget_hide (search);
1394 individual_view_search_key_navigation_cb (GtkWidget *search,
1396 EmpathyIndividualView *view)
1398 GdkEventKey *eventkey = ((GdkEventKey *) event);
1399 gboolean ret = FALSE;
1401 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1402 || eventkey->keyval == GDK_KEY_F2)
1404 GdkEvent *new_event;
1406 new_event = gdk_event_copy (event);
1407 gtk_widget_grab_focus (GTK_WIDGET (view));
1408 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1409 gtk_widget_grab_focus (search);
1411 gdk_event_free (new_event);
1418 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1419 EmpathyIndividualView *view)
1421 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1422 GtkTreeModel *model;
1423 GtkTreePath *cursor_path;
1425 gboolean valid = FALSE;
1427 /* block expand or collapse handlers, they would write the
1428 * expand or collapsed setting to file otherwise */
1429 g_signal_handlers_block_by_func (view,
1430 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1431 g_signal_handlers_block_by_func (view,
1432 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1434 /* restore which groups are expanded and which are not */
1435 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1436 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1437 valid; valid = gtk_tree_model_iter_next (model, &iter))
1443 gtk_tree_model_get (model, &iter,
1444 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1445 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1454 path = gtk_tree_model_get_path (model, &iter);
1455 if ((priv->view_features &
1456 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1457 empathy_contact_group_get_expanded (name))
1459 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1463 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1466 gtk_tree_path_free (path);
1470 /* unblock expand or collapse handlers */
1471 g_signal_handlers_unblock_by_func (view,
1472 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1473 g_signal_handlers_unblock_by_func (view,
1474 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1476 /* keep the selected contact visible */
1477 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1479 if (cursor_path != NULL)
1480 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1483 gtk_tree_path_free (cursor_path);
1487 individual_view_search_show_cb (EmpathyLiveSearch *search,
1488 EmpathyIndividualView *view)
1490 /* block expand or collapse handlers during expand all, they would
1491 * write the expand or collapsed setting to file otherwise */
1492 g_signal_handlers_block_by_func (view,
1493 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1495 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1497 g_signal_handlers_unblock_by_func (view,
1498 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1502 expand_idle_foreach_cb (GtkTreeModel *model,
1505 EmpathyIndividualView *self)
1507 EmpathyIndividualViewPriv *priv;
1509 gpointer should_expand;
1512 /* We only want groups */
1513 if (gtk_tree_path_get_depth (path) > 1)
1516 gtk_tree_model_get (model, iter,
1517 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1518 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1521 if (is_group == FALSE)
1527 priv = GET_PRIV (self);
1529 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1530 &should_expand) == TRUE)
1532 if (GPOINTER_TO_INT (should_expand) == TRUE)
1533 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1535 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1537 g_hash_table_remove (priv->expand_groups, name);
1546 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1548 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1550 DEBUG ("individual_view_expand_idle_cb");
1552 g_signal_handlers_block_by_func (self,
1553 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1554 g_signal_handlers_block_by_func (self,
1555 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1557 /* The store/filter could've been removed while we were in the idle queue */
1558 if (priv->filter != NULL)
1560 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1561 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1564 g_signal_handlers_unblock_by_func (self,
1565 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1566 g_signal_handlers_unblock_by_func (self,
1567 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1569 /* Empty the table of groups to expand/contract, since it may contain groups
1570 * which no longer exist in the tree view. This can happen after going
1571 * offline, for example. */
1572 g_hash_table_remove_all (priv->expand_groups);
1573 priv->expand_groups_idle_handler = 0;
1574 g_object_unref (self);
1580 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1583 EmpathyIndividualView *view)
1585 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1586 gboolean should_expand, is_group = FALSE;
1588 gpointer will_expand;
1590 gtk_tree_model_get (model, iter,
1591 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1592 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1595 if (!is_group || EMP_STR_EMPTY (name))
1601 should_expand = (priv->view_features &
1602 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1603 (priv->search_widget != NULL &&
1604 gtk_widget_get_visible (priv->search_widget)) ||
1605 empathy_contact_group_get_expanded (name);
1607 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1608 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1609 * a hash table, and expand or contract them as appropriate all at once in
1610 * an idle handler which iterates over all the group rows. */
1611 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1612 &will_expand) == FALSE ||
1613 GPOINTER_TO_INT (will_expand) != should_expand)
1615 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1616 GINT_TO_POINTER (should_expand));
1618 if (priv->expand_groups_idle_handler == 0)
1620 priv->expand_groups_idle_handler =
1621 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1622 g_object_ref (view));
1629 /* FIXME: This is a workaround for bgo#621076 */
1631 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1634 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1635 GtkTreeModel *model;
1636 GtkTreePath *parent_path;
1637 GtkTreeIter parent_iter;
1639 if (gtk_tree_path_get_depth (path) < 2)
1642 /* A group row is visible if and only if at least one if its child is visible.
1643 * So when a row is inserted/deleted/changed in the base model, that could
1644 * modify the visibility of its parent in the filter model.
1647 model = GTK_TREE_MODEL (priv->store);
1648 parent_path = gtk_tree_path_copy (path);
1649 gtk_tree_path_up (parent_path);
1650 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1652 /* This tells the filter to verify the visibility of that row, and
1653 * show/hide it if necessary */
1654 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1655 parent_path, &parent_iter);
1657 gtk_tree_path_free (parent_path);
1661 individual_view_store_row_changed_cb (GtkTreeModel *model,
1664 EmpathyIndividualView *view)
1666 individual_view_verify_group_visibility (view, path);
1670 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1672 EmpathyIndividualView *view)
1674 individual_view_verify_group_visibility (view, path);
1678 individual_view_is_visible_individual (EmpathyIndividualView *self,
1679 FolksIndividual *individual,
1681 gboolean is_searching)
1683 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1684 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1686 GList *personas, *l;
1687 gboolean is_favorite, contains_interesting_persona = FALSE;
1689 /* We're only giving the visibility wrt filtering here, not things like
1691 if (priv->show_untrusted == FALSE &&
1692 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1697 /* Hide all individuals which consist entirely of uninteresting personas */
1698 personas = folks_individual_get_personas (individual);
1699 for (l = personas; l; l = l->next)
1701 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1703 contains_interesting_persona = TRUE;
1708 if (contains_interesting_persona == FALSE)
1711 is_favorite = folks_favourite_details_get_is_favourite (
1712 FOLKS_FAVOURITE_DETAILS (individual));
1713 if (is_searching == FALSE)
1714 return (priv->show_offline || is_online || is_favorite);
1716 /* check alias name */
1717 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1719 if (empathy_live_search_match (live, str))
1722 /* check contact id, remove the @server.com part */
1723 for (l = personas; l; l = l->next)
1726 gchar *dup_str = NULL;
1729 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1732 str = folks_persona_get_display_id (l->data);
1733 p = strstr (str, "@");
1735 str = dup_str = g_strndup (str, p - str);
1737 visible = empathy_live_search_match (live, str);
1743 /* FIXME: Add more rules here, we could check phone numbers in
1744 * contact's vCard for example. */
1750 individual_view_filter_visible_func (GtkTreeModel *model,
1754 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1755 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1756 FolksIndividual *individual = NULL;
1757 gboolean is_group, is_separator, valid;
1758 GtkTreeIter child_iter;
1759 gboolean visible, is_online;
1760 gboolean is_searching = TRUE;
1762 if (priv->search_widget == NULL ||
1763 !gtk_widget_get_visible (priv->search_widget))
1764 is_searching = FALSE;
1766 gtk_tree_model_get (model, iter,
1767 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1768 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1769 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1770 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1773 if (individual != NULL)
1775 visible = individual_view_is_visible_individual (self, individual,
1776 is_online, is_searching);
1778 g_object_unref (individual);
1780 /* FIXME: Work around bgo#626552/bgo#621076 */
1781 if (visible == TRUE)
1783 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1784 individual_view_verify_group_visibility (self, path);
1785 gtk_tree_path_free (path);
1794 /* Not a contact, not a separator, must be a group */
1795 g_return_val_if_fail (is_group, FALSE);
1797 /* only show groups which are not empty */
1798 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1799 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1801 gtk_tree_model_get (model, &child_iter,
1802 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1803 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1806 if (individual == NULL)
1809 visible = individual_view_is_visible_individual (self, individual,
1810 is_online, is_searching);
1811 g_object_unref (individual);
1813 /* show group if it has at least one visible contact in it */
1814 if (visible == TRUE)
1822 individual_view_constructed (GObject *object)
1824 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1825 GtkCellRenderer *cell;
1826 GtkTreeViewColumn *col;
1831 "headers-visible", FALSE,
1832 "show-expanders", FALSE,
1835 col = gtk_tree_view_column_new ();
1838 cell = gtk_cell_renderer_pixbuf_new ();
1839 gtk_tree_view_column_pack_start (col, cell, FALSE);
1840 gtk_tree_view_column_set_cell_data_func (col, cell,
1841 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1851 cell = gtk_cell_renderer_pixbuf_new ();
1852 gtk_tree_view_column_pack_start (col, cell, FALSE);
1853 gtk_tree_view_column_set_cell_data_func (col, cell,
1854 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1866 cell = empathy_cell_renderer_text_new ();
1867 gtk_tree_view_column_pack_start (col, cell, TRUE);
1868 gtk_tree_view_column_set_cell_data_func (col, cell,
1869 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1871 gtk_tree_view_column_add_attribute (col, cell,
1872 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1873 gtk_tree_view_column_add_attribute (col, cell,
1874 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1875 gtk_tree_view_column_add_attribute (col, cell,
1876 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1877 gtk_tree_view_column_add_attribute (col, cell,
1878 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1879 gtk_tree_view_column_add_attribute (col, cell,
1880 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1881 gtk_tree_view_column_add_attribute (col, cell,
1882 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1883 gtk_tree_view_column_add_attribute (col, cell,
1884 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1886 /* Audio Call Icon */
1887 cell = empathy_cell_renderer_activatable_new ();
1888 gtk_tree_view_column_pack_start (col, cell, FALSE);
1889 gtk_tree_view_column_set_cell_data_func (col, cell,
1890 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1893 g_object_set (cell, "visible", FALSE, NULL);
1895 g_signal_connect (cell, "path-activated",
1896 G_CALLBACK (individual_view_call_activated_cb), view);
1899 cell = gtk_cell_renderer_pixbuf_new ();
1900 gtk_tree_view_column_pack_start (col, cell, FALSE);
1901 gtk_tree_view_column_set_cell_data_func (col, cell,
1902 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1914 cell = empathy_cell_renderer_expander_new ();
1915 gtk_tree_view_column_pack_end (col, cell, FALSE);
1916 gtk_tree_view_column_set_cell_data_func (col, cell,
1917 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1920 /* Actually add the column now we have added all cell renderers */
1921 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1924 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1926 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1929 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1931 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1937 individual_view_set_view_features (EmpathyIndividualView *view,
1938 EmpathyIndividualFeatureFlags features)
1940 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1941 gboolean has_tooltip;
1943 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1945 priv->view_features = features;
1947 /* Setting reorderable is a hack that gets us row previews as drag icons
1948 for free. We override all the drag handlers. It's tricky to get the
1949 position of the drag icon right in drag_begin. GtkTreeView has special
1950 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1953 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1954 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1956 /* Update DnD source/dest */
1957 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1959 gtk_drag_source_set (GTK_WIDGET (view),
1962 G_N_ELEMENTS (drag_types_source),
1963 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1967 gtk_drag_source_unset (GTK_WIDGET (view));
1971 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1973 gtk_drag_dest_set (GTK_WIDGET (view),
1974 GTK_DEST_DEFAULT_ALL,
1976 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1980 /* FIXME: URI could still be droped depending on FT feature */
1981 gtk_drag_dest_unset (GTK_WIDGET (view));
1984 /* Update has-tooltip */
1986 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1987 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1991 individual_view_dispose (GObject *object)
1993 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1994 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1996 tp_clear_object (&priv->store);
1997 tp_clear_object (&priv->filter);
1998 tp_clear_object (&priv->tooltip_widget);
2000 empathy_individual_view_set_live_search (view, NULL);
2002 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2006 individual_view_finalize (GObject *object)
2008 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2010 if (priv->expand_groups_idle_handler != 0)
2011 g_source_remove (priv->expand_groups_idle_handler);
2012 g_hash_table_destroy (priv->expand_groups);
2014 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2018 individual_view_get_property (GObject *object,
2023 EmpathyIndividualViewPriv *priv;
2025 priv = GET_PRIV (object);
2030 g_value_set_object (value, priv->store);
2032 case PROP_VIEW_FEATURES:
2033 g_value_set_flags (value, priv->view_features);
2035 case PROP_INDIVIDUAL_FEATURES:
2036 g_value_set_flags (value, priv->individual_features);
2038 case PROP_SHOW_OFFLINE:
2039 g_value_set_boolean (value, priv->show_offline);
2041 case PROP_SHOW_UNTRUSTED:
2042 g_value_set_boolean (value, priv->show_untrusted);
2045 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2051 individual_view_set_property (GObject *object,
2053 const GValue *value,
2056 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2057 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2062 empathy_individual_view_set_store (view, g_value_get_object (value));
2064 case PROP_VIEW_FEATURES:
2065 individual_view_set_view_features (view, g_value_get_flags (value));
2067 case PROP_INDIVIDUAL_FEATURES:
2068 priv->individual_features = g_value_get_flags (value);
2070 case PROP_SHOW_OFFLINE:
2071 empathy_individual_view_set_show_offline (view,
2072 g_value_get_boolean (value));
2074 case PROP_SHOW_UNTRUSTED:
2075 empathy_individual_view_set_show_untrusted (view,
2076 g_value_get_boolean (value));
2079 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2085 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2087 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2088 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2089 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2091 object_class->constructed = individual_view_constructed;
2092 object_class->dispose = individual_view_dispose;
2093 object_class->finalize = individual_view_finalize;
2094 object_class->get_property = individual_view_get_property;
2095 object_class->set_property = individual_view_set_property;
2097 widget_class->drag_data_received = individual_view_drag_data_received;
2098 widget_class->drag_drop = individual_view_drag_drop;
2099 widget_class->drag_begin = individual_view_drag_begin;
2100 widget_class->drag_data_get = individual_view_drag_data_get;
2101 widget_class->drag_end = individual_view_drag_end;
2102 widget_class->drag_motion = individual_view_drag_motion;
2104 /* We use the class method to let user of this widget to connect to
2105 * the signal and stop emission of the signal so the default handler
2106 * won't be called. */
2107 tree_view_class->row_activated = individual_view_row_activated;
2109 klass->drag_individual_received = real_drag_individual_received_cb;
2111 signals[DRAG_INDIVIDUAL_RECEIVED] =
2112 g_signal_new ("drag-individual-received",
2113 G_OBJECT_CLASS_TYPE (klass),
2115 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2117 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2118 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2119 G_TYPE_STRING, G_TYPE_STRING);
2121 signals[DRAG_PERSONA_RECEIVED] =
2122 g_signal_new ("drag-persona-received",
2123 G_OBJECT_CLASS_TYPE (klass),
2125 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2127 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2128 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2130 g_object_class_install_property (object_class,
2132 g_param_spec_object ("store",
2133 "The store of the view",
2134 "The store of the view",
2135 EMPATHY_TYPE_INDIVIDUAL_STORE,
2136 G_PARAM_READWRITE));
2137 g_object_class_install_property (object_class,
2139 g_param_spec_flags ("view-features",
2140 "Features of the view",
2141 "Flags for all enabled features",
2142 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2143 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2144 g_object_class_install_property (object_class,
2145 PROP_INDIVIDUAL_FEATURES,
2146 g_param_spec_flags ("individual-features",
2147 "Features of the individual menu",
2148 "Flags for all enabled features for the menu",
2149 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2150 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2151 g_object_class_install_property (object_class,
2153 g_param_spec_boolean ("show-offline",
2155 "Whether contact list should display "
2156 "offline contacts", FALSE, G_PARAM_READWRITE));
2157 g_object_class_install_property (object_class,
2158 PROP_SHOW_UNTRUSTED,
2159 g_param_spec_boolean ("show-untrusted",
2160 "Show Untrusted Individuals",
2161 "Whether the view should display untrusted individuals; "
2162 "those who could not be who they say they are.",
2163 TRUE, G_PARAM_READWRITE));
2165 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2169 empathy_individual_view_init (EmpathyIndividualView *view)
2171 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2172 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2176 priv->show_untrusted = TRUE;
2178 /* Get saved group states. */
2179 empathy_contact_groups_get_all ();
2181 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2182 (GDestroyNotify) g_free, NULL);
2184 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2185 empathy_individual_store_row_separator_func, NULL, NULL);
2187 /* Connect to tree view signals rather than override. */
2188 g_signal_connect (view, "button-press-event",
2189 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2190 g_signal_connect (view, "key-press-event",
2191 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2192 g_signal_connect (view, "row-expanded",
2193 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2194 GINT_TO_POINTER (TRUE));
2195 g_signal_connect (view, "row-collapsed",
2196 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2197 GINT_TO_POINTER (FALSE));
2198 g_signal_connect (view, "query-tooltip",
2199 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2202 EmpathyIndividualView *
2203 empathy_individual_view_new (EmpathyIndividualStore *store,
2204 EmpathyIndividualViewFeatureFlags view_features,
2205 EmpathyIndividualFeatureFlags individual_features)
2207 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2209 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2211 "individual-features", individual_features,
2212 "view-features", view_features, NULL);
2216 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2218 EmpathyIndividualViewPriv *priv;
2219 GtkTreeSelection *selection;
2221 GtkTreeModel *model;
2222 FolksIndividual *individual;
2224 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2226 priv = GET_PRIV (view);
2228 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2229 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2232 gtk_tree_model_get (model, &iter,
2233 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2239 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2240 gboolean *is_fake_group)
2242 EmpathyIndividualViewPriv *priv;
2243 GtkTreeSelection *selection;
2245 GtkTreeModel *model;
2250 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2252 priv = GET_PRIV (view);
2254 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2255 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2258 gtk_tree_model_get (model, &iter,
2259 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2260 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2261 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2269 if (is_fake_group != NULL)
2270 *is_fake_group = fake;
2276 individual_view_remove_dialog_show (GtkWindow *parent,
2277 const gchar *message,
2278 const gchar *secondary_text,
2279 gboolean block_button)
2284 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2285 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2291 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2292 * mnemonic so we have to create the button manually. */
2293 button = gtk_button_new_with_mnemonic (
2294 _("Delete and _Block"));
2296 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2297 GTK_RESPONSE_REJECT);
2299 gtk_widget_show (button);
2302 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2303 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2304 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2305 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2306 "%s", secondary_text);
2308 gtk_widget_show (dialog);
2310 res = gtk_dialog_run (GTK_DIALOG (dialog));
2311 gtk_widget_destroy (dialog);
2317 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2318 EmpathyIndividualView *view)
2322 group = empathy_individual_view_dup_selected_group (view, NULL);
2329 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2331 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2332 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2333 text, FALSE) == GTK_RESPONSE_YES)
2335 EmpathyIndividualManager *manager =
2336 empathy_individual_manager_dup_singleton ();
2337 empathy_individual_manager_remove_group (manager, group);
2338 g_object_unref (G_OBJECT (manager));
2348 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2350 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2355 gboolean is_fake_group;
2357 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2359 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2360 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2363 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2364 if (!group || is_fake_group)
2366 /* We can't alter fake groups */
2371 menu = gtk_menu_new ();
2374 if (priv->view_features &
2375 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2376 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2377 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2378 gtk_widget_show (item);
2379 g_signal_connect (item, "activate",
2380 G_CALLBACK (individual_view_group_rename_activate_cb),
2385 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2387 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2388 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2389 GTK_ICON_SIZE_MENU);
2390 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2391 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2392 gtk_widget_show (item);
2393 g_signal_connect (item, "activate",
2394 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2403 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2404 EmpathyIndividualView *view)
2406 FolksIndividual *individual;
2408 individual = empathy_individual_view_dup_selected (view);
2410 if (individual != NULL)
2412 EmpathyIndividualManager *manager;
2415 GList *l, *personas;
2416 guint persona_count = 0;
2420 personas = folks_individual_get_personas (individual);
2422 /* If we have more than one TpfPersona, display a different message
2423 * ensuring the user knows that *all* of the meta-contacts' personas will
2425 for (l = personas; l != NULL; l = l->next)
2427 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2431 if (persona_count >= 2)
2435 if (persona_count < 2)
2437 /* Not a meta-contact */
2440 _("Do you really want to remove the contact '%s'?"),
2441 folks_alias_details_get_alias (
2442 FOLKS_ALIAS_DETAILS (individual)));
2449 _("Do you really want to remove the linked contact '%s'? "
2450 "Note that this will remove all the contacts which make up "
2451 "this linked contact."),
2452 folks_alias_details_get_alias (
2453 FOLKS_ALIAS_DETAILS (individual)));
2457 manager = empathy_individual_manager_dup_singleton ();
2458 can_block = empathy_individual_manager_supports_blocking (manager,
2460 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2461 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2464 if (res == GTK_RESPONSE_YES || res == GTK_RESPONSE_REJECT)
2468 if (res == GTK_RESPONSE_REJECT &&
2469 empathy_block_individual_dialog_show (parent, individual,
2472 empathy_individual_manager_set_blocked (manager, individual,
2480 empathy_individual_manager_remove (manager, individual, "");
2485 g_object_unref (individual);
2486 g_object_unref (manager);
2491 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2492 EmpathyLinkingDialog *linking_dialog,
2493 EmpathyIndividualView *self)
2495 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2496 EmpathyIndividualLinker *linker;
2498 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2499 empathy_individual_linker_set_search_text (linker,
2500 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2504 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2506 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2507 FolksIndividual *individual;
2508 GtkWidget *menu = NULL;
2511 gboolean can_remove = FALSE;
2514 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2516 individual = empathy_individual_view_dup_selected (view);
2517 if (individual == NULL)
2520 /* If any of the Individual's personas can be removed, add an option to
2521 * remove. This will act as a best-effort option. If any Personas cannot be
2522 * removed from the server, then this option will just be inactive upon
2523 * subsequent menu openings */
2524 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2526 FolksPersona *persona = FOLKS_PERSONA (l->data);
2527 FolksPersonaStore *store = folks_persona_get_store (persona);
2528 FolksMaybeBool maybe_can_remove =
2529 folks_persona_store_get_can_remove_personas (store);
2531 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2538 menu = empathy_individual_menu_new (individual, priv->individual_features);
2540 /* Remove contact */
2541 if ((priv->view_features &
2542 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2545 /* create the menu if required, or just add a separator */
2547 menu = gtk_menu_new ();
2550 item = gtk_separator_menu_item_new ();
2551 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2552 gtk_widget_show (item);
2556 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2557 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2558 GTK_ICON_SIZE_MENU);
2559 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2560 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2561 gtk_widget_show (item);
2562 g_signal_connect (item, "activate",
2563 G_CALLBACK (individual_view_remove_activate_cb), view);
2566 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2567 * set the live search text on the new linking dialogue to be the same as
2569 g_signal_connect (menu, "link-contacts-activated",
2570 (GCallback) individual_menu_link_contacts_activated_cb, view);
2572 g_object_unref (individual);
2578 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2579 EmpathyLiveSearch *search)
2581 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2583 /* remove old handlers if old search was not null */
2584 if (priv->search_widget != NULL)
2586 g_signal_handlers_disconnect_by_func (view,
2587 individual_view_start_search_cb, NULL);
2589 g_signal_handlers_disconnect_by_func (priv->search_widget,
2590 individual_view_search_text_notify_cb, view);
2591 g_signal_handlers_disconnect_by_func (priv->search_widget,
2592 individual_view_search_activate_cb, view);
2593 g_signal_handlers_disconnect_by_func (priv->search_widget,
2594 individual_view_search_key_navigation_cb, view);
2595 g_signal_handlers_disconnect_by_func (priv->search_widget,
2596 individual_view_search_hide_cb, view);
2597 g_signal_handlers_disconnect_by_func (priv->search_widget,
2598 individual_view_search_show_cb, view);
2599 g_object_unref (priv->search_widget);
2600 priv->search_widget = NULL;
2603 /* connect handlers if new search is not null */
2606 priv->search_widget = g_object_ref (search);
2608 g_signal_connect (view, "start-interactive-search",
2609 G_CALLBACK (individual_view_start_search_cb), NULL);
2611 g_signal_connect (priv->search_widget, "notify::text",
2612 G_CALLBACK (individual_view_search_text_notify_cb), view);
2613 g_signal_connect (priv->search_widget, "activate",
2614 G_CALLBACK (individual_view_search_activate_cb), view);
2615 g_signal_connect (priv->search_widget, "key-navigation",
2616 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2617 g_signal_connect (priv->search_widget, "hide",
2618 G_CALLBACK (individual_view_search_hide_cb), view);
2619 g_signal_connect (priv->search_widget, "show",
2620 G_CALLBACK (individual_view_search_show_cb), view);
2625 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2627 EmpathyIndividualViewPriv *priv;
2629 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2631 priv = GET_PRIV (self);
2633 return (priv->search_widget != NULL &&
2634 gtk_widget_get_visible (priv->search_widget));
2638 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2640 EmpathyIndividualViewPriv *priv;
2642 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2644 priv = GET_PRIV (self);
2646 return priv->show_offline;
2650 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2651 gboolean show_offline)
2653 EmpathyIndividualViewPriv *priv;
2655 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2657 priv = GET_PRIV (self);
2659 priv->show_offline = show_offline;
2661 g_object_notify (G_OBJECT (self), "show-offline");
2662 gtk_tree_model_filter_refilter (priv->filter);
2666 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2668 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2670 return GET_PRIV (self)->show_untrusted;
2674 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2675 gboolean show_untrusted)
2677 EmpathyIndividualViewPriv *priv;
2679 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2681 priv = GET_PRIV (self);
2683 priv->show_untrusted = show_untrusted;
2685 g_object_notify (G_OBJECT (self), "show-untrusted");
2686 gtk_tree_model_filter_refilter (priv->filter);
2689 EmpathyIndividualStore *
2690 empathy_individual_view_get_store (EmpathyIndividualView *self)
2692 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2694 return GET_PRIV (self)->store;
2698 empathy_individual_view_set_store (EmpathyIndividualView *self,
2699 EmpathyIndividualStore *store)
2701 EmpathyIndividualViewPriv *priv;
2703 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2704 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2706 priv = GET_PRIV (self);
2708 /* Destroy the old filter and remove the old store */
2709 if (priv->store != NULL)
2711 g_signal_handlers_disconnect_by_func (priv->store,
2712 individual_view_store_row_changed_cb, self);
2713 g_signal_handlers_disconnect_by_func (priv->store,
2714 individual_view_store_row_deleted_cb, self);
2716 g_signal_handlers_disconnect_by_func (priv->filter,
2717 individual_view_row_has_child_toggled_cb, self);
2719 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2722 tp_clear_object (&priv->filter);
2723 tp_clear_object (&priv->store);
2725 /* Set the new store */
2726 priv->store = store;
2730 g_object_ref (store);
2732 /* Create a new filter */
2733 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2734 GTK_TREE_MODEL (priv->store), NULL));
2735 gtk_tree_model_filter_set_visible_func (priv->filter,
2736 individual_view_filter_visible_func, self, NULL);
2738 g_signal_connect (priv->filter, "row-has-child-toggled",
2739 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2740 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2741 GTK_TREE_MODEL (priv->filter));
2743 tp_g_signal_connect_object (priv->store, "row-changed",
2744 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2745 tp_g_signal_connect_object (priv->store, "row-inserted",
2746 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2747 tp_g_signal_connect_object (priv->store, "row-deleted",
2748 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2753 empathy_individual_view_start_search (EmpathyIndividualView *self)
2755 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2757 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2758 g_return_if_fail (priv->search_widget != NULL);
2760 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2761 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2763 gtk_widget_show (GTK_WIDGET (priv->search_widget));