1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-individual-manager.h>
42 #include <libempathy/empathy-contact-groups.h>
43 #include <libempathy/empathy-request-util.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-contact-dialogs.h"
50 #include "empathy-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
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_UNKNOWN = -1,
122 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
123 DND_DRAG_TYPE_PERSONA_ID,
124 DND_DRAG_TYPE_URI_LIST,
125 DND_DRAG_TYPE_STRING,
128 #define DRAG_TYPE(T,I) \
129 { (gchar *) T, 0, I }
131 static const GtkTargetEntry drag_types_dest[] = {
132 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
133 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
134 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
135 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
136 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
137 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
140 static const GtkTargetEntry drag_types_source[] = {
141 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
146 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
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 EmpathyIndividualManager *manager = NULL;
413 FolksIndividual *individual = NULL;
414 FolksPersona *persona = NULL;
415 const gchar *persona_uid;
416 GList *individuals, *l;
417 gboolean retval = FALSE;
419 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
421 /* FIXME: This is slow, but the only way to find the Persona we're having
423 manager = empathy_individual_manager_dup_singleton ();
424 individuals = empathy_individual_manager_get_members (manager);
426 for (l = individuals; l != NULL; l = l->next)
430 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
432 for (p = personas; p != NULL; p = p->next)
434 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
437 persona = g_object_ref (p->data);
438 individual = g_object_ref (l->data);
445 g_list_free (individuals);
447 if (persona == NULL || individual == NULL)
449 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
453 /* Emit a signal notifying of the drag. We change the Individual's groups in
454 * the default signal handler. */
455 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
456 gdk_drag_context_get_selected_action (context), persona, individual,
460 tp_clear_object (&manager);
461 tp_clear_object (&persona);
462 tp_clear_object (&individual);
468 individual_view_file_drag_received (GtkWidget *view,
469 GdkDragContext *context,
472 GtkSelectionData *selection)
475 const gchar *sel_data;
476 FolksIndividual *individual;
477 EmpathyContact *contact;
479 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
481 gtk_tree_model_get_iter (model, &iter, path);
482 gtk_tree_model_get (model, &iter,
483 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
484 if (individual == NULL)
487 contact = empathy_contact_dup_from_folks_individual (individual);
488 empathy_send_file_from_uri_list (contact, sel_data);
490 g_object_unref (individual);
491 tp_clear_object (&contact);
497 individual_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
501 GtkSelectionData *selection,
507 GtkTreeViewDropPosition position;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
515 x, y, &path, &position);
520 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
522 success = individual_view_individual_drag_received (view,
523 context, model, path, selection);
525 else if (info == DND_DRAG_TYPE_PERSONA_ID)
527 success = individual_view_persona_drag_received (view, context, model,
530 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
532 success = individual_view_file_drag_received (view,
533 context, model, path, selection);
536 gtk_tree_path_free (path);
537 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
541 individual_view_drag_motion_cb (DragMotionData *data)
543 if (data->view != NULL)
545 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
546 g_object_remove_weak_pointer (G_OBJECT (data->view),
547 (gpointer *) &data->view);
550 data->timeout_id = 0;
555 /* Minimum distance between the mouse pointer and a horizontal border when we
556 start auto scrolling. */
557 #define AUTO_SCROLL_MARGIN_SIZE 20
558 /* How far to scroll per one tick. */
559 #define AUTO_SCROLL_PITCH 10
562 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
564 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
568 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
570 if (priv->distance < 0)
571 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
573 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
575 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
576 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
578 gtk_adjustment_set_value (adj, new_value);
584 individual_view_drag_motion (GtkWidget *widget,
585 GdkDragContext *context,
590 EmpathyIndividualViewPriv *priv;
594 static DragMotionData *dm = NULL;
597 gboolean is_different = FALSE;
598 gboolean cleanup = TRUE;
599 gboolean retval = TRUE;
600 GtkAllocation allocation;
602 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
604 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
605 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
608 if (priv->auto_scroll_timeout_id != 0)
610 g_source_remove (priv->auto_scroll_timeout_id);
611 priv->auto_scroll_timeout_id = 0;
614 gtk_widget_get_allocation (widget, &allocation);
616 if (y < AUTO_SCROLL_MARGIN_SIZE ||
617 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
619 if (y < AUTO_SCROLL_MARGIN_SIZE)
620 priv->distance = MIN (-y, -1);
622 priv->distance = MAX (allocation.height - y, 1);
624 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
625 (GSourceFunc) individual_view_auto_scroll_cb, widget);
628 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
629 x, y, &path, NULL, NULL, NULL);
631 cleanup &= (dm == NULL);
635 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
636 is_different = ((dm == NULL) || ((dm != NULL)
637 && gtk_tree_path_compare (dm->path, path) != 0));
644 /* Coordinates don't point to an actual row, so make sure the pointer
645 and highlighting don't indicate that a drag is possible.
647 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
648 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
651 target = gtk_drag_dest_find_target (widget, context, NULL);
652 gtk_tree_model_get_iter (model, &iter, path);
654 /* Determine the DndDragType of the data */
655 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
657 if (target == drag_atoms_dest[i])
659 drag_type = drag_types_dest[i].info;
664 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
665 drag_type == DND_DRAG_TYPE_STRING)
667 /* This is a file drag, and it can only be dropped on contacts,
669 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
670 * even if we have a valid target. */
671 FolksIndividual *individual = NULL;
672 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
674 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
676 gtk_tree_model_get (model, &iter,
677 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
681 if (individual != NULL)
683 EmpathyContact *contact = NULL;
685 contact = empathy_contact_dup_from_folks_individual (individual);
686 caps = empathy_contact_get_capabilities (contact);
688 tp_clear_object (&contact);
691 if (individual != NULL &&
692 folks_presence_details_is_online (
693 FOLKS_PRESENCE_DETAILS (individual)) &&
694 (caps & EMPATHY_CAPABILITIES_FT))
696 gdk_drag_status (context, GDK_ACTION_COPY, time_);
697 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
698 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
702 gdk_drag_status (context, 0, time_);
703 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
707 if (individual != NULL)
708 g_object_unref (individual);
710 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
711 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
712 priv->drag_row == NULL)) ||
713 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
714 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
716 /* If target != GDK_NONE, then we have a contact (individual or persona)
717 drag. If we're pointing to a group, highlight it. Otherwise, if the
718 contact we're pointing to is in a group, highlight that. Otherwise,
719 set the drag position to before the first row for a drag into
720 the "non-group" at the top.
721 If it's an Individual:
722 We only highlight things if the contact is from a different
723 Individual view, or if this Individual view has
724 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
725 which don't have FEATURE_GROUPS_CHANGE, but do have
726 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
728 We only highlight things if we have FEATURE_PERSONA_DROP.
730 GtkTreeIter group_iter;
732 GtkTreePath *group_path;
733 gtk_tree_model_get (model, &iter,
734 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
741 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
742 gtk_tree_model_get (model, &group_iter,
743 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
747 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
748 group_path = gtk_tree_model_get_path (model, &group_iter);
749 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
750 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
751 gtk_tree_path_free (group_path);
755 group_path = gtk_tree_path_new_first ();
756 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
757 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
758 group_path, GTK_TREE_VIEW_DROP_BEFORE);
762 if (!is_different && !cleanup)
767 gtk_tree_path_free (dm->path);
770 g_source_remove (dm->timeout_id);
778 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
780 dm = g_new0 (DragMotionData, 1);
782 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
783 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
784 dm->path = gtk_tree_path_copy (path);
786 dm->timeout_id = g_timeout_add_seconds (1,
787 (GSourceFunc) individual_view_drag_motion_cb, dm);
794 individual_view_drag_begin (GtkWidget *widget,
795 GdkDragContext *context)
797 EmpathyIndividualViewPriv *priv;
798 GtkTreeSelection *selection;
803 priv = GET_PRIV (widget);
805 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
808 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
809 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
812 path = gtk_tree_model_get_path (model, &iter);
813 priv->drag_row = gtk_tree_row_reference_new (model, path);
814 gtk_tree_path_free (path);
818 individual_view_drag_data_get (GtkWidget *widget,
819 GdkDragContext *context,
820 GtkSelectionData *selection,
824 EmpathyIndividualViewPriv *priv;
825 GtkTreePath *src_path;
828 FolksIndividual *individual;
829 const gchar *individual_id;
831 priv = GET_PRIV (widget);
833 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
834 if (priv->drag_row == NULL)
837 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
838 if (src_path == NULL)
841 if (!gtk_tree_model_get_iter (model, &iter, src_path))
843 gtk_tree_path_free (src_path);
847 gtk_tree_path_free (src_path);
850 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
851 if (individual == NULL)
854 individual_id = folks_individual_get_id (individual);
856 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
858 gtk_selection_data_set (selection,
859 gdk_atom_intern ("text/individual-id", FALSE), 8,
860 (guchar *) individual_id, strlen (individual_id) + 1);
863 g_object_unref (individual);
867 individual_view_drag_end (GtkWidget *widget,
868 GdkDragContext *context)
870 EmpathyIndividualViewPriv *priv;
872 priv = GET_PRIV (widget);
874 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
879 gtk_tree_row_reference_free (priv->drag_row);
880 priv->drag_row = NULL;
885 individual_view_drag_drop (GtkWidget *widget,
886 GdkDragContext *drag_context,
896 EmpathyIndividualView *view;
902 menu_deactivate_cb (GtkMenuShell *menushell,
905 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
906 g_signal_handlers_disconnect_by_func (menushell,
907 menu_deactivate_cb, user_data);
909 gtk_menu_detach (GTK_MENU (menushell));
913 individual_view_popup_menu_idle_cb (gpointer user_data)
915 MenuPopupData *data = user_data;
918 menu = empathy_individual_view_get_individual_menu (data->view);
920 menu = empathy_individual_view_get_group_menu (data->view);
924 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
926 gtk_widget_show (menu);
927 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
930 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
931 * floating ref. We can either wait that the treeview releases its ref
932 * when it will be destroyed (when leaving Empathy) or explicitely
933 * detach the menu when it's not displayed any more.
934 * We go for the latter as we don't want to keep useless menus in memory
935 * during the whole lifetime of Empathy. */
936 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
940 g_slice_free (MenuPopupData, data);
946 individual_view_button_press_event_cb (EmpathyIndividualView *view,
947 GdkEventButton *event,
950 if (event->button == 3)
954 data = g_slice_new (MenuPopupData);
956 data->button = event->button;
957 data->time = event->time;
958 g_idle_add (individual_view_popup_menu_idle_cb, data);
965 individual_view_key_press_event_cb (EmpathyIndividualView *view,
969 if (event->keyval == GDK_KEY_Menu)
973 data = g_slice_new (MenuPopupData);
976 data->time = event->time;
977 g_idle_add (individual_view_popup_menu_idle_cb, data);
978 } else if (event->keyval == GDK_KEY_F2) {
979 FolksIndividual *individual;
980 EmpathyContact *contact;
982 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
984 individual = empathy_individual_view_dup_selected (view);
985 if (individual == NULL)
988 contact = empathy_contact_dup_from_folks_individual (individual);
989 if (contact == NULL) {
990 g_object_unref (individual);
993 empathy_contact_edit_dialog_show (contact, NULL);
995 g_object_unref (individual);
996 g_object_unref (contact);
1003 individual_view_row_activated (GtkTreeView *view,
1005 GtkTreeViewColumn *column)
1007 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1008 FolksIndividual *individual;
1009 EmpathyContact *contact;
1010 GtkTreeModel *model;
1013 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1016 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1017 gtk_tree_model_get_iter (model, &iter, path);
1018 gtk_tree_model_get (model, &iter,
1019 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1021 if (individual == NULL)
1024 /* Determine which Persona to chat to, by choosing the most available one. */
1025 contact = empathy_contact_dup_best_for_action (individual,
1026 EMPATHY_ACTION_CHAT);
1028 if (contact != NULL)
1030 DEBUG ("Starting a chat");
1032 empathy_chat_with_contact (contact,
1033 gtk_get_current_event_time ());
1036 g_object_unref (individual);
1037 tp_clear_object (&contact);
1041 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1042 const gchar *path_string,
1043 EmpathyIndividualView *view)
1045 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1047 GtkTreeModel *model;
1049 FolksIndividual *individual;
1050 GdkEventButton *event;
1051 GtkMenuShell *shell;
1054 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1057 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1058 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1061 gtk_tree_model_get (model, &iter,
1062 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1063 if (individual == NULL)
1066 event = (GdkEventButton *) gtk_get_current_event ();
1068 menu = empathy_context_menu_new (GTK_WIDGET (view));
1069 shell = GTK_MENU_SHELL (menu);
1072 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1073 gtk_menu_shell_append (shell, item);
1074 gtk_widget_show (item);
1077 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1078 gtk_menu_shell_append (shell, item);
1079 gtk_widget_show (item);
1081 gtk_widget_show (menu);
1082 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1083 event->button, event->time);
1085 g_object_unref (individual);
1089 individual_view_cell_set_background (EmpathyIndividualView *view,
1090 GtkCellRenderer *cell,
1094 if (!is_group && is_active)
1096 GtkStyleContext *style;
1099 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1101 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1104 /* Here we take the current theme colour and add it to
1105 * the colour for white and average the two. This
1106 * gives a colour which is inline with the theme but
1109 empathy_make_color_whiter (&color);
1111 g_object_set (cell, "cell-background-rgba", &color, NULL);
1114 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1118 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1119 GtkCellRenderer *cell,
1120 GtkTreeModel *model,
1122 EmpathyIndividualView *view)
1128 gtk_tree_model_get (model, iter,
1129 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1130 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1131 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1134 "visible", !is_group,
1138 tp_clear_object (&pixbuf);
1140 individual_view_cell_set_background (view, cell, is_group, is_active);
1144 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1145 GtkCellRenderer *cell,
1146 GtkTreeModel *model,
1148 EmpathyIndividualView *view)
1150 GdkPixbuf *pixbuf = NULL;
1154 gtk_tree_model_get (model, iter,
1155 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1156 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1161 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1163 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1164 GTK_ICON_SIZE_MENU);
1166 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1168 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1169 GTK_ICON_SIZE_MENU);
1174 "visible", pixbuf != NULL,
1178 tp_clear_object (&pixbuf);
1184 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1185 GtkCellRenderer *cell,
1186 GtkTreeModel *model,
1188 EmpathyIndividualView *view)
1192 gboolean can_audio, can_video;
1194 gtk_tree_model_get (model, iter,
1195 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1196 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1197 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1198 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1201 "visible", !is_group && (can_audio || can_video),
1202 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1205 individual_view_cell_set_background (view, cell, is_group, is_active);
1209 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1210 GtkCellRenderer *cell,
1211 GtkTreeModel *model,
1213 EmpathyIndividualView *view)
1216 gboolean show_avatar;
1220 gtk_tree_model_get (model, iter,
1221 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1222 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1223 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1224 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1227 "visible", !is_group && show_avatar,
1231 tp_clear_object (&pixbuf);
1233 individual_view_cell_set_background (view, cell, is_group, is_active);
1237 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1238 GtkCellRenderer *cell,
1239 GtkTreeModel *model,
1241 EmpathyIndividualView *view)
1246 gtk_tree_model_get (model, iter,
1247 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1248 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1250 individual_view_cell_set_background (view, cell, is_group, is_active);
1254 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1255 GtkCellRenderer *cell,
1256 GtkTreeModel *model,
1258 EmpathyIndividualView *view)
1263 gtk_tree_model_get (model, iter,
1264 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1265 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1267 if (gtk_tree_model_iter_has_child (model, iter))
1270 gboolean row_expanded;
1272 path = gtk_tree_model_get_path (model, iter);
1274 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1275 (gtk_tree_view_column_get_tree_view (column)), path);
1276 gtk_tree_path_free (path);
1281 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1285 g_object_set (cell, "visible", FALSE, NULL);
1287 individual_view_cell_set_background (view, cell, is_group, is_active);
1291 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1296 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1297 GtkTreeModel *model;
1301 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1304 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1306 gtk_tree_model_get (model, iter,
1307 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1309 expanded = GPOINTER_TO_INT (user_data);
1310 empathy_contact_group_set_expanded (name, expanded);
1316 individual_view_start_search_cb (EmpathyIndividualView *view,
1319 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1321 if (priv->search_widget == NULL)
1324 empathy_individual_view_start_search (view);
1330 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1332 EmpathyIndividualView *view)
1334 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1336 GtkTreeViewColumn *focus_column;
1337 GtkTreeModel *model;
1339 gboolean set_cursor = FALSE;
1341 gtk_tree_model_filter_refilter (priv->filter);
1343 /* Set cursor on the first contact. If it is already set on a group,
1344 * set it on its first child contact. Note that first child of a group
1345 * is its separator, that's why we actually set to the 2nd
1348 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1349 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1353 path = gtk_tree_path_new_from_string ("0:1");
1356 else if (gtk_tree_path_get_depth (path) < 2)
1360 gtk_tree_model_get_iter (model, &iter, path);
1361 gtk_tree_model_get (model, &iter,
1362 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1367 gtk_tree_path_down (path);
1368 gtk_tree_path_next (path);
1375 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1377 if (gtk_tree_model_get_iter (model, &iter, path))
1379 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1384 gtk_tree_path_free (path);
1388 individual_view_search_activate_cb (GtkWidget *search,
1389 EmpathyIndividualView *view)
1392 GtkTreeViewColumn *focus_column;
1394 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1397 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1398 gtk_tree_path_free (path);
1400 gtk_widget_hide (search);
1405 individual_view_search_key_navigation_cb (GtkWidget *search,
1407 EmpathyIndividualView *view)
1409 GdkEventKey *eventkey = ((GdkEventKey *) event);
1410 gboolean ret = FALSE;
1412 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1413 || eventkey->keyval == GDK_KEY_F2)
1415 GdkEvent *new_event;
1417 new_event = gdk_event_copy (event);
1418 gtk_widget_grab_focus (GTK_WIDGET (view));
1419 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1420 gtk_widget_grab_focus (search);
1422 gdk_event_free (new_event);
1429 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1430 EmpathyIndividualView *view)
1432 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1433 GtkTreeModel *model;
1434 GtkTreePath *cursor_path;
1436 gboolean valid = FALSE;
1438 /* block expand or collapse handlers, they would write the
1439 * expand or collapsed setting to file otherwise */
1440 g_signal_handlers_block_by_func (view,
1441 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1442 g_signal_handlers_block_by_func (view,
1443 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1445 /* restore which groups are expanded and which are not */
1446 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1447 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1448 valid; valid = gtk_tree_model_iter_next (model, &iter))
1454 gtk_tree_model_get (model, &iter,
1455 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1456 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1465 path = gtk_tree_model_get_path (model, &iter);
1466 if ((priv->view_features &
1467 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1468 empathy_contact_group_get_expanded (name))
1470 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1474 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1477 gtk_tree_path_free (path);
1481 /* unblock expand or collapse handlers */
1482 g_signal_handlers_unblock_by_func (view,
1483 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1484 g_signal_handlers_unblock_by_func (view,
1485 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1487 /* keep the selected contact visible */
1488 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1490 if (cursor_path != NULL)
1491 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1494 gtk_tree_path_free (cursor_path);
1498 individual_view_search_show_cb (EmpathyLiveSearch *search,
1499 EmpathyIndividualView *view)
1501 /* block expand or collapse handlers during expand all, they would
1502 * write the expand or collapsed setting to file otherwise */
1503 g_signal_handlers_block_by_func (view,
1504 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1506 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1508 g_signal_handlers_unblock_by_func (view,
1509 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1513 expand_idle_foreach_cb (GtkTreeModel *model,
1516 EmpathyIndividualView *self)
1518 EmpathyIndividualViewPriv *priv;
1520 gpointer should_expand;
1523 /* We only want groups */
1524 if (gtk_tree_path_get_depth (path) > 1)
1527 gtk_tree_model_get (model, iter,
1528 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1529 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1532 if (is_group == FALSE)
1538 priv = GET_PRIV (self);
1540 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1541 &should_expand) == TRUE)
1543 if (GPOINTER_TO_INT (should_expand) == TRUE)
1544 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1546 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1548 g_hash_table_remove (priv->expand_groups, name);
1557 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1559 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1561 DEBUG ("individual_view_expand_idle_cb");
1563 g_signal_handlers_block_by_func (self,
1564 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1565 g_signal_handlers_block_by_func (self,
1566 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1568 /* The store/filter could've been removed while we were in the idle queue */
1569 if (priv->filter != NULL)
1571 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1572 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1575 g_signal_handlers_unblock_by_func (self,
1576 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1577 g_signal_handlers_unblock_by_func (self,
1578 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1580 /* Empty the table of groups to expand/contract, since it may contain groups
1581 * which no longer exist in the tree view. This can happen after going
1582 * offline, for example. */
1583 g_hash_table_remove_all (priv->expand_groups);
1584 priv->expand_groups_idle_handler = 0;
1585 g_object_unref (self);
1591 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1594 EmpathyIndividualView *view)
1596 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1597 gboolean should_expand, is_group = FALSE;
1599 gpointer will_expand;
1601 gtk_tree_model_get (model, iter,
1602 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1603 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1606 if (!is_group || EMP_STR_EMPTY (name))
1612 should_expand = (priv->view_features &
1613 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1614 (priv->search_widget != NULL &&
1615 gtk_widget_get_visible (priv->search_widget)) ||
1616 empathy_contact_group_get_expanded (name);
1618 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1619 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1620 * a hash table, and expand or contract them as appropriate all at once in
1621 * an idle handler which iterates over all the group rows. */
1622 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1623 &will_expand) == FALSE ||
1624 GPOINTER_TO_INT (will_expand) != should_expand)
1626 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1627 GINT_TO_POINTER (should_expand));
1629 if (priv->expand_groups_idle_handler == 0)
1631 priv->expand_groups_idle_handler =
1632 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1633 g_object_ref (view));
1640 /* FIXME: This is a workaround for bgo#621076 */
1642 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1645 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1646 GtkTreeModel *model;
1647 GtkTreePath *parent_path;
1648 GtkTreeIter parent_iter;
1650 if (gtk_tree_path_get_depth (path) < 2)
1653 /* A group row is visible if and only if at least one if its child is visible.
1654 * So when a row is inserted/deleted/changed in the base model, that could
1655 * modify the visibility of its parent in the filter model.
1658 model = GTK_TREE_MODEL (priv->store);
1659 parent_path = gtk_tree_path_copy (path);
1660 gtk_tree_path_up (parent_path);
1661 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1663 /* This tells the filter to verify the visibility of that row, and
1664 * show/hide it if necessary */
1665 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1666 parent_path, &parent_iter);
1668 gtk_tree_path_free (parent_path);
1672 individual_view_store_row_changed_cb (GtkTreeModel *model,
1675 EmpathyIndividualView *view)
1677 individual_view_verify_group_visibility (view, path);
1681 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1683 EmpathyIndividualView *view)
1685 individual_view_verify_group_visibility (view, path);
1689 individual_view_is_visible_individual (EmpathyIndividualView *self,
1690 FolksIndividual *individual,
1692 gboolean is_searching)
1694 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1695 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1697 GList *personas, *l;
1698 gboolean is_favorite, contains_interesting_persona = FALSE;
1700 /* We're only giving the visibility wrt filtering here, not things like
1702 if (priv->show_untrusted == FALSE &&
1703 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1708 /* Hide all individuals which consist entirely of uninteresting personas */
1709 personas = folks_individual_get_personas (individual);
1710 for (l = personas; l; l = l->next)
1712 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1714 contains_interesting_persona = TRUE;
1719 if (contains_interesting_persona == FALSE)
1722 is_favorite = folks_favourite_details_get_is_favourite (
1723 FOLKS_FAVOURITE_DETAILS (individual));
1724 if (is_searching == FALSE)
1725 return (priv->show_offline || is_online || is_favorite);
1727 /* check alias name */
1728 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1730 if (empathy_live_search_match (live, str))
1733 /* check contact id, remove the @server.com part */
1734 for (l = personas; l; l = l->next)
1737 gchar *dup_str = NULL;
1740 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1743 str = folks_persona_get_display_id (l->data);
1744 p = strstr (str, "@");
1746 str = dup_str = g_strndup (str, p - str);
1748 visible = empathy_live_search_match (live, str);
1754 /* FIXME: Add more rules here, we could check phone numbers in
1755 * contact's vCard for example. */
1761 individual_view_filter_visible_func (GtkTreeModel *model,
1765 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1766 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1767 FolksIndividual *individual = NULL;
1768 gboolean is_group, is_separator, valid;
1769 GtkTreeIter child_iter;
1770 gboolean visible, is_online;
1771 gboolean is_searching = TRUE;
1773 if (priv->search_widget == NULL ||
1774 !gtk_widget_get_visible (priv->search_widget))
1775 is_searching = FALSE;
1777 gtk_tree_model_get (model, iter,
1778 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1779 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1780 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1781 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1784 if (individual != NULL)
1786 visible = individual_view_is_visible_individual (self, individual,
1787 is_online, is_searching);
1789 g_object_unref (individual);
1791 /* FIXME: Work around bgo#626552/bgo#621076 */
1792 if (visible == TRUE)
1794 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1795 individual_view_verify_group_visibility (self, path);
1796 gtk_tree_path_free (path);
1805 /* Not a contact, not a separator, must be a group */
1806 g_return_val_if_fail (is_group, FALSE);
1808 /* only show groups which are not empty */
1809 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1810 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1812 gtk_tree_model_get (model, &child_iter,
1813 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1814 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1817 if (individual == NULL)
1820 visible = individual_view_is_visible_individual (self, individual,
1821 is_online, is_searching);
1822 g_object_unref (individual);
1824 /* show group if it has at least one visible contact in it */
1825 if (visible == TRUE)
1833 individual_view_constructed (GObject *object)
1835 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1836 GtkCellRenderer *cell;
1837 GtkTreeViewColumn *col;
1842 "headers-visible", FALSE,
1843 "show-expanders", FALSE,
1846 col = gtk_tree_view_column_new ();
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_pixbuf_cell_data_func,
1862 cell = gtk_cell_renderer_pixbuf_new ();
1863 gtk_tree_view_column_pack_start (col, cell, FALSE);
1864 gtk_tree_view_column_set_cell_data_func (col, cell,
1865 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1877 cell = empathy_cell_renderer_text_new ();
1878 gtk_tree_view_column_pack_start (col, cell, TRUE);
1879 gtk_tree_view_column_set_cell_data_func (col, cell,
1880 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1882 gtk_tree_view_column_add_attribute (col, cell,
1883 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1884 gtk_tree_view_column_add_attribute (col, cell,
1885 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1886 gtk_tree_view_column_add_attribute (col, cell,
1887 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1888 gtk_tree_view_column_add_attribute (col, cell,
1889 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1890 gtk_tree_view_column_add_attribute (col, cell,
1891 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1892 gtk_tree_view_column_add_attribute (col, cell,
1893 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1894 gtk_tree_view_column_add_attribute (col, cell,
1895 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1897 /* Audio Call Icon */
1898 cell = empathy_cell_renderer_activatable_new ();
1899 gtk_tree_view_column_pack_start (col, cell, FALSE);
1900 gtk_tree_view_column_set_cell_data_func (col, cell,
1901 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1904 g_object_set (cell, "visible", FALSE, NULL);
1906 g_signal_connect (cell, "path-activated",
1907 G_CALLBACK (individual_view_call_activated_cb), view);
1910 cell = gtk_cell_renderer_pixbuf_new ();
1911 gtk_tree_view_column_pack_start (col, cell, FALSE);
1912 gtk_tree_view_column_set_cell_data_func (col, cell,
1913 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1925 cell = empathy_cell_renderer_expander_new ();
1926 gtk_tree_view_column_pack_end (col, cell, FALSE);
1927 gtk_tree_view_column_set_cell_data_func (col, cell,
1928 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1931 /* Actually add the column now we have added all cell renderers */
1932 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1935 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1937 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1942 individual_view_set_view_features (EmpathyIndividualView *view,
1943 EmpathyIndividualFeatureFlags features)
1945 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1946 gboolean has_tooltip;
1948 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1950 priv->view_features = features;
1952 /* Setting reorderable is a hack that gets us row previews as drag icons
1953 for free. We override all the drag handlers. It's tricky to get the
1954 position of the drag icon right in drag_begin. GtkTreeView has special
1955 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1958 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1959 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1961 /* Update DnD source/dest */
1962 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1964 gtk_drag_source_set (GTK_WIDGET (view),
1967 G_N_ELEMENTS (drag_types_source),
1968 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1972 gtk_drag_source_unset (GTK_WIDGET (view));
1976 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1978 gtk_drag_dest_set (GTK_WIDGET (view),
1979 GTK_DEST_DEFAULT_ALL,
1981 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1985 /* FIXME: URI could still be droped depending on FT feature */
1986 gtk_drag_dest_unset (GTK_WIDGET (view));
1989 /* Update has-tooltip */
1991 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1992 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1996 individual_view_dispose (GObject *object)
1998 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1999 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2001 tp_clear_object (&priv->store);
2002 tp_clear_object (&priv->filter);
2003 tp_clear_object (&priv->tooltip_widget);
2005 empathy_individual_view_set_live_search (view, NULL);
2007 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2011 individual_view_finalize (GObject *object)
2013 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2015 if (priv->expand_groups_idle_handler != 0)
2016 g_source_remove (priv->expand_groups_idle_handler);
2017 g_hash_table_destroy (priv->expand_groups);
2019 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2023 individual_view_get_property (GObject *object,
2028 EmpathyIndividualViewPriv *priv;
2030 priv = GET_PRIV (object);
2035 g_value_set_object (value, priv->store);
2037 case PROP_VIEW_FEATURES:
2038 g_value_set_flags (value, priv->view_features);
2040 case PROP_INDIVIDUAL_FEATURES:
2041 g_value_set_flags (value, priv->individual_features);
2043 case PROP_SHOW_OFFLINE:
2044 g_value_set_boolean (value, priv->show_offline);
2046 case PROP_SHOW_UNTRUSTED:
2047 g_value_set_boolean (value, priv->show_untrusted);
2050 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2056 individual_view_set_property (GObject *object,
2058 const GValue *value,
2061 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2062 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2067 empathy_individual_view_set_store (view, g_value_get_object (value));
2069 case PROP_VIEW_FEATURES:
2070 individual_view_set_view_features (view, g_value_get_flags (value));
2072 case PROP_INDIVIDUAL_FEATURES:
2073 priv->individual_features = g_value_get_flags (value);
2075 case PROP_SHOW_OFFLINE:
2076 empathy_individual_view_set_show_offline (view,
2077 g_value_get_boolean (value));
2079 case PROP_SHOW_UNTRUSTED:
2080 empathy_individual_view_set_show_untrusted (view,
2081 g_value_get_boolean (value));
2084 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2090 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2092 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2093 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2094 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2096 object_class->constructed = individual_view_constructed;
2097 object_class->dispose = individual_view_dispose;
2098 object_class->finalize = individual_view_finalize;
2099 object_class->get_property = individual_view_get_property;
2100 object_class->set_property = individual_view_set_property;
2102 widget_class->drag_data_received = individual_view_drag_data_received;
2103 widget_class->drag_drop = individual_view_drag_drop;
2104 widget_class->drag_begin = individual_view_drag_begin;
2105 widget_class->drag_data_get = individual_view_drag_data_get;
2106 widget_class->drag_end = individual_view_drag_end;
2107 widget_class->drag_motion = individual_view_drag_motion;
2109 /* We use the class method to let user of this widget to connect to
2110 * the signal and stop emission of the signal so the default handler
2111 * won't be called. */
2112 tree_view_class->row_activated = individual_view_row_activated;
2114 klass->drag_individual_received = real_drag_individual_received_cb;
2116 signals[DRAG_INDIVIDUAL_RECEIVED] =
2117 g_signal_new ("drag-individual-received",
2118 G_OBJECT_CLASS_TYPE (klass),
2120 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2122 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2123 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2124 G_TYPE_STRING, G_TYPE_STRING);
2126 signals[DRAG_PERSONA_RECEIVED] =
2127 g_signal_new ("drag-persona-received",
2128 G_OBJECT_CLASS_TYPE (klass),
2130 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2132 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2133 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2135 g_object_class_install_property (object_class,
2137 g_param_spec_object ("store",
2138 "The store of the view",
2139 "The store of the view",
2140 EMPATHY_TYPE_INDIVIDUAL_STORE,
2141 G_PARAM_READWRITE));
2142 g_object_class_install_property (object_class,
2144 g_param_spec_flags ("view-features",
2145 "Features of the view",
2146 "Flags for all enabled features",
2147 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2148 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2149 g_object_class_install_property (object_class,
2150 PROP_INDIVIDUAL_FEATURES,
2151 g_param_spec_flags ("individual-features",
2152 "Features of the individual menu",
2153 "Flags for all enabled features for the menu",
2154 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2155 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2156 g_object_class_install_property (object_class,
2158 g_param_spec_boolean ("show-offline",
2160 "Whether contact list should display "
2161 "offline contacts", FALSE, G_PARAM_READWRITE));
2162 g_object_class_install_property (object_class,
2163 PROP_SHOW_UNTRUSTED,
2164 g_param_spec_boolean ("show-untrusted",
2165 "Show Untrusted Individuals",
2166 "Whether the view should display untrusted individuals; "
2167 "those who could not be who they say they are.",
2168 TRUE, G_PARAM_READWRITE));
2170 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2174 empathy_individual_view_init (EmpathyIndividualView *view)
2176 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2177 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2181 priv->show_untrusted = TRUE;
2183 /* Get saved group states. */
2184 empathy_contact_groups_get_all ();
2186 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2187 (GDestroyNotify) g_free, NULL);
2189 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2190 empathy_individual_store_row_separator_func, NULL, NULL);
2192 /* Connect to tree view signals rather than override. */
2193 g_signal_connect (view, "button-press-event",
2194 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2195 g_signal_connect (view, "key-press-event",
2196 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2197 g_signal_connect (view, "row-expanded",
2198 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2199 GINT_TO_POINTER (TRUE));
2200 g_signal_connect (view, "row-collapsed",
2201 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2202 GINT_TO_POINTER (FALSE));
2203 g_signal_connect (view, "query-tooltip",
2204 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2207 EmpathyIndividualView *
2208 empathy_individual_view_new (EmpathyIndividualStore *store,
2209 EmpathyIndividualViewFeatureFlags view_features,
2210 EmpathyIndividualFeatureFlags individual_features)
2212 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2214 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2216 "individual-features", individual_features,
2217 "view-features", view_features, NULL);
2221 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2223 GtkTreeSelection *selection;
2225 GtkTreeModel *model;
2226 FolksIndividual *individual;
2228 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2230 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2231 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2234 gtk_tree_model_get (model, &iter,
2235 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2241 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2242 gboolean *is_fake_group)
2244 GtkTreeSelection *selection;
2246 GtkTreeModel *model;
2251 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2253 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2254 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2257 gtk_tree_model_get (model, &iter,
2258 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2259 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2260 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2268 if (is_fake_group != NULL)
2269 *is_fake_group = fake;
2276 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2277 REMOVE_DIALOG_RESPONSE_DELETE,
2278 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2282 individual_view_remove_dialog_show (GtkWindow *parent,
2283 const gchar *message,
2284 const gchar *secondary_text,
2285 gboolean block_button,
2291 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2292 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2296 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2297 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2298 gtk_widget_show (image);
2305 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2306 * mnemonic so we have to create the button manually. */
2307 button = gtk_button_new_with_mnemonic (
2308 _("Delete and _Block"));
2310 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2311 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2313 gtk_widget_show (button);
2316 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2317 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2318 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2319 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2320 "%s", secondary_text);
2322 gtk_widget_show (dialog);
2324 res = gtk_dialog_run (GTK_DIALOG (dialog));
2325 gtk_widget_destroy (dialog);
2331 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2332 EmpathyIndividualView *view)
2336 group = empathy_individual_view_dup_selected_group (view, NULL);
2343 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2345 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2346 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2347 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2349 EmpathyIndividualManager *manager =
2350 empathy_individual_manager_dup_singleton ();
2351 empathy_individual_manager_remove_group (manager, group);
2352 g_object_unref (G_OBJECT (manager));
2362 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2364 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2369 gboolean is_fake_group;
2371 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2373 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2374 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2377 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2378 if (!group || is_fake_group)
2380 /* We can't alter fake groups */
2385 menu = gtk_menu_new ();
2388 if (priv->view_features &
2389 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2390 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
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_rename_activate_cb),
2399 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2401 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2402 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2403 GTK_ICON_SIZE_MENU);
2404 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2405 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2406 gtk_widget_show (item);
2407 g_signal_connect (item, "activate",
2408 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2417 got_avatar (GObject *source_object,
2418 GAsyncResult *result,
2421 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2422 EmpathyIndividualView *view = user_data;
2424 EmpathyIndividualManager *manager;
2427 GList *l, *personas;
2428 guint persona_count = 0;
2430 GError *error = NULL;
2433 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2438 DEBUG ("Could not get avatar: %s", error->message);
2439 g_error_free (error);
2442 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2443 * so we still display the remove dialog. */
2445 personas = folks_individual_get_personas (individual);
2447 /* If we have more than one TpfPersona, display a different message
2448 * ensuring the user knows that *all* of the meta-contacts' personas will
2450 for (l = personas; l != NULL; l = l->next)
2452 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2456 if (persona_count >= 2)
2460 if (persona_count < 2)
2462 /* Not a meta-contact */
2465 _("Do you really want to remove the contact '%s'?"),
2466 folks_alias_details_get_alias (
2467 FOLKS_ALIAS_DETAILS (individual)));
2474 _("Do you really want to remove the linked contact '%s'? "
2475 "Note that this will remove all the contacts which make up "
2476 "this linked contact."),
2477 folks_alias_details_get_alias (
2478 FOLKS_ALIAS_DETAILS (individual)));
2482 manager = empathy_individual_manager_dup_singleton ();
2483 can_block = empathy_individual_manager_supports_blocking (manager,
2485 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2486 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2487 text, can_block, avatar);
2489 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2490 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2494 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2496 if (!empathy_block_individual_dialog_show (parent, individual,
2500 empathy_individual_manager_set_blocked (manager, individual,
2504 empathy_individual_manager_remove (manager, individual, "");
2509 g_object_unref (manager);
2513 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2514 EmpathyIndividualView *view)
2516 FolksIndividual *individual;
2518 individual = empathy_individual_view_dup_selected (view);
2520 if (individual != NULL)
2522 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2523 48, 48, NULL, got_avatar, view);
2524 g_object_unref (individual);
2529 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2530 EmpathyLinkingDialog *linking_dialog,
2531 EmpathyIndividualView *self)
2533 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2534 EmpathyIndividualLinker *linker;
2536 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2537 empathy_individual_linker_set_search_text (linker,
2538 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2542 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2544 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2545 FolksIndividual *individual;
2546 GtkWidget *menu = NULL;
2549 gboolean can_remove = FALSE;
2552 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2554 individual = empathy_individual_view_dup_selected (view);
2555 if (individual == NULL)
2558 /* If any of the Individual's personas can be removed, add an option to
2559 * remove. This will act as a best-effort option. If any Personas cannot be
2560 * removed from the server, then this option will just be inactive upon
2561 * subsequent menu openings */
2562 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2564 FolksPersona *persona = FOLKS_PERSONA (l->data);
2565 FolksPersonaStore *store = folks_persona_get_store (persona);
2566 FolksMaybeBool maybe_can_remove =
2567 folks_persona_store_get_can_remove_personas (store);
2569 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2576 menu = empathy_individual_menu_new (individual, priv->individual_features);
2578 /* Remove contact */
2579 if ((priv->view_features &
2580 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2583 /* create the menu if required, or just add a separator */
2585 menu = gtk_menu_new ();
2588 item = gtk_separator_menu_item_new ();
2589 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2590 gtk_widget_show (item);
2594 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2595 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2596 GTK_ICON_SIZE_MENU);
2597 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2598 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2599 gtk_widget_show (item);
2600 g_signal_connect (item, "activate",
2601 G_CALLBACK (individual_view_remove_activate_cb), view);
2604 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2605 * set the live search text on the new linking dialogue to be the same as
2607 g_signal_connect (menu, "link-contacts-activated",
2608 (GCallback) individual_menu_link_contacts_activated_cb, view);
2610 g_object_unref (individual);
2616 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2617 EmpathyLiveSearch *search)
2619 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2621 /* remove old handlers if old search was not null */
2622 if (priv->search_widget != NULL)
2624 g_signal_handlers_disconnect_by_func (view,
2625 individual_view_start_search_cb, NULL);
2627 g_signal_handlers_disconnect_by_func (priv->search_widget,
2628 individual_view_search_text_notify_cb, view);
2629 g_signal_handlers_disconnect_by_func (priv->search_widget,
2630 individual_view_search_activate_cb, view);
2631 g_signal_handlers_disconnect_by_func (priv->search_widget,
2632 individual_view_search_key_navigation_cb, view);
2633 g_signal_handlers_disconnect_by_func (priv->search_widget,
2634 individual_view_search_hide_cb, view);
2635 g_signal_handlers_disconnect_by_func (priv->search_widget,
2636 individual_view_search_show_cb, view);
2637 g_object_unref (priv->search_widget);
2638 priv->search_widget = NULL;
2641 /* connect handlers if new search is not null */
2644 priv->search_widget = g_object_ref (search);
2646 g_signal_connect (view, "start-interactive-search",
2647 G_CALLBACK (individual_view_start_search_cb), NULL);
2649 g_signal_connect (priv->search_widget, "notify::text",
2650 G_CALLBACK (individual_view_search_text_notify_cb), view);
2651 g_signal_connect (priv->search_widget, "activate",
2652 G_CALLBACK (individual_view_search_activate_cb), view);
2653 g_signal_connect (priv->search_widget, "key-navigation",
2654 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2655 g_signal_connect (priv->search_widget, "hide",
2656 G_CALLBACK (individual_view_search_hide_cb), view);
2657 g_signal_connect (priv->search_widget, "show",
2658 G_CALLBACK (individual_view_search_show_cb), view);
2663 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2665 EmpathyIndividualViewPriv *priv;
2667 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2669 priv = GET_PRIV (self);
2671 return (priv->search_widget != NULL &&
2672 gtk_widget_get_visible (priv->search_widget));
2676 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2678 EmpathyIndividualViewPriv *priv;
2680 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2682 priv = GET_PRIV (self);
2684 return priv->show_offline;
2688 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2689 gboolean show_offline)
2691 EmpathyIndividualViewPriv *priv;
2693 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2695 priv = GET_PRIV (self);
2697 priv->show_offline = show_offline;
2699 g_object_notify (G_OBJECT (self), "show-offline");
2700 gtk_tree_model_filter_refilter (priv->filter);
2704 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2706 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2708 return GET_PRIV (self)->show_untrusted;
2712 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2713 gboolean show_untrusted)
2715 EmpathyIndividualViewPriv *priv;
2717 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2719 priv = GET_PRIV (self);
2721 priv->show_untrusted = show_untrusted;
2723 g_object_notify (G_OBJECT (self), "show-untrusted");
2724 gtk_tree_model_filter_refilter (priv->filter);
2727 EmpathyIndividualStore *
2728 empathy_individual_view_get_store (EmpathyIndividualView *self)
2730 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2732 return GET_PRIV (self)->store;
2736 empathy_individual_view_set_store (EmpathyIndividualView *self,
2737 EmpathyIndividualStore *store)
2739 EmpathyIndividualViewPriv *priv;
2741 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2742 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2744 priv = GET_PRIV (self);
2746 /* Destroy the old filter and remove the old store */
2747 if (priv->store != NULL)
2749 g_signal_handlers_disconnect_by_func (priv->store,
2750 individual_view_store_row_changed_cb, self);
2751 g_signal_handlers_disconnect_by_func (priv->store,
2752 individual_view_store_row_deleted_cb, self);
2754 g_signal_handlers_disconnect_by_func (priv->filter,
2755 individual_view_row_has_child_toggled_cb, self);
2757 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2760 tp_clear_object (&priv->filter);
2761 tp_clear_object (&priv->store);
2763 /* Set the new store */
2764 priv->store = store;
2768 g_object_ref (store);
2770 /* Create a new filter */
2771 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2772 GTK_TREE_MODEL (priv->store), NULL));
2773 gtk_tree_model_filter_set_visible_func (priv->filter,
2774 individual_view_filter_visible_func, self, NULL);
2776 g_signal_connect (priv->filter, "row-has-child-toggled",
2777 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2778 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2779 GTK_TREE_MODEL (priv->filter));
2781 tp_g_signal_connect_object (priv->store, "row-changed",
2782 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2783 tp_g_signal_connect_object (priv->store, "row-inserted",
2784 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2785 tp_g_signal_connect_object (priv->store, "row-deleted",
2786 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2791 empathy_individual_view_start_search (EmpathyIndividualView *self)
2793 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2795 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2796 g_return_if_fail (priv->search_widget != NULL);
2798 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2799 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2801 gtk_widget_show (GTK_WIDGET (priv->search_widget));