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-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-images.h"
51 #include "empathy-linking-dialog.h"
52 #include "empathy-cell-renderer-expander.h"
53 #include "empathy-cell-renderer-text.h"
54 #include "empathy-cell-renderer-activatable.h"
55 #include "empathy-ui-utils.h"
56 #include "empathy-gtk-enum-types.h"
57 #include "empathy-gtk-marshal.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;
78 GtkTreeModelFilter *filter;
79 GtkWidget *search_widget;
81 guint expand_groups_idle_handler;
82 /* owned string (group name) -> bool (whether to expand/contract) */
83 GHashTable *expand_groups;
86 guint auto_scroll_timeout_id;
87 /* Distance between mouse pointer and the nearby border. Negative when
90 } EmpathyIndividualViewPriv;
94 EmpathyIndividualView *view;
101 EmpathyIndividualView *view;
102 FolksIndividual *individual;
111 PROP_INDIVIDUAL_FEATURES,
116 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
117 * specific EmpathyContacts (between/in/out of Individuals) */
120 DND_DRAG_TYPE_INDIVIDUAL_ID,
121 DND_DRAG_TYPE_PERSONA_ID,
122 DND_DRAG_TYPE_URI_LIST,
123 DND_DRAG_TYPE_STRING,
126 #define DRAG_TYPE(T,I) \
127 { (gchar *) T, 0, I }
129 static const GtkTargetEntry drag_types_dest[] = {
130 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
131 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
132 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
133 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
135 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
138 static const GtkTargetEntry drag_types_source[] = {
139 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
144 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
145 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
149 DRAG_INDIVIDUAL_RECEIVED,
150 DRAG_PERSONA_RECEIVED,
154 static guint signals[LAST_SIGNAL];
156 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
160 individual_view_tooltip_destroy_cb (GtkWidget *widget,
161 EmpathyIndividualView *view)
163 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
165 if (priv->tooltip_widget != NULL)
167 DEBUG ("Tooltip destroyed");
168 tp_clear_object (&priv->tooltip_widget);
173 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
176 gboolean keyboard_mode,
180 EmpathyIndividualViewPriv *priv;
181 FolksIndividual *individual;
185 static gint running = 0;
186 gboolean ret = FALSE;
188 priv = GET_PRIV (view);
190 /* Avoid an infinite loop. See GNOME bug #574377 */
196 /* Don't show the tooltip if there's already a popup menu */
197 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
200 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
201 keyboard_mode, &model, &path, &iter))
204 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
205 gtk_tree_path_free (path);
207 gtk_tree_model_get (model, &iter,
208 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
210 if (individual == NULL)
213 if (priv->tooltip_widget == NULL)
215 priv->tooltip_widget = empathy_individual_widget_new (individual,
216 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
217 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
218 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
219 g_object_ref (priv->tooltip_widget);
220 g_signal_connect (priv->tooltip_widget, "destroy",
221 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
222 gtk_widget_show (priv->tooltip_widget);
226 empathy_individual_widget_set_individual (
227 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
230 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
233 g_object_unref (individual);
241 groups_change_group_cb (GObject *source,
242 GAsyncResult *result,
245 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
246 GError *error = NULL;
248 folks_groupable_change_group_finish (groupable, result, &error);
251 g_warning ("failed to change group: %s", error->message);
252 g_clear_error (&error);
257 group_can_be_modified (const gchar *name,
258 gboolean is_fake_group,
261 /* Real groups can always be modified */
265 /* The favorite fake group can be modified so users can
266 * add/remove favorites using DnD */
267 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
270 /* We can remove contacts from the 'ungrouped' fake group */
271 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
278 individual_view_individual_drag_received (GtkWidget *self,
279 GdkDragContext *context,
282 GtkSelectionData *selection)
284 EmpathyIndividualViewPriv *priv;
285 EmpathyIndividualManager *manager = NULL;
286 FolksIndividual *individual;
287 GtkTreePath *source_path;
288 const gchar *sel_data;
289 gchar *new_group = NULL;
290 gchar *old_group = NULL;
291 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
293 priv = GET_PRIV (self);
295 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
296 new_group = empathy_individual_store_get_parent_group (model, path,
297 NULL, &new_group_is_fake);
299 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
302 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
303 * feature. Otherwise, we just add the dropped contact to whichever group
304 * they were dropped in, and don't remove them from their old group. This
305 * allows for Individual views which shouldn't allow Individuals to have
306 * their groups changed, and also for dragging Individuals between Individual
308 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
309 priv->drag_row != NULL)
311 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
315 empathy_individual_store_get_parent_group (model, source_path,
316 NULL, &old_group_is_fake);
317 gtk_tree_path_free (source_path);
320 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
323 if (!tp_strdiff (old_group, new_group))
326 else if (priv->drag_row != NULL)
328 /* We don't allow changing Individuals' groups, and this Individual was
329 * dragged from another group in *this* Individual view, so we disallow
334 /* XXX: for contacts, we used to ensure the account, create the contact
335 * factory, and then wait on the contacts. But they should already be
336 * created by this point */
338 manager = empathy_individual_manager_dup_singleton ();
339 individual = empathy_individual_manager_lookup_member (manager, sel_data);
341 if (individual == NULL)
343 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
347 /* FIXME: We should probably wait for the cb before calling
350 /* Emit a signal notifying of the drag. We change the Individual's groups in
351 * the default signal handler. */
352 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
353 gdk_drag_context_get_selected_action (context), individual, new_group,
359 tp_clear_object (&manager);
367 real_drag_individual_received_cb (EmpathyIndividualView *self,
368 GdkDragAction action,
369 FolksIndividual *individual,
370 const gchar *new_group,
371 const gchar *old_group)
373 DEBUG ("individual %s dragged from '%s' to '%s'",
374 folks_individual_get_id (individual), old_group, new_group);
376 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
378 /* Mark contact as favourite */
379 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
383 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
385 /* Remove contact as favourite */
386 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
388 /* Don't try to remove it */
392 if (new_group != NULL)
394 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
395 groups_change_group_cb, NULL);
398 if (old_group != NULL && action == GDK_ACTION_MOVE)
400 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
401 FALSE, groups_change_group_cb, NULL);
406 individual_view_persona_drag_received (GtkWidget *self,
407 GdkDragContext *context,
410 GtkSelectionData *selection)
412 EmpathyIndividualViewPriv *priv;
413 EmpathyIndividualManager *manager = NULL;
414 FolksIndividual *individual = NULL;
415 FolksPersona *persona = NULL;
416 const gchar *persona_uid;
417 GList *individuals, *l;
418 gboolean retval = FALSE;
420 priv = GET_PRIV (self);
422 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
424 /* FIXME: This is slow, but the only way to find the Persona we're having
426 manager = empathy_individual_manager_dup_singleton ();
427 individuals = empathy_individual_manager_get_members (manager);
429 for (l = individuals; l != NULL; l = l->next)
433 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435 for (p = personas; p != NULL; p = p->next)
437 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
440 persona = g_object_ref (p->data);
441 individual = g_object_ref (l->data);
448 g_list_free (individuals);
450 if (persona == NULL || individual == NULL)
452 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
456 /* Emit a signal notifying of the drag. We change the Individual's groups in
457 * the default signal handler. */
458 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
459 gdk_drag_context_get_selected_action (context), persona, individual,
463 tp_clear_object (&manager);
464 tp_clear_object (&persona);
465 tp_clear_object (&individual);
471 individual_view_file_drag_received (GtkWidget *view,
472 GdkDragContext *context,
475 GtkSelectionData *selection)
478 const gchar *sel_data;
479 FolksIndividual *individual;
480 EmpathyContact *contact;
482 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
484 gtk_tree_model_get_iter (model, &iter, path);
485 gtk_tree_model_get (model, &iter,
486 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
487 if (individual == NULL)
490 contact = empathy_contact_dup_from_folks_individual (individual);
491 empathy_send_file_from_uri_list (contact, sel_data);
493 g_object_unref (individual);
494 tp_clear_object (&contact);
500 individual_view_drag_data_received (GtkWidget *view,
501 GdkDragContext *context,
504 GtkSelectionData *selection,
510 GtkTreeViewDropPosition position;
512 gboolean success = TRUE;
514 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
516 /* Get destination group information. */
517 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
518 x, y, &path, &position);
523 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
525 success = individual_view_individual_drag_received (view,
526 context, model, path, selection);
528 else if (info == DND_DRAG_TYPE_PERSONA_ID)
530 success = individual_view_persona_drag_received (view, context, model,
533 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
535 success = individual_view_file_drag_received (view,
536 context, model, path, selection);
539 gtk_tree_path_free (path);
540 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
544 individual_view_drag_motion_cb (DragMotionData *data)
546 if (data->view != NULL)
548 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
549 g_object_remove_weak_pointer (G_OBJECT (data->view),
550 (gpointer *) &data->view);
553 data->timeout_id = 0;
558 /* Minimum distance between the mouse pointer and a horizontal border when we
559 start auto scrolling. */
560 #define AUTO_SCROLL_MARGIN_SIZE 20
561 /* How far to scroll per one tick. */
562 #define AUTO_SCROLL_PITCH 10
565 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
567 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
571 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
573 if (priv->distance < 0)
574 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
576 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
578 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
579 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
581 gtk_adjustment_set_value (adj, new_value);
587 individual_view_drag_motion (GtkWidget *widget,
588 GdkDragContext *context,
593 EmpathyIndividualViewPriv *priv;
597 static DragMotionData *dm = NULL;
600 gboolean is_different = FALSE;
601 gboolean cleanup = TRUE;
602 gboolean retval = TRUE;
603 GtkAllocation allocation;
605 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
606 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
609 if (priv->auto_scroll_timeout_id != 0)
611 g_source_remove (priv->auto_scroll_timeout_id);
612 priv->auto_scroll_timeout_id = 0;
615 gtk_widget_get_allocation (widget, &allocation);
617 if (y < AUTO_SCROLL_MARGIN_SIZE ||
618 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
620 if (y < AUTO_SCROLL_MARGIN_SIZE)
621 priv->distance = MIN (-y, -1);
623 priv->distance = MAX (allocation.height - y, 1);
625 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
626 (GSourceFunc) individual_view_auto_scroll_cb, widget);
629 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
630 x, y, &path, NULL, NULL, NULL);
632 cleanup &= (dm == NULL);
636 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
637 is_different = ((dm == NULL) || ((dm != NULL)
638 && gtk_tree_path_compare (dm->path, path) != 0));
645 /* Coordinates don't point to an actual row, so make sure the pointer
646 and highlighting don't indicate that a drag is possible.
648 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
649 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
652 target = gtk_drag_dest_find_target (widget, context, NULL);
653 gtk_tree_model_get_iter (model, &iter, path);
655 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
656 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
658 /* This is a file drag, and it can only be dropped on contacts,
660 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
661 * even if we have a valid target. */
662 FolksIndividual *individual = NULL;
663 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
665 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
667 gtk_tree_model_get (model, &iter,
668 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
672 if (individual != NULL)
674 EmpathyContact *contact = NULL;
676 contact = empathy_contact_dup_from_folks_individual (individual);
677 caps = empathy_contact_get_capabilities (contact);
679 tp_clear_object (&contact);
682 if (individual != NULL &&
683 folks_presence_is_online (FOLKS_PRESENCE (individual)) &&
684 (caps & EMPATHY_CAPABILITIES_FT))
686 gdk_drag_status (context, GDK_ACTION_COPY, time_);
687 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
688 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
692 gdk_drag_status (context, 0, time_);
693 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
697 if (individual != NULL)
698 g_object_unref (individual);
700 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
701 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
702 priv->drag_row == NULL)) ||
703 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
704 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
706 /* If target != GDK_NONE, then we have a contact (individual or persona)
707 drag. If we're pointing to a group, highlight it. Otherwise, if the
708 contact we're pointing to is in a group, highlight that. Otherwise,
709 set the drag position to before the first row for a drag into
710 the "non-group" at the top.
711 If it's an Individual:
712 We only highlight things if the contact is from a different
713 Individual view, or if this Individual view has
714 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
715 which don't have FEATURE_GROUPS_CHANGE, but do have
716 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
718 We only highlight things if we have FEATURE_PERSONA_DROP.
720 GtkTreeIter group_iter;
722 GtkTreePath *group_path;
723 gtk_tree_model_get (model, &iter,
724 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
731 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
732 gtk_tree_model_get (model, &group_iter,
733 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
737 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
738 group_path = gtk_tree_model_get_path (model, &group_iter);
739 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
740 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
741 gtk_tree_path_free (group_path);
745 group_path = gtk_tree_path_new_first ();
746 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
747 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
748 group_path, GTK_TREE_VIEW_DROP_BEFORE);
752 if (!is_different && !cleanup)
757 gtk_tree_path_free (dm->path);
760 g_source_remove (dm->timeout_id);
768 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
770 dm = g_new0 (DragMotionData, 1);
772 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
773 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
774 dm->path = gtk_tree_path_copy (path);
776 dm->timeout_id = g_timeout_add_seconds (1,
777 (GSourceFunc) individual_view_drag_motion_cb, dm);
784 individual_view_drag_begin (GtkWidget *widget,
785 GdkDragContext *context)
787 EmpathyIndividualViewPriv *priv;
788 GtkTreeSelection *selection;
793 priv = GET_PRIV (widget);
795 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
798 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
799 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
802 path = gtk_tree_model_get_path (model, &iter);
803 priv->drag_row = gtk_tree_row_reference_new (model, path);
804 gtk_tree_path_free (path);
808 individual_view_drag_data_get (GtkWidget *widget,
809 GdkDragContext *context,
810 GtkSelectionData *selection,
814 EmpathyIndividualViewPriv *priv;
815 GtkTreePath *src_path;
818 FolksIndividual *individual;
819 const gchar *individual_id;
821 priv = GET_PRIV (widget);
823 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
824 if (priv->drag_row == NULL)
827 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
828 if (src_path == NULL)
831 if (!gtk_tree_model_get_iter (model, &iter, src_path))
833 gtk_tree_path_free (src_path);
837 gtk_tree_path_free (src_path);
840 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
841 if (individual == NULL)
844 individual_id = folks_individual_get_id (individual);
846 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
848 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
849 (guchar *) individual_id, strlen (individual_id) + 1);
852 g_object_unref (individual);
856 individual_view_drag_end (GtkWidget *widget,
857 GdkDragContext *context)
859 EmpathyIndividualViewPriv *priv;
861 priv = GET_PRIV (widget);
863 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
868 gtk_tree_row_reference_free (priv->drag_row);
869 priv->drag_row = NULL;
874 individual_view_drag_drop (GtkWidget *widget,
875 GdkDragContext *drag_context,
885 EmpathyIndividualView *view;
891 individual_view_popup_menu_idle_cb (gpointer user_data)
893 MenuPopupData *data = user_data;
896 menu = empathy_individual_view_get_individual_menu (data->view);
898 menu = empathy_individual_view_get_group_menu (data->view);
902 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
903 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
905 gtk_widget_show (menu);
906 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
908 g_object_ref_sink (menu);
909 g_object_unref (menu);
912 g_slice_free (MenuPopupData, data);
918 individual_view_button_press_event_cb (EmpathyIndividualView *view,
919 GdkEventButton *event,
922 if (event->button == 3)
926 data = g_slice_new (MenuPopupData);
928 data->button = event->button;
929 data->time = event->time;
930 g_idle_add (individual_view_popup_menu_idle_cb, data);
937 individual_view_key_press_event_cb (EmpathyIndividualView *view,
941 if (event->keyval == GDK_KEY_Menu)
945 data = g_slice_new (MenuPopupData);
948 data->time = event->time;
949 g_idle_add (individual_view_popup_menu_idle_cb, data);
956 individual_view_row_activated (GtkTreeView *view,
958 GtkTreeViewColumn *column)
960 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
961 FolksIndividual *individual;
962 EmpathyContact *contact;
966 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
969 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
970 gtk_tree_model_get_iter (model, &iter, path);
971 gtk_tree_model_get (model, &iter,
972 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
974 if (individual == NULL)
977 /* Determine which Persona to chat to, by choosing the most available one. */
978 contact = empathy_contact_dup_best_for_action (individual,
979 EMPATHY_ACTION_CHAT);
983 DEBUG ("Starting a chat");
985 empathy_dispatcher_chat_with_contact (contact,
986 gtk_get_current_event_time ());
989 g_object_unref (individual);
990 tp_clear_object (&contact);
994 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
995 const gchar *path_string,
996 EmpathyIndividualView *view)
998 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1000 GtkTreeModel *model;
1002 FolksIndividual *individual;
1003 GdkEventButton *event;
1004 GtkMenuShell *shell;
1007 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1010 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1011 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1014 gtk_tree_model_get (model, &iter,
1015 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1016 if (individual == NULL)
1019 event = (GdkEventButton *) gtk_get_current_event ();
1021 menu = gtk_menu_new ();
1022 shell = GTK_MENU_SHELL (menu);
1025 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1026 gtk_menu_shell_append (shell, item);
1027 gtk_widget_show (item);
1030 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1031 gtk_menu_shell_append (shell, item);
1032 gtk_widget_show (item);
1034 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
1035 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1036 gtk_widget_show (menu);
1037 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1038 event->button, event->time);
1039 g_object_ref_sink (menu);
1040 g_object_unref (menu);
1042 g_object_unref (individual);
1046 individual_view_cell_set_background (EmpathyIndividualView *view,
1047 GtkCellRenderer *cell,
1054 style = gtk_widget_get_style (GTK_WIDGET (view));
1056 if (!is_group && is_active)
1058 color = style->bg[GTK_STATE_SELECTED];
1060 /* Here we take the current theme colour and add it to
1061 * the colour for white and average the two. This
1062 * gives a colour which is inline with the theme but
1065 color.red = (color.red + (style->white).red) / 2;
1066 color.green = (color.green + (style->white).green) / 2;
1067 color.blue = (color.blue + (style->white).blue) / 2;
1069 g_object_set (cell, "cell-background-gdk", &color, NULL);
1072 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1076 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1077 GtkCellRenderer *cell,
1078 GtkTreeModel *model,
1080 EmpathyIndividualView *view)
1086 gtk_tree_model_get (model, iter,
1087 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1088 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1089 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1092 "visible", !is_group,
1096 tp_clear_object (&pixbuf);
1098 individual_view_cell_set_background (view, cell, is_group, is_active);
1102 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1103 GtkCellRenderer *cell,
1104 GtkTreeModel *model,
1106 EmpathyIndividualView *view)
1108 GdkPixbuf *pixbuf = NULL;
1112 gtk_tree_model_get (model, iter,
1113 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1114 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1119 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1121 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1122 GTK_ICON_SIZE_MENU);
1124 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1126 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1127 GTK_ICON_SIZE_MENU);
1132 "visible", pixbuf != NULL,
1136 tp_clear_object (&pixbuf);
1142 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1143 GtkCellRenderer *cell,
1144 GtkTreeModel *model,
1146 EmpathyIndividualView *view)
1150 gboolean can_audio, can_video;
1152 gtk_tree_model_get (model, iter,
1153 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1154 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1155 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1156 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1159 "visible", !is_group && (can_audio || can_video),
1160 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1163 individual_view_cell_set_background (view, cell, is_group, is_active);
1167 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1168 GtkCellRenderer *cell,
1169 GtkTreeModel *model,
1171 EmpathyIndividualView *view)
1174 gboolean show_avatar;
1178 gtk_tree_model_get (model, iter,
1179 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1180 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1181 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1182 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1185 "visible", !is_group && show_avatar,
1189 tp_clear_object (&pixbuf);
1191 individual_view_cell_set_background (view, cell, is_group, is_active);
1195 individual_view_phone_cell_data_func (GtkTreeViewColumn *tree_column,
1196 GtkCellRenderer *cell,
1197 GtkTreeModel *model,
1199 EmpathyIndividualView *view)
1205 gtk_tree_model_get (model, iter,
1206 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1207 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1208 EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES, &types,
1215 && g_strv_length (types) > 0
1216 && !tp_strdiff (types[0], "phone"),
1221 individual_view_cell_set_background (view, cell, is_group, is_active);
1225 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1226 GtkCellRenderer *cell,
1227 GtkTreeModel *model,
1229 EmpathyIndividualView *view)
1234 gtk_tree_model_get (model, iter,
1235 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1236 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1238 individual_view_cell_set_background (view, cell, is_group, is_active);
1242 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1243 GtkCellRenderer *cell,
1244 GtkTreeModel *model,
1246 EmpathyIndividualView *view)
1251 gtk_tree_model_get (model, iter,
1252 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1253 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1255 if (gtk_tree_model_iter_has_child (model, iter))
1258 gboolean row_expanded;
1260 path = gtk_tree_model_get_path (model, iter);
1262 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1263 (gtk_tree_view_column_get_tree_view (column)), path);
1264 gtk_tree_path_free (path);
1269 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1273 g_object_set (cell, "visible", FALSE, NULL);
1275 individual_view_cell_set_background (view, cell, is_group, is_active);
1279 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1284 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1285 GtkTreeModel *model;
1289 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1292 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1294 gtk_tree_model_get (model, iter,
1295 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1297 expanded = GPOINTER_TO_INT (user_data);
1298 empathy_contact_group_set_expanded (name, expanded);
1304 individual_view_start_search_cb (EmpathyIndividualView *view,
1307 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1309 if (priv->search_widget == NULL)
1312 empathy_individual_view_start_search (view);
1318 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1320 EmpathyIndividualView *view)
1322 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1324 GtkTreeViewColumn *focus_column;
1325 GtkTreeModel *model;
1327 gboolean set_cursor = FALSE;
1329 gtk_tree_model_filter_refilter (priv->filter);
1331 /* Set cursor on the first contact. If it is already set on a group,
1332 * set it on its first child contact. Note that first child of a group
1333 * is its separator, that's why we actually set to the 2nd
1336 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1337 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1341 path = gtk_tree_path_new_from_string ("0:1");
1344 else if (gtk_tree_path_get_depth (path) < 2)
1348 gtk_tree_model_get_iter (model, &iter, path);
1349 gtk_tree_model_get (model, &iter,
1350 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1355 gtk_tree_path_down (path);
1356 gtk_tree_path_next (path);
1363 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1365 if (gtk_tree_model_get_iter (model, &iter, path))
1367 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1372 gtk_tree_path_free (path);
1376 individual_view_search_activate_cb (GtkWidget *search,
1377 EmpathyIndividualView *view)
1380 GtkTreeViewColumn *focus_column;
1382 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1385 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1386 gtk_tree_path_free (path);
1388 gtk_widget_hide (search);
1393 individual_view_search_key_navigation_cb (GtkWidget *search,
1395 EmpathyIndividualView *view)
1397 GdkEventKey *eventkey = ((GdkEventKey *) event);
1398 gboolean ret = FALSE;
1400 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
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;
1686 /* We're only giving the visibility wrt filtering here, not things like
1688 if (priv->show_untrusted == FALSE &&
1689 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1694 if (is_searching == FALSE)
1695 return (priv->show_offline || is_online);
1697 /* check alias name */
1698 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1700 if (empathy_live_search_match (live, str))
1703 /* check contact id, remove the @server.com part */
1704 personas = folks_individual_get_personas (individual);
1705 for (l = personas; l; l = l->next)
1708 gchar *dup_str = NULL;
1711 if (!TPF_IS_PERSONA (l->data))
1714 str = folks_persona_get_display_id (l->data);
1715 p = strstr (str, "@");
1717 str = dup_str = g_strndup (str, p - str);
1719 visible = empathy_live_search_match (live, str);
1725 /* FIXME: Add more rules here, we could check phone numbers in
1726 * contact's vCard for example. */
1732 individual_view_filter_visible_func (GtkTreeModel *model,
1736 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1737 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1738 FolksIndividual *individual = NULL;
1739 gboolean is_group, is_separator, valid;
1740 GtkTreeIter child_iter;
1741 gboolean visible, is_online;
1742 gboolean is_searching = TRUE;
1744 if (priv->search_widget == NULL ||
1745 !gtk_widget_get_visible (priv->search_widget))
1746 is_searching = FALSE;
1748 gtk_tree_model_get (model, iter,
1749 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1750 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1751 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1752 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1755 if (individual != NULL)
1757 visible = individual_view_is_visible_individual (self, individual,
1758 is_online, is_searching);
1760 g_object_unref (individual);
1762 /* FIXME: Work around bgo#626552/bgo#621076 */
1763 if (visible == TRUE)
1765 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1766 individual_view_verify_group_visibility (self, path);
1767 gtk_tree_path_free (path);
1776 /* Not a contact, not a separator, must be a group */
1777 g_return_val_if_fail (is_group, FALSE);
1779 /* only show groups which are not empty */
1780 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1781 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1783 gtk_tree_model_get (model, &child_iter,
1784 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1785 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1788 if (individual == NULL)
1791 visible = individual_view_is_visible_individual (self, individual,
1792 is_online, is_searching);
1793 g_object_unref (individual);
1795 /* show group if it has at least one visible contact in it */
1796 if (visible == TRUE)
1804 individual_view_constructed (GObject *object)
1806 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1807 GtkCellRenderer *cell;
1808 GtkTreeViewColumn *col;
1813 "headers-visible", FALSE,
1814 "show-expanders", FALSE,
1817 col = gtk_tree_view_column_new ();
1820 cell = gtk_cell_renderer_pixbuf_new ();
1821 gtk_tree_view_column_pack_start (col, cell, FALSE);
1822 gtk_tree_view_column_set_cell_data_func (col, cell,
1823 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1833 cell = gtk_cell_renderer_pixbuf_new ();
1834 gtk_tree_view_column_pack_start (col, cell, FALSE);
1835 gtk_tree_view_column_set_cell_data_func (col, cell,
1836 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1848 cell = empathy_cell_renderer_text_new ();
1849 gtk_tree_view_column_pack_start (col, cell, TRUE);
1850 gtk_tree_view_column_set_cell_data_func (col, cell,
1851 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1853 gtk_tree_view_column_add_attribute (col, cell,
1854 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1855 gtk_tree_view_column_add_attribute (col, cell,
1856 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1857 gtk_tree_view_column_add_attribute (col, cell,
1858 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1859 gtk_tree_view_column_add_attribute (col, cell,
1860 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1861 gtk_tree_view_column_add_attribute (col, cell,
1862 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1863 gtk_tree_view_column_add_attribute (col, cell,
1864 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1867 cell = gtk_cell_renderer_pixbuf_new ();
1868 gtk_tree_view_column_pack_start (col, cell, FALSE);
1869 gtk_tree_view_column_set_cell_data_func (col, cell,
1870 (GtkTreeCellDataFunc) individual_view_phone_cell_data_func,
1873 g_object_set (cell, "visible", FALSE, "icon-name", "phone", NULL);
1875 /* Audio Call Icon */
1876 cell = empathy_cell_renderer_activatable_new ();
1877 gtk_tree_view_column_pack_start (col, cell, FALSE);
1878 gtk_tree_view_column_set_cell_data_func (col, cell,
1879 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1882 g_object_set (cell, "visible", FALSE, NULL);
1884 g_signal_connect (cell, "path-activated",
1885 G_CALLBACK (individual_view_call_activated_cb), view);
1888 cell = gtk_cell_renderer_pixbuf_new ();
1889 gtk_tree_view_column_pack_start (col, cell, FALSE);
1890 gtk_tree_view_column_set_cell_data_func (col, cell,
1891 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1903 cell = empathy_cell_renderer_expander_new ();
1904 gtk_tree_view_column_pack_end (col, cell, FALSE);
1905 gtk_tree_view_column_set_cell_data_func (col, cell,
1906 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1909 /* Actually add the column now we have added all cell renderers */
1910 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1913 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1915 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1918 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1920 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1926 individual_view_set_view_features (EmpathyIndividualView *view,
1927 EmpathyIndividualFeatureFlags features)
1929 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1930 gboolean has_tooltip;
1932 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1934 priv->view_features = features;
1936 /* Setting reorderable is a hack that gets us row previews as drag icons
1937 for free. We override all the drag handlers. It's tricky to get the
1938 position of the drag icon right in drag_begin. GtkTreeView has special
1939 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1942 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1943 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1945 /* Update DnD source/dest */
1946 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1948 gtk_drag_source_set (GTK_WIDGET (view),
1951 G_N_ELEMENTS (drag_types_source),
1952 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1956 gtk_drag_source_unset (GTK_WIDGET (view));
1960 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1962 gtk_drag_dest_set (GTK_WIDGET (view),
1963 GTK_DEST_DEFAULT_ALL,
1965 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1969 /* FIXME: URI could still be droped depending on FT feature */
1970 gtk_drag_dest_unset (GTK_WIDGET (view));
1973 /* Update has-tooltip */
1975 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1976 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1980 individual_view_dispose (GObject *object)
1982 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1983 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1985 tp_clear_object (&priv->store);
1986 tp_clear_object (&priv->filter);
1987 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1989 empathy_individual_view_set_live_search (view, NULL);
1991 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1995 individual_view_finalize (GObject *object)
1997 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1999 if (priv->expand_groups_idle_handler != 0)
2000 g_source_remove (priv->expand_groups_idle_handler);
2001 g_hash_table_destroy (priv->expand_groups);
2003 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2007 individual_view_get_property (GObject *object,
2012 EmpathyIndividualViewPriv *priv;
2014 priv = GET_PRIV (object);
2019 g_value_set_object (value, priv->store);
2021 case PROP_VIEW_FEATURES:
2022 g_value_set_flags (value, priv->view_features);
2024 case PROP_INDIVIDUAL_FEATURES:
2025 g_value_set_flags (value, priv->individual_features);
2027 case PROP_SHOW_OFFLINE:
2028 g_value_set_boolean (value, priv->show_offline);
2030 case PROP_SHOW_UNTRUSTED:
2031 g_value_set_boolean (value, priv->show_untrusted);
2034 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2040 individual_view_set_property (GObject *object,
2042 const GValue *value,
2045 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2046 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2051 empathy_individual_view_set_store (view, g_value_get_object (value));
2053 case PROP_VIEW_FEATURES:
2054 individual_view_set_view_features (view, g_value_get_flags (value));
2056 case PROP_INDIVIDUAL_FEATURES:
2057 priv->individual_features = g_value_get_flags (value);
2059 case PROP_SHOW_OFFLINE:
2060 empathy_individual_view_set_show_offline (view,
2061 g_value_get_boolean (value));
2063 case PROP_SHOW_UNTRUSTED:
2064 empathy_individual_view_set_show_untrusted (view,
2065 g_value_get_boolean (value));
2068 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2074 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2076 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2077 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2078 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2080 object_class->constructed = individual_view_constructed;
2081 object_class->dispose = individual_view_dispose;
2082 object_class->finalize = individual_view_finalize;
2083 object_class->get_property = individual_view_get_property;
2084 object_class->set_property = individual_view_set_property;
2086 widget_class->drag_data_received = individual_view_drag_data_received;
2087 widget_class->drag_drop = individual_view_drag_drop;
2088 widget_class->drag_begin = individual_view_drag_begin;
2089 widget_class->drag_data_get = individual_view_drag_data_get;
2090 widget_class->drag_end = individual_view_drag_end;
2091 widget_class->drag_motion = individual_view_drag_motion;
2093 /* We use the class method to let user of this widget to connect to
2094 * the signal and stop emission of the signal so the default handler
2095 * won't be called. */
2096 tree_view_class->row_activated = individual_view_row_activated;
2098 klass->drag_individual_received = real_drag_individual_received_cb;
2100 signals[DRAG_INDIVIDUAL_RECEIVED] =
2101 g_signal_new ("drag-individual-received",
2102 G_OBJECT_CLASS_TYPE (klass),
2104 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2106 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2107 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2108 G_TYPE_STRING, G_TYPE_STRING);
2110 signals[DRAG_PERSONA_RECEIVED] =
2111 g_signal_new ("drag-persona-received",
2112 G_OBJECT_CLASS_TYPE (klass),
2114 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2116 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2117 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2119 g_object_class_install_property (object_class,
2121 g_param_spec_object ("store",
2122 "The store of the view",
2123 "The store of the view",
2124 EMPATHY_TYPE_INDIVIDUAL_STORE,
2125 G_PARAM_READWRITE));
2126 g_object_class_install_property (object_class,
2128 g_param_spec_flags ("view-features",
2129 "Features of the view",
2130 "Flags for all enabled features",
2131 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2132 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2133 g_object_class_install_property (object_class,
2134 PROP_INDIVIDUAL_FEATURES,
2135 g_param_spec_flags ("individual-features",
2136 "Features of the individual menu",
2137 "Flags for all enabled features for the menu",
2138 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2139 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2140 g_object_class_install_property (object_class,
2142 g_param_spec_boolean ("show-offline",
2144 "Whether contact list should display "
2145 "offline contacts", FALSE, G_PARAM_READWRITE));
2146 g_object_class_install_property (object_class,
2147 PROP_SHOW_UNTRUSTED,
2148 g_param_spec_boolean ("show-untrusted",
2149 "Show Untrusted Individuals",
2150 "Whether the view should display untrusted individuals; "
2151 "those who could not be who they say they are.",
2152 TRUE, G_PARAM_READWRITE));
2154 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2158 empathy_individual_view_init (EmpathyIndividualView *view)
2160 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2161 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2165 priv->show_untrusted = TRUE;
2167 /* Get saved group states. */
2168 empathy_contact_groups_get_all ();
2170 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2171 (GDestroyNotify) g_free, NULL);
2173 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2174 empathy_individual_store_row_separator_func, NULL, NULL);
2176 /* Connect to tree view signals rather than override. */
2177 g_signal_connect (view, "button-press-event",
2178 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2179 g_signal_connect (view, "key-press-event",
2180 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2181 g_signal_connect (view, "row-expanded",
2182 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2183 GINT_TO_POINTER (TRUE));
2184 g_signal_connect (view, "row-collapsed",
2185 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2186 GINT_TO_POINTER (FALSE));
2187 g_signal_connect (view, "query-tooltip",
2188 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2191 EmpathyIndividualView *
2192 empathy_individual_view_new (EmpathyIndividualStore *store,
2193 EmpathyIndividualViewFeatureFlags view_features,
2194 EmpathyIndividualFeatureFlags individual_features)
2196 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2198 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2200 "individual-features", individual_features,
2201 "view-features", view_features, NULL);
2205 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2207 EmpathyIndividualViewPriv *priv;
2208 GtkTreeSelection *selection;
2210 GtkTreeModel *model;
2211 FolksIndividual *individual;
2213 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2215 priv = GET_PRIV (view);
2217 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2218 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2221 gtk_tree_model_get (model, &iter,
2222 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2228 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2229 gboolean *is_fake_group)
2231 EmpathyIndividualViewPriv *priv;
2232 GtkTreeSelection *selection;
2234 GtkTreeModel *model;
2239 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2241 priv = GET_PRIV (view);
2243 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2244 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2247 gtk_tree_model_get (model, &iter,
2248 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2249 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2250 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2258 if (is_fake_group != NULL)
2259 *is_fake_group = fake;
2265 individual_view_remove_dialog_show (GtkWindow *parent,
2266 const gchar *message,
2267 const gchar *secondary_text)
2272 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2273 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2274 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2275 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2276 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2277 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2278 "%s", secondary_text);
2280 gtk_widget_show (dialog);
2282 res = gtk_dialog_run (GTK_DIALOG (dialog));
2283 gtk_widget_destroy (dialog);
2285 return (res == GTK_RESPONSE_YES);
2289 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2290 EmpathyIndividualView *view)
2294 group = empathy_individual_view_get_selected_group (view, NULL);
2301 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2303 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2304 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2307 EmpathyIndividualManager *manager =
2308 empathy_individual_manager_dup_singleton ();
2309 empathy_individual_manager_remove_group (manager, group);
2310 g_object_unref (G_OBJECT (manager));
2320 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2322 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2327 gboolean is_fake_group;
2329 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2331 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2332 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2335 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2336 if (!group || is_fake_group)
2338 /* We can't alter fake groups */
2342 menu = gtk_menu_new ();
2345 if (priv->view_features &
2346 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2347 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2348 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2349 gtk_widget_show (item);
2350 g_signal_connect (item, "activate",
2351 G_CALLBACK (individual_view_group_rename_activate_cb),
2356 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2358 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2359 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2360 GTK_ICON_SIZE_MENU);
2361 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2362 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2363 gtk_widget_show (item);
2364 g_signal_connect (item, "activate",
2365 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2374 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2375 EmpathyIndividualView *view)
2377 FolksIndividual *individual;
2379 individual = empathy_individual_view_dup_selected (view);
2381 if (individual != NULL)
2386 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2389 ("Do you really want to remove the contact '%s'?"),
2390 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2391 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2394 EmpathyIndividualManager *manager;
2396 manager = empathy_individual_manager_dup_singleton ();
2397 empathy_individual_manager_remove (manager, individual, "");
2398 g_object_unref (G_OBJECT (manager));
2402 g_object_unref (individual);
2407 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2408 EmpathyLinkingDialog *linking_dialog,
2409 EmpathyIndividualView *self)
2411 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2412 EmpathyIndividualLinker *linker;
2414 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2415 empathy_individual_linker_set_search_text (linker,
2416 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2420 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2422 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2423 FolksIndividual *individual;
2424 GtkWidget *menu = NULL;
2427 gboolean can_remove = FALSE;
2430 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2432 individual = empathy_individual_view_dup_selected (view);
2433 if (individual == NULL)
2436 /* If any of the Individual's personas can be removed, add an option to
2437 * remove. This will act as a best-effort option. If any Personas cannot be
2438 * removed from the server, then this option will just be inactive upon
2439 * subsequent menu openings */
2440 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2442 FolksPersona *persona = FOLKS_PERSONA (l->data);
2443 FolksPersonaStore *store = folks_persona_get_store (persona);
2444 FolksMaybeBool maybe_can_remove =
2445 folks_persona_store_get_can_remove_personas (store);
2447 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2454 menu = empathy_individual_menu_new (individual, priv->individual_features);
2456 /* Remove contact */
2457 if ((priv->view_features &
2458 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2461 /* create the menu if required, or just add a separator */
2463 menu = gtk_menu_new ();
2466 item = gtk_separator_menu_item_new ();
2467 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2468 gtk_widget_show (item);
2472 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2473 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2474 GTK_ICON_SIZE_MENU);
2475 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2476 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2477 gtk_widget_show (item);
2478 g_signal_connect (item, "activate",
2479 G_CALLBACK (individual_view_remove_activate_cb), view);
2482 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2483 * set the live search text on the new linking dialogue to be the same as
2485 g_signal_connect (menu, "link-contacts-activated",
2486 (GCallback) individual_menu_link_contacts_activated_cb, view);
2488 g_object_unref (individual);
2494 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2495 EmpathyLiveSearch *search)
2497 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2499 /* remove old handlers if old search was not null */
2500 if (priv->search_widget != NULL)
2502 g_signal_handlers_disconnect_by_func (view,
2503 individual_view_start_search_cb, NULL);
2505 g_signal_handlers_disconnect_by_func (priv->search_widget,
2506 individual_view_search_text_notify_cb, view);
2507 g_signal_handlers_disconnect_by_func (priv->search_widget,
2508 individual_view_search_activate_cb, view);
2509 g_signal_handlers_disconnect_by_func (priv->search_widget,
2510 individual_view_search_key_navigation_cb, view);
2511 g_signal_handlers_disconnect_by_func (priv->search_widget,
2512 individual_view_search_hide_cb, view);
2513 g_signal_handlers_disconnect_by_func (priv->search_widget,
2514 individual_view_search_show_cb, view);
2515 g_object_unref (priv->search_widget);
2516 priv->search_widget = NULL;
2519 /* connect handlers if new search is not null */
2522 priv->search_widget = g_object_ref (search);
2524 g_signal_connect (view, "start-interactive-search",
2525 G_CALLBACK (individual_view_start_search_cb), NULL);
2527 g_signal_connect (priv->search_widget, "notify::text",
2528 G_CALLBACK (individual_view_search_text_notify_cb), view);
2529 g_signal_connect (priv->search_widget, "activate",
2530 G_CALLBACK (individual_view_search_activate_cb), view);
2531 g_signal_connect (priv->search_widget, "key-navigation",
2532 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2533 g_signal_connect (priv->search_widget, "hide",
2534 G_CALLBACK (individual_view_search_hide_cb), view);
2535 g_signal_connect (priv->search_widget, "show",
2536 G_CALLBACK (individual_view_search_show_cb), view);
2541 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2543 EmpathyIndividualViewPriv *priv;
2545 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2547 priv = GET_PRIV (self);
2549 return (priv->search_widget != NULL &&
2550 gtk_widget_get_visible (priv->search_widget));
2554 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2556 EmpathyIndividualViewPriv *priv;
2558 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2560 priv = GET_PRIV (self);
2562 return priv->show_offline;
2566 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2567 gboolean show_offline)
2569 EmpathyIndividualViewPriv *priv;
2571 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2573 priv = GET_PRIV (self);
2575 priv->show_offline = show_offline;
2577 g_object_notify (G_OBJECT (self), "show-offline");
2578 gtk_tree_model_filter_refilter (priv->filter);
2582 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2584 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2586 return GET_PRIV (self)->show_untrusted;
2590 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2591 gboolean show_untrusted)
2593 EmpathyIndividualViewPriv *priv;
2595 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2597 priv = GET_PRIV (self);
2599 priv->show_untrusted = show_untrusted;
2601 g_object_notify (G_OBJECT (self), "show-untrusted");
2602 gtk_tree_model_filter_refilter (priv->filter);
2605 EmpathyIndividualStore *
2606 empathy_individual_view_get_store (EmpathyIndividualView *self)
2608 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2610 return GET_PRIV (self)->store;
2614 empathy_individual_view_set_store (EmpathyIndividualView *self,
2615 EmpathyIndividualStore *store)
2617 EmpathyIndividualViewPriv *priv;
2619 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2620 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2622 priv = GET_PRIV (self);
2624 /* Destroy the old filter and remove the old store */
2625 if (priv->store != NULL)
2627 g_signal_handlers_disconnect_by_func (priv->store,
2628 individual_view_store_row_changed_cb, self);
2629 g_signal_handlers_disconnect_by_func (priv->store,
2630 individual_view_store_row_deleted_cb, self);
2632 g_signal_handlers_disconnect_by_func (priv->filter,
2633 individual_view_row_has_child_toggled_cb, self);
2635 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2638 tp_clear_object (&priv->filter);
2639 tp_clear_object (&priv->store);
2641 /* Set the new store */
2642 priv->store = store;
2646 g_object_ref (store);
2648 /* Create a new filter */
2649 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2650 GTK_TREE_MODEL (priv->store), NULL));
2651 gtk_tree_model_filter_set_visible_func (priv->filter,
2652 individual_view_filter_visible_func, self, NULL);
2654 g_signal_connect (priv->filter, "row-has-child-toggled",
2655 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2656 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2657 GTK_TREE_MODEL (priv->filter));
2659 tp_g_signal_connect_object (priv->store, "row-changed",
2660 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2661 tp_g_signal_connect_object (priv->store, "row-inserted",
2662 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2663 tp_g_signal_connect_object (priv->store, "row-deleted",
2664 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2669 empathy_individual_view_start_search (EmpathyIndividualView *self)
2671 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2673 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2674 g_return_if_fail (priv->search_widget != NULL);
2676 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2677 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2679 gtk_widget_show (GTK_WIDGET (priv->search_widget));