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_INDIVIDUAL_ID,
122 DND_DRAG_TYPE_PERSONA_ID,
123 DND_DRAG_TYPE_URI_LIST,
124 DND_DRAG_TYPE_STRING,
127 #define DRAG_TYPE(T,I) \
128 { (gchar *) T, 0, I }
130 static const GtkTargetEntry drag_types_dest[] = {
131 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
132 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
133 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
135 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
136 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
139 static const GtkTargetEntry drag_types_source[] = {
140 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
145 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
146 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
150 DRAG_INDIVIDUAL_RECEIVED,
151 DRAG_PERSONA_RECEIVED,
155 static guint signals[LAST_SIGNAL];
157 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
161 individual_view_tooltip_destroy_cb (GtkWidget *widget,
162 EmpathyIndividualView *view)
164 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
166 tp_clear_object (&priv->tooltip_widget);
170 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
173 gboolean keyboard_mode,
177 EmpathyIndividualViewPriv *priv;
178 FolksIndividual *individual;
182 static gint running = 0;
183 gboolean ret = FALSE;
185 priv = GET_PRIV (view);
187 /* Avoid an infinite loop. See GNOME bug #574377 */
193 /* Don't show the tooltip if there's already a popup menu */
194 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
197 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
198 keyboard_mode, &model, &path, &iter))
201 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
202 gtk_tree_path_free (path);
204 gtk_tree_model_get (model, &iter,
205 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
207 if (individual == NULL)
210 if (priv->tooltip_widget == NULL)
212 priv->tooltip_widget = empathy_individual_widget_new (individual,
213 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
214 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
215 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
216 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
217 g_object_ref (priv->tooltip_widget);
218 g_signal_connect (priv->tooltip_widget, "destroy",
219 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
220 gtk_widget_show (priv->tooltip_widget);
224 empathy_individual_widget_set_individual (
225 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
228 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
231 g_object_unref (individual);
239 groups_change_group_cb (GObject *source,
240 GAsyncResult *result,
243 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
244 GError *error = NULL;
246 folks_group_details_change_group_finish (group_details, result, &error);
249 g_warning ("failed to change group: %s", error->message);
250 g_clear_error (&error);
255 group_can_be_modified (const gchar *name,
256 gboolean is_fake_group,
259 /* Real groups can always be modified */
263 /* The favorite fake group can be modified so users can
264 * add/remove favorites using DnD */
265 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
268 /* We can remove contacts from the 'ungrouped' fake group */
269 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
276 individual_view_individual_drag_received (GtkWidget *self,
277 GdkDragContext *context,
280 GtkSelectionData *selection)
282 EmpathyIndividualViewPriv *priv;
283 EmpathyIndividualManager *manager = NULL;
284 FolksIndividual *individual;
285 GtkTreePath *source_path;
286 const gchar *sel_data;
287 gchar *new_group = NULL;
288 gchar *old_group = NULL;
289 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
291 priv = GET_PRIV (self);
293 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
294 new_group = empathy_individual_store_get_parent_group (model, path,
295 NULL, &new_group_is_fake);
297 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
300 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
301 * feature. Otherwise, we just add the dropped contact to whichever group
302 * they were dropped in, and don't remove them from their old group. This
303 * allows for Individual views which shouldn't allow Individuals to have
304 * their groups changed, and also for dragging Individuals between Individual
306 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
307 priv->drag_row != NULL)
309 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
313 empathy_individual_store_get_parent_group (model, source_path,
314 NULL, &old_group_is_fake);
315 gtk_tree_path_free (source_path);
318 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
321 if (!tp_strdiff (old_group, new_group))
324 else if (priv->drag_row != NULL)
326 /* We don't allow changing Individuals' groups, and this Individual was
327 * dragged from another group in *this* Individual view, so we disallow
332 /* XXX: for contacts, we used to ensure the account, create the contact
333 * factory, and then wait on the contacts. But they should already be
334 * created by this point */
336 manager = empathy_individual_manager_dup_singleton ();
337 individual = empathy_individual_manager_lookup_member (manager, sel_data);
339 if (individual == NULL)
341 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
345 /* FIXME: We should probably wait for the cb before calling
348 /* Emit a signal notifying of the drag. We change the Individual's groups in
349 * the default signal handler. */
350 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
351 gdk_drag_context_get_selected_action (context), individual, new_group,
357 tp_clear_object (&manager);
365 real_drag_individual_received_cb (EmpathyIndividualView *self,
366 GdkDragAction action,
367 FolksIndividual *individual,
368 const gchar *new_group,
369 const gchar *old_group)
371 DEBUG ("individual %s dragged from '%s' to '%s'",
372 folks_individual_get_id (individual), old_group, new_group);
374 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
376 /* Mark contact as favourite */
377 folks_favourite_details_set_is_favourite (
378 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
382 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
384 /* Remove contact as favourite */
385 folks_favourite_details_set_is_favourite (
386 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
388 /* Don't try to remove it */
392 if (new_group != NULL)
394 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
395 new_group, TRUE, groups_change_group_cb, NULL);
398 if (old_group != NULL && action == GDK_ACTION_MOVE)
400 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
401 old_group, FALSE, groups_change_group_cb, NULL);
406 individual_view_persona_drag_received (GtkWidget *self,
407 GdkDragContext *context,
410 GtkSelectionData *selection)
412 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 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
603 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
606 if (priv->auto_scroll_timeout_id != 0)
608 g_source_remove (priv->auto_scroll_timeout_id);
609 priv->auto_scroll_timeout_id = 0;
612 gtk_widget_get_allocation (widget, &allocation);
614 if (y < AUTO_SCROLL_MARGIN_SIZE ||
615 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
617 if (y < AUTO_SCROLL_MARGIN_SIZE)
618 priv->distance = MIN (-y, -1);
620 priv->distance = MAX (allocation.height - y, 1);
622 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
623 (GSourceFunc) individual_view_auto_scroll_cb, widget);
626 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
627 x, y, &path, NULL, NULL, NULL);
629 cleanup &= (dm == NULL);
633 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
634 is_different = ((dm == NULL) || ((dm != NULL)
635 && gtk_tree_path_compare (dm->path, path) != 0));
642 /* Coordinates don't point to an actual row, so make sure the pointer
643 and highlighting don't indicate that a drag is possible.
645 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
646 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
649 target = gtk_drag_dest_find_target (widget, context, NULL);
650 gtk_tree_model_get_iter (model, &iter, path);
652 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
653 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
655 /* This is a file drag, and it can only be dropped on contacts,
657 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
658 * even if we have a valid target. */
659 FolksIndividual *individual = NULL;
660 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
662 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
664 gtk_tree_model_get (model, &iter,
665 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
669 if (individual != NULL)
671 EmpathyContact *contact = NULL;
673 contact = empathy_contact_dup_from_folks_individual (individual);
674 caps = empathy_contact_get_capabilities (contact);
676 tp_clear_object (&contact);
679 if (individual != NULL &&
680 folks_presence_details_is_online (
681 FOLKS_PRESENCE_DETAILS (individual)) &&
682 (caps & EMPATHY_CAPABILITIES_FT))
684 gdk_drag_status (context, GDK_ACTION_COPY, time_);
685 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
686 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
690 gdk_drag_status (context, 0, time_);
691 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
695 if (individual != NULL)
696 g_object_unref (individual);
698 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
699 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
700 priv->drag_row == NULL)) ||
701 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
702 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
704 /* If target != GDK_NONE, then we have a contact (individual or persona)
705 drag. If we're pointing to a group, highlight it. Otherwise, if the
706 contact we're pointing to is in a group, highlight that. Otherwise,
707 set the drag position to before the first row for a drag into
708 the "non-group" at the top.
709 If it's an Individual:
710 We only highlight things if the contact is from a different
711 Individual view, or if this Individual view has
712 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
713 which don't have FEATURE_GROUPS_CHANGE, but do have
714 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
716 We only highlight things if we have FEATURE_PERSONA_DROP.
718 GtkTreeIter group_iter;
720 GtkTreePath *group_path;
721 gtk_tree_model_get (model, &iter,
722 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
729 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
730 gtk_tree_model_get (model, &group_iter,
731 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
735 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
736 group_path = gtk_tree_model_get_path (model, &group_iter);
737 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
738 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
739 gtk_tree_path_free (group_path);
743 group_path = gtk_tree_path_new_first ();
744 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
745 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
746 group_path, GTK_TREE_VIEW_DROP_BEFORE);
750 if (!is_different && !cleanup)
755 gtk_tree_path_free (dm->path);
758 g_source_remove (dm->timeout_id);
766 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
768 dm = g_new0 (DragMotionData, 1);
770 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
771 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
772 dm->path = gtk_tree_path_copy (path);
774 dm->timeout_id = g_timeout_add_seconds (1,
775 (GSourceFunc) individual_view_drag_motion_cb, dm);
782 individual_view_drag_begin (GtkWidget *widget,
783 GdkDragContext *context)
785 EmpathyIndividualViewPriv *priv;
786 GtkTreeSelection *selection;
791 priv = GET_PRIV (widget);
793 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
796 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
797 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
800 path = gtk_tree_model_get_path (model, &iter);
801 priv->drag_row = gtk_tree_row_reference_new (model, path);
802 gtk_tree_path_free (path);
806 individual_view_drag_data_get (GtkWidget *widget,
807 GdkDragContext *context,
808 GtkSelectionData *selection,
812 EmpathyIndividualViewPriv *priv;
813 GtkTreePath *src_path;
816 FolksIndividual *individual;
817 const gchar *individual_id;
819 priv = GET_PRIV (widget);
821 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
822 if (priv->drag_row == NULL)
825 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
826 if (src_path == NULL)
829 if (!gtk_tree_model_get_iter (model, &iter, src_path))
831 gtk_tree_path_free (src_path);
835 gtk_tree_path_free (src_path);
838 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
839 if (individual == NULL)
842 individual_id = folks_individual_get_id (individual);
844 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
846 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
847 (guchar *) individual_id, strlen (individual_id) + 1);
850 g_object_unref (individual);
854 individual_view_drag_end (GtkWidget *widget,
855 GdkDragContext *context)
857 EmpathyIndividualViewPriv *priv;
859 priv = GET_PRIV (widget);
861 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
866 gtk_tree_row_reference_free (priv->drag_row);
867 priv->drag_row = NULL;
872 individual_view_drag_drop (GtkWidget *widget,
873 GdkDragContext *drag_context,
883 EmpathyIndividualView *view;
889 menu_deactivate_cb (GtkMenuShell *menushell,
892 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
893 g_signal_handlers_disconnect_by_func (menushell,
894 menu_deactivate_cb, user_data);
896 gtk_menu_detach (GTK_MENU (menushell));
900 individual_view_popup_menu_idle_cb (gpointer user_data)
902 MenuPopupData *data = user_data;
905 menu = empathy_individual_view_get_individual_menu (data->view);
907 menu = empathy_individual_view_get_group_menu (data->view);
911 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
913 gtk_widget_show (menu);
914 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
917 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
918 * floating ref. We can either wait that the treeview releases its ref
919 * when it will be destroyed (when leaving Empathy) or explicitely
920 * detach the menu when it's not displayed any more.
921 * We go for the latter as we don't want to keep useless menus in memory
922 * during the whole lifetime of Empathy. */
923 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
927 g_slice_free (MenuPopupData, data);
933 individual_view_button_press_event_cb (EmpathyIndividualView *view,
934 GdkEventButton *event,
937 if (event->button == 3)
941 data = g_slice_new (MenuPopupData);
943 data->button = event->button;
944 data->time = event->time;
945 g_idle_add (individual_view_popup_menu_idle_cb, data);
952 individual_view_key_press_event_cb (EmpathyIndividualView *view,
956 if (event->keyval == GDK_KEY_Menu)
960 data = g_slice_new (MenuPopupData);
963 data->time = event->time;
964 g_idle_add (individual_view_popup_menu_idle_cb, data);
965 } else if (event->keyval == GDK_KEY_F2) {
966 FolksIndividual *individual;
967 EmpathyContact *contact;
969 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
971 individual = empathy_individual_view_dup_selected (view);
972 if (individual == NULL)
975 contact = empathy_contact_dup_from_folks_individual (individual);
976 if (contact == NULL) {
977 g_object_unref (individual);
980 empathy_contact_edit_dialog_show (contact, NULL);
982 g_object_unref (individual);
983 g_object_unref (contact);
990 individual_view_row_activated (GtkTreeView *view,
992 GtkTreeViewColumn *column)
994 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
995 FolksIndividual *individual;
996 EmpathyContact *contact;
1000 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1003 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1004 gtk_tree_model_get_iter (model, &iter, path);
1005 gtk_tree_model_get (model, &iter,
1006 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1008 if (individual == NULL)
1011 /* Determine which Persona to chat to, by choosing the most available one. */
1012 contact = empathy_contact_dup_best_for_action (individual,
1013 EMPATHY_ACTION_CHAT);
1015 if (contact != NULL)
1017 DEBUG ("Starting a chat");
1019 empathy_chat_with_contact (contact,
1020 gtk_get_current_event_time ());
1023 g_object_unref (individual);
1024 tp_clear_object (&contact);
1028 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1029 const gchar *path_string,
1030 EmpathyIndividualView *view)
1032 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1034 GtkTreeModel *model;
1036 FolksIndividual *individual;
1037 GdkEventButton *event;
1038 GtkMenuShell *shell;
1041 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1044 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1045 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1048 gtk_tree_model_get (model, &iter,
1049 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1050 if (individual == NULL)
1053 event = (GdkEventButton *) gtk_get_current_event ();
1055 menu = empathy_context_menu_new (GTK_WIDGET (view));
1056 shell = GTK_MENU_SHELL (menu);
1059 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1060 gtk_menu_shell_append (shell, item);
1061 gtk_widget_show (item);
1064 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1065 gtk_menu_shell_append (shell, item);
1066 gtk_widget_show (item);
1068 gtk_widget_show (menu);
1069 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1070 event->button, event->time);
1072 g_object_unref (individual);
1076 individual_view_cell_set_background (EmpathyIndividualView *view,
1077 GtkCellRenderer *cell,
1081 if (!is_group && is_active)
1083 GtkStyleContext *style;
1086 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1088 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1091 /* Here we take the current theme colour and add it to
1092 * the colour for white and average the two. This
1093 * gives a colour which is inline with the theme but
1096 empathy_make_color_whiter (&color);
1098 g_object_set (cell, "cell-background-rgba", &color, NULL);
1101 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1105 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1106 GtkCellRenderer *cell,
1107 GtkTreeModel *model,
1109 EmpathyIndividualView *view)
1115 gtk_tree_model_get (model, iter,
1116 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1117 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1118 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1121 "visible", !is_group,
1125 tp_clear_object (&pixbuf);
1127 individual_view_cell_set_background (view, cell, is_group, is_active);
1131 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1132 GtkCellRenderer *cell,
1133 GtkTreeModel *model,
1135 EmpathyIndividualView *view)
1137 GdkPixbuf *pixbuf = NULL;
1141 gtk_tree_model_get (model, iter,
1142 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1143 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1148 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1150 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1151 GTK_ICON_SIZE_MENU);
1153 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1155 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1156 GTK_ICON_SIZE_MENU);
1161 "visible", pixbuf != NULL,
1165 tp_clear_object (&pixbuf);
1171 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1172 GtkCellRenderer *cell,
1173 GtkTreeModel *model,
1175 EmpathyIndividualView *view)
1179 gboolean can_audio, can_video;
1181 gtk_tree_model_get (model, iter,
1182 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1183 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1184 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1185 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1188 "visible", !is_group && (can_audio || can_video),
1189 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1192 individual_view_cell_set_background (view, cell, is_group, is_active);
1196 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1197 GtkCellRenderer *cell,
1198 GtkTreeModel *model,
1200 EmpathyIndividualView *view)
1203 gboolean show_avatar;
1207 gtk_tree_model_get (model, iter,
1208 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1209 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1210 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1211 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1214 "visible", !is_group && show_avatar,
1218 tp_clear_object (&pixbuf);
1220 individual_view_cell_set_background (view, cell, is_group, is_active);
1224 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1225 GtkCellRenderer *cell,
1226 GtkTreeModel *model,
1228 EmpathyIndividualView *view)
1233 gtk_tree_model_get (model, iter,
1234 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1235 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1237 individual_view_cell_set_background (view, cell, is_group, is_active);
1241 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1242 GtkCellRenderer *cell,
1243 GtkTreeModel *model,
1245 EmpathyIndividualView *view)
1250 gtk_tree_model_get (model, iter,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1252 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1254 if (gtk_tree_model_iter_has_child (model, iter))
1257 gboolean row_expanded;
1259 path = gtk_tree_model_get_path (model, iter);
1261 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1262 (gtk_tree_view_column_get_tree_view (column)), path);
1263 gtk_tree_path_free (path);
1268 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1272 g_object_set (cell, "visible", FALSE, NULL);
1274 individual_view_cell_set_background (view, cell, is_group, is_active);
1278 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1283 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1284 GtkTreeModel *model;
1288 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1291 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1293 gtk_tree_model_get (model, iter,
1294 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1296 expanded = GPOINTER_TO_INT (user_data);
1297 empathy_contact_group_set_expanded (name, expanded);
1303 individual_view_start_search_cb (EmpathyIndividualView *view,
1306 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1308 if (priv->search_widget == NULL)
1311 empathy_individual_view_start_search (view);
1317 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1319 EmpathyIndividualView *view)
1321 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1323 GtkTreeViewColumn *focus_column;
1324 GtkTreeModel *model;
1326 gboolean set_cursor = FALSE;
1328 gtk_tree_model_filter_refilter (priv->filter);
1330 /* Set cursor on the first contact. If it is already set on a group,
1331 * set it on its first child contact. Note that first child of a group
1332 * is its separator, that's why we actually set to the 2nd
1335 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1336 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1340 path = gtk_tree_path_new_from_string ("0:1");
1343 else if (gtk_tree_path_get_depth (path) < 2)
1347 gtk_tree_model_get_iter (model, &iter, path);
1348 gtk_tree_model_get (model, &iter,
1349 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1354 gtk_tree_path_down (path);
1355 gtk_tree_path_next (path);
1362 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1364 if (gtk_tree_model_get_iter (model, &iter, path))
1366 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1371 gtk_tree_path_free (path);
1375 individual_view_search_activate_cb (GtkWidget *search,
1376 EmpathyIndividualView *view)
1379 GtkTreeViewColumn *focus_column;
1381 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1384 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1385 gtk_tree_path_free (path);
1387 gtk_widget_hide (search);
1392 individual_view_search_key_navigation_cb (GtkWidget *search,
1394 EmpathyIndividualView *view)
1396 GdkEventKey *eventkey = ((GdkEventKey *) event);
1397 gboolean ret = FALSE;
1399 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1400 || eventkey->keyval == GDK_KEY_F2)
1402 GdkEvent *new_event;
1404 new_event = gdk_event_copy (event);
1405 gtk_widget_grab_focus (GTK_WIDGET (view));
1406 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1407 gtk_widget_grab_focus (search);
1409 gdk_event_free (new_event);
1416 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1417 EmpathyIndividualView *view)
1419 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1420 GtkTreeModel *model;
1421 GtkTreePath *cursor_path;
1423 gboolean valid = FALSE;
1425 /* block expand or collapse handlers, they would write the
1426 * expand or collapsed setting to file otherwise */
1427 g_signal_handlers_block_by_func (view,
1428 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1429 g_signal_handlers_block_by_func (view,
1430 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1432 /* restore which groups are expanded and which are not */
1433 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1434 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1435 valid; valid = gtk_tree_model_iter_next (model, &iter))
1441 gtk_tree_model_get (model, &iter,
1442 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1443 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1452 path = gtk_tree_model_get_path (model, &iter);
1453 if ((priv->view_features &
1454 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1455 empathy_contact_group_get_expanded (name))
1457 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1461 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1464 gtk_tree_path_free (path);
1468 /* unblock expand or collapse handlers */
1469 g_signal_handlers_unblock_by_func (view,
1470 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1471 g_signal_handlers_unblock_by_func (view,
1472 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1474 /* keep the selected contact visible */
1475 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1477 if (cursor_path != NULL)
1478 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1481 gtk_tree_path_free (cursor_path);
1485 individual_view_search_show_cb (EmpathyLiveSearch *search,
1486 EmpathyIndividualView *view)
1488 /* block expand or collapse handlers during expand all, they would
1489 * write the expand or collapsed setting to file otherwise */
1490 g_signal_handlers_block_by_func (view,
1491 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1493 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1495 g_signal_handlers_unblock_by_func (view,
1496 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1500 expand_idle_foreach_cb (GtkTreeModel *model,
1503 EmpathyIndividualView *self)
1505 EmpathyIndividualViewPriv *priv;
1507 gpointer should_expand;
1510 /* We only want groups */
1511 if (gtk_tree_path_get_depth (path) > 1)
1514 gtk_tree_model_get (model, iter,
1515 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1516 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1519 if (is_group == FALSE)
1525 priv = GET_PRIV (self);
1527 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1528 &should_expand) == TRUE)
1530 if (GPOINTER_TO_INT (should_expand) == TRUE)
1531 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1533 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1535 g_hash_table_remove (priv->expand_groups, name);
1544 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1546 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1548 DEBUG ("individual_view_expand_idle_cb");
1550 g_signal_handlers_block_by_func (self,
1551 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1552 g_signal_handlers_block_by_func (self,
1553 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1555 /* The store/filter could've been removed while we were in the idle queue */
1556 if (priv->filter != NULL)
1558 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1559 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1562 g_signal_handlers_unblock_by_func (self,
1563 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1564 g_signal_handlers_unblock_by_func (self,
1565 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1567 /* Empty the table of groups to expand/contract, since it may contain groups
1568 * which no longer exist in the tree view. This can happen after going
1569 * offline, for example. */
1570 g_hash_table_remove_all (priv->expand_groups);
1571 priv->expand_groups_idle_handler = 0;
1572 g_object_unref (self);
1578 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1581 EmpathyIndividualView *view)
1583 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1584 gboolean should_expand, is_group = FALSE;
1586 gpointer will_expand;
1588 gtk_tree_model_get (model, iter,
1589 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1590 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1593 if (!is_group || EMP_STR_EMPTY (name))
1599 should_expand = (priv->view_features &
1600 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1601 (priv->search_widget != NULL &&
1602 gtk_widget_get_visible (priv->search_widget)) ||
1603 empathy_contact_group_get_expanded (name);
1605 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1606 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1607 * a hash table, and expand or contract them as appropriate all at once in
1608 * an idle handler which iterates over all the group rows. */
1609 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1610 &will_expand) == FALSE ||
1611 GPOINTER_TO_INT (will_expand) != should_expand)
1613 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1614 GINT_TO_POINTER (should_expand));
1616 if (priv->expand_groups_idle_handler == 0)
1618 priv->expand_groups_idle_handler =
1619 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1620 g_object_ref (view));
1627 /* FIXME: This is a workaround for bgo#621076 */
1629 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1632 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1633 GtkTreeModel *model;
1634 GtkTreePath *parent_path;
1635 GtkTreeIter parent_iter;
1637 if (gtk_tree_path_get_depth (path) < 2)
1640 /* A group row is visible if and only if at least one if its child is visible.
1641 * So when a row is inserted/deleted/changed in the base model, that could
1642 * modify the visibility of its parent in the filter model.
1645 model = GTK_TREE_MODEL (priv->store);
1646 parent_path = gtk_tree_path_copy (path);
1647 gtk_tree_path_up (parent_path);
1648 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1650 /* This tells the filter to verify the visibility of that row, and
1651 * show/hide it if necessary */
1652 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1653 parent_path, &parent_iter);
1655 gtk_tree_path_free (parent_path);
1659 individual_view_store_row_changed_cb (GtkTreeModel *model,
1662 EmpathyIndividualView *view)
1664 individual_view_verify_group_visibility (view, path);
1668 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1670 EmpathyIndividualView *view)
1672 individual_view_verify_group_visibility (view, path);
1676 individual_view_is_visible_individual (EmpathyIndividualView *self,
1677 FolksIndividual *individual,
1679 gboolean is_searching)
1681 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1682 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1684 GList *personas, *l;
1685 gboolean is_favorite, contains_interesting_persona = FALSE;
1687 /* We're only giving the visibility wrt filtering here, not things like
1689 if (priv->show_untrusted == FALSE &&
1690 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1695 /* Hide all individuals which consist entirely of uninteresting personas */
1696 personas = folks_individual_get_personas (individual);
1697 for (l = personas; l; l = l->next)
1699 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1701 contains_interesting_persona = TRUE;
1706 if (contains_interesting_persona == FALSE)
1709 is_favorite = folks_favourite_details_get_is_favourite (
1710 FOLKS_FAVOURITE_DETAILS (individual));
1711 if (is_searching == FALSE)
1712 return (priv->show_offline || is_online || is_favorite);
1714 /* check alias name */
1715 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1717 if (empathy_live_search_match (live, str))
1720 /* check contact id, remove the @server.com part */
1721 for (l = personas; l; l = l->next)
1724 gchar *dup_str = NULL;
1727 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1730 str = folks_persona_get_display_id (l->data);
1731 p = strstr (str, "@");
1733 str = dup_str = g_strndup (str, p - str);
1735 visible = empathy_live_search_match (live, str);
1741 /* FIXME: Add more rules here, we could check phone numbers in
1742 * contact's vCard for example. */
1748 individual_view_filter_visible_func (GtkTreeModel *model,
1752 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1753 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1754 FolksIndividual *individual = NULL;
1755 gboolean is_group, is_separator, valid;
1756 GtkTreeIter child_iter;
1757 gboolean visible, is_online;
1758 gboolean is_searching = TRUE;
1760 if (priv->search_widget == NULL ||
1761 !gtk_widget_get_visible (priv->search_widget))
1762 is_searching = FALSE;
1764 gtk_tree_model_get (model, iter,
1765 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1766 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1767 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1768 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1771 if (individual != NULL)
1773 visible = individual_view_is_visible_individual (self, individual,
1774 is_online, is_searching);
1776 g_object_unref (individual);
1778 /* FIXME: Work around bgo#626552/bgo#621076 */
1779 if (visible == TRUE)
1781 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1782 individual_view_verify_group_visibility (self, path);
1783 gtk_tree_path_free (path);
1792 /* Not a contact, not a separator, must be a group */
1793 g_return_val_if_fail (is_group, FALSE);
1795 /* only show groups which are not empty */
1796 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1797 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1799 gtk_tree_model_get (model, &child_iter,
1800 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1801 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1804 if (individual == NULL)
1807 visible = individual_view_is_visible_individual (self, individual,
1808 is_online, is_searching);
1809 g_object_unref (individual);
1811 /* show group if it has at least one visible contact in it */
1812 if (visible == TRUE)
1820 individual_view_constructed (GObject *object)
1822 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1823 GtkCellRenderer *cell;
1824 GtkTreeViewColumn *col;
1829 "headers-visible", FALSE,
1830 "show-expanders", FALSE,
1833 col = gtk_tree_view_column_new ();
1836 cell = gtk_cell_renderer_pixbuf_new ();
1837 gtk_tree_view_column_pack_start (col, cell, FALSE);
1838 gtk_tree_view_column_set_cell_data_func (col, cell,
1839 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1849 cell = gtk_cell_renderer_pixbuf_new ();
1850 gtk_tree_view_column_pack_start (col, cell, FALSE);
1851 gtk_tree_view_column_set_cell_data_func (col, cell,
1852 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1864 cell = empathy_cell_renderer_text_new ();
1865 gtk_tree_view_column_pack_start (col, cell, TRUE);
1866 gtk_tree_view_column_set_cell_data_func (col, cell,
1867 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1869 gtk_tree_view_column_add_attribute (col, cell,
1870 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1871 gtk_tree_view_column_add_attribute (col, cell,
1872 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1873 gtk_tree_view_column_add_attribute (col, cell,
1874 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1875 gtk_tree_view_column_add_attribute (col, cell,
1876 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1877 gtk_tree_view_column_add_attribute (col, cell,
1878 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1879 gtk_tree_view_column_add_attribute (col, cell,
1880 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1881 gtk_tree_view_column_add_attribute (col, cell,
1882 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1884 /* Audio Call Icon */
1885 cell = empathy_cell_renderer_activatable_new ();
1886 gtk_tree_view_column_pack_start (col, cell, FALSE);
1887 gtk_tree_view_column_set_cell_data_func (col, cell,
1888 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1891 g_object_set (cell, "visible", FALSE, NULL);
1893 g_signal_connect (cell, "path-activated",
1894 G_CALLBACK (individual_view_call_activated_cb), view);
1897 cell = gtk_cell_renderer_pixbuf_new ();
1898 gtk_tree_view_column_pack_start (col, cell, FALSE);
1899 gtk_tree_view_column_set_cell_data_func (col, cell,
1900 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1912 cell = empathy_cell_renderer_expander_new ();
1913 gtk_tree_view_column_pack_end (col, cell, FALSE);
1914 gtk_tree_view_column_set_cell_data_func (col, cell,
1915 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1918 /* Actually add the column now we have added all cell renderers */
1919 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1922 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1924 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1927 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1929 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1935 individual_view_set_view_features (EmpathyIndividualView *view,
1936 EmpathyIndividualFeatureFlags features)
1938 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1939 gboolean has_tooltip;
1941 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1943 priv->view_features = features;
1945 /* Setting reorderable is a hack that gets us row previews as drag icons
1946 for free. We override all the drag handlers. It's tricky to get the
1947 position of the drag icon right in drag_begin. GtkTreeView has special
1948 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1951 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1952 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1954 /* Update DnD source/dest */
1955 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1957 gtk_drag_source_set (GTK_WIDGET (view),
1960 G_N_ELEMENTS (drag_types_source),
1961 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1965 gtk_drag_source_unset (GTK_WIDGET (view));
1969 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1971 gtk_drag_dest_set (GTK_WIDGET (view),
1972 GTK_DEST_DEFAULT_ALL,
1974 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1978 /* FIXME: URI could still be droped depending on FT feature */
1979 gtk_drag_dest_unset (GTK_WIDGET (view));
1982 /* Update has-tooltip */
1984 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1985 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1989 individual_view_dispose (GObject *object)
1991 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1992 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1994 tp_clear_object (&priv->store);
1995 tp_clear_object (&priv->filter);
1996 tp_clear_object (&priv->tooltip_widget);
1998 empathy_individual_view_set_live_search (view, NULL);
2000 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2004 individual_view_finalize (GObject *object)
2006 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2008 if (priv->expand_groups_idle_handler != 0)
2009 g_source_remove (priv->expand_groups_idle_handler);
2010 g_hash_table_destroy (priv->expand_groups);
2012 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2016 individual_view_get_property (GObject *object,
2021 EmpathyIndividualViewPriv *priv;
2023 priv = GET_PRIV (object);
2028 g_value_set_object (value, priv->store);
2030 case PROP_VIEW_FEATURES:
2031 g_value_set_flags (value, priv->view_features);
2033 case PROP_INDIVIDUAL_FEATURES:
2034 g_value_set_flags (value, priv->individual_features);
2036 case PROP_SHOW_OFFLINE:
2037 g_value_set_boolean (value, priv->show_offline);
2039 case PROP_SHOW_UNTRUSTED:
2040 g_value_set_boolean (value, priv->show_untrusted);
2043 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2049 individual_view_set_property (GObject *object,
2051 const GValue *value,
2054 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2055 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2060 empathy_individual_view_set_store (view, g_value_get_object (value));
2062 case PROP_VIEW_FEATURES:
2063 individual_view_set_view_features (view, g_value_get_flags (value));
2065 case PROP_INDIVIDUAL_FEATURES:
2066 priv->individual_features = g_value_get_flags (value);
2068 case PROP_SHOW_OFFLINE:
2069 empathy_individual_view_set_show_offline (view,
2070 g_value_get_boolean (value));
2072 case PROP_SHOW_UNTRUSTED:
2073 empathy_individual_view_set_show_untrusted (view,
2074 g_value_get_boolean (value));
2077 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2083 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2085 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2086 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2087 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2089 object_class->constructed = individual_view_constructed;
2090 object_class->dispose = individual_view_dispose;
2091 object_class->finalize = individual_view_finalize;
2092 object_class->get_property = individual_view_get_property;
2093 object_class->set_property = individual_view_set_property;
2095 widget_class->drag_data_received = individual_view_drag_data_received;
2096 widget_class->drag_drop = individual_view_drag_drop;
2097 widget_class->drag_begin = individual_view_drag_begin;
2098 widget_class->drag_data_get = individual_view_drag_data_get;
2099 widget_class->drag_end = individual_view_drag_end;
2100 widget_class->drag_motion = individual_view_drag_motion;
2102 /* We use the class method to let user of this widget to connect to
2103 * the signal and stop emission of the signal so the default handler
2104 * won't be called. */
2105 tree_view_class->row_activated = individual_view_row_activated;
2107 klass->drag_individual_received = real_drag_individual_received_cb;
2109 signals[DRAG_INDIVIDUAL_RECEIVED] =
2110 g_signal_new ("drag-individual-received",
2111 G_OBJECT_CLASS_TYPE (klass),
2113 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2115 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2116 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2117 G_TYPE_STRING, G_TYPE_STRING);
2119 signals[DRAG_PERSONA_RECEIVED] =
2120 g_signal_new ("drag-persona-received",
2121 G_OBJECT_CLASS_TYPE (klass),
2123 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2125 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2126 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2128 g_object_class_install_property (object_class,
2130 g_param_spec_object ("store",
2131 "The store of the view",
2132 "The store of the view",
2133 EMPATHY_TYPE_INDIVIDUAL_STORE,
2134 G_PARAM_READWRITE));
2135 g_object_class_install_property (object_class,
2137 g_param_spec_flags ("view-features",
2138 "Features of the view",
2139 "Flags for all enabled features",
2140 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2141 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2142 g_object_class_install_property (object_class,
2143 PROP_INDIVIDUAL_FEATURES,
2144 g_param_spec_flags ("individual-features",
2145 "Features of the individual menu",
2146 "Flags for all enabled features for the menu",
2147 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2148 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2149 g_object_class_install_property (object_class,
2151 g_param_spec_boolean ("show-offline",
2153 "Whether contact list should display "
2154 "offline contacts", FALSE, G_PARAM_READWRITE));
2155 g_object_class_install_property (object_class,
2156 PROP_SHOW_UNTRUSTED,
2157 g_param_spec_boolean ("show-untrusted",
2158 "Show Untrusted Individuals",
2159 "Whether the view should display untrusted individuals; "
2160 "those who could not be who they say they are.",
2161 TRUE, G_PARAM_READWRITE));
2163 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2167 empathy_individual_view_init (EmpathyIndividualView *view)
2169 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2170 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2174 priv->show_untrusted = TRUE;
2176 /* Get saved group states. */
2177 empathy_contact_groups_get_all ();
2179 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2180 (GDestroyNotify) g_free, NULL);
2182 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2183 empathy_individual_store_row_separator_func, NULL, NULL);
2185 /* Connect to tree view signals rather than override. */
2186 g_signal_connect (view, "button-press-event",
2187 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2188 g_signal_connect (view, "key-press-event",
2189 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2190 g_signal_connect (view, "row-expanded",
2191 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2192 GINT_TO_POINTER (TRUE));
2193 g_signal_connect (view, "row-collapsed",
2194 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2195 GINT_TO_POINTER (FALSE));
2196 g_signal_connect (view, "query-tooltip",
2197 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2200 EmpathyIndividualView *
2201 empathy_individual_view_new (EmpathyIndividualStore *store,
2202 EmpathyIndividualViewFeatureFlags view_features,
2203 EmpathyIndividualFeatureFlags individual_features)
2205 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2207 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2209 "individual-features", individual_features,
2210 "view-features", view_features, NULL);
2214 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2216 GtkTreeSelection *selection;
2218 GtkTreeModel *model;
2219 FolksIndividual *individual;
2221 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2223 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2224 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2227 gtk_tree_model_get (model, &iter,
2228 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2234 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2235 gboolean *is_fake_group)
2237 GtkTreeSelection *selection;
2239 GtkTreeModel *model;
2244 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2246 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2247 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2250 gtk_tree_model_get (model, &iter,
2251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2252 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2253 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2261 if (is_fake_group != NULL)
2262 *is_fake_group = fake;
2269 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2270 REMOVE_DIALOG_RESPONSE_DELETE,
2271 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2275 individual_view_remove_dialog_show (GtkWindow *parent,
2276 const gchar *message,
2277 const gchar *secondary_text,
2278 gboolean block_button,
2284 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2285 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2289 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2290 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2291 gtk_widget_show (image);
2298 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2299 * mnemonic so we have to create the button manually. */
2300 button = gtk_button_new_with_mnemonic (
2301 _("Delete and _Block"));
2303 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2304 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2306 gtk_widget_show (button);
2309 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2310 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2311 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2312 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2313 "%s", secondary_text);
2315 gtk_widget_show (dialog);
2317 res = gtk_dialog_run (GTK_DIALOG (dialog));
2318 gtk_widget_destroy (dialog);
2324 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2325 EmpathyIndividualView *view)
2329 group = empathy_individual_view_dup_selected_group (view, NULL);
2336 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2338 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2339 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2340 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2342 EmpathyIndividualManager *manager =
2343 empathy_individual_manager_dup_singleton ();
2344 empathy_individual_manager_remove_group (manager, group);
2345 g_object_unref (G_OBJECT (manager));
2355 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2357 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2362 gboolean is_fake_group;
2364 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2366 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2367 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2370 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2371 if (!group || is_fake_group)
2373 /* We can't alter fake groups */
2378 menu = gtk_menu_new ();
2381 if (priv->view_features &
2382 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2383 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2384 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2385 gtk_widget_show (item);
2386 g_signal_connect (item, "activate",
2387 G_CALLBACK (individual_view_group_rename_activate_cb),
2392 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2394 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2395 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2396 GTK_ICON_SIZE_MENU);
2397 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2398 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2399 gtk_widget_show (item);
2400 g_signal_connect (item, "activate",
2401 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2410 got_avatar (GObject *source_object,
2411 GAsyncResult *result,
2414 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2415 EmpathyIndividualView *view = user_data;
2417 EmpathyIndividualManager *manager;
2420 GList *l, *personas;
2421 guint persona_count = 0;
2423 GError *error = NULL;
2426 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2431 DEBUG ("Could not get avatar: %s", error->message);
2432 g_error_free (error);
2435 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2436 * so we still display the remove dialog. */
2438 personas = folks_individual_get_personas (individual);
2440 /* If we have more than one TpfPersona, display a different message
2441 * ensuring the user knows that *all* of the meta-contacts' personas will
2443 for (l = personas; l != NULL; l = l->next)
2445 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2449 if (persona_count >= 2)
2453 if (persona_count < 2)
2455 /* Not a meta-contact */
2458 _("Do you really want to remove the contact '%s'?"),
2459 folks_alias_details_get_alias (
2460 FOLKS_ALIAS_DETAILS (individual)));
2467 _("Do you really want to remove the linked contact '%s'? "
2468 "Note that this will remove all the contacts which make up "
2469 "this linked contact."),
2470 folks_alias_details_get_alias (
2471 FOLKS_ALIAS_DETAILS (individual)));
2475 manager = empathy_individual_manager_dup_singleton ();
2476 can_block = empathy_individual_manager_supports_blocking (manager,
2478 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2479 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2480 text, can_block, avatar);
2482 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2483 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2487 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2489 if (!empathy_block_individual_dialog_show (parent, individual,
2493 empathy_individual_manager_set_blocked (manager, individual,
2497 empathy_individual_manager_remove (manager, individual, "");
2502 g_object_unref (individual);
2503 g_object_unref (manager);
2507 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2508 EmpathyIndividualView *view)
2510 FolksIndividual *individual;
2512 individual = empathy_individual_view_dup_selected (view);
2514 if (individual != NULL)
2516 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2517 48, 48, NULL, got_avatar, view);
2518 g_object_unref (individual);
2523 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2524 EmpathyLinkingDialog *linking_dialog,
2525 EmpathyIndividualView *self)
2527 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2528 EmpathyIndividualLinker *linker;
2530 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2531 empathy_individual_linker_set_search_text (linker,
2532 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2536 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2538 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2539 FolksIndividual *individual;
2540 GtkWidget *menu = NULL;
2543 gboolean can_remove = FALSE;
2546 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2548 individual = empathy_individual_view_dup_selected (view);
2549 if (individual == NULL)
2552 /* If any of the Individual's personas can be removed, add an option to
2553 * remove. This will act as a best-effort option. If any Personas cannot be
2554 * removed from the server, then this option will just be inactive upon
2555 * subsequent menu openings */
2556 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2558 FolksPersona *persona = FOLKS_PERSONA (l->data);
2559 FolksPersonaStore *store = folks_persona_get_store (persona);
2560 FolksMaybeBool maybe_can_remove =
2561 folks_persona_store_get_can_remove_personas (store);
2563 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2570 menu = empathy_individual_menu_new (individual, priv->individual_features);
2572 /* Remove contact */
2573 if ((priv->view_features &
2574 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2577 /* create the menu if required, or just add a separator */
2579 menu = gtk_menu_new ();
2582 item = gtk_separator_menu_item_new ();
2583 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2584 gtk_widget_show (item);
2588 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2589 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2590 GTK_ICON_SIZE_MENU);
2591 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2592 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2593 gtk_widget_show (item);
2594 g_signal_connect (item, "activate",
2595 G_CALLBACK (individual_view_remove_activate_cb), view);
2598 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2599 * set the live search text on the new linking dialogue to be the same as
2601 g_signal_connect (menu, "link-contacts-activated",
2602 (GCallback) individual_menu_link_contacts_activated_cb, view);
2604 g_object_unref (individual);
2610 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2611 EmpathyLiveSearch *search)
2613 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2615 /* remove old handlers if old search was not null */
2616 if (priv->search_widget != NULL)
2618 g_signal_handlers_disconnect_by_func (view,
2619 individual_view_start_search_cb, NULL);
2621 g_signal_handlers_disconnect_by_func (priv->search_widget,
2622 individual_view_search_text_notify_cb, view);
2623 g_signal_handlers_disconnect_by_func (priv->search_widget,
2624 individual_view_search_activate_cb, view);
2625 g_signal_handlers_disconnect_by_func (priv->search_widget,
2626 individual_view_search_key_navigation_cb, view);
2627 g_signal_handlers_disconnect_by_func (priv->search_widget,
2628 individual_view_search_hide_cb, view);
2629 g_signal_handlers_disconnect_by_func (priv->search_widget,
2630 individual_view_search_show_cb, view);
2631 g_object_unref (priv->search_widget);
2632 priv->search_widget = NULL;
2635 /* connect handlers if new search is not null */
2638 priv->search_widget = g_object_ref (search);
2640 g_signal_connect (view, "start-interactive-search",
2641 G_CALLBACK (individual_view_start_search_cb), NULL);
2643 g_signal_connect (priv->search_widget, "notify::text",
2644 G_CALLBACK (individual_view_search_text_notify_cb), view);
2645 g_signal_connect (priv->search_widget, "activate",
2646 G_CALLBACK (individual_view_search_activate_cb), view);
2647 g_signal_connect (priv->search_widget, "key-navigation",
2648 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2649 g_signal_connect (priv->search_widget, "hide",
2650 G_CALLBACK (individual_view_search_hide_cb), view);
2651 g_signal_connect (priv->search_widget, "show",
2652 G_CALLBACK (individual_view_search_show_cb), view);
2657 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2659 EmpathyIndividualViewPriv *priv;
2661 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2663 priv = GET_PRIV (self);
2665 return (priv->search_widget != NULL &&
2666 gtk_widget_get_visible (priv->search_widget));
2670 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2672 EmpathyIndividualViewPriv *priv;
2674 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2676 priv = GET_PRIV (self);
2678 return priv->show_offline;
2682 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2683 gboolean show_offline)
2685 EmpathyIndividualViewPriv *priv;
2687 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2689 priv = GET_PRIV (self);
2691 priv->show_offline = show_offline;
2693 g_object_notify (G_OBJECT (self), "show-offline");
2694 gtk_tree_model_filter_refilter (priv->filter);
2698 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2700 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2702 return GET_PRIV (self)->show_untrusted;
2706 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2707 gboolean show_untrusted)
2709 EmpathyIndividualViewPriv *priv;
2711 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2713 priv = GET_PRIV (self);
2715 priv->show_untrusted = show_untrusted;
2717 g_object_notify (G_OBJECT (self), "show-untrusted");
2718 gtk_tree_model_filter_refilter (priv->filter);
2721 EmpathyIndividualStore *
2722 empathy_individual_view_get_store (EmpathyIndividualView *self)
2724 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2726 return GET_PRIV (self)->store;
2730 empathy_individual_view_set_store (EmpathyIndividualView *self,
2731 EmpathyIndividualStore *store)
2733 EmpathyIndividualViewPriv *priv;
2735 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2736 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2738 priv = GET_PRIV (self);
2740 /* Destroy the old filter and remove the old store */
2741 if (priv->store != NULL)
2743 g_signal_handlers_disconnect_by_func (priv->store,
2744 individual_view_store_row_changed_cb, self);
2745 g_signal_handlers_disconnect_by_func (priv->store,
2746 individual_view_store_row_deleted_cb, self);
2748 g_signal_handlers_disconnect_by_func (priv->filter,
2749 individual_view_row_has_child_toggled_cb, self);
2751 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2754 tp_clear_object (&priv->filter);
2755 tp_clear_object (&priv->store);
2757 /* Set the new store */
2758 priv->store = store;
2762 g_object_ref (store);
2764 /* Create a new filter */
2765 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2766 GTK_TREE_MODEL (priv->store), NULL));
2767 gtk_tree_model_filter_set_visible_func (priv->filter,
2768 individual_view_filter_visible_func, self, NULL);
2770 g_signal_connect (priv->filter, "row-has-child-toggled",
2771 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2772 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2773 GTK_TREE_MODEL (priv->filter));
2775 tp_g_signal_connect_object (priv->store, "row-changed",
2776 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2777 tp_g_signal_connect_object (priv->store, "row-inserted",
2778 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2779 tp_g_signal_connect_object (priv->store, "row-deleted",
2780 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2785 empathy_individual_view_start_search (EmpathyIndividualView *self)
2787 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2789 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2790 g_return_if_fail (priv->search_widget != NULL);
2792 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2793 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2795 gtk_widget_show (GTK_WIDGET (priv->search_widget));