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 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
244 GError *error = NULL;
246 folks_groupable_change_group_finish (groupable, 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_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
381 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
383 /* Remove contact as favourite */
384 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
386 /* Don't try to remove it */
390 if (new_group != NULL)
392 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
393 groups_change_group_cb, NULL);
396 if (old_group != NULL && action == GDK_ACTION_MOVE)
398 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
399 FALSE, groups_change_group_cb, NULL);
404 individual_view_persona_drag_received (GtkWidget *self,
405 GdkDragContext *context,
408 GtkSelectionData *selection)
410 EmpathyIndividualViewPriv *priv;
411 EmpathyIndividualManager *manager = NULL;
412 FolksIndividual *individual = NULL;
413 FolksPersona *persona = NULL;
414 const gchar *persona_uid;
415 GList *individuals, *l;
416 gboolean retval = FALSE;
418 priv = GET_PRIV (self);
420 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
422 /* FIXME: This is slow, but the only way to find the Persona we're having
424 manager = empathy_individual_manager_dup_singleton ();
425 individuals = empathy_individual_manager_get_members (manager);
427 for (l = individuals; l != NULL; l = l->next)
431 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
433 for (p = personas; p != NULL; p = p->next)
435 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
438 persona = g_object_ref (p->data);
439 individual = g_object_ref (l->data);
446 g_list_free (individuals);
448 if (persona == NULL || individual == NULL)
450 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
454 /* Emit a signal notifying of the drag. We change the Individual's groups in
455 * the default signal handler. */
456 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
457 gdk_drag_context_get_selected_action (context), persona, individual,
461 tp_clear_object (&manager);
462 tp_clear_object (&persona);
463 tp_clear_object (&individual);
469 individual_view_file_drag_received (GtkWidget *view,
470 GdkDragContext *context,
473 GtkSelectionData *selection)
476 const gchar *sel_data;
477 FolksIndividual *individual;
478 EmpathyContact *contact;
480 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
482 gtk_tree_model_get_iter (model, &iter, path);
483 gtk_tree_model_get (model, &iter,
484 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
485 if (individual == NULL)
488 contact = empathy_contact_dup_from_folks_individual (individual);
489 empathy_send_file_from_uri_list (contact, sel_data);
491 g_object_unref (individual);
492 tp_clear_object (&contact);
498 individual_view_drag_data_received (GtkWidget *view,
499 GdkDragContext *context,
502 GtkSelectionData *selection,
508 GtkTreeViewDropPosition position;
510 gboolean success = TRUE;
512 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
514 /* Get destination group information. */
515 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
516 x, y, &path, &position);
521 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
523 success = individual_view_individual_drag_received (view,
524 context, model, path, selection);
526 else if (info == DND_DRAG_TYPE_PERSONA_ID)
528 success = individual_view_persona_drag_received (view, context, model,
531 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
533 success = individual_view_file_drag_received (view,
534 context, model, path, selection);
537 gtk_tree_path_free (path);
538 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
542 individual_view_drag_motion_cb (DragMotionData *data)
544 if (data->view != NULL)
546 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
547 g_object_remove_weak_pointer (G_OBJECT (data->view),
548 (gpointer *) &data->view);
551 data->timeout_id = 0;
556 /* Minimum distance between the mouse pointer and a horizontal border when we
557 start auto scrolling. */
558 #define AUTO_SCROLL_MARGIN_SIZE 20
559 /* How far to scroll per one tick. */
560 #define AUTO_SCROLL_PITCH 10
563 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
565 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
569 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
571 if (priv->distance < 0)
572 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
574 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
576 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
577 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
579 gtk_adjustment_set_value (adj, new_value);
585 individual_view_drag_motion (GtkWidget *widget,
586 GdkDragContext *context,
591 EmpathyIndividualViewPriv *priv;
595 static DragMotionData *dm = NULL;
598 gboolean is_different = FALSE;
599 gboolean cleanup = TRUE;
600 gboolean retval = TRUE;
601 GtkAllocation allocation;
603 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
604 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
607 if (priv->auto_scroll_timeout_id != 0)
609 g_source_remove (priv->auto_scroll_timeout_id);
610 priv->auto_scroll_timeout_id = 0;
613 gtk_widget_get_allocation (widget, &allocation);
615 if (y < AUTO_SCROLL_MARGIN_SIZE ||
616 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
618 if (y < AUTO_SCROLL_MARGIN_SIZE)
619 priv->distance = MIN (-y, -1);
621 priv->distance = MAX (allocation.height - y, 1);
623 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
624 (GSourceFunc) individual_view_auto_scroll_cb, widget);
627 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
628 x, y, &path, NULL, NULL, NULL);
630 cleanup &= (dm == NULL);
634 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
635 is_different = ((dm == NULL) || ((dm != NULL)
636 && gtk_tree_path_compare (dm->path, path) != 0));
643 /* Coordinates don't point to an actual row, so make sure the pointer
644 and highlighting don't indicate that a drag is possible.
646 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
647 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
650 target = gtk_drag_dest_find_target (widget, context, NULL);
651 gtk_tree_model_get_iter (model, &iter, path);
653 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
654 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
656 /* This is a file drag, and it can only be dropped on contacts,
658 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
659 * even if we have a valid target. */
660 FolksIndividual *individual = NULL;
661 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
663 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
665 gtk_tree_model_get (model, &iter,
666 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
670 if (individual != NULL)
672 EmpathyContact *contact = NULL;
674 contact = empathy_contact_dup_from_folks_individual (individual);
675 caps = empathy_contact_get_capabilities (contact);
677 tp_clear_object (&contact);
680 if (individual != NULL &&
681 folks_presence_owner_is_online (FOLKS_PRESENCE_OWNER (individual)) &&
682 (caps & EMPATHY_CAPABILITIES_FT))
684 gdk_drag_status (context, GDK_ACTION_COPY, time_);
685 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
686 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
690 gdk_drag_status (context, 0, time_);
691 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
695 if (individual != NULL)
696 g_object_unref (individual);
698 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
699 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
700 priv->drag_row == NULL)) ||
701 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
702 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
704 /* If target != GDK_NONE, then we have a contact (individual or persona)
705 drag. If we're pointing to a group, highlight it. Otherwise, if the
706 contact we're pointing to is in a group, highlight that. Otherwise,
707 set the drag position to before the first row for a drag into
708 the "non-group" at the top.
709 If it's an Individual:
710 We only highlight things if the contact is from a different
711 Individual view, or if this Individual view has
712 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
713 which don't have FEATURE_GROUPS_CHANGE, but do have
714 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
716 We only highlight things if we have FEATURE_PERSONA_DROP.
718 GtkTreeIter group_iter;
720 GtkTreePath *group_path;
721 gtk_tree_model_get (model, &iter,
722 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
729 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
730 gtk_tree_model_get (model, &group_iter,
731 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
735 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
736 group_path = gtk_tree_model_get_path (model, &group_iter);
737 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
738 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
739 gtk_tree_path_free (group_path);
743 group_path = gtk_tree_path_new_first ();
744 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
745 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
746 group_path, GTK_TREE_VIEW_DROP_BEFORE);
750 if (!is_different && !cleanup)
755 gtk_tree_path_free (dm->path);
758 g_source_remove (dm->timeout_id);
766 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
768 dm = g_new0 (DragMotionData, 1);
770 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
771 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
772 dm->path = gtk_tree_path_copy (path);
774 dm->timeout_id = g_timeout_add_seconds (1,
775 (GSourceFunc) individual_view_drag_motion_cb, dm);
782 individual_view_drag_begin (GtkWidget *widget,
783 GdkDragContext *context)
785 EmpathyIndividualViewPriv *priv;
786 GtkTreeSelection *selection;
791 priv = GET_PRIV (widget);
793 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
796 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
797 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
800 path = gtk_tree_model_get_path (model, &iter);
801 priv->drag_row = gtk_tree_row_reference_new (model, path);
802 gtk_tree_path_free (path);
806 individual_view_drag_data_get (GtkWidget *widget,
807 GdkDragContext *context,
808 GtkSelectionData *selection,
812 EmpathyIndividualViewPriv *priv;
813 GtkTreePath *src_path;
816 FolksIndividual *individual;
817 const gchar *individual_id;
819 priv = GET_PRIV (widget);
821 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
822 if (priv->drag_row == NULL)
825 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
826 if (src_path == NULL)
829 if (!gtk_tree_model_get_iter (model, &iter, src_path))
831 gtk_tree_path_free (src_path);
835 gtk_tree_path_free (src_path);
838 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
839 if (individual == NULL)
842 individual_id = folks_individual_get_id (individual);
844 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
846 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
847 (guchar *) individual_id, strlen (individual_id) + 1);
850 g_object_unref (individual);
854 individual_view_drag_end (GtkWidget *widget,
855 GdkDragContext *context)
857 EmpathyIndividualViewPriv *priv;
859 priv = GET_PRIV (widget);
861 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
866 gtk_tree_row_reference_free (priv->drag_row);
867 priv->drag_row = NULL;
872 individual_view_drag_drop (GtkWidget *widget,
873 GdkDragContext *drag_context,
883 EmpathyIndividualView *view;
889 menu_deactivate_cb (GtkMenuShell *menushell,
892 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
893 g_signal_handlers_disconnect_by_func (menushell,
894 menu_deactivate_cb, user_data);
896 gtk_menu_detach (GTK_MENU (menushell));
900 individual_view_popup_menu_idle_cb (gpointer user_data)
902 MenuPopupData *data = user_data;
905 menu = empathy_individual_view_get_individual_menu (data->view);
907 menu = empathy_individual_view_get_group_menu (data->view);
911 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
913 gtk_widget_show (menu);
914 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
917 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
918 * floating ref. We can either wait that the treeview releases its ref
919 * when it will be destroyed (when leaving Empathy) or explicitely
920 * detach the menu when it's not displayed any more.
921 * We go for the latter as we don't want to keep useless menus in memory
922 * during the whole lifetime of Empathy. */
923 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
927 g_slice_free (MenuPopupData, data);
933 individual_view_button_press_event_cb (EmpathyIndividualView *view,
934 GdkEventButton *event,
937 if (event->button == 3)
941 data = g_slice_new (MenuPopupData);
943 data->button = event->button;
944 data->time = event->time;
945 g_idle_add (individual_view_popup_menu_idle_cb, data);
952 individual_view_key_press_event_cb (EmpathyIndividualView *view,
956 if (event->keyval == GDK_KEY_Menu)
960 data = g_slice_new (MenuPopupData);
963 data->time = event->time;
964 g_idle_add (individual_view_popup_menu_idle_cb, data);
965 } else if (event->keyval == GDK_KEY_F2) {
966 FolksIndividual *individual;
967 EmpathyContact *contact;
969 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
971 individual = empathy_individual_view_dup_selected (view);
972 if (individual == NULL)
975 contact = empathy_contact_dup_from_folks_individual (individual);
976 if (contact == NULL) {
977 g_object_unref (individual);
980 empathy_contact_edit_dialog_show (contact, NULL);
982 g_object_unref (individual);
983 g_object_unref (contact);
990 individual_view_row_activated (GtkTreeView *view,
992 GtkTreeViewColumn *column)
994 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
995 FolksIndividual *individual;
996 EmpathyContact *contact;
1000 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1003 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1004 gtk_tree_model_get_iter (model, &iter, path);
1005 gtk_tree_model_get (model, &iter,
1006 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1008 if (individual == NULL)
1011 /* Determine which Persona to chat to, by choosing the most available one. */
1012 contact = empathy_contact_dup_best_for_action (individual,
1013 EMPATHY_ACTION_CHAT);
1015 if (contact != NULL)
1017 DEBUG ("Starting a chat");
1019 empathy_dispatcher_chat_with_contact (contact,
1020 gtk_get_current_event_time ());
1023 g_object_unref (individual);
1024 tp_clear_object (&contact);
1028 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1029 const gchar *path_string,
1030 EmpathyIndividualView *view)
1032 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1034 GtkTreeModel *model;
1036 FolksIndividual *individual;
1037 GdkEventButton *event;
1038 GtkMenuShell *shell;
1041 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1044 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1045 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1048 gtk_tree_model_get (model, &iter,
1049 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1050 if (individual == NULL)
1053 event = (GdkEventButton *) gtk_get_current_event ();
1055 menu = empathy_context_menu_new (GTK_WIDGET (view));
1056 shell = GTK_MENU_SHELL (menu);
1059 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1060 gtk_menu_shell_append (shell, item);
1061 gtk_widget_show (item);
1064 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1065 gtk_menu_shell_append (shell, item);
1066 gtk_widget_show (item);
1068 gtk_widget_show (menu);
1069 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1070 event->button, event->time);
1072 g_object_unref (individual);
1076 individual_view_cell_set_background (EmpathyIndividualView *view,
1077 GtkCellRenderer *cell,
1081 if (!is_group && is_active)
1083 GtkStyleContext *style;
1086 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1088 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1091 /* Here we take the current theme colour and add it to
1092 * the colour for white and average the two. This
1093 * gives a colour which is inline with the theme but
1096 empathy_make_color_whiter (&color);
1098 g_object_set (cell, "cell-background-rgba", &color, NULL);
1101 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1105 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1106 GtkCellRenderer *cell,
1107 GtkTreeModel *model,
1109 EmpathyIndividualView *view)
1115 gtk_tree_model_get (model, iter,
1116 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1117 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1118 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1121 "visible", !is_group,
1125 tp_clear_object (&pixbuf);
1127 individual_view_cell_set_background (view, cell, is_group, is_active);
1131 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1132 GtkCellRenderer *cell,
1133 GtkTreeModel *model,
1135 EmpathyIndividualView *view)
1137 GdkPixbuf *pixbuf = NULL;
1141 gtk_tree_model_get (model, iter,
1142 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1143 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1148 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1150 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1151 GTK_ICON_SIZE_MENU);
1153 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1155 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1156 GTK_ICON_SIZE_MENU);
1161 "visible", pixbuf != NULL,
1165 tp_clear_object (&pixbuf);
1171 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1172 GtkCellRenderer *cell,
1173 GtkTreeModel *model,
1175 EmpathyIndividualView *view)
1179 gboolean can_audio, can_video;
1181 gtk_tree_model_get (model, iter,
1182 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1183 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1184 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1185 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1188 "visible", !is_group && (can_audio || can_video),
1189 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1192 individual_view_cell_set_background (view, cell, is_group, is_active);
1196 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1197 GtkCellRenderer *cell,
1198 GtkTreeModel *model,
1200 EmpathyIndividualView *view)
1203 gboolean show_avatar;
1207 gtk_tree_model_get (model, iter,
1208 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1209 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1210 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1211 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1214 "visible", !is_group && show_avatar,
1218 tp_clear_object (&pixbuf);
1220 individual_view_cell_set_background (view, cell, is_group, is_active);
1224 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1225 GtkCellRenderer *cell,
1226 GtkTreeModel *model,
1228 EmpathyIndividualView *view)
1233 gtk_tree_model_get (model, iter,
1234 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1235 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1237 individual_view_cell_set_background (view, cell, is_group, is_active);
1241 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1242 GtkCellRenderer *cell,
1243 GtkTreeModel *model,
1245 EmpathyIndividualView *view)
1250 gtk_tree_model_get (model, iter,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1252 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1254 if (gtk_tree_model_iter_has_child (model, iter))
1257 gboolean row_expanded;
1259 path = gtk_tree_model_get_path (model, iter);
1261 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1262 (gtk_tree_view_column_get_tree_view (column)), path);
1263 gtk_tree_path_free (path);
1268 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1272 g_object_set (cell, "visible", FALSE, NULL);
1274 individual_view_cell_set_background (view, cell, is_group, is_active);
1278 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1283 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1284 GtkTreeModel *model;
1288 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1291 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1293 gtk_tree_model_get (model, iter,
1294 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1296 expanded = GPOINTER_TO_INT (user_data);
1297 empathy_contact_group_set_expanded (name, expanded);
1303 individual_view_start_search_cb (EmpathyIndividualView *view,
1306 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1308 if (priv->search_widget == NULL)
1311 empathy_individual_view_start_search (view);
1317 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1319 EmpathyIndividualView *view)
1321 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1323 GtkTreeViewColumn *focus_column;
1324 GtkTreeModel *model;
1326 gboolean set_cursor = FALSE;
1328 gtk_tree_model_filter_refilter (priv->filter);
1330 /* Set cursor on the first contact. If it is already set on a group,
1331 * set it on its first child contact. Note that first child of a group
1332 * is its separator, that's why we actually set to the 2nd
1335 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1336 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1340 path = gtk_tree_path_new_from_string ("0:1");
1343 else if (gtk_tree_path_get_depth (path) < 2)
1347 gtk_tree_model_get_iter (model, &iter, path);
1348 gtk_tree_model_get (model, &iter,
1349 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1354 gtk_tree_path_down (path);
1355 gtk_tree_path_next (path);
1362 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1364 if (gtk_tree_model_get_iter (model, &iter, path))
1366 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1371 gtk_tree_path_free (path);
1375 individual_view_search_activate_cb (GtkWidget *search,
1376 EmpathyIndividualView *view)
1379 GtkTreeViewColumn *focus_column;
1381 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1384 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1385 gtk_tree_path_free (path);
1387 gtk_widget_hide (search);
1392 individual_view_search_key_navigation_cb (GtkWidget *search,
1394 EmpathyIndividualView *view)
1396 GdkEventKey *eventkey = ((GdkEventKey *) event);
1397 gboolean ret = FALSE;
1399 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1400 || eventkey->keyval == GDK_KEY_F2)
1402 GdkEvent *new_event;
1404 new_event = gdk_event_copy (event);
1405 gtk_widget_grab_focus (GTK_WIDGET (view));
1406 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1407 gtk_widget_grab_focus (search);
1409 gdk_event_free (new_event);
1416 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1417 EmpathyIndividualView *view)
1419 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1420 GtkTreeModel *model;
1421 GtkTreePath *cursor_path;
1423 gboolean valid = FALSE;
1425 /* block expand or collapse handlers, they would write the
1426 * expand or collapsed setting to file otherwise */
1427 g_signal_handlers_block_by_func (view,
1428 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1429 g_signal_handlers_block_by_func (view,
1430 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1432 /* restore which groups are expanded and which are not */
1433 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1434 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1435 valid; valid = gtk_tree_model_iter_next (model, &iter))
1441 gtk_tree_model_get (model, &iter,
1442 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1443 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1452 path = gtk_tree_model_get_path (model, &iter);
1453 if ((priv->view_features &
1454 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1455 empathy_contact_group_get_expanded (name))
1457 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1461 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1464 gtk_tree_path_free (path);
1468 /* unblock expand or collapse handlers */
1469 g_signal_handlers_unblock_by_func (view,
1470 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1471 g_signal_handlers_unblock_by_func (view,
1472 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1474 /* keep the selected contact visible */
1475 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1477 if (cursor_path != NULL)
1478 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1481 gtk_tree_path_free (cursor_path);
1485 individual_view_search_show_cb (EmpathyLiveSearch *search,
1486 EmpathyIndividualView *view)
1488 /* block expand or collapse handlers during expand all, they would
1489 * write the expand or collapsed setting to file otherwise */
1490 g_signal_handlers_block_by_func (view,
1491 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1493 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1495 g_signal_handlers_unblock_by_func (view,
1496 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1500 expand_idle_foreach_cb (GtkTreeModel *model,
1503 EmpathyIndividualView *self)
1505 EmpathyIndividualViewPriv *priv;
1507 gpointer should_expand;
1510 /* We only want groups */
1511 if (gtk_tree_path_get_depth (path) > 1)
1514 gtk_tree_model_get (model, iter,
1515 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1516 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1519 if (is_group == FALSE)
1525 priv = GET_PRIV (self);
1527 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1528 &should_expand) == TRUE)
1530 if (GPOINTER_TO_INT (should_expand) == TRUE)
1531 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1533 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1535 g_hash_table_remove (priv->expand_groups, name);
1544 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1546 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1548 DEBUG ("individual_view_expand_idle_cb");
1550 g_signal_handlers_block_by_func (self,
1551 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1552 g_signal_handlers_block_by_func (self,
1553 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1555 /* The store/filter could've been removed while we were in the idle queue */
1556 if (priv->filter != NULL)
1558 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1559 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1562 g_signal_handlers_unblock_by_func (self,
1563 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1564 g_signal_handlers_unblock_by_func (self,
1565 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1567 /* Empty the table of groups to expand/contract, since it may contain groups
1568 * which no longer exist in the tree view. This can happen after going
1569 * offline, for example. */
1570 g_hash_table_remove_all (priv->expand_groups);
1571 priv->expand_groups_idle_handler = 0;
1572 g_object_unref (self);
1578 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1581 EmpathyIndividualView *view)
1583 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1584 gboolean should_expand, is_group = FALSE;
1586 gpointer will_expand;
1588 gtk_tree_model_get (model, iter,
1589 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1590 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1593 if (!is_group || EMP_STR_EMPTY (name))
1599 should_expand = (priv->view_features &
1600 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1601 (priv->search_widget != NULL &&
1602 gtk_widget_get_visible (priv->search_widget)) ||
1603 empathy_contact_group_get_expanded (name);
1605 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1606 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1607 * a hash table, and expand or contract them as appropriate all at once in
1608 * an idle handler which iterates over all the group rows. */
1609 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1610 &will_expand) == FALSE ||
1611 GPOINTER_TO_INT (will_expand) != should_expand)
1613 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1614 GINT_TO_POINTER (should_expand));
1616 if (priv->expand_groups_idle_handler == 0)
1618 priv->expand_groups_idle_handler =
1619 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1620 g_object_ref (view));
1627 /* FIXME: This is a workaround for bgo#621076 */
1629 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1632 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1633 GtkTreeModel *model;
1634 GtkTreePath *parent_path;
1635 GtkTreeIter parent_iter;
1637 if (gtk_tree_path_get_depth (path) < 2)
1640 /* A group row is visible if and only if at least one if its child is visible.
1641 * So when a row is inserted/deleted/changed in the base model, that could
1642 * modify the visibility of its parent in the filter model.
1645 model = GTK_TREE_MODEL (priv->store);
1646 parent_path = gtk_tree_path_copy (path);
1647 gtk_tree_path_up (parent_path);
1648 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1650 /* This tells the filter to verify the visibility of that row, and
1651 * show/hide it if necessary */
1652 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1653 parent_path, &parent_iter);
1655 gtk_tree_path_free (parent_path);
1659 individual_view_store_row_changed_cb (GtkTreeModel *model,
1662 EmpathyIndividualView *view)
1664 individual_view_verify_group_visibility (view, path);
1668 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1670 EmpathyIndividualView *view)
1672 individual_view_verify_group_visibility (view, path);
1676 individual_view_is_visible_individual (EmpathyIndividualView *self,
1677 FolksIndividual *individual,
1679 gboolean is_searching)
1681 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1682 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1684 GList *personas, *l;
1685 gboolean is_favorite, contains_interesting_persona = FALSE;
1687 /* We're only giving the visibility wrt filtering here, not things like
1689 if (priv->show_untrusted == FALSE &&
1690 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1695 /* Hide all individuals which consist entirely of uninteresting personas */
1696 personas = folks_individual_get_personas (individual);
1697 for (l = personas; l; l = l->next)
1699 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1701 contains_interesting_persona = TRUE;
1706 if (contains_interesting_persona == FALSE)
1709 is_favorite = folks_favouritable_get_is_favourite (
1710 FOLKS_FAVOURITABLE (individual));
1711 if (is_searching == FALSE)
1712 return (priv->show_offline || is_online || is_favorite);
1714 /* check alias name */
1715 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1717 if (empathy_live_search_match (live, str))
1720 /* check contact id, remove the @server.com part */
1721 for (l = personas; l; l = l->next)
1724 gchar *dup_str = NULL;
1727 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1730 str = folks_persona_get_display_id (l->data);
1731 p = strstr (str, "@");
1733 str = dup_str = g_strndup (str, p - str);
1735 visible = empathy_live_search_match (live, str);
1741 /* FIXME: Add more rules here, we could check phone numbers in
1742 * contact's vCard for example. */
1748 individual_view_filter_visible_func (GtkTreeModel *model,
1752 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1753 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1754 FolksIndividual *individual = NULL;
1755 gboolean is_group, is_separator, valid;
1756 GtkTreeIter child_iter;
1757 gboolean visible, is_online;
1758 gboolean is_searching = TRUE;
1760 if (priv->search_widget == NULL ||
1761 !gtk_widget_get_visible (priv->search_widget))
1762 is_searching = FALSE;
1764 gtk_tree_model_get (model, iter,
1765 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1766 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1767 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1768 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1771 if (individual != NULL)
1773 visible = individual_view_is_visible_individual (self, individual,
1774 is_online, is_searching);
1776 g_object_unref (individual);
1778 /* FIXME: Work around bgo#626552/bgo#621076 */
1779 if (visible == TRUE)
1781 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1782 individual_view_verify_group_visibility (self, path);
1783 gtk_tree_path_free (path);
1792 /* Not a contact, not a separator, must be a group */
1793 g_return_val_if_fail (is_group, FALSE);
1795 /* only show groups which are not empty */
1796 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1797 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1799 gtk_tree_model_get (model, &child_iter,
1800 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1801 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1804 if (individual == NULL)
1807 visible = individual_view_is_visible_individual (self, individual,
1808 is_online, is_searching);
1809 g_object_unref (individual);
1811 /* show group if it has at least one visible contact in it */
1812 if (visible == TRUE)
1820 individual_view_constructed (GObject *object)
1822 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1823 GtkCellRenderer *cell;
1824 GtkTreeViewColumn *col;
1829 "headers-visible", FALSE,
1830 "show-expanders", FALSE,
1833 col = gtk_tree_view_column_new ();
1836 cell = gtk_cell_renderer_pixbuf_new ();
1837 gtk_tree_view_column_pack_start (col, cell, FALSE);
1838 gtk_tree_view_column_set_cell_data_func (col, cell,
1839 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1849 cell = gtk_cell_renderer_pixbuf_new ();
1850 gtk_tree_view_column_pack_start (col, cell, FALSE);
1851 gtk_tree_view_column_set_cell_data_func (col, cell,
1852 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1864 cell = empathy_cell_renderer_text_new ();
1865 gtk_tree_view_column_pack_start (col, cell, TRUE);
1866 gtk_tree_view_column_set_cell_data_func (col, cell,
1867 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1869 gtk_tree_view_column_add_attribute (col, cell,
1870 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1871 gtk_tree_view_column_add_attribute (col, cell,
1872 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1873 gtk_tree_view_column_add_attribute (col, cell,
1874 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1875 gtk_tree_view_column_add_attribute (col, cell,
1876 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1877 gtk_tree_view_column_add_attribute (col, cell,
1878 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1879 gtk_tree_view_column_add_attribute (col, cell,
1880 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1881 gtk_tree_view_column_add_attribute (col, cell,
1882 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1884 /* Audio Call Icon */
1885 cell = empathy_cell_renderer_activatable_new ();
1886 gtk_tree_view_column_pack_start (col, cell, FALSE);
1887 gtk_tree_view_column_set_cell_data_func (col, cell,
1888 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1891 g_object_set (cell, "visible", FALSE, NULL);
1893 g_signal_connect (cell, "path-activated",
1894 G_CALLBACK (individual_view_call_activated_cb), view);
1897 cell = gtk_cell_renderer_pixbuf_new ();
1898 gtk_tree_view_column_pack_start (col, cell, FALSE);
1899 gtk_tree_view_column_set_cell_data_func (col, cell,
1900 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1912 cell = empathy_cell_renderer_expander_new ();
1913 gtk_tree_view_column_pack_end (col, cell, FALSE);
1914 gtk_tree_view_column_set_cell_data_func (col, cell,
1915 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1918 /* Actually add the column now we have added all cell renderers */
1919 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1922 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1924 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1927 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1929 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1935 individual_view_set_view_features (EmpathyIndividualView *view,
1936 EmpathyIndividualFeatureFlags features)
1938 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1939 gboolean has_tooltip;
1941 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1943 priv->view_features = features;
1945 /* Setting reorderable is a hack that gets us row previews as drag icons
1946 for free. We override all the drag handlers. It's tricky to get the
1947 position of the drag icon right in drag_begin. GtkTreeView has special
1948 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1951 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1952 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1954 /* Update DnD source/dest */
1955 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1957 gtk_drag_source_set (GTK_WIDGET (view),
1960 G_N_ELEMENTS (drag_types_source),
1961 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1965 gtk_drag_source_unset (GTK_WIDGET (view));
1969 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1971 gtk_drag_dest_set (GTK_WIDGET (view),
1972 GTK_DEST_DEFAULT_ALL,
1974 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1978 /* FIXME: URI could still be droped depending on FT feature */
1979 gtk_drag_dest_unset (GTK_WIDGET (view));
1982 /* Update has-tooltip */
1984 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1985 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1989 individual_view_dispose (GObject *object)
1991 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1992 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1994 tp_clear_object (&priv->store);
1995 tp_clear_object (&priv->filter);
1996 tp_clear_object (&priv->tooltip_widget);
1998 empathy_individual_view_set_live_search (view, NULL);
2000 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2004 individual_view_finalize (GObject *object)
2006 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2008 if (priv->expand_groups_idle_handler != 0)
2009 g_source_remove (priv->expand_groups_idle_handler);
2010 g_hash_table_destroy (priv->expand_groups);
2012 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2016 individual_view_get_property (GObject *object,
2021 EmpathyIndividualViewPriv *priv;
2023 priv = GET_PRIV (object);
2028 g_value_set_object (value, priv->store);
2030 case PROP_VIEW_FEATURES:
2031 g_value_set_flags (value, priv->view_features);
2033 case PROP_INDIVIDUAL_FEATURES:
2034 g_value_set_flags (value, priv->individual_features);
2036 case PROP_SHOW_OFFLINE:
2037 g_value_set_boolean (value, priv->show_offline);
2039 case PROP_SHOW_UNTRUSTED:
2040 g_value_set_boolean (value, priv->show_untrusted);
2043 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2049 individual_view_set_property (GObject *object,
2051 const GValue *value,
2054 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2055 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2060 empathy_individual_view_set_store (view, g_value_get_object (value));
2062 case PROP_VIEW_FEATURES:
2063 individual_view_set_view_features (view, g_value_get_flags (value));
2065 case PROP_INDIVIDUAL_FEATURES:
2066 priv->individual_features = g_value_get_flags (value);
2068 case PROP_SHOW_OFFLINE:
2069 empathy_individual_view_set_show_offline (view,
2070 g_value_get_boolean (value));
2072 case PROP_SHOW_UNTRUSTED:
2073 empathy_individual_view_set_show_untrusted (view,
2074 g_value_get_boolean (value));
2077 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2083 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2085 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2086 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2087 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2089 object_class->constructed = individual_view_constructed;
2090 object_class->dispose = individual_view_dispose;
2091 object_class->finalize = individual_view_finalize;
2092 object_class->get_property = individual_view_get_property;
2093 object_class->set_property = individual_view_set_property;
2095 widget_class->drag_data_received = individual_view_drag_data_received;
2096 widget_class->drag_drop = individual_view_drag_drop;
2097 widget_class->drag_begin = individual_view_drag_begin;
2098 widget_class->drag_data_get = individual_view_drag_data_get;
2099 widget_class->drag_end = individual_view_drag_end;
2100 widget_class->drag_motion = individual_view_drag_motion;
2102 /* We use the class method to let user of this widget to connect to
2103 * the signal and stop emission of the signal so the default handler
2104 * won't be called. */
2105 tree_view_class->row_activated = individual_view_row_activated;
2107 klass->drag_individual_received = real_drag_individual_received_cb;
2109 signals[DRAG_INDIVIDUAL_RECEIVED] =
2110 g_signal_new ("drag-individual-received",
2111 G_OBJECT_CLASS_TYPE (klass),
2113 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2115 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2116 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2117 G_TYPE_STRING, G_TYPE_STRING);
2119 signals[DRAG_PERSONA_RECEIVED] =
2120 g_signal_new ("drag-persona-received",
2121 G_OBJECT_CLASS_TYPE (klass),
2123 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2125 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2126 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2128 g_object_class_install_property (object_class,
2130 g_param_spec_object ("store",
2131 "The store of the view",
2132 "The store of the view",
2133 EMPATHY_TYPE_INDIVIDUAL_STORE,
2134 G_PARAM_READWRITE));
2135 g_object_class_install_property (object_class,
2137 g_param_spec_flags ("view-features",
2138 "Features of the view",
2139 "Flags for all enabled features",
2140 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2141 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2142 g_object_class_install_property (object_class,
2143 PROP_INDIVIDUAL_FEATURES,
2144 g_param_spec_flags ("individual-features",
2145 "Features of the individual menu",
2146 "Flags for all enabled features for the menu",
2147 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2148 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2149 g_object_class_install_property (object_class,
2151 g_param_spec_boolean ("show-offline",
2153 "Whether contact list should display "
2154 "offline contacts", FALSE, G_PARAM_READWRITE));
2155 g_object_class_install_property (object_class,
2156 PROP_SHOW_UNTRUSTED,
2157 g_param_spec_boolean ("show-untrusted",
2158 "Show Untrusted Individuals",
2159 "Whether the view should display untrusted individuals; "
2160 "those who could not be who they say they are.",
2161 TRUE, G_PARAM_READWRITE));
2163 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2167 empathy_individual_view_init (EmpathyIndividualView *view)
2169 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2170 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2174 priv->show_untrusted = TRUE;
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 EmpathyIndividualViewPriv *priv;
2217 GtkTreeSelection *selection;
2219 GtkTreeModel *model;
2220 FolksIndividual *individual;
2222 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2224 priv = GET_PRIV (view);
2226 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2227 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2230 gtk_tree_model_get (model, &iter,
2231 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2237 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2238 gboolean *is_fake_group)
2240 EmpathyIndividualViewPriv *priv;
2241 GtkTreeSelection *selection;
2243 GtkTreeModel *model;
2248 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2250 priv = GET_PRIV (view);
2252 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2253 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2256 gtk_tree_model_get (model, &iter,
2257 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2258 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2259 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2267 if (is_fake_group != NULL)
2268 *is_fake_group = fake;
2274 individual_view_remove_dialog_show (GtkWindow *parent,
2275 const gchar *message,
2276 const gchar *secondary_text,
2277 gboolean block_button)
2282 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2283 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2286 gtk_dialog_add_button (GTK_DIALOG (dialog),
2287 _("Delete and Block"), GTK_RESPONSE_REJECT);
2289 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2290 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2291 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2292 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2293 "%s", secondary_text);
2295 gtk_widget_show (dialog);
2297 res = gtk_dialog_run (GTK_DIALOG (dialog));
2298 gtk_widget_destroy (dialog);
2304 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2305 EmpathyIndividualView *view)
2309 group = empathy_individual_view_dup_selected_group (view, NULL);
2316 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2318 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2319 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2320 text, FALSE) == GTK_RESPONSE_YES)
2322 EmpathyIndividualManager *manager =
2323 empathy_individual_manager_dup_singleton ();
2324 empathy_individual_manager_remove_group (manager, group);
2325 g_object_unref (G_OBJECT (manager));
2335 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2337 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2342 gboolean is_fake_group;
2344 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2346 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2347 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2350 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2351 if (!group || is_fake_group)
2353 /* We can't alter fake groups */
2358 menu = gtk_menu_new ();
2361 if (priv->view_features &
2362 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2363 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2364 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2365 gtk_widget_show (item);
2366 g_signal_connect (item, "activate",
2367 G_CALLBACK (individual_view_group_rename_activate_cb),
2372 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2374 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2375 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2376 GTK_ICON_SIZE_MENU);
2377 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2378 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2379 gtk_widget_show (item);
2380 g_signal_connect (item, "activate",
2381 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2390 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2391 EmpathyIndividualView *view)
2393 FolksIndividual *individual;
2395 individual = empathy_individual_view_dup_selected (view);
2397 if (individual != NULL)
2399 EmpathyIndividualManager *manager;
2402 GList *l, *personas;
2403 guint persona_count = 0;
2407 personas = folks_individual_get_personas (individual);
2409 /* If we have more than one TpfPersona, display a different message
2410 * ensuring the user knows that *all* of the meta-contacts' personas will
2412 for (l = personas; l != NULL; l = l->next)
2414 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2418 if (persona_count >= 2)
2422 if (persona_count < 2)
2424 /* Not a meta-contact */
2427 _("Do you really want to remove the contact '%s'?"),
2428 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2435 _("Do you really want to remove the linked contact '%s'? "
2436 "Note that this will remove all the contacts which make up "
2437 "this linked contact."),
2438 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2442 manager = empathy_individual_manager_dup_singleton ();
2443 can_block = empathy_individual_manager_supports_blocking (manager,
2445 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2446 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2449 if (res == GTK_RESPONSE_YES || res == GTK_RESPONSE_REJECT)
2451 if (res == GTK_RESPONSE_REJECT &&
2452 empathy_block_individual_dialog_show (parent, individual, NULL))
2454 empathy_individual_manager_set_blocked (manager, individual,
2462 empathy_individual_manager_remove (manager, individual, "");
2467 g_object_unref (individual);
2468 g_object_unref (manager);
2473 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2474 EmpathyLinkingDialog *linking_dialog,
2475 EmpathyIndividualView *self)
2477 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2478 EmpathyIndividualLinker *linker;
2480 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2481 empathy_individual_linker_set_search_text (linker,
2482 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2486 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2488 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2489 FolksIndividual *individual;
2490 GtkWidget *menu = NULL;
2493 gboolean can_remove = FALSE;
2496 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2498 individual = empathy_individual_view_dup_selected (view);
2499 if (individual == NULL)
2502 /* If any of the Individual's personas can be removed, add an option to
2503 * remove. This will act as a best-effort option. If any Personas cannot be
2504 * removed from the server, then this option will just be inactive upon
2505 * subsequent menu openings */
2506 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2508 FolksPersona *persona = FOLKS_PERSONA (l->data);
2509 FolksPersonaStore *store = folks_persona_get_store (persona);
2510 FolksMaybeBool maybe_can_remove =
2511 folks_persona_store_get_can_remove_personas (store);
2513 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2520 menu = empathy_individual_menu_new (individual, priv->individual_features);
2522 /* Remove contact */
2523 if ((priv->view_features &
2524 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2527 /* create the menu if required, or just add a separator */
2529 menu = gtk_menu_new ();
2532 item = gtk_separator_menu_item_new ();
2533 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2534 gtk_widget_show (item);
2538 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2539 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2540 GTK_ICON_SIZE_MENU);
2541 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2542 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2543 gtk_widget_show (item);
2544 g_signal_connect (item, "activate",
2545 G_CALLBACK (individual_view_remove_activate_cb), view);
2548 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2549 * set the live search text on the new linking dialogue to be the same as
2551 g_signal_connect (menu, "link-contacts-activated",
2552 (GCallback) individual_menu_link_contacts_activated_cb, view);
2554 g_object_unref (individual);
2560 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2561 EmpathyLiveSearch *search)
2563 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2565 /* remove old handlers if old search was not null */
2566 if (priv->search_widget != NULL)
2568 g_signal_handlers_disconnect_by_func (view,
2569 individual_view_start_search_cb, NULL);
2571 g_signal_handlers_disconnect_by_func (priv->search_widget,
2572 individual_view_search_text_notify_cb, view);
2573 g_signal_handlers_disconnect_by_func (priv->search_widget,
2574 individual_view_search_activate_cb, view);
2575 g_signal_handlers_disconnect_by_func (priv->search_widget,
2576 individual_view_search_key_navigation_cb, view);
2577 g_signal_handlers_disconnect_by_func (priv->search_widget,
2578 individual_view_search_hide_cb, view);
2579 g_signal_handlers_disconnect_by_func (priv->search_widget,
2580 individual_view_search_show_cb, view);
2581 g_object_unref (priv->search_widget);
2582 priv->search_widget = NULL;
2585 /* connect handlers if new search is not null */
2588 priv->search_widget = g_object_ref (search);
2590 g_signal_connect (view, "start-interactive-search",
2591 G_CALLBACK (individual_view_start_search_cb), NULL);
2593 g_signal_connect (priv->search_widget, "notify::text",
2594 G_CALLBACK (individual_view_search_text_notify_cb), view);
2595 g_signal_connect (priv->search_widget, "activate",
2596 G_CALLBACK (individual_view_search_activate_cb), view);
2597 g_signal_connect (priv->search_widget, "key-navigation",
2598 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2599 g_signal_connect (priv->search_widget, "hide",
2600 G_CALLBACK (individual_view_search_hide_cb), view);
2601 g_signal_connect (priv->search_widget, "show",
2602 G_CALLBACK (individual_view_search_show_cb), view);
2607 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2609 EmpathyIndividualViewPriv *priv;
2611 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2613 priv = GET_PRIV (self);
2615 return (priv->search_widget != NULL &&
2616 gtk_widget_get_visible (priv->search_widget));
2620 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2622 EmpathyIndividualViewPriv *priv;
2624 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2626 priv = GET_PRIV (self);
2628 return priv->show_offline;
2632 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2633 gboolean show_offline)
2635 EmpathyIndividualViewPriv *priv;
2637 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2639 priv = GET_PRIV (self);
2641 priv->show_offline = show_offline;
2643 g_object_notify (G_OBJECT (self), "show-offline");
2644 gtk_tree_model_filter_refilter (priv->filter);
2648 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2650 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2652 return GET_PRIV (self)->show_untrusted;
2656 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2657 gboolean show_untrusted)
2659 EmpathyIndividualViewPriv *priv;
2661 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2663 priv = GET_PRIV (self);
2665 priv->show_untrusted = show_untrusted;
2667 g_object_notify (G_OBJECT (self), "show-untrusted");
2668 gtk_tree_model_filter_refilter (priv->filter);
2671 EmpathyIndividualStore *
2672 empathy_individual_view_get_store (EmpathyIndividualView *self)
2674 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2676 return GET_PRIV (self)->store;
2680 empathy_individual_view_set_store (EmpathyIndividualView *self,
2681 EmpathyIndividualStore *store)
2683 EmpathyIndividualViewPriv *priv;
2685 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2686 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2688 priv = GET_PRIV (self);
2690 /* Destroy the old filter and remove the old store */
2691 if (priv->store != NULL)
2693 g_signal_handlers_disconnect_by_func (priv->store,
2694 individual_view_store_row_changed_cb, self);
2695 g_signal_handlers_disconnect_by_func (priv->store,
2696 individual_view_store_row_deleted_cb, self);
2698 g_signal_handlers_disconnect_by_func (priv->filter,
2699 individual_view_row_has_child_toggled_cb, self);
2701 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2704 tp_clear_object (&priv->filter);
2705 tp_clear_object (&priv->store);
2707 /* Set the new store */
2708 priv->store = store;
2712 g_object_ref (store);
2714 /* Create a new filter */
2715 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2716 GTK_TREE_MODEL (priv->store), NULL));
2717 gtk_tree_model_filter_set_visible_func (priv->filter,
2718 individual_view_filter_visible_func, self, NULL);
2720 g_signal_connect (priv->filter, "row-has-child-toggled",
2721 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2722 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2723 GTK_TREE_MODEL (priv->filter));
2725 tp_g_signal_connect_object (priv->store, "row-changed",
2726 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2727 tp_g_signal_connect_object (priv->store, "row-inserted",
2728 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2729 tp_g_signal_connect_object (priv->store, "row-deleted",
2730 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2735 empathy_individual_view_start_search (EmpathyIndividualView *self)
2737 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2739 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2740 g_return_if_fail (priv->search_widget != NULL);
2742 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2743 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2745 gtk_widget_show (GTK_WIDGET (priv->search_widget));