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 gboolean is_fake_group)
1696 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1697 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1699 GList *personas, *l;
1700 gboolean is_favorite, contains_interesting_persona = FALSE;
1702 /* We're only giving the visibility wrt filtering here, not things like
1704 if (priv->show_untrusted == FALSE &&
1705 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1710 /* Hide all individuals which consist entirely of uninteresting personas */
1711 personas = folks_individual_get_personas (individual);
1712 for (l = personas; l; l = l->next)
1714 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1716 contains_interesting_persona = TRUE;
1721 if (contains_interesting_persona == FALSE)
1724 is_favorite = folks_favourite_details_get_is_favourite (
1725 FOLKS_FAVOURITE_DETAILS (individual));
1726 if (is_searching == FALSE) {
1727 if (is_favorite && is_fake_group &&
1728 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1729 /* Always display favorite contacts in the favorite group */
1732 return (priv->show_offline || is_online);
1735 /* check alias name */
1736 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1738 if (empathy_live_search_match (live, str))
1741 /* check contact id, remove the @server.com part */
1742 for (l = personas; l; l = l->next)
1745 gchar *dup_str = NULL;
1748 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1751 str = folks_persona_get_display_id (l->data);
1752 p = strstr (str, "@");
1754 str = dup_str = g_strndup (str, p - str);
1756 visible = empathy_live_search_match (live, str);
1762 /* FIXME: Add more rules here, we could check phone numbers in
1763 * contact's vCard for example. */
1769 get_group (GtkTreeModel *model,
1773 GtkTreeIter parent_iter;
1778 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1781 gtk_tree_model_get (model, &parent_iter,
1782 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1783 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1791 individual_view_filter_visible_func (GtkTreeModel *model,
1795 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1796 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1797 FolksIndividual *individual = NULL;
1798 gboolean is_group, is_separator, valid;
1799 GtkTreeIter child_iter;
1800 gboolean visible, is_online;
1801 gboolean is_searching = TRUE;
1803 if (priv->search_widget == NULL ||
1804 !gtk_widget_get_visible (priv->search_widget))
1805 is_searching = FALSE;
1807 gtk_tree_model_get (model, iter,
1808 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1809 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1810 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1811 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1814 if (individual != NULL)
1817 gboolean is_fake_group;
1819 group = get_group (model, iter, &is_fake_group);
1821 visible = individual_view_is_visible_individual (self, individual,
1822 is_online, is_searching, group, is_fake_group);
1824 g_object_unref (individual);
1827 /* FIXME: Work around bgo#626552/bgo#621076 */
1828 if (visible == TRUE)
1830 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1831 individual_view_verify_group_visibility (self, path);
1832 gtk_tree_path_free (path);
1841 /* Not a contact, not a separator, must be a group */
1842 g_return_val_if_fail (is_group, FALSE);
1844 /* only show groups which are not empty */
1845 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1846 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1849 gboolean is_fake_group;
1851 gtk_tree_model_get (model, &child_iter,
1852 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1853 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1856 if (individual == NULL)
1859 group = get_group (model, &child_iter, &is_fake_group);
1861 visible = individual_view_is_visible_individual (self, individual,
1862 is_online, is_searching, group, is_fake_group);
1864 g_object_unref (individual);
1867 /* show group if it has at least one visible contact in it */
1868 if (visible == TRUE)
1876 individual_view_constructed (GObject *object)
1878 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1879 GtkCellRenderer *cell;
1880 GtkTreeViewColumn *col;
1885 "headers-visible", FALSE,
1886 "show-expanders", FALSE,
1889 col = gtk_tree_view_column_new ();
1892 cell = gtk_cell_renderer_pixbuf_new ();
1893 gtk_tree_view_column_pack_start (col, cell, FALSE);
1894 gtk_tree_view_column_set_cell_data_func (col, cell,
1895 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1905 cell = gtk_cell_renderer_pixbuf_new ();
1906 gtk_tree_view_column_pack_start (col, cell, FALSE);
1907 gtk_tree_view_column_set_cell_data_func (col, cell,
1908 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1920 cell = empathy_cell_renderer_text_new ();
1921 gtk_tree_view_column_pack_start (col, cell, TRUE);
1922 gtk_tree_view_column_set_cell_data_func (col, cell,
1923 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1925 gtk_tree_view_column_add_attribute (col, cell,
1926 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1927 gtk_tree_view_column_add_attribute (col, cell,
1928 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1929 gtk_tree_view_column_add_attribute (col, cell,
1930 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1931 gtk_tree_view_column_add_attribute (col, cell,
1932 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1933 gtk_tree_view_column_add_attribute (col, cell,
1934 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1935 gtk_tree_view_column_add_attribute (col, cell,
1936 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1937 gtk_tree_view_column_add_attribute (col, cell,
1938 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1940 /* Audio Call Icon */
1941 cell = empathy_cell_renderer_activatable_new ();
1942 gtk_tree_view_column_pack_start (col, cell, FALSE);
1943 gtk_tree_view_column_set_cell_data_func (col, cell,
1944 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1947 g_object_set (cell, "visible", FALSE, NULL);
1949 g_signal_connect (cell, "path-activated",
1950 G_CALLBACK (individual_view_call_activated_cb), view);
1953 cell = gtk_cell_renderer_pixbuf_new ();
1954 gtk_tree_view_column_pack_start (col, cell, FALSE);
1955 gtk_tree_view_column_set_cell_data_func (col, cell,
1956 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1968 cell = empathy_cell_renderer_expander_new ();
1969 gtk_tree_view_column_pack_end (col, cell, FALSE);
1970 gtk_tree_view_column_set_cell_data_func (col, cell,
1971 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1974 /* Actually add the column now we have added all cell renderers */
1975 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1978 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1980 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1985 individual_view_set_view_features (EmpathyIndividualView *view,
1986 EmpathyIndividualFeatureFlags features)
1988 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1989 gboolean has_tooltip;
1991 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1993 priv->view_features = features;
1995 /* Setting reorderable is a hack that gets us row previews as drag icons
1996 for free. We override all the drag handlers. It's tricky to get the
1997 position of the drag icon right in drag_begin. GtkTreeView has special
1998 voodoo for it, so we let it do the voodoo that he do (but only if dragging
2001 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
2002 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
2004 /* Update DnD source/dest */
2005 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
2007 gtk_drag_source_set (GTK_WIDGET (view),
2010 G_N_ELEMENTS (drag_types_source),
2011 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2015 gtk_drag_source_unset (GTK_WIDGET (view));
2019 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2021 gtk_drag_dest_set (GTK_WIDGET (view),
2022 GTK_DEST_DEFAULT_ALL,
2024 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2028 /* FIXME: URI could still be droped depending on FT feature */
2029 gtk_drag_dest_unset (GTK_WIDGET (view));
2032 /* Update has-tooltip */
2034 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2035 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2039 individual_view_dispose (GObject *object)
2041 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2042 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2044 tp_clear_object (&priv->store);
2045 tp_clear_object (&priv->filter);
2046 tp_clear_object (&priv->tooltip_widget);
2048 empathy_individual_view_set_live_search (view, NULL);
2050 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2054 individual_view_finalize (GObject *object)
2056 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2058 if (priv->expand_groups_idle_handler != 0)
2059 g_source_remove (priv->expand_groups_idle_handler);
2060 g_hash_table_destroy (priv->expand_groups);
2062 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2066 individual_view_get_property (GObject *object,
2071 EmpathyIndividualViewPriv *priv;
2073 priv = GET_PRIV (object);
2078 g_value_set_object (value, priv->store);
2080 case PROP_VIEW_FEATURES:
2081 g_value_set_flags (value, priv->view_features);
2083 case PROP_INDIVIDUAL_FEATURES:
2084 g_value_set_flags (value, priv->individual_features);
2086 case PROP_SHOW_OFFLINE:
2087 g_value_set_boolean (value, priv->show_offline);
2089 case PROP_SHOW_UNTRUSTED:
2090 g_value_set_boolean (value, priv->show_untrusted);
2093 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2099 individual_view_set_property (GObject *object,
2101 const GValue *value,
2104 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2105 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2110 empathy_individual_view_set_store (view, g_value_get_object (value));
2112 case PROP_VIEW_FEATURES:
2113 individual_view_set_view_features (view, g_value_get_flags (value));
2115 case PROP_INDIVIDUAL_FEATURES:
2116 priv->individual_features = g_value_get_flags (value);
2118 case PROP_SHOW_OFFLINE:
2119 empathy_individual_view_set_show_offline (view,
2120 g_value_get_boolean (value));
2122 case PROP_SHOW_UNTRUSTED:
2123 empathy_individual_view_set_show_untrusted (view,
2124 g_value_get_boolean (value));
2127 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2133 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2135 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2136 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2137 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2139 object_class->constructed = individual_view_constructed;
2140 object_class->dispose = individual_view_dispose;
2141 object_class->finalize = individual_view_finalize;
2142 object_class->get_property = individual_view_get_property;
2143 object_class->set_property = individual_view_set_property;
2145 widget_class->drag_data_received = individual_view_drag_data_received;
2146 widget_class->drag_drop = individual_view_drag_drop;
2147 widget_class->drag_begin = individual_view_drag_begin;
2148 widget_class->drag_data_get = individual_view_drag_data_get;
2149 widget_class->drag_end = individual_view_drag_end;
2150 widget_class->drag_motion = individual_view_drag_motion;
2152 /* We use the class method to let user of this widget to connect to
2153 * the signal and stop emission of the signal so the default handler
2154 * won't be called. */
2155 tree_view_class->row_activated = individual_view_row_activated;
2157 klass->drag_individual_received = real_drag_individual_received_cb;
2159 signals[DRAG_INDIVIDUAL_RECEIVED] =
2160 g_signal_new ("drag-individual-received",
2161 G_OBJECT_CLASS_TYPE (klass),
2163 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2165 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2166 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2167 G_TYPE_STRING, G_TYPE_STRING);
2169 signals[DRAG_PERSONA_RECEIVED] =
2170 g_signal_new ("drag-persona-received",
2171 G_OBJECT_CLASS_TYPE (klass),
2173 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2175 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2176 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2178 g_object_class_install_property (object_class,
2180 g_param_spec_object ("store",
2181 "The store of the view",
2182 "The store of the view",
2183 EMPATHY_TYPE_INDIVIDUAL_STORE,
2184 G_PARAM_READWRITE));
2185 g_object_class_install_property (object_class,
2187 g_param_spec_flags ("view-features",
2188 "Features of the view",
2189 "Flags for all enabled features",
2190 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2191 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2192 g_object_class_install_property (object_class,
2193 PROP_INDIVIDUAL_FEATURES,
2194 g_param_spec_flags ("individual-features",
2195 "Features of the individual menu",
2196 "Flags for all enabled features for the menu",
2197 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2198 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2199 g_object_class_install_property (object_class,
2201 g_param_spec_boolean ("show-offline",
2203 "Whether contact list should display "
2204 "offline contacts", FALSE, G_PARAM_READWRITE));
2205 g_object_class_install_property (object_class,
2206 PROP_SHOW_UNTRUSTED,
2207 g_param_spec_boolean ("show-untrusted",
2208 "Show Untrusted Individuals",
2209 "Whether the view should display untrusted individuals; "
2210 "those who could not be who they say they are.",
2211 TRUE, G_PARAM_READWRITE));
2213 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2217 empathy_individual_view_init (EmpathyIndividualView *view)
2219 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2220 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2224 priv->show_untrusted = TRUE;
2226 /* Get saved group states. */
2227 empathy_contact_groups_get_all ();
2229 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2230 (GDestroyNotify) g_free, NULL);
2232 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2233 empathy_individual_store_row_separator_func, NULL, NULL);
2235 /* Connect to tree view signals rather than override. */
2236 g_signal_connect (view, "button-press-event",
2237 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2238 g_signal_connect (view, "key-press-event",
2239 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2240 g_signal_connect (view, "row-expanded",
2241 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2242 GINT_TO_POINTER (TRUE));
2243 g_signal_connect (view, "row-collapsed",
2244 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2245 GINT_TO_POINTER (FALSE));
2246 g_signal_connect (view, "query-tooltip",
2247 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2250 EmpathyIndividualView *
2251 empathy_individual_view_new (EmpathyIndividualStore *store,
2252 EmpathyIndividualViewFeatureFlags view_features,
2253 EmpathyIndividualFeatureFlags individual_features)
2255 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2257 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2259 "individual-features", individual_features,
2260 "view-features", view_features, NULL);
2264 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2266 GtkTreeSelection *selection;
2268 GtkTreeModel *model;
2269 FolksIndividual *individual;
2271 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2273 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2274 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2277 gtk_tree_model_get (model, &iter,
2278 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2284 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2285 gboolean *is_fake_group)
2287 GtkTreeSelection *selection;
2289 GtkTreeModel *model;
2294 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2296 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2297 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2300 gtk_tree_model_get (model, &iter,
2301 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2302 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2303 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2311 if (is_fake_group != NULL)
2312 *is_fake_group = fake;
2319 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2320 REMOVE_DIALOG_RESPONSE_DELETE,
2321 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2325 individual_view_remove_dialog_show (GtkWindow *parent,
2326 const gchar *message,
2327 const gchar *secondary_text,
2328 gboolean block_button,
2334 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2335 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2339 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2340 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2341 gtk_widget_show (image);
2348 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2349 * mnemonic so we have to create the button manually. */
2350 button = gtk_button_new_with_mnemonic (
2351 _("Delete and _Block"));
2353 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2354 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2356 gtk_widget_show (button);
2359 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2360 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2361 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2362 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2363 "%s", secondary_text);
2365 gtk_widget_show (dialog);
2367 res = gtk_dialog_run (GTK_DIALOG (dialog));
2368 gtk_widget_destroy (dialog);
2374 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2375 EmpathyIndividualView *view)
2379 group = empathy_individual_view_dup_selected_group (view, NULL);
2386 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2388 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2389 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2390 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2392 EmpathyIndividualManager *manager =
2393 empathy_individual_manager_dup_singleton ();
2394 empathy_individual_manager_remove_group (manager, group);
2395 g_object_unref (G_OBJECT (manager));
2405 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2407 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2412 gboolean is_fake_group;
2414 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2416 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2417 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2420 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2421 if (!group || is_fake_group)
2423 /* We can't alter fake groups */
2428 menu = gtk_menu_new ();
2431 if (priv->view_features &
2432 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2433 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2434 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2435 gtk_widget_show (item);
2436 g_signal_connect (item, "activate",
2437 G_CALLBACK (individual_view_group_rename_activate_cb),
2442 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2444 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2445 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2446 GTK_ICON_SIZE_MENU);
2447 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2448 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2449 gtk_widget_show (item);
2450 g_signal_connect (item, "activate",
2451 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2460 got_avatar (GObject *source_object,
2461 GAsyncResult *result,
2464 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2465 EmpathyIndividualView *view = user_data;
2467 EmpathyIndividualManager *manager;
2470 GList *l, *personas;
2471 guint persona_count = 0;
2473 GError *error = NULL;
2476 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2481 DEBUG ("Could not get avatar: %s", error->message);
2482 g_error_free (error);
2485 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2486 * so we still display the remove dialog. */
2488 personas = folks_individual_get_personas (individual);
2490 /* If we have more than one TpfPersona, display a different message
2491 * ensuring the user knows that *all* of the meta-contacts' personas will
2493 for (l = personas; l != NULL; l = l->next)
2495 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2499 if (persona_count >= 2)
2503 if (persona_count < 2)
2505 /* Not a meta-contact */
2508 _("Do you really want to remove the contact '%s'?"),
2509 folks_alias_details_get_alias (
2510 FOLKS_ALIAS_DETAILS (individual)));
2517 _("Do you really want to remove the linked contact '%s'? "
2518 "Note that this will remove all the contacts which make up "
2519 "this linked contact."),
2520 folks_alias_details_get_alias (
2521 FOLKS_ALIAS_DETAILS (individual)));
2525 manager = empathy_individual_manager_dup_singleton ();
2526 can_block = empathy_individual_manager_supports_blocking (manager,
2528 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2529 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2530 text, can_block, avatar);
2532 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2533 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2537 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2539 if (!empathy_block_individual_dialog_show (parent, individual,
2543 empathy_individual_manager_set_blocked (manager, individual,
2547 empathy_individual_manager_remove (manager, individual, "");
2552 g_object_unref (manager);
2556 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2557 EmpathyIndividualView *view)
2559 FolksIndividual *individual;
2561 individual = empathy_individual_view_dup_selected (view);
2563 if (individual != NULL)
2565 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2566 48, 48, NULL, got_avatar, view);
2567 g_object_unref (individual);
2572 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2573 EmpathyLinkingDialog *linking_dialog,
2574 EmpathyIndividualView *self)
2576 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2577 EmpathyIndividualLinker *linker;
2579 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2580 empathy_individual_linker_set_search_text (linker,
2581 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2585 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2587 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2588 FolksIndividual *individual;
2589 GtkWidget *menu = NULL;
2592 gboolean can_remove = FALSE;
2595 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2597 individual = empathy_individual_view_dup_selected (view);
2598 if (individual == NULL)
2601 /* If any of the Individual's personas can be removed, add an option to
2602 * remove. This will act as a best-effort option. If any Personas cannot be
2603 * removed from the server, then this option will just be inactive upon
2604 * subsequent menu openings */
2605 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2607 FolksPersona *persona = FOLKS_PERSONA (l->data);
2608 FolksPersonaStore *store = folks_persona_get_store (persona);
2609 FolksMaybeBool maybe_can_remove =
2610 folks_persona_store_get_can_remove_personas (store);
2612 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2619 menu = empathy_individual_menu_new (individual, priv->individual_features);
2621 /* Remove contact */
2622 if ((priv->view_features &
2623 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2626 /* create the menu if required, or just add a separator */
2628 menu = gtk_menu_new ();
2631 item = gtk_separator_menu_item_new ();
2632 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2633 gtk_widget_show (item);
2637 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2638 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2639 GTK_ICON_SIZE_MENU);
2640 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2641 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2642 gtk_widget_show (item);
2643 g_signal_connect (item, "activate",
2644 G_CALLBACK (individual_view_remove_activate_cb), view);
2647 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2648 * set the live search text on the new linking dialogue to be the same as
2650 g_signal_connect (menu, "link-contacts-activated",
2651 (GCallback) individual_menu_link_contacts_activated_cb, view);
2653 g_object_unref (individual);
2659 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2660 EmpathyLiveSearch *search)
2662 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2664 /* remove old handlers if old search was not null */
2665 if (priv->search_widget != NULL)
2667 g_signal_handlers_disconnect_by_func (view,
2668 individual_view_start_search_cb, NULL);
2670 g_signal_handlers_disconnect_by_func (priv->search_widget,
2671 individual_view_search_text_notify_cb, view);
2672 g_signal_handlers_disconnect_by_func (priv->search_widget,
2673 individual_view_search_activate_cb, view);
2674 g_signal_handlers_disconnect_by_func (priv->search_widget,
2675 individual_view_search_key_navigation_cb, view);
2676 g_signal_handlers_disconnect_by_func (priv->search_widget,
2677 individual_view_search_hide_cb, view);
2678 g_signal_handlers_disconnect_by_func (priv->search_widget,
2679 individual_view_search_show_cb, view);
2680 g_object_unref (priv->search_widget);
2681 priv->search_widget = NULL;
2684 /* connect handlers if new search is not null */
2687 priv->search_widget = g_object_ref (search);
2689 g_signal_connect (view, "start-interactive-search",
2690 G_CALLBACK (individual_view_start_search_cb), NULL);
2692 g_signal_connect (priv->search_widget, "notify::text",
2693 G_CALLBACK (individual_view_search_text_notify_cb), view);
2694 g_signal_connect (priv->search_widget, "activate",
2695 G_CALLBACK (individual_view_search_activate_cb), view);
2696 g_signal_connect (priv->search_widget, "key-navigation",
2697 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2698 g_signal_connect (priv->search_widget, "hide",
2699 G_CALLBACK (individual_view_search_hide_cb), view);
2700 g_signal_connect (priv->search_widget, "show",
2701 G_CALLBACK (individual_view_search_show_cb), view);
2706 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2708 EmpathyIndividualViewPriv *priv;
2710 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2712 priv = GET_PRIV (self);
2714 return (priv->search_widget != NULL &&
2715 gtk_widget_get_visible (priv->search_widget));
2719 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2721 EmpathyIndividualViewPriv *priv;
2723 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2725 priv = GET_PRIV (self);
2727 return priv->show_offline;
2731 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2732 gboolean show_offline)
2734 EmpathyIndividualViewPriv *priv;
2736 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2738 priv = GET_PRIV (self);
2740 priv->show_offline = show_offline;
2742 g_object_notify (G_OBJECT (self), "show-offline");
2743 gtk_tree_model_filter_refilter (priv->filter);
2747 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2749 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2751 return GET_PRIV (self)->show_untrusted;
2755 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2756 gboolean show_untrusted)
2758 EmpathyIndividualViewPriv *priv;
2760 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2762 priv = GET_PRIV (self);
2764 priv->show_untrusted = show_untrusted;
2766 g_object_notify (G_OBJECT (self), "show-untrusted");
2767 gtk_tree_model_filter_refilter (priv->filter);
2770 EmpathyIndividualStore *
2771 empathy_individual_view_get_store (EmpathyIndividualView *self)
2773 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2775 return GET_PRIV (self)->store;
2779 empathy_individual_view_set_store (EmpathyIndividualView *self,
2780 EmpathyIndividualStore *store)
2782 EmpathyIndividualViewPriv *priv;
2784 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2785 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2787 priv = GET_PRIV (self);
2789 /* Destroy the old filter and remove the old store */
2790 if (priv->store != NULL)
2792 g_signal_handlers_disconnect_by_func (priv->store,
2793 individual_view_store_row_changed_cb, self);
2794 g_signal_handlers_disconnect_by_func (priv->store,
2795 individual_view_store_row_deleted_cb, self);
2797 g_signal_handlers_disconnect_by_func (priv->filter,
2798 individual_view_row_has_child_toggled_cb, self);
2800 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2803 tp_clear_object (&priv->filter);
2804 tp_clear_object (&priv->store);
2806 /* Set the new store */
2807 priv->store = store;
2811 g_object_ref (store);
2813 /* Create a new filter */
2814 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2815 GTK_TREE_MODEL (priv->store), NULL));
2816 gtk_tree_model_filter_set_visible_func (priv->filter,
2817 individual_view_filter_visible_func, self, NULL);
2819 g_signal_connect (priv->filter, "row-has-child-toggled",
2820 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2821 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2822 GTK_TREE_MODEL (priv->filter));
2824 tp_g_signal_connect_object (priv->store, "row-changed",
2825 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2826 tp_g_signal_connect_object (priv->store, "row-inserted",
2827 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2828 tp_g_signal_connect_object (priv->store, "row-deleted",
2829 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2834 empathy_individual_view_start_search (EmpathyIndividualView *self)
2836 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2838 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2839 g_return_if_fail (priv->search_widget != NULL);
2841 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2842 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2844 gtk_widget_show (GTK_WIDGET (priv->search_widget));