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-individual-edit-dialog.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"
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
62 /* Active users are those which have recently changed state
63 * (e.g. online, offline or from normal to a busy state).
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
69 EmpathyIndividualStore *store;
70 GtkTreeRowReference *drag_row;
71 EmpathyIndividualViewFeatureFlags view_features;
72 EmpathyIndividualFeatureFlags individual_features;
73 GtkWidget *tooltip_widget;
75 gboolean show_offline;
76 gboolean show_untrusted;
77 gboolean show_uninteresting;
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
92 GtkTreeModelFilterVisibleFunc custom_filter;
93 gpointer custom_filter_data;
94 } EmpathyIndividualViewPriv;
98 EmpathyIndividualView *view;
105 EmpathyIndividualView *view;
106 FolksIndividual *individual;
115 PROP_INDIVIDUAL_FEATURES,
118 PROP_SHOW_UNINTERESTING,
121 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
122 * specific EmpathyContacts (between/in/out of Individuals) */
125 DND_DRAG_TYPE_UNKNOWN = -1,
126 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
127 DND_DRAG_TYPE_PERSONA_ID,
128 DND_DRAG_TYPE_URI_LIST,
129 DND_DRAG_TYPE_STRING,
132 #define DRAG_TYPE(T,I) \
133 { (gchar *) T, 0, I }
135 static const GtkTargetEntry drag_types_dest[] = {
136 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
137 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
138 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
139 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
140 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
141 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
144 static const GtkTargetEntry drag_types_source[] = {
145 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
150 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
154 DRAG_INDIVIDUAL_RECEIVED,
155 DRAG_PERSONA_RECEIVED,
159 static guint signals[LAST_SIGNAL];
161 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
165 individual_view_tooltip_destroy_cb (GtkWidget *widget,
166 EmpathyIndividualView *view)
168 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
170 tp_clear_object (&priv->tooltip_widget);
174 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
177 gboolean keyboard_mode,
181 EmpathyIndividualViewPriv *priv;
182 FolksIndividual *individual;
186 static gint running = 0;
187 gboolean ret = FALSE;
189 priv = GET_PRIV (view);
191 /* Avoid an infinite loop. See GNOME bug #574377 */
197 /* Don't show the tooltip if there's already a popup menu */
198 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
201 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
202 keyboard_mode, &model, &path, &iter))
205 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
206 gtk_tree_path_free (path);
208 gtk_tree_model_get (model, &iter,
209 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
211 if (individual == NULL)
214 if (priv->tooltip_widget == NULL)
216 priv->tooltip_widget = empathy_individual_widget_new (individual,
217 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
219 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
220 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
221 g_object_ref (priv->tooltip_widget);
223 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
224 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
226 gtk_widget_show (priv->tooltip_widget);
230 empathy_individual_widget_set_individual (
231 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
234 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
237 g_object_unref (individual);
245 groups_change_group_cb (GObject *source,
246 GAsyncResult *result,
249 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
250 GError *error = NULL;
252 folks_group_details_change_group_finish (group_details, result, &error);
255 g_warning ("failed to change group: %s", error->message);
256 g_clear_error (&error);
261 group_can_be_modified (const gchar *name,
262 gboolean is_fake_group,
265 /* Real groups can always be modified */
269 /* The favorite fake group can be modified so users can
270 * add/remove favorites using DnD */
271 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
274 /* We can remove contacts from the 'ungrouped' fake group */
275 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
282 individual_view_individual_drag_received (GtkWidget *self,
283 GdkDragContext *context,
286 GtkSelectionData *selection)
288 EmpathyIndividualViewPriv *priv;
289 EmpathyIndividualManager *manager = NULL;
290 FolksIndividual *individual;
291 GtkTreePath *source_path;
292 const gchar *sel_data;
293 gchar *new_group = NULL;
294 gchar *old_group = NULL;
295 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
297 priv = GET_PRIV (self);
299 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
300 new_group = empathy_individual_store_get_parent_group (model, path,
301 NULL, &new_group_is_fake);
303 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
306 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
307 * feature. Otherwise, we just add the dropped contact to whichever group
308 * they were dropped in, and don't remove them from their old group. This
309 * allows for Individual views which shouldn't allow Individuals to have
310 * their groups changed, and also for dragging Individuals between Individual
312 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
313 priv->drag_row != NULL)
315 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
319 empathy_individual_store_get_parent_group (model, source_path,
320 NULL, &old_group_is_fake);
321 gtk_tree_path_free (source_path);
324 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
327 if (!tp_strdiff (old_group, new_group))
330 else if (priv->drag_row != NULL)
332 /* We don't allow changing Individuals' groups, and this Individual was
333 * dragged from another group in *this* Individual view, so we disallow
338 /* XXX: for contacts, we used to ensure the account, create the contact
339 * factory, and then wait on the contacts. But they should already be
340 * created by this point */
342 manager = empathy_individual_manager_dup_singleton ();
343 individual = empathy_individual_manager_lookup_member (manager, sel_data);
345 if (individual == NULL)
347 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
351 /* FIXME: We should probably wait for the cb before calling
354 /* Emit a signal notifying of the drag. We change the Individual's groups in
355 * the default signal handler. */
356 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
357 gdk_drag_context_get_selected_action (context), individual, new_group,
363 tp_clear_object (&manager);
371 real_drag_individual_received_cb (EmpathyIndividualView *self,
372 GdkDragAction action,
373 FolksIndividual *individual,
374 const gchar *new_group,
375 const gchar *old_group)
377 DEBUG ("individual %s dragged from '%s' to '%s'",
378 folks_individual_get_id (individual), old_group, new_group);
380 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
382 /* Mark contact as favourite */
383 folks_favourite_details_set_is_favourite (
384 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
388 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
390 /* Remove contact as favourite */
391 folks_favourite_details_set_is_favourite (
392 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
394 /* Don't try to remove it */
398 if (new_group != NULL)
400 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
401 new_group, TRUE, groups_change_group_cb, NULL);
404 if (old_group != NULL && action == GDK_ACTION_MOVE)
406 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
407 old_group, FALSE, groups_change_group_cb, NULL);
412 individual_view_persona_drag_received (GtkWidget *self,
413 GdkDragContext *context,
416 GtkSelectionData *selection)
418 EmpathyIndividualManager *manager = NULL;
419 FolksIndividual *individual = NULL;
420 FolksPersona *persona = NULL;
421 const gchar *persona_uid;
422 GList *individuals, *l;
423 GeeIterator *iter = NULL;
424 gboolean retval = FALSE;
426 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
428 /* FIXME: This is slow, but the only way to find the Persona we're having
430 manager = empathy_individual_manager_dup_singleton ();
431 individuals = empathy_individual_manager_get_members (manager);
433 for (l = individuals; l != NULL; l = l->next)
437 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
438 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
439 while (gee_iterator_next (iter))
441 FolksPersona *persona_cur = gee_iterator_get (iter);
443 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
445 /* takes ownership of the ref */
446 persona = persona_cur;
447 individual = g_object_ref (l->data);
450 g_clear_object (&persona_cur);
452 g_clear_object (&iter);
456 g_clear_object (&iter);
457 g_list_free (individuals);
459 if (persona == NULL || individual == NULL)
461 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
465 /* Emit a signal notifying of the drag. We change the Individual's groups in
466 * the default signal handler. */
467 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
468 gdk_drag_context_get_selected_action (context), persona, individual,
472 tp_clear_object (&manager);
473 tp_clear_object (&persona);
474 tp_clear_object (&individual);
480 individual_view_file_drag_received (GtkWidget *view,
481 GdkDragContext *context,
484 GtkSelectionData *selection)
487 const gchar *sel_data;
488 FolksIndividual *individual;
489 EmpathyContact *contact;
491 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
493 gtk_tree_model_get_iter (model, &iter, path);
494 gtk_tree_model_get (model, &iter,
495 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
496 if (individual == NULL)
499 contact = empathy_contact_dup_from_folks_individual (individual);
500 empathy_send_file_from_uri_list (contact, sel_data);
502 g_object_unref (individual);
503 tp_clear_object (&contact);
509 individual_view_drag_data_received (GtkWidget *view,
510 GdkDragContext *context,
513 GtkSelectionData *selection,
519 GtkTreeViewDropPosition position;
521 gboolean success = TRUE;
523 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
525 /* Get destination group information. */
526 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
527 x, y, &path, &position);
532 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
534 success = individual_view_individual_drag_received (view,
535 context, model, path, selection);
537 else if (info == DND_DRAG_TYPE_PERSONA_ID)
539 success = individual_view_persona_drag_received (view, context, model,
542 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
544 success = individual_view_file_drag_received (view,
545 context, model, path, selection);
548 gtk_tree_path_free (path);
549 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
553 individual_view_drag_motion_cb (DragMotionData *data)
555 if (data->view != NULL)
557 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
558 g_object_remove_weak_pointer (G_OBJECT (data->view),
559 (gpointer *) &data->view);
562 data->timeout_id = 0;
567 /* Minimum distance between the mouse pointer and a horizontal border when we
568 start auto scrolling. */
569 #define AUTO_SCROLL_MARGIN_SIZE 20
570 /* How far to scroll per one tick. */
571 #define AUTO_SCROLL_PITCH 10
574 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
576 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
580 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
582 if (priv->distance < 0)
583 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
585 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
587 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
588 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
590 gtk_adjustment_set_value (adj, new_value);
596 individual_view_drag_motion (GtkWidget *widget,
597 GdkDragContext *context,
602 EmpathyIndividualViewPriv *priv;
606 static DragMotionData *dm = NULL;
609 gboolean is_different = FALSE;
610 gboolean cleanup = TRUE;
611 gboolean retval = TRUE;
612 GtkAllocation allocation;
614 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
616 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
617 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
620 if (priv->auto_scroll_timeout_id != 0)
622 g_source_remove (priv->auto_scroll_timeout_id);
623 priv->auto_scroll_timeout_id = 0;
626 gtk_widget_get_allocation (widget, &allocation);
628 if (y < AUTO_SCROLL_MARGIN_SIZE ||
629 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
631 if (y < AUTO_SCROLL_MARGIN_SIZE)
632 priv->distance = MIN (-y, -1);
634 priv->distance = MAX (allocation.height - y, 1);
636 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
637 (GSourceFunc) individual_view_auto_scroll_cb, widget);
640 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
641 x, y, &path, NULL, NULL, NULL);
643 cleanup &= (dm == NULL);
647 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
648 is_different = ((dm == NULL) || ((dm != NULL)
649 && gtk_tree_path_compare (dm->path, path) != 0));
656 /* Coordinates don't point to an actual row, so make sure the pointer
657 and highlighting don't indicate that a drag is possible.
659 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
660 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
663 target = gtk_drag_dest_find_target (widget, context, NULL);
664 gtk_tree_model_get_iter (model, &iter, path);
666 /* Determine the DndDragType of the data */
667 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
669 if (target == drag_atoms_dest[i])
671 drag_type = drag_types_dest[i].info;
676 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
677 drag_type == DND_DRAG_TYPE_STRING)
679 /* This is a file drag, and it can only be dropped on contacts,
681 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
682 * even if we have a valid target. */
683 FolksIndividual *individual = NULL;
684 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
686 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
688 gtk_tree_model_get (model, &iter,
689 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
693 if (individual != NULL)
695 EmpathyContact *contact = NULL;
697 contact = empathy_contact_dup_from_folks_individual (individual);
699 caps = empathy_contact_get_capabilities (contact);
701 tp_clear_object (&contact);
704 if (individual != NULL &&
705 folks_presence_details_is_online (
706 FOLKS_PRESENCE_DETAILS (individual)) &&
707 (caps & EMPATHY_CAPABILITIES_FT))
709 gdk_drag_status (context, GDK_ACTION_COPY, time_);
710 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
711 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
715 gdk_drag_status (context, 0, time_);
716 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
720 if (individual != NULL)
721 g_object_unref (individual);
723 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
724 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
725 priv->drag_row == NULL)) ||
726 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
727 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
729 /* If target != GDK_NONE, then we have a contact (individual or persona)
730 drag. If we're pointing to a group, highlight it. Otherwise, if the
731 contact we're pointing to is in a group, highlight that. Otherwise,
732 set the drag position to before the first row for a drag into
733 the "non-group" at the top.
734 If it's an Individual:
735 We only highlight things if the contact is from a different
736 Individual view, or if this Individual view has
737 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
738 which don't have FEATURE_GROUPS_CHANGE, but do have
739 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
741 We only highlight things if we have FEATURE_PERSONA_DROP.
743 GtkTreeIter group_iter;
745 GtkTreePath *group_path;
746 gtk_tree_model_get (model, &iter,
747 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
754 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
755 gtk_tree_model_get (model, &group_iter,
756 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
760 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
761 group_path = gtk_tree_model_get_path (model, &group_iter);
762 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
763 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
764 gtk_tree_path_free (group_path);
768 group_path = gtk_tree_path_new_first ();
769 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
770 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
771 group_path, GTK_TREE_VIEW_DROP_BEFORE);
775 if (!is_different && !cleanup)
780 gtk_tree_path_free (dm->path);
783 g_source_remove (dm->timeout_id);
791 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
793 dm = g_new0 (DragMotionData, 1);
795 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
796 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
797 dm->path = gtk_tree_path_copy (path);
799 dm->timeout_id = g_timeout_add_seconds (1,
800 (GSourceFunc) individual_view_drag_motion_cb, dm);
807 individual_view_drag_begin (GtkWidget *widget,
808 GdkDragContext *context)
810 EmpathyIndividualViewPriv *priv;
811 GtkTreeSelection *selection;
816 priv = GET_PRIV (widget);
818 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
819 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
822 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
825 path = gtk_tree_model_get_path (model, &iter);
826 priv->drag_row = gtk_tree_row_reference_new (model, path);
827 gtk_tree_path_free (path);
831 individual_view_drag_data_get (GtkWidget *widget,
832 GdkDragContext *context,
833 GtkSelectionData *selection,
837 EmpathyIndividualViewPriv *priv;
838 GtkTreePath *src_path;
841 FolksIndividual *individual;
842 const gchar *individual_id;
844 priv = GET_PRIV (widget);
846 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
847 if (priv->drag_row == NULL)
850 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
851 if (src_path == NULL)
854 if (!gtk_tree_model_get_iter (model, &iter, src_path))
856 gtk_tree_path_free (src_path);
860 gtk_tree_path_free (src_path);
863 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
864 if (individual == NULL)
867 individual_id = folks_individual_get_id (individual);
869 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
871 gtk_selection_data_set (selection,
872 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
873 (guchar *) individual_id, strlen (individual_id) + 1);
876 g_object_unref (individual);
880 individual_view_drag_end (GtkWidget *widget,
881 GdkDragContext *context)
883 EmpathyIndividualViewPriv *priv;
885 priv = GET_PRIV (widget);
887 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
892 gtk_tree_row_reference_free (priv->drag_row);
893 priv->drag_row = NULL;
898 individual_view_drag_drop (GtkWidget *widget,
899 GdkDragContext *drag_context,
909 EmpathyIndividualView *view;
915 menu_deactivate_cb (GtkMenuShell *menushell,
918 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
919 g_signal_handlers_disconnect_by_func (menushell,
920 menu_deactivate_cb, user_data);
922 gtk_menu_detach (GTK_MENU (menushell));
926 individual_view_popup_menu_idle_cb (gpointer user_data)
928 MenuPopupData *data = user_data;
931 menu = empathy_individual_view_get_individual_menu (data->view);
933 menu = empathy_individual_view_get_group_menu (data->view);
937 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
939 gtk_widget_show (menu);
940 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
943 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
944 * floating ref. We can either wait that the treeview releases its ref
945 * when it will be destroyed (when leaving Empathy) or explicitely
946 * detach the menu when it's not displayed any more.
947 * We go for the latter as we don't want to keep useless menus in memory
948 * during the whole lifetime of Empathy. */
949 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
953 g_slice_free (MenuPopupData, data);
959 individual_view_button_press_event_cb (EmpathyIndividualView *view,
960 GdkEventButton *event,
963 if (event->button == 3)
967 data = g_slice_new (MenuPopupData);
969 data->button = event->button;
970 data->time = event->time;
971 g_idle_add (individual_view_popup_menu_idle_cb, data);
978 individual_view_key_press_event_cb (EmpathyIndividualView *view,
982 if (event->keyval == GDK_KEY_Menu)
986 data = g_slice_new (MenuPopupData);
989 data->time = event->time;
990 g_idle_add (individual_view_popup_menu_idle_cb, data);
991 } else if (event->keyval == GDK_KEY_F2) {
992 FolksIndividual *individual;
994 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
996 individual = empathy_individual_view_dup_selected (view);
997 if (individual == NULL)
1000 empathy_individual_edit_dialog_show (individual, NULL);
1002 g_object_unref (individual);
1009 individual_view_row_activated (GtkTreeView *view,
1011 GtkTreeViewColumn *column)
1013 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1014 FolksIndividual *individual;
1015 EmpathyContact *contact;
1016 GtkTreeModel *model;
1019 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1022 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1023 gtk_tree_model_get_iter (model, &iter, path);
1024 gtk_tree_model_get (model, &iter,
1025 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1027 if (individual == NULL)
1030 /* Determine which Persona to chat to, by choosing the most available one. */
1031 contact = empathy_contact_dup_best_for_action (individual,
1032 EMPATHY_ACTION_CHAT);
1034 if (contact != NULL)
1036 DEBUG ("Starting a chat");
1038 empathy_chat_with_contact (contact,
1039 gtk_get_current_event_time ());
1042 g_object_unref (individual);
1043 tp_clear_object (&contact);
1047 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1048 const gchar *path_string,
1049 EmpathyIndividualView *view)
1051 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1053 GtkTreeModel *model;
1055 FolksIndividual *individual;
1056 GdkEventButton *event;
1057 GtkMenuShell *shell;
1060 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1063 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1064 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1067 gtk_tree_model_get (model, &iter,
1068 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1069 if (individual == NULL)
1072 event = (GdkEventButton *) gtk_get_current_event ();
1074 menu = empathy_context_menu_new (GTK_WIDGET (view));
1075 shell = GTK_MENU_SHELL (menu);
1078 item = empathy_individual_audio_call_menu_item_new (individual);
1079 gtk_menu_shell_append (shell, item);
1080 gtk_widget_show (item);
1083 item = empathy_individual_video_call_menu_item_new (individual);
1084 gtk_menu_shell_append (shell, item);
1085 gtk_widget_show (item);
1087 gtk_widget_show (menu);
1088 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1089 event->button, event->time);
1091 g_object_unref (individual);
1095 individual_view_cell_set_background (EmpathyIndividualView *view,
1096 GtkCellRenderer *cell,
1100 if (!is_group && is_active)
1102 GtkStyleContext *style;
1105 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1107 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1110 /* Here we take the current theme colour and add it to
1111 * the colour for white and average the two. This
1112 * gives a colour which is inline with the theme but
1115 empathy_make_color_whiter (&color);
1117 g_object_set (cell, "cell-background-rgba", &color, NULL);
1120 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1124 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1125 GtkCellRenderer *cell,
1126 GtkTreeModel *model,
1128 EmpathyIndividualView *view)
1134 gtk_tree_model_get (model, iter,
1135 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1136 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1137 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1140 "visible", !is_group,
1144 tp_clear_object (&pixbuf);
1146 individual_view_cell_set_background (view, cell, is_group, is_active);
1150 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1151 GtkCellRenderer *cell,
1152 GtkTreeModel *model,
1154 EmpathyIndividualView *view)
1156 GdkPixbuf *pixbuf = NULL;
1160 gtk_tree_model_get (model, iter,
1161 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1162 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1167 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1169 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1170 GTK_ICON_SIZE_MENU);
1172 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1174 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1175 GTK_ICON_SIZE_MENU);
1180 "visible", pixbuf != NULL,
1184 tp_clear_object (&pixbuf);
1190 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1191 GtkCellRenderer *cell,
1192 GtkTreeModel *model,
1194 EmpathyIndividualView *view)
1198 gboolean can_audio, can_video;
1200 gtk_tree_model_get (model, iter,
1201 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1202 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1203 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1204 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1207 "visible", !is_group && (can_audio || can_video),
1208 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1211 individual_view_cell_set_background (view, cell, is_group, is_active);
1215 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1216 GtkCellRenderer *cell,
1217 GtkTreeModel *model,
1219 EmpathyIndividualView *view)
1222 gboolean show_avatar;
1226 gtk_tree_model_get (model, iter,
1227 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1228 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1229 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1230 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1233 "visible", !is_group && show_avatar,
1237 tp_clear_object (&pixbuf);
1239 individual_view_cell_set_background (view, cell, is_group, is_active);
1243 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1244 GtkCellRenderer *cell,
1245 GtkTreeModel *model,
1247 EmpathyIndividualView *view)
1252 gtk_tree_model_get (model, iter,
1253 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1254 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1256 individual_view_cell_set_background (view, cell, is_group, is_active);
1260 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1261 GtkCellRenderer *cell,
1262 GtkTreeModel *model,
1264 EmpathyIndividualView *view)
1269 gtk_tree_model_get (model, iter,
1270 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1271 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1273 if (gtk_tree_model_iter_has_child (model, iter))
1276 gboolean row_expanded;
1278 path = gtk_tree_model_get_path (model, iter);
1280 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1281 (gtk_tree_view_column_get_tree_view (column)), path);
1282 gtk_tree_path_free (path);
1287 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1291 g_object_set (cell, "visible", FALSE, NULL);
1293 individual_view_cell_set_background (view, cell, is_group, is_active);
1297 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1302 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1303 GtkTreeModel *model;
1307 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1310 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1312 gtk_tree_model_get (model, iter,
1313 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1315 expanded = GPOINTER_TO_INT (user_data);
1316 empathy_contact_group_set_expanded (name, expanded);
1322 individual_view_start_search_cb (EmpathyIndividualView *view,
1325 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1327 if (priv->search_widget == NULL)
1330 empathy_individual_view_start_search (view);
1336 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1338 EmpathyIndividualView *view)
1340 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1342 GtkTreeViewColumn *focus_column;
1343 GtkTreeModel *model;
1345 gboolean set_cursor = FALSE;
1347 gtk_tree_model_filter_refilter (priv->filter);
1349 /* Set cursor on the first contact. If it is already set on a group,
1350 * set it on its first child contact. Note that first child of a group
1351 * is its separator, that's why we actually set to the 2nd
1354 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1355 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1359 path = gtk_tree_path_new_from_string ("0:1");
1362 else if (gtk_tree_path_get_depth (path) < 2)
1366 gtk_tree_model_get_iter (model, &iter, path);
1367 gtk_tree_model_get (model, &iter,
1368 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1373 gtk_tree_path_down (path);
1374 gtk_tree_path_next (path);
1381 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1383 if (gtk_tree_model_get_iter (model, &iter, path))
1385 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1390 gtk_tree_path_free (path);
1394 individual_view_search_activate_cb (GtkWidget *search,
1395 EmpathyIndividualView *view)
1398 GtkTreeViewColumn *focus_column;
1400 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1403 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1404 gtk_tree_path_free (path);
1406 gtk_widget_hide (search);
1411 individual_view_search_key_navigation_cb (GtkWidget *search,
1413 EmpathyIndividualView *view)
1415 GdkEvent *new_event;
1416 gboolean ret = FALSE;
1418 new_event = gdk_event_copy (event);
1419 gtk_widget_grab_focus (GTK_WIDGET (view));
1420 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1421 gtk_widget_grab_focus (search);
1423 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,
1538 priv = GET_PRIV (self);
1540 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1543 if (GPOINTER_TO_INT (should_expand))
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,
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,
1697 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1698 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1701 gboolean is_favorite;
1703 /* Always display individuals having pending events */
1704 if (event_count > 0)
1707 /* We're only giving the visibility wrt filtering here, not things like
1709 if (!priv->show_untrusted &&
1710 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1715 if (!priv->show_uninteresting)
1717 gboolean contains_interesting_persona = FALSE;
1719 /* Hide all individuals which consist entirely of uninteresting
1721 personas = folks_individual_get_personas (individual);
1722 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1723 while (!contains_interesting_persona && gee_iterator_next (iter))
1725 FolksPersona *persona = gee_iterator_get (iter);
1727 if (empathy_folks_persona_is_interesting (persona))
1728 contains_interesting_persona = TRUE;
1730 g_clear_object (&persona);
1732 g_clear_object (&iter);
1734 if (!contains_interesting_persona)
1738 is_favorite = folks_favourite_details_get_is_favourite (
1739 FOLKS_FAVOURITE_DETAILS (individual));
1740 if (!is_searching) {
1741 if (is_favorite && is_fake_group &&
1742 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1743 /* Always display favorite contacts in the favorite group */
1746 return (priv->show_offline || is_online);
1749 return empathy_individual_match_string (individual,
1750 empathy_live_search_get_text (live),
1751 empathy_live_search_get_words (live));
1755 get_group (GtkTreeModel *model,
1759 GtkTreeIter parent_iter;
1764 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1767 gtk_tree_model_get (model, &parent_iter,
1768 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1769 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1777 individual_view_filter_visible_func (GtkTreeModel *model,
1781 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1782 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1783 FolksIndividual *individual = NULL;
1784 gboolean is_group, is_separator, valid;
1785 GtkTreeIter child_iter;
1786 gboolean visible, is_online;
1787 gboolean is_searching = TRUE;
1790 if (priv->custom_filter != NULL)
1791 return priv->custom_filter (model, iter, priv->custom_filter_data);
1793 if (priv->search_widget == NULL ||
1794 !gtk_widget_get_visible (priv->search_widget))
1795 is_searching = FALSE;
1797 gtk_tree_model_get (model, iter,
1798 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1799 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1800 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1801 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1802 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1805 if (individual != NULL)
1808 gboolean is_fake_group;
1810 group = get_group (model, iter, &is_fake_group);
1812 visible = individual_view_is_visible_individual (self, individual,
1813 is_online, is_searching, group, is_fake_group, event_count);
1815 g_object_unref (individual);
1818 /* FIXME: Work around bgo#626552/bgo#621076 */
1821 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1822 individual_view_verify_group_visibility (self, path);
1823 gtk_tree_path_free (path);
1832 /* Not a contact, not a separator, must be a group */
1833 g_return_val_if_fail (is_group, FALSE);
1835 /* only show groups which are not empty */
1836 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1837 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1840 gboolean is_fake_group;
1842 gtk_tree_model_get (model, &child_iter,
1843 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1844 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1845 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1848 if (individual == NULL)
1851 group = get_group (model, &child_iter, &is_fake_group);
1853 visible = individual_view_is_visible_individual (self, individual,
1854 is_online, is_searching, group, is_fake_group, event_count);
1856 g_object_unref (individual);
1859 /* show group if it has at least one visible contact in it */
1868 individual_view_constructed (GObject *object)
1870 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1871 GtkCellRenderer *cell;
1872 GtkTreeViewColumn *col;
1877 "headers-visible", FALSE,
1878 "show-expanders", FALSE,
1881 col = gtk_tree_view_column_new ();
1884 cell = gtk_cell_renderer_pixbuf_new ();
1885 gtk_tree_view_column_pack_start (col, cell, FALSE);
1886 gtk_tree_view_column_set_cell_data_func (col, cell,
1887 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
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_group_icon_cell_data_func,
1912 cell = empathy_cell_renderer_text_new ();
1913 gtk_tree_view_column_pack_start (col, cell, TRUE);
1914 gtk_tree_view_column_set_cell_data_func (col, cell,
1915 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1917 gtk_tree_view_column_add_attribute (col, cell,
1918 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1919 gtk_tree_view_column_add_attribute (col, cell,
1920 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1921 gtk_tree_view_column_add_attribute (col, cell,
1922 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1923 gtk_tree_view_column_add_attribute (col, cell,
1924 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1925 gtk_tree_view_column_add_attribute (col, cell,
1926 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1927 gtk_tree_view_column_add_attribute (col, cell,
1928 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1929 gtk_tree_view_column_add_attribute (col, cell,
1930 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1932 /* Audio Call Icon */
1933 cell = empathy_cell_renderer_activatable_new ();
1934 gtk_tree_view_column_pack_start (col, cell, FALSE);
1935 gtk_tree_view_column_set_cell_data_func (col, cell,
1936 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1939 g_object_set (cell, "visible", FALSE, NULL);
1941 g_signal_connect (cell, "path-activated",
1942 G_CALLBACK (individual_view_call_activated_cb), view);
1945 cell = gtk_cell_renderer_pixbuf_new ();
1946 gtk_tree_view_column_pack_start (col, cell, FALSE);
1947 gtk_tree_view_column_set_cell_data_func (col, cell,
1948 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1960 cell = empathy_cell_renderer_expander_new ();
1961 gtk_tree_view_column_pack_end (col, cell, FALSE);
1962 gtk_tree_view_column_set_cell_data_func (col, cell,
1963 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1966 /* Actually add the column now we have added all cell renderers */
1967 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1970 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1972 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1977 individual_view_set_view_features (EmpathyIndividualView *view,
1978 EmpathyIndividualFeatureFlags features)
1980 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1981 gboolean has_tooltip;
1983 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1985 priv->view_features = features;
1987 /* Setting reorderable is a hack that gets us row previews as drag icons
1988 for free. We override all the drag handlers. It's tricky to get the
1989 position of the drag icon right in drag_begin. GtkTreeView has special
1990 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1993 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1994 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1996 /* Update DnD source/dest */
1997 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1999 gtk_drag_source_set (GTK_WIDGET (view),
2002 G_N_ELEMENTS (drag_types_source),
2003 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2007 gtk_drag_source_unset (GTK_WIDGET (view));
2011 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2013 gtk_drag_dest_set (GTK_WIDGET (view),
2014 GTK_DEST_DEFAULT_ALL,
2016 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2020 /* FIXME: URI could still be droped depending on FT feature */
2021 gtk_drag_dest_unset (GTK_WIDGET (view));
2024 /* Update has-tooltip */
2026 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2027 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2031 individual_view_dispose (GObject *object)
2033 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2034 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2036 tp_clear_object (&priv->store);
2037 tp_clear_object (&priv->filter);
2038 tp_clear_object (&priv->tooltip_widget);
2040 empathy_individual_view_set_live_search (view, NULL);
2042 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2046 individual_view_finalize (GObject *object)
2048 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2050 if (priv->expand_groups_idle_handler != 0)
2051 g_source_remove (priv->expand_groups_idle_handler);
2052 g_hash_table_unref (priv->expand_groups);
2054 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2058 individual_view_get_property (GObject *object,
2063 EmpathyIndividualViewPriv *priv;
2065 priv = GET_PRIV (object);
2070 g_value_set_object (value, priv->store);
2072 case PROP_VIEW_FEATURES:
2073 g_value_set_flags (value, priv->view_features);
2075 case PROP_INDIVIDUAL_FEATURES:
2076 g_value_set_flags (value, priv->individual_features);
2078 case PROP_SHOW_OFFLINE:
2079 g_value_set_boolean (value, priv->show_offline);
2081 case PROP_SHOW_UNTRUSTED:
2082 g_value_set_boolean (value, priv->show_untrusted);
2084 case PROP_SHOW_UNINTERESTING:
2085 g_value_set_boolean (value, priv->show_uninteresting);
2088 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2094 individual_view_set_property (GObject *object,
2096 const GValue *value,
2099 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2100 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2105 empathy_individual_view_set_store (view, g_value_get_object (value));
2107 case PROP_VIEW_FEATURES:
2108 individual_view_set_view_features (view, g_value_get_flags (value));
2110 case PROP_INDIVIDUAL_FEATURES:
2111 priv->individual_features = g_value_get_flags (value);
2113 case PROP_SHOW_OFFLINE:
2114 empathy_individual_view_set_show_offline (view,
2115 g_value_get_boolean (value));
2117 case PROP_SHOW_UNTRUSTED:
2118 empathy_individual_view_set_show_untrusted (view,
2119 g_value_get_boolean (value));
2121 case PROP_SHOW_UNINTERESTING:
2122 empathy_individual_view_set_show_uninteresting (view,
2123 g_value_get_boolean (value));
2125 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2131 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2133 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2134 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2135 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2137 object_class->constructed = individual_view_constructed;
2138 object_class->dispose = individual_view_dispose;
2139 object_class->finalize = individual_view_finalize;
2140 object_class->get_property = individual_view_get_property;
2141 object_class->set_property = individual_view_set_property;
2143 widget_class->drag_data_received = individual_view_drag_data_received;
2144 widget_class->drag_drop = individual_view_drag_drop;
2145 widget_class->drag_begin = individual_view_drag_begin;
2146 widget_class->drag_data_get = individual_view_drag_data_get;
2147 widget_class->drag_end = individual_view_drag_end;
2148 widget_class->drag_motion = individual_view_drag_motion;
2150 /* We use the class method to let user of this widget to connect to
2151 * the signal and stop emission of the signal so the default handler
2152 * won't be called. */
2153 tree_view_class->row_activated = individual_view_row_activated;
2155 klass->drag_individual_received = real_drag_individual_received_cb;
2157 signals[DRAG_INDIVIDUAL_RECEIVED] =
2158 g_signal_new ("drag-individual-received",
2159 G_OBJECT_CLASS_TYPE (klass),
2161 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2163 g_cclosure_marshal_generic,
2164 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2165 G_TYPE_STRING, G_TYPE_STRING);
2167 signals[DRAG_PERSONA_RECEIVED] =
2168 g_signal_new ("drag-persona-received",
2169 G_OBJECT_CLASS_TYPE (klass),
2171 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2173 g_cclosure_marshal_generic,
2174 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2176 g_object_class_install_property (object_class,
2178 g_param_spec_object ("store",
2179 "The store of the view",
2180 "The store of the view",
2181 EMPATHY_TYPE_INDIVIDUAL_STORE,
2182 G_PARAM_READWRITE));
2183 g_object_class_install_property (object_class,
2185 g_param_spec_flags ("view-features",
2186 "Features of the view",
2187 "Flags for all enabled features",
2188 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2189 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2190 g_object_class_install_property (object_class,
2191 PROP_INDIVIDUAL_FEATURES,
2192 g_param_spec_flags ("individual-features",
2193 "Features of the individual menu",
2194 "Flags for all enabled features for the menu",
2195 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2196 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2197 g_object_class_install_property (object_class,
2199 g_param_spec_boolean ("show-offline",
2201 "Whether contact list should display "
2202 "offline contacts", FALSE, G_PARAM_READWRITE));
2203 g_object_class_install_property (object_class,
2204 PROP_SHOW_UNTRUSTED,
2205 g_param_spec_boolean ("show-untrusted",
2206 "Show Untrusted Individuals",
2207 "Whether the view should display untrusted individuals; "
2208 "those who could not be who they say they are.",
2209 TRUE, G_PARAM_READWRITE));
2210 g_object_class_install_property (object_class,
2211 PROP_SHOW_UNINTERESTING,
2212 g_param_spec_boolean ("show-uninteresting",
2213 "Show Uninteresting Individuals",
2214 "Whether the view should not filter out individuals using "
2215 "empathy_folks_persona_is_interesting.",
2216 FALSE, G_PARAM_READWRITE));
2218 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2222 empathy_individual_view_init (EmpathyIndividualView *view)
2224 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2225 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2229 priv->show_untrusted = TRUE;
2230 priv->show_uninteresting = FALSE;
2232 /* Get saved group states. */
2233 empathy_contact_groups_get_all ();
2235 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2236 (GDestroyNotify) g_free, NULL);
2238 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2239 empathy_individual_store_row_separator_func, NULL, NULL);
2241 /* Connect to tree view signals rather than override. */
2242 g_signal_connect (view, "button-press-event",
2243 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2244 g_signal_connect (view, "key-press-event",
2245 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2246 g_signal_connect (view, "row-expanded",
2247 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2248 GINT_TO_POINTER (TRUE));
2249 g_signal_connect (view, "row-collapsed",
2250 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2251 GINT_TO_POINTER (FALSE));
2252 g_signal_connect (view, "query-tooltip",
2253 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2256 EmpathyIndividualView *
2257 empathy_individual_view_new (EmpathyIndividualStore *store,
2258 EmpathyIndividualViewFeatureFlags view_features,
2259 EmpathyIndividualFeatureFlags individual_features)
2261 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2263 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2265 "individual-features", individual_features,
2266 "view-features", view_features, NULL);
2270 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2272 GtkTreeSelection *selection;
2274 GtkTreeModel *model;
2275 FolksIndividual *individual;
2277 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2279 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2280 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2283 gtk_tree_model_get (model, &iter,
2284 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2290 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2291 gboolean *is_fake_group)
2293 GtkTreeSelection *selection;
2295 GtkTreeModel *model;
2300 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2302 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2303 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2306 gtk_tree_model_get (model, &iter,
2307 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2308 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2309 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2317 if (is_fake_group != NULL)
2318 *is_fake_group = fake;
2325 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2326 REMOVE_DIALOG_RESPONSE_DELETE,
2327 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2331 individual_view_remove_dialog_show (GtkWindow *parent,
2332 const gchar *message,
2333 const gchar *secondary_text,
2334 gboolean block_button,
2340 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2341 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2345 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2346 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2347 gtk_widget_show (image);
2354 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2355 * mnemonic so we have to create the button manually. */
2356 button = gtk_button_new_with_mnemonic (
2357 _("Delete and _Block"));
2359 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2360 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2362 gtk_widget_show (button);
2365 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2366 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2367 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2368 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2369 "%s", secondary_text);
2371 gtk_widget_show (dialog);
2373 res = gtk_dialog_run (GTK_DIALOG (dialog));
2374 gtk_widget_destroy (dialog);
2380 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2381 EmpathyIndividualView *view)
2385 group = empathy_individual_view_dup_selected_group (view, NULL);
2392 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2394 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2395 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2396 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2398 EmpathyIndividualManager *manager =
2399 empathy_individual_manager_dup_singleton ();
2400 empathy_individual_manager_remove_group (manager, group);
2401 g_object_unref (G_OBJECT (manager));
2411 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2413 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2418 gboolean is_fake_group;
2420 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2422 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2423 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2426 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2427 if (!group || is_fake_group)
2429 /* We can't alter fake groups */
2434 menu = gtk_menu_new ();
2437 if (priv->view_features &
2438 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2439 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2440 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2441 gtk_widget_show (item);
2442 g_signal_connect (item, "activate",
2443 G_CALLBACK (individual_view_group_rename_activate_cb),
2448 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2450 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2451 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2452 GTK_ICON_SIZE_MENU);
2453 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2454 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2455 gtk_widget_show (item);
2456 g_signal_connect (item, "activate",
2457 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2466 got_avatar (GObject *source_object,
2467 GAsyncResult *result,
2470 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2471 EmpathyIndividualView *view = user_data;
2472 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2474 EmpathyIndividualManager *manager;
2478 guint persona_count = 0;
2480 GError *error = NULL;
2483 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2488 DEBUG ("Could not get avatar: %s", error->message);
2489 g_error_free (error);
2492 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2493 * so we still display the remove dialog. */
2495 personas = folks_individual_get_personas (individual);
2497 if (priv->show_uninteresting)
2499 persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
2505 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2506 while (persona_count < 2 && gee_iterator_next (iter))
2508 FolksPersona *persona = gee_iterator_get (iter);
2510 if (empathy_folks_persona_is_interesting (persona))
2513 g_clear_object (&persona);
2515 g_clear_object (&iter);
2518 /* If we have more than one TpfPersona, display a different message
2519 * ensuring the user knows that *all* of the meta-contacts' personas will
2522 if (persona_count < 2)
2524 /* Not a meta-contact */
2527 _("Do you really want to remove the contact '%s'?"),
2528 folks_alias_details_get_alias (
2529 FOLKS_ALIAS_DETAILS (individual)));
2536 _("Do you really want to remove the linked contact '%s'? "
2537 "Note that this will remove all the contacts which make up "
2538 "this linked contact."),
2539 folks_alias_details_get_alias (
2540 FOLKS_ALIAS_DETAILS (individual)));
2544 manager = empathy_individual_manager_dup_singleton ();
2545 can_block = empathy_individual_manager_supports_blocking (manager,
2547 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2548 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2549 text, can_block, avatar);
2551 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2552 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2556 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2558 if (!empathy_block_individual_dialog_show (parent, individual,
2562 empathy_individual_manager_set_blocked (manager, individual,
2566 empathy_individual_manager_remove (manager, individual, "");
2571 g_object_unref (manager);
2575 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2576 EmpathyIndividualView *view)
2578 FolksIndividual *individual;
2580 individual = empathy_individual_view_dup_selected (view);
2582 if (individual != NULL)
2584 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2585 48, 48, NULL, got_avatar, view);
2586 g_object_unref (individual);
2591 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2592 EmpathyLinkingDialog *linking_dialog,
2593 EmpathyIndividualView *self)
2595 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2596 EmpathyIndividualLinker *linker;
2598 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2599 empathy_individual_linker_set_search_text (linker,
2600 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2604 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2606 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2607 FolksIndividual *individual;
2608 GtkWidget *menu = NULL;
2611 gboolean can_remove = FALSE;
2615 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2617 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2618 /* No need to create a context menu */
2621 individual = empathy_individual_view_dup_selected (view);
2622 if (individual == NULL)
2625 if (!empathy_folks_individual_contains_contact (individual))
2628 /* If any of the Individual's personas can be removed, add an option to
2629 * remove. This will act as a best-effort option. If any Personas cannot be
2630 * removed from the server, then this option will just be inactive upon
2631 * subsequent menu openings */
2632 personas = folks_individual_get_personas (individual);
2633 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2634 while (!can_remove && gee_iterator_next (iter))
2636 FolksPersona *persona = gee_iterator_get (iter);
2637 FolksPersonaStore *store = folks_persona_get_store (persona);
2638 FolksMaybeBool maybe_can_remove =
2639 folks_persona_store_get_can_remove_personas (store);
2641 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2644 g_clear_object (&persona);
2646 g_clear_object (&iter);
2648 menu = empathy_individual_menu_new (individual, priv->individual_features,
2651 /* Remove contact */
2652 if ((priv->view_features &
2653 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2656 /* create the menu if required, or just add a separator */
2658 menu = gtk_menu_new ();
2661 item = gtk_separator_menu_item_new ();
2662 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2663 gtk_widget_show (item);
2667 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2668 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2669 GTK_ICON_SIZE_MENU);
2670 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2671 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2672 gtk_widget_show (item);
2673 g_signal_connect (item, "activate",
2674 G_CALLBACK (individual_view_remove_activate_cb), view);
2677 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2678 * set the live search text on the new linking dialogue to be the same as
2680 g_signal_connect (menu, "link-contacts-activated",
2681 (GCallback) individual_menu_link_contacts_activated_cb, view);
2684 g_object_unref (individual);
2690 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2691 EmpathyLiveSearch *search)
2693 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2695 /* remove old handlers if old search was not null */
2696 if (priv->search_widget != NULL)
2698 g_signal_handlers_disconnect_by_func (view,
2699 individual_view_start_search_cb, NULL);
2701 g_signal_handlers_disconnect_by_func (priv->search_widget,
2702 individual_view_search_text_notify_cb, view);
2703 g_signal_handlers_disconnect_by_func (priv->search_widget,
2704 individual_view_search_activate_cb, view);
2705 g_signal_handlers_disconnect_by_func (priv->search_widget,
2706 individual_view_search_key_navigation_cb, view);
2707 g_signal_handlers_disconnect_by_func (priv->search_widget,
2708 individual_view_search_hide_cb, view);
2709 g_signal_handlers_disconnect_by_func (priv->search_widget,
2710 individual_view_search_show_cb, view);
2711 g_object_unref (priv->search_widget);
2712 priv->search_widget = NULL;
2715 /* connect handlers if new search is not null */
2718 priv->search_widget = g_object_ref (search);
2720 g_signal_connect (view, "start-interactive-search",
2721 G_CALLBACK (individual_view_start_search_cb), NULL);
2723 g_signal_connect (priv->search_widget, "notify::text",
2724 G_CALLBACK (individual_view_search_text_notify_cb), view);
2725 g_signal_connect (priv->search_widget, "activate",
2726 G_CALLBACK (individual_view_search_activate_cb), view);
2727 g_signal_connect (priv->search_widget, "key-navigation",
2728 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2729 g_signal_connect (priv->search_widget, "hide",
2730 G_CALLBACK (individual_view_search_hide_cb), view);
2731 g_signal_connect (priv->search_widget, "show",
2732 G_CALLBACK (individual_view_search_show_cb), view);
2737 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2739 EmpathyIndividualViewPriv *priv;
2741 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2743 priv = GET_PRIV (self);
2745 return (priv->search_widget != NULL &&
2746 gtk_widget_get_visible (priv->search_widget));
2750 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2752 EmpathyIndividualViewPriv *priv;
2754 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2756 priv = GET_PRIV (self);
2758 return priv->show_offline;
2762 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2763 gboolean show_offline)
2765 EmpathyIndividualViewPriv *priv;
2767 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2769 priv = GET_PRIV (self);
2771 priv->show_offline = show_offline;
2773 g_object_notify (G_OBJECT (self), "show-offline");
2774 gtk_tree_model_filter_refilter (priv->filter);
2778 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2780 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2782 return GET_PRIV (self)->show_untrusted;
2786 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2787 gboolean show_untrusted)
2789 EmpathyIndividualViewPriv *priv;
2791 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2793 priv = GET_PRIV (self);
2795 priv->show_untrusted = show_untrusted;
2797 g_object_notify (G_OBJECT (self), "show-untrusted");
2798 gtk_tree_model_filter_refilter (priv->filter);
2801 EmpathyIndividualStore *
2802 empathy_individual_view_get_store (EmpathyIndividualView *self)
2804 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2806 return GET_PRIV (self)->store;
2810 empathy_individual_view_set_store (EmpathyIndividualView *self,
2811 EmpathyIndividualStore *store)
2813 EmpathyIndividualViewPriv *priv;
2815 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2816 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2818 priv = GET_PRIV (self);
2820 /* Destroy the old filter and remove the old store */
2821 if (priv->store != NULL)
2823 g_signal_handlers_disconnect_by_func (priv->store,
2824 individual_view_store_row_changed_cb, self);
2825 g_signal_handlers_disconnect_by_func (priv->store,
2826 individual_view_store_row_deleted_cb, self);
2828 g_signal_handlers_disconnect_by_func (priv->filter,
2829 individual_view_row_has_child_toggled_cb, self);
2831 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2834 tp_clear_object (&priv->filter);
2835 tp_clear_object (&priv->store);
2837 /* Set the new store */
2838 priv->store = store;
2842 g_object_ref (store);
2844 /* Create a new filter */
2845 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2846 GTK_TREE_MODEL (priv->store), NULL));
2847 gtk_tree_model_filter_set_visible_func (priv->filter,
2848 individual_view_filter_visible_func, self, NULL);
2850 g_signal_connect (priv->filter, "row-has-child-toggled",
2851 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2852 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2853 GTK_TREE_MODEL (priv->filter));
2855 tp_g_signal_connect_object (priv->store, "row-changed",
2856 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2857 tp_g_signal_connect_object (priv->store, "row-inserted",
2858 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2859 tp_g_signal_connect_object (priv->store, "row-deleted",
2860 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2865 empathy_individual_view_start_search (EmpathyIndividualView *self)
2867 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2869 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2870 g_return_if_fail (priv->search_widget != NULL);
2872 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2873 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2875 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2879 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2880 GtkTreeModelFilterVisibleFunc filter,
2883 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2885 priv->custom_filter = filter;
2886 priv->custom_filter_data = data;
2890 empathy_individual_view_refilter (EmpathyIndividualView *self)
2892 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2894 gtk_tree_model_filter_refilter (priv->filter);
2898 empathy_individual_view_select_first (EmpathyIndividualView *self)
2900 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2903 gtk_tree_model_filter_refilter (priv->filter);
2905 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2907 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2908 GTK_TREE_VIEW (self));
2910 gtk_tree_selection_select_iter (selection, &iter);
2915 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2916 gboolean show_uninteresting)
2918 EmpathyIndividualViewPriv *priv;
2920 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2922 priv = GET_PRIV (self);
2924 priv->show_uninteresting = show_uninteresting;
2926 g_object_notify (G_OBJECT (self), "show-uninteresting");
2927 gtk_tree_model_filter_refilter (priv->filter);