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-dispatcher.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-contact-dialogs.h"
50 #include "empathy-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 tp_clear_object (&priv->tooltip_widget);
169 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
172 gboolean keyboard_mode,
176 EmpathyIndividualViewPriv *priv;
177 FolksIndividual *individual;
181 static gint running = 0;
182 gboolean ret = FALSE;
184 priv = GET_PRIV (view);
186 /* Avoid an infinite loop. See GNOME bug #574377 */
192 /* Don't show the tooltip if there's already a popup menu */
193 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
196 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
197 keyboard_mode, &model, &path, &iter))
200 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
201 gtk_tree_path_free (path);
203 gtk_tree_model_get (model, &iter,
204 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
206 if (individual == NULL)
209 if (priv->tooltip_widget == NULL)
211 priv->tooltip_widget = empathy_individual_widget_new (individual,
212 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
213 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
214 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
215 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
216 g_object_ref (priv->tooltip_widget);
217 g_signal_connect (priv->tooltip_widget, "destroy",
218 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
219 gtk_widget_show (priv->tooltip_widget);
223 empathy_individual_widget_set_individual (
224 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
227 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
230 g_object_unref (individual);
238 groups_change_group_cb (GObject *source,
239 GAsyncResult *result,
242 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
243 GError *error = NULL;
245 folks_groupable_change_group_finish (groupable, result, &error);
248 g_warning ("failed to change group: %s", error->message);
249 g_clear_error (&error);
254 group_can_be_modified (const gchar *name,
255 gboolean is_fake_group,
258 /* Real groups can always be modified */
262 /* The favorite fake group can be modified so users can
263 * add/remove favorites using DnD */
264 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
267 /* We can remove contacts from the 'ungrouped' fake group */
268 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
275 individual_view_individual_drag_received (GtkWidget *self,
276 GdkDragContext *context,
279 GtkSelectionData *selection)
281 EmpathyIndividualViewPriv *priv;
282 EmpathyIndividualManager *manager = NULL;
283 FolksIndividual *individual;
284 GtkTreePath *source_path;
285 const gchar *sel_data;
286 gchar *new_group = NULL;
287 gchar *old_group = NULL;
288 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
290 priv = GET_PRIV (self);
292 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
293 new_group = empathy_individual_store_get_parent_group (model, path,
294 NULL, &new_group_is_fake);
296 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
299 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
300 * feature. Otherwise, we just add the dropped contact to whichever group
301 * they were dropped in, and don't remove them from their old group. This
302 * allows for Individual views which shouldn't allow Individuals to have
303 * their groups changed, and also for dragging Individuals between Individual
305 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
306 priv->drag_row != NULL)
308 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
312 empathy_individual_store_get_parent_group (model, source_path,
313 NULL, &old_group_is_fake);
314 gtk_tree_path_free (source_path);
317 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
320 if (!tp_strdiff (old_group, new_group))
323 else if (priv->drag_row != NULL)
325 /* We don't allow changing Individuals' groups, and this Individual was
326 * dragged from another group in *this* Individual view, so we disallow
331 /* XXX: for contacts, we used to ensure the account, create the contact
332 * factory, and then wait on the contacts. But they should already be
333 * created by this point */
335 manager = empathy_individual_manager_dup_singleton ();
336 individual = empathy_individual_manager_lookup_member (manager, sel_data);
338 if (individual == NULL)
340 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
344 /* FIXME: We should probably wait for the cb before calling
347 /* Emit a signal notifying of the drag. We change the Individual's groups in
348 * the default signal handler. */
349 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
350 gdk_drag_context_get_selected_action (context), individual, new_group,
356 tp_clear_object (&manager);
364 real_drag_individual_received_cb (EmpathyIndividualView *self,
365 GdkDragAction action,
366 FolksIndividual *individual,
367 const gchar *new_group,
368 const gchar *old_group)
370 DEBUG ("individual %s dragged from '%s' to '%s'",
371 folks_individual_get_id (individual), old_group, new_group);
373 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
375 /* Mark contact as favourite */
376 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
380 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
382 /* Remove contact as favourite */
383 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
385 /* Don't try to remove it */
389 if (new_group != NULL)
391 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
392 groups_change_group_cb, NULL);
395 if (old_group != NULL && action == GDK_ACTION_MOVE)
397 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
398 FALSE, groups_change_group_cb, NULL);
403 individual_view_persona_drag_received (GtkWidget *self,
404 GdkDragContext *context,
407 GtkSelectionData *selection)
409 EmpathyIndividualViewPriv *priv;
410 EmpathyIndividualManager *manager = NULL;
411 FolksIndividual *individual = NULL;
412 FolksPersona *persona = NULL;
413 const gchar *persona_uid;
414 GList *individuals, *l;
415 gboolean retval = FALSE;
417 priv = GET_PRIV (self);
419 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
421 /* FIXME: This is slow, but the only way to find the Persona we're having
423 manager = empathy_individual_manager_dup_singleton ();
424 individuals = empathy_individual_manager_get_members (manager);
426 for (l = individuals; l != NULL; l = l->next)
430 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
432 for (p = personas; p != NULL; p = p->next)
434 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
437 persona = g_object_ref (p->data);
438 individual = g_object_ref (l->data);
445 g_list_free (individuals);
447 if (persona == NULL || individual == NULL)
449 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
453 /* Emit a signal notifying of the drag. We change the Individual's groups in
454 * the default signal handler. */
455 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
456 gdk_drag_context_get_selected_action (context), persona, individual,
460 tp_clear_object (&manager);
461 tp_clear_object (&persona);
462 tp_clear_object (&individual);
468 individual_view_file_drag_received (GtkWidget *view,
469 GdkDragContext *context,
472 GtkSelectionData *selection)
475 const gchar *sel_data;
476 FolksIndividual *individual;
477 EmpathyContact *contact;
479 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
481 gtk_tree_model_get_iter (model, &iter, path);
482 gtk_tree_model_get (model, &iter,
483 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
484 if (individual == NULL)
487 contact = empathy_contact_dup_from_folks_individual (individual);
488 empathy_send_file_from_uri_list (contact, sel_data);
490 g_object_unref (individual);
491 tp_clear_object (&contact);
497 individual_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
501 GtkSelectionData *selection,
507 GtkTreeViewDropPosition position;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
515 x, y, &path, &position);
520 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
522 success = individual_view_individual_drag_received (view,
523 context, model, path, selection);
525 else if (info == DND_DRAG_TYPE_PERSONA_ID)
527 success = individual_view_persona_drag_received (view, context, model,
530 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
532 success = individual_view_file_drag_received (view,
533 context, model, path, selection);
536 gtk_tree_path_free (path);
537 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
541 individual_view_drag_motion_cb (DragMotionData *data)
543 if (data->view != NULL)
545 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
546 g_object_remove_weak_pointer (G_OBJECT (data->view),
547 (gpointer *) &data->view);
550 data->timeout_id = 0;
555 /* Minimum distance between the mouse pointer and a horizontal border when we
556 start auto scrolling. */
557 #define AUTO_SCROLL_MARGIN_SIZE 20
558 /* How far to scroll per one tick. */
559 #define AUTO_SCROLL_PITCH 10
562 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
564 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
568 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
570 if (priv->distance < 0)
571 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
573 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
575 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
576 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
578 gtk_adjustment_set_value (adj, new_value);
584 individual_view_drag_motion (GtkWidget *widget,
585 GdkDragContext *context,
590 EmpathyIndividualViewPriv *priv;
594 static DragMotionData *dm = NULL;
597 gboolean is_different = FALSE;
598 gboolean cleanup = TRUE;
599 gboolean retval = TRUE;
600 GtkAllocation allocation;
602 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
603 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
606 if (priv->auto_scroll_timeout_id != 0)
608 g_source_remove (priv->auto_scroll_timeout_id);
609 priv->auto_scroll_timeout_id = 0;
612 gtk_widget_get_allocation (widget, &allocation);
614 if (y < AUTO_SCROLL_MARGIN_SIZE ||
615 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
617 if (y < AUTO_SCROLL_MARGIN_SIZE)
618 priv->distance = MIN (-y, -1);
620 priv->distance = MAX (allocation.height - y, 1);
622 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
623 (GSourceFunc) individual_view_auto_scroll_cb, widget);
626 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
627 x, y, &path, NULL, NULL, NULL);
629 cleanup &= (dm == NULL);
633 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
634 is_different = ((dm == NULL) || ((dm != NULL)
635 && gtk_tree_path_compare (dm->path, path) != 0));
642 /* Coordinates don't point to an actual row, so make sure the pointer
643 and highlighting don't indicate that a drag is possible.
645 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
646 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
649 target = gtk_drag_dest_find_target (widget, context, NULL);
650 gtk_tree_model_get_iter (model, &iter, path);
652 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
653 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
655 /* This is a file drag, and it can only be dropped on contacts,
657 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
658 * even if we have a valid target. */
659 FolksIndividual *individual = NULL;
660 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
662 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
664 gtk_tree_model_get (model, &iter,
665 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
669 if (individual != NULL)
671 EmpathyContact *contact = NULL;
673 contact = empathy_contact_dup_from_folks_individual (individual);
674 caps = empathy_contact_get_capabilities (contact);
676 tp_clear_object (&contact);
679 if (individual != NULL &&
680 folks_presence_owner_is_online (FOLKS_PRESENCE_OWNER (individual)) &&
681 (caps & EMPATHY_CAPABILITIES_FT))
683 gdk_drag_status (context, GDK_ACTION_COPY, time_);
684 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
685 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
689 gdk_drag_status (context, 0, time_);
690 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
694 if (individual != NULL)
695 g_object_unref (individual);
697 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
698 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
699 priv->drag_row == NULL)) ||
700 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
701 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
703 /* If target != GDK_NONE, then we have a contact (individual or persona)
704 drag. If we're pointing to a group, highlight it. Otherwise, if the
705 contact we're pointing to is in a group, highlight that. Otherwise,
706 set the drag position to before the first row for a drag into
707 the "non-group" at the top.
708 If it's an Individual:
709 We only highlight things if the contact is from a different
710 Individual view, or if this Individual view has
711 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
712 which don't have FEATURE_GROUPS_CHANGE, but do have
713 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
715 We only highlight things if we have FEATURE_PERSONA_DROP.
717 GtkTreeIter group_iter;
719 GtkTreePath *group_path;
720 gtk_tree_model_get (model, &iter,
721 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
728 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
729 gtk_tree_model_get (model, &group_iter,
730 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
734 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
735 group_path = gtk_tree_model_get_path (model, &group_iter);
736 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
737 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
738 gtk_tree_path_free (group_path);
742 group_path = gtk_tree_path_new_first ();
743 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
744 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
745 group_path, GTK_TREE_VIEW_DROP_BEFORE);
749 if (!is_different && !cleanup)
754 gtk_tree_path_free (dm->path);
757 g_source_remove (dm->timeout_id);
765 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
767 dm = g_new0 (DragMotionData, 1);
769 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
770 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
771 dm->path = gtk_tree_path_copy (path);
773 dm->timeout_id = g_timeout_add_seconds (1,
774 (GSourceFunc) individual_view_drag_motion_cb, dm);
781 individual_view_drag_begin (GtkWidget *widget,
782 GdkDragContext *context)
784 EmpathyIndividualViewPriv *priv;
785 GtkTreeSelection *selection;
790 priv = GET_PRIV (widget);
792 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
795 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
796 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
799 path = gtk_tree_model_get_path (model, &iter);
800 priv->drag_row = gtk_tree_row_reference_new (model, path);
801 gtk_tree_path_free (path);
805 individual_view_drag_data_get (GtkWidget *widget,
806 GdkDragContext *context,
807 GtkSelectionData *selection,
811 EmpathyIndividualViewPriv *priv;
812 GtkTreePath *src_path;
815 FolksIndividual *individual;
816 const gchar *individual_id;
818 priv = GET_PRIV (widget);
820 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
821 if (priv->drag_row == NULL)
824 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
825 if (src_path == NULL)
828 if (!gtk_tree_model_get_iter (model, &iter, src_path))
830 gtk_tree_path_free (src_path);
834 gtk_tree_path_free (src_path);
837 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
838 if (individual == NULL)
841 individual_id = folks_individual_get_id (individual);
843 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
845 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
846 (guchar *) individual_id, strlen (individual_id) + 1);
849 g_object_unref (individual);
853 individual_view_drag_end (GtkWidget *widget,
854 GdkDragContext *context)
856 EmpathyIndividualViewPriv *priv;
858 priv = GET_PRIV (widget);
860 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
865 gtk_tree_row_reference_free (priv->drag_row);
866 priv->drag_row = NULL;
871 individual_view_drag_drop (GtkWidget *widget,
872 GdkDragContext *drag_context,
882 EmpathyIndividualView *view;
888 menu_deactivate_cb (GtkMenuShell *menushell,
891 gtk_menu_detach (GTK_MENU (menushell));
893 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
894 g_signal_handlers_disconnect_by_func (menushell,
895 menu_deactivate_cb, user_data);
899 individual_view_popup_menu_idle_cb (gpointer user_data)
901 MenuPopupData *data = user_data;
904 menu = empathy_individual_view_get_individual_menu (data->view);
906 menu = empathy_individual_view_get_group_menu (data->view);
910 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
912 gtk_widget_show (menu);
913 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
916 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
917 * floating ref. We can either wait that the treeview releases its ref
918 * when it will be destroyed (when leaving Empathy) or explicitely
919 * detach the menu when it's not displayed any more.
920 * We go for the latter as we don't want to keep useless menus in memory
921 * during the whole lifetime of Empathy. */
922 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
926 g_slice_free (MenuPopupData, data);
932 individual_view_button_press_event_cb (EmpathyIndividualView *view,
933 GdkEventButton *event,
936 if (event->button == 3)
940 data = g_slice_new (MenuPopupData);
942 data->button = event->button;
943 data->time = event->time;
944 g_idle_add (individual_view_popup_menu_idle_cb, data);
951 individual_view_key_press_event_cb (EmpathyIndividualView *view,
955 if (event->keyval == GDK_KEY_Menu)
959 data = g_slice_new (MenuPopupData);
962 data->time = event->time;
963 g_idle_add (individual_view_popup_menu_idle_cb, data);
964 } else if (event->keyval == GDK_KEY_F2) {
965 FolksIndividual *individual;
966 EmpathyContact *contact;
968 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
970 individual = empathy_individual_view_dup_selected (view);
971 if (individual == NULL)
974 contact = empathy_contact_dup_from_folks_individual (individual);
975 if (contact == NULL) {
976 g_object_unref (individual);
979 empathy_contact_edit_dialog_show (contact, NULL);
981 g_object_unref (individual);
982 g_object_unref (contact);
989 individual_view_row_activated (GtkTreeView *view,
991 GtkTreeViewColumn *column)
993 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
994 FolksIndividual *individual;
995 EmpathyContact *contact;
999 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1002 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1003 gtk_tree_model_get_iter (model, &iter, path);
1004 gtk_tree_model_get (model, &iter,
1005 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1007 if (individual == NULL)
1010 /* Determine which Persona to chat to, by choosing the most available one. */
1011 contact = empathy_contact_dup_best_for_action (individual,
1012 EMPATHY_ACTION_CHAT);
1014 if (contact != NULL)
1016 DEBUG ("Starting a chat");
1018 empathy_dispatcher_chat_with_contact (contact,
1019 gtk_get_current_event_time ());
1022 g_object_unref (individual);
1023 tp_clear_object (&contact);
1027 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1028 const gchar *path_string,
1029 EmpathyIndividualView *view)
1031 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1033 GtkTreeModel *model;
1035 FolksIndividual *individual;
1036 GdkEventButton *event;
1037 GtkMenuShell *shell;
1040 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1043 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1044 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1047 gtk_tree_model_get (model, &iter,
1048 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1049 if (individual == NULL)
1052 event = (GdkEventButton *) gtk_get_current_event ();
1054 menu = gtk_menu_new ();
1055 shell = GTK_MENU_SHELL (menu);
1058 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1059 gtk_menu_shell_append (shell, item);
1060 gtk_widget_show (item);
1063 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1064 gtk_menu_shell_append (shell, item);
1065 gtk_widget_show (item);
1067 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1068 gtk_widget_show (menu);
1069 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1070 event->button, event->time);
1071 g_object_ref_sink (menu);
1072 g_object_unref (menu);
1074 g_object_unref (individual);
1078 individual_view_cell_set_background (EmpathyIndividualView *view,
1079 GtkCellRenderer *cell,
1083 if (!is_group && is_active)
1085 GtkStyleContext *style;
1088 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1090 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1093 /* Here we take the current theme colour and add it to
1094 * the colour for white and average the two. This
1095 * gives a colour which is inline with the theme but
1098 empathy_make_color_whiter (&color);
1100 g_object_set (cell, "cell-background-rgba", &color, NULL);
1103 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1107 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1108 GtkCellRenderer *cell,
1109 GtkTreeModel *model,
1111 EmpathyIndividualView *view)
1117 gtk_tree_model_get (model, iter,
1118 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1119 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1120 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1123 "visible", !is_group,
1127 tp_clear_object (&pixbuf);
1129 individual_view_cell_set_background (view, cell, is_group, is_active);
1133 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1134 GtkCellRenderer *cell,
1135 GtkTreeModel *model,
1137 EmpathyIndividualView *view)
1139 GdkPixbuf *pixbuf = NULL;
1143 gtk_tree_model_get (model, iter,
1144 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1145 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1150 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1152 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1153 GTK_ICON_SIZE_MENU);
1155 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1157 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1158 GTK_ICON_SIZE_MENU);
1163 "visible", pixbuf != NULL,
1167 tp_clear_object (&pixbuf);
1173 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1174 GtkCellRenderer *cell,
1175 GtkTreeModel *model,
1177 EmpathyIndividualView *view)
1181 gboolean can_audio, can_video;
1183 gtk_tree_model_get (model, iter,
1184 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1185 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1186 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1187 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1190 "visible", !is_group && (can_audio || can_video),
1191 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1194 individual_view_cell_set_background (view, cell, is_group, is_active);
1198 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1199 GtkCellRenderer *cell,
1200 GtkTreeModel *model,
1202 EmpathyIndividualView *view)
1205 gboolean show_avatar;
1209 gtk_tree_model_get (model, iter,
1210 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1211 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1212 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1213 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1216 "visible", !is_group && show_avatar,
1220 tp_clear_object (&pixbuf);
1222 individual_view_cell_set_background (view, cell, is_group, is_active);
1226 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1227 GtkCellRenderer *cell,
1228 GtkTreeModel *model,
1230 EmpathyIndividualView *view)
1235 gtk_tree_model_get (model, iter,
1236 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1237 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1239 individual_view_cell_set_background (view, cell, is_group, is_active);
1243 individual_view_expander_cell_data_func (GtkTreeViewColumn *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 if (gtk_tree_model_iter_has_child (model, iter))
1259 gboolean row_expanded;
1261 path = gtk_tree_model_get_path (model, iter);
1263 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1264 (gtk_tree_view_column_get_tree_view (column)), path);
1265 gtk_tree_path_free (path);
1270 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1274 g_object_set (cell, "visible", FALSE, NULL);
1276 individual_view_cell_set_background (view, cell, is_group, is_active);
1280 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1285 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1286 GtkTreeModel *model;
1290 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1293 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1295 gtk_tree_model_get (model, iter,
1296 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1298 expanded = GPOINTER_TO_INT (user_data);
1299 empathy_contact_group_set_expanded (name, expanded);
1305 individual_view_start_search_cb (EmpathyIndividualView *view,
1308 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1310 if (priv->search_widget == NULL)
1313 empathy_individual_view_start_search (view);
1319 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1321 EmpathyIndividualView *view)
1323 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1325 GtkTreeViewColumn *focus_column;
1326 GtkTreeModel *model;
1328 gboolean set_cursor = FALSE;
1330 gtk_tree_model_filter_refilter (priv->filter);
1332 /* Set cursor on the first contact. If it is already set on a group,
1333 * set it on its first child contact. Note that first child of a group
1334 * is its separator, that's why we actually set to the 2nd
1337 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1338 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1342 path = gtk_tree_path_new_from_string ("0:1");
1345 else if (gtk_tree_path_get_depth (path) < 2)
1349 gtk_tree_model_get_iter (model, &iter, path);
1350 gtk_tree_model_get (model, &iter,
1351 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1356 gtk_tree_path_down (path);
1357 gtk_tree_path_next (path);
1364 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1366 if (gtk_tree_model_get_iter (model, &iter, path))
1368 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1373 gtk_tree_path_free (path);
1377 individual_view_search_activate_cb (GtkWidget *search,
1378 EmpathyIndividualView *view)
1381 GtkTreeViewColumn *focus_column;
1383 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1386 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1387 gtk_tree_path_free (path);
1389 gtk_widget_hide (search);
1394 individual_view_search_key_navigation_cb (GtkWidget *search,
1396 EmpathyIndividualView *view)
1398 GdkEventKey *eventkey = ((GdkEventKey *) event);
1399 gboolean ret = FALSE;
1401 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1402 || eventkey->keyval == GDK_KEY_F2)
1404 GdkEvent *new_event;
1406 new_event = gdk_event_copy (event);
1407 gtk_widget_grab_focus (GTK_WIDGET (view));
1408 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1409 gtk_widget_grab_focus (search);
1411 gdk_event_free (new_event);
1418 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1419 EmpathyIndividualView *view)
1421 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1422 GtkTreeModel *model;
1423 GtkTreePath *cursor_path;
1425 gboolean valid = FALSE;
1427 /* block expand or collapse handlers, they would write the
1428 * expand or collapsed setting to file otherwise */
1429 g_signal_handlers_block_by_func (view,
1430 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1431 g_signal_handlers_block_by_func (view,
1432 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1434 /* restore which groups are expanded and which are not */
1435 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1436 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1437 valid; valid = gtk_tree_model_iter_next (model, &iter))
1443 gtk_tree_model_get (model, &iter,
1444 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1445 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1454 path = gtk_tree_model_get_path (model, &iter);
1455 if ((priv->view_features &
1456 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1457 empathy_contact_group_get_expanded (name))
1459 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1463 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1466 gtk_tree_path_free (path);
1470 /* unblock expand or collapse handlers */
1471 g_signal_handlers_unblock_by_func (view,
1472 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1473 g_signal_handlers_unblock_by_func (view,
1474 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1476 /* keep the selected contact visible */
1477 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1479 if (cursor_path != NULL)
1480 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1483 gtk_tree_path_free (cursor_path);
1487 individual_view_search_show_cb (EmpathyLiveSearch *search,
1488 EmpathyIndividualView *view)
1490 /* block expand or collapse handlers during expand all, they would
1491 * write the expand or collapsed setting to file otherwise */
1492 g_signal_handlers_block_by_func (view,
1493 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1495 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1497 g_signal_handlers_unblock_by_func (view,
1498 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1502 expand_idle_foreach_cb (GtkTreeModel *model,
1505 EmpathyIndividualView *self)
1507 EmpathyIndividualViewPriv *priv;
1509 gpointer should_expand;
1512 /* We only want groups */
1513 if (gtk_tree_path_get_depth (path) > 1)
1516 gtk_tree_model_get (model, iter,
1517 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1518 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1521 if (is_group == FALSE)
1527 priv = GET_PRIV (self);
1529 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1530 &should_expand) == TRUE)
1532 if (GPOINTER_TO_INT (should_expand) == TRUE)
1533 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1535 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1537 g_hash_table_remove (priv->expand_groups, name);
1546 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1548 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1550 DEBUG ("individual_view_expand_idle_cb");
1552 g_signal_handlers_block_by_func (self,
1553 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1554 g_signal_handlers_block_by_func (self,
1555 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1557 /* The store/filter could've been removed while we were in the idle queue */
1558 if (priv->filter != NULL)
1560 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1561 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1564 g_signal_handlers_unblock_by_func (self,
1565 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1566 g_signal_handlers_unblock_by_func (self,
1567 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1569 /* Empty the table of groups to expand/contract, since it may contain groups
1570 * which no longer exist in the tree view. This can happen after going
1571 * offline, for example. */
1572 g_hash_table_remove_all (priv->expand_groups);
1573 priv->expand_groups_idle_handler = 0;
1574 g_object_unref (self);
1580 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1583 EmpathyIndividualView *view)
1585 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1586 gboolean should_expand, is_group = FALSE;
1588 gpointer will_expand;
1590 gtk_tree_model_get (model, iter,
1591 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1592 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1595 if (!is_group || EMP_STR_EMPTY (name))
1601 should_expand = (priv->view_features &
1602 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1603 (priv->search_widget != NULL &&
1604 gtk_widget_get_visible (priv->search_widget)) ||
1605 empathy_contact_group_get_expanded (name);
1607 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1608 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1609 * a hash table, and expand or contract them as appropriate all at once in
1610 * an idle handler which iterates over all the group rows. */
1611 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1612 &will_expand) == FALSE ||
1613 GPOINTER_TO_INT (will_expand) != should_expand)
1615 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1616 GINT_TO_POINTER (should_expand));
1618 if (priv->expand_groups_idle_handler == 0)
1620 priv->expand_groups_idle_handler =
1621 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1622 g_object_ref (view));
1629 /* FIXME: This is a workaround for bgo#621076 */
1631 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1634 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1635 GtkTreeModel *model;
1636 GtkTreePath *parent_path;
1637 GtkTreeIter parent_iter;
1639 if (gtk_tree_path_get_depth (path) < 2)
1642 /* A group row is visible if and only if at least one if its child is visible.
1643 * So when a row is inserted/deleted/changed in the base model, that could
1644 * modify the visibility of its parent in the filter model.
1647 model = GTK_TREE_MODEL (priv->store);
1648 parent_path = gtk_tree_path_copy (path);
1649 gtk_tree_path_up (parent_path);
1650 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1652 /* This tells the filter to verify the visibility of that row, and
1653 * show/hide it if necessary */
1654 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1655 parent_path, &parent_iter);
1657 gtk_tree_path_free (parent_path);
1661 individual_view_store_row_changed_cb (GtkTreeModel *model,
1664 EmpathyIndividualView *view)
1666 individual_view_verify_group_visibility (view, path);
1670 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1672 EmpathyIndividualView *view)
1674 individual_view_verify_group_visibility (view, path);
1678 individual_view_is_visible_individual (EmpathyIndividualView *self,
1679 FolksIndividual *individual,
1681 gboolean is_searching)
1683 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1684 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1686 GList *personas, *l;
1688 /* We're only giving the visibility wrt filtering here, not things like
1690 if (priv->show_untrusted == FALSE &&
1691 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1696 if (is_searching == FALSE)
1697 return (priv->show_offline || is_online);
1699 /* check alias name */
1700 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1702 if (empathy_live_search_match (live, str))
1705 /* check contact id, remove the @server.com part */
1706 personas = folks_individual_get_personas (individual);
1707 for (l = personas; l; l = l->next)
1710 gchar *dup_str = NULL;
1713 if (!TPF_IS_PERSONA (l->data))
1716 str = folks_persona_get_display_id (l->data);
1717 p = strstr (str, "@");
1719 str = dup_str = g_strndup (str, p - str);
1721 visible = empathy_live_search_match (live, str);
1727 /* FIXME: Add more rules here, we could check phone numbers in
1728 * contact's vCard for example. */
1734 individual_view_filter_visible_func (GtkTreeModel *model,
1738 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1739 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1740 FolksIndividual *individual = NULL;
1741 gboolean is_group, is_separator, valid;
1742 GtkTreeIter child_iter;
1743 gboolean visible, is_online;
1744 gboolean is_searching = TRUE;
1746 if (priv->search_widget == NULL ||
1747 !gtk_widget_get_visible (priv->search_widget))
1748 is_searching = FALSE;
1750 gtk_tree_model_get (model, iter,
1751 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1752 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1753 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1754 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1757 if (individual != NULL)
1759 visible = individual_view_is_visible_individual (self, individual,
1760 is_online, is_searching);
1762 g_object_unref (individual);
1764 /* FIXME: Work around bgo#626552/bgo#621076 */
1765 if (visible == TRUE)
1767 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1768 individual_view_verify_group_visibility (self, path);
1769 gtk_tree_path_free (path);
1778 /* Not a contact, not a separator, must be a group */
1779 g_return_val_if_fail (is_group, FALSE);
1781 /* only show groups which are not empty */
1782 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1783 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1785 gtk_tree_model_get (model, &child_iter,
1786 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1787 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1790 if (individual == NULL)
1793 visible = individual_view_is_visible_individual (self, individual,
1794 is_online, is_searching);
1795 g_object_unref (individual);
1797 /* show group if it has at least one visible contact in it */
1798 if (visible == TRUE)
1806 individual_view_constructed (GObject *object)
1808 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1809 GtkCellRenderer *cell;
1810 GtkTreeViewColumn *col;
1815 "headers-visible", FALSE,
1816 "show-expanders", FALSE,
1819 col = gtk_tree_view_column_new ();
1822 cell = gtk_cell_renderer_pixbuf_new ();
1823 gtk_tree_view_column_pack_start (col, cell, FALSE);
1824 gtk_tree_view_column_set_cell_data_func (col, cell,
1825 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1835 cell = gtk_cell_renderer_pixbuf_new ();
1836 gtk_tree_view_column_pack_start (col, cell, FALSE);
1837 gtk_tree_view_column_set_cell_data_func (col, cell,
1838 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1850 cell = empathy_cell_renderer_text_new ();
1851 gtk_tree_view_column_pack_start (col, cell, TRUE);
1852 gtk_tree_view_column_set_cell_data_func (col, cell,
1853 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1855 gtk_tree_view_column_add_attribute (col, cell,
1856 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1857 gtk_tree_view_column_add_attribute (col, cell,
1858 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1859 gtk_tree_view_column_add_attribute (col, cell,
1860 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1861 gtk_tree_view_column_add_attribute (col, cell,
1862 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1863 gtk_tree_view_column_add_attribute (col, cell,
1864 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1865 gtk_tree_view_column_add_attribute (col, cell,
1866 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1867 gtk_tree_view_column_add_attribute (col, cell,
1868 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1870 /* Audio Call Icon */
1871 cell = empathy_cell_renderer_activatable_new ();
1872 gtk_tree_view_column_pack_start (col, cell, FALSE);
1873 gtk_tree_view_column_set_cell_data_func (col, cell,
1874 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1877 g_object_set (cell, "visible", FALSE, NULL);
1879 g_signal_connect (cell, "path-activated",
1880 G_CALLBACK (individual_view_call_activated_cb), view);
1883 cell = gtk_cell_renderer_pixbuf_new ();
1884 gtk_tree_view_column_pack_start (col, cell, FALSE);
1885 gtk_tree_view_column_set_cell_data_func (col, cell,
1886 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1898 cell = empathy_cell_renderer_expander_new ();
1899 gtk_tree_view_column_pack_end (col, cell, FALSE);
1900 gtk_tree_view_column_set_cell_data_func (col, cell,
1901 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1904 /* Actually add the column now we have added all cell renderers */
1905 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1908 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1910 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1913 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1915 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1921 individual_view_set_view_features (EmpathyIndividualView *view,
1922 EmpathyIndividualFeatureFlags features)
1924 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1925 gboolean has_tooltip;
1927 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1929 priv->view_features = features;
1931 /* Setting reorderable is a hack that gets us row previews as drag icons
1932 for free. We override all the drag handlers. It's tricky to get the
1933 position of the drag icon right in drag_begin. GtkTreeView has special
1934 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1937 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1938 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1940 /* Update DnD source/dest */
1941 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1943 gtk_drag_source_set (GTK_WIDGET (view),
1946 G_N_ELEMENTS (drag_types_source),
1947 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1951 gtk_drag_source_unset (GTK_WIDGET (view));
1955 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1957 gtk_drag_dest_set (GTK_WIDGET (view),
1958 GTK_DEST_DEFAULT_ALL,
1960 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1964 /* FIXME: URI could still be droped depending on FT feature */
1965 gtk_drag_dest_unset (GTK_WIDGET (view));
1968 /* Update has-tooltip */
1970 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1971 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1975 individual_view_dispose (GObject *object)
1977 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1978 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1980 tp_clear_object (&priv->store);
1981 tp_clear_object (&priv->filter);
1982 tp_clear_object (&priv->tooltip_widget);
1984 empathy_individual_view_set_live_search (view, NULL);
1986 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1990 individual_view_finalize (GObject *object)
1992 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1994 if (priv->expand_groups_idle_handler != 0)
1995 g_source_remove (priv->expand_groups_idle_handler);
1996 g_hash_table_destroy (priv->expand_groups);
1998 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2002 individual_view_get_property (GObject *object,
2007 EmpathyIndividualViewPriv *priv;
2009 priv = GET_PRIV (object);
2014 g_value_set_object (value, priv->store);
2016 case PROP_VIEW_FEATURES:
2017 g_value_set_flags (value, priv->view_features);
2019 case PROP_INDIVIDUAL_FEATURES:
2020 g_value_set_flags (value, priv->individual_features);
2022 case PROP_SHOW_OFFLINE:
2023 g_value_set_boolean (value, priv->show_offline);
2025 case PROP_SHOW_UNTRUSTED:
2026 g_value_set_boolean (value, priv->show_untrusted);
2029 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2035 individual_view_set_property (GObject *object,
2037 const GValue *value,
2040 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2041 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2046 empathy_individual_view_set_store (view, g_value_get_object (value));
2048 case PROP_VIEW_FEATURES:
2049 individual_view_set_view_features (view, g_value_get_flags (value));
2051 case PROP_INDIVIDUAL_FEATURES:
2052 priv->individual_features = g_value_get_flags (value);
2054 case PROP_SHOW_OFFLINE:
2055 empathy_individual_view_set_show_offline (view,
2056 g_value_get_boolean (value));
2058 case PROP_SHOW_UNTRUSTED:
2059 empathy_individual_view_set_show_untrusted (view,
2060 g_value_get_boolean (value));
2063 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2069 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2071 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2072 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2073 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2075 object_class->constructed = individual_view_constructed;
2076 object_class->dispose = individual_view_dispose;
2077 object_class->finalize = individual_view_finalize;
2078 object_class->get_property = individual_view_get_property;
2079 object_class->set_property = individual_view_set_property;
2081 widget_class->drag_data_received = individual_view_drag_data_received;
2082 widget_class->drag_drop = individual_view_drag_drop;
2083 widget_class->drag_begin = individual_view_drag_begin;
2084 widget_class->drag_data_get = individual_view_drag_data_get;
2085 widget_class->drag_end = individual_view_drag_end;
2086 widget_class->drag_motion = individual_view_drag_motion;
2088 /* We use the class method to let user of this widget to connect to
2089 * the signal and stop emission of the signal so the default handler
2090 * won't be called. */
2091 tree_view_class->row_activated = individual_view_row_activated;
2093 klass->drag_individual_received = real_drag_individual_received_cb;
2095 signals[DRAG_INDIVIDUAL_RECEIVED] =
2096 g_signal_new ("drag-individual-received",
2097 G_OBJECT_CLASS_TYPE (klass),
2099 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2101 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2102 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2103 G_TYPE_STRING, G_TYPE_STRING);
2105 signals[DRAG_PERSONA_RECEIVED] =
2106 g_signal_new ("drag-persona-received",
2107 G_OBJECT_CLASS_TYPE (klass),
2109 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2111 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2112 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2114 g_object_class_install_property (object_class,
2116 g_param_spec_object ("store",
2117 "The store of the view",
2118 "The store of the view",
2119 EMPATHY_TYPE_INDIVIDUAL_STORE,
2120 G_PARAM_READWRITE));
2121 g_object_class_install_property (object_class,
2123 g_param_spec_flags ("view-features",
2124 "Features of the view",
2125 "Flags for all enabled features",
2126 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2127 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2128 g_object_class_install_property (object_class,
2129 PROP_INDIVIDUAL_FEATURES,
2130 g_param_spec_flags ("individual-features",
2131 "Features of the individual menu",
2132 "Flags for all enabled features for the menu",
2133 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2134 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2135 g_object_class_install_property (object_class,
2137 g_param_spec_boolean ("show-offline",
2139 "Whether contact list should display "
2140 "offline contacts", FALSE, G_PARAM_READWRITE));
2141 g_object_class_install_property (object_class,
2142 PROP_SHOW_UNTRUSTED,
2143 g_param_spec_boolean ("show-untrusted",
2144 "Show Untrusted Individuals",
2145 "Whether the view should display untrusted individuals; "
2146 "those who could not be who they say they are.",
2147 TRUE, G_PARAM_READWRITE));
2149 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2153 empathy_individual_view_init (EmpathyIndividualView *view)
2155 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2156 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2160 priv->show_untrusted = TRUE;
2162 /* Get saved group states. */
2163 empathy_contact_groups_get_all ();
2165 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2166 (GDestroyNotify) g_free, NULL);
2168 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2169 empathy_individual_store_row_separator_func, NULL, NULL);
2171 /* Connect to tree view signals rather than override. */
2172 g_signal_connect (view, "button-press-event",
2173 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2174 g_signal_connect (view, "key-press-event",
2175 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2176 g_signal_connect (view, "row-expanded",
2177 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2178 GINT_TO_POINTER (TRUE));
2179 g_signal_connect (view, "row-collapsed",
2180 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2181 GINT_TO_POINTER (FALSE));
2182 g_signal_connect (view, "query-tooltip",
2183 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2186 EmpathyIndividualView *
2187 empathy_individual_view_new (EmpathyIndividualStore *store,
2188 EmpathyIndividualViewFeatureFlags view_features,
2189 EmpathyIndividualFeatureFlags individual_features)
2191 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2193 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2195 "individual-features", individual_features,
2196 "view-features", view_features, NULL);
2200 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2202 EmpathyIndividualViewPriv *priv;
2203 GtkTreeSelection *selection;
2205 GtkTreeModel *model;
2206 FolksIndividual *individual;
2208 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2210 priv = GET_PRIV (view);
2212 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2213 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2216 gtk_tree_model_get (model, &iter,
2217 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2223 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2224 gboolean *is_fake_group)
2226 EmpathyIndividualViewPriv *priv;
2227 GtkTreeSelection *selection;
2229 GtkTreeModel *model;
2234 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2236 priv = GET_PRIV (view);
2238 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2239 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2242 gtk_tree_model_get (model, &iter,
2243 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2244 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2245 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2253 if (is_fake_group != NULL)
2254 *is_fake_group = fake;
2260 individual_view_remove_dialog_show (GtkWindow *parent,
2261 const gchar *message,
2262 const gchar *secondary_text)
2267 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2268 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2269 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2270 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2271 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2272 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2273 "%s", secondary_text);
2275 gtk_widget_show (dialog);
2277 res = gtk_dialog_run (GTK_DIALOG (dialog));
2278 gtk_widget_destroy (dialog);
2280 return (res == GTK_RESPONSE_YES);
2284 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2285 EmpathyIndividualView *view)
2289 group = empathy_individual_view_dup_selected_group (view, NULL);
2296 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2298 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2299 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2302 EmpathyIndividualManager *manager =
2303 empathy_individual_manager_dup_singleton ();
2304 empathy_individual_manager_remove_group (manager, group);
2305 g_object_unref (G_OBJECT (manager));
2315 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2317 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2322 gboolean is_fake_group;
2324 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2326 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2327 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2330 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2331 if (!group || is_fake_group)
2333 /* We can't alter fake groups */
2338 menu = gtk_menu_new ();
2341 if (priv->view_features &
2342 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2343 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2344 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2345 gtk_widget_show (item);
2346 g_signal_connect (item, "activate",
2347 G_CALLBACK (individual_view_group_rename_activate_cb),
2352 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2354 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2355 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2356 GTK_ICON_SIZE_MENU);
2357 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2358 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2359 gtk_widget_show (item);
2360 g_signal_connect (item, "activate",
2361 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2370 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2371 EmpathyIndividualView *view)
2373 FolksIndividual *individual;
2375 individual = empathy_individual_view_dup_selected (view);
2377 if (individual != NULL)
2381 GList *l, *personas;
2382 guint persona_count = 0;
2384 personas = folks_individual_get_personas (individual);
2386 /* If we have more than one TpfPersona, display a different message
2387 * ensuring the user knows that *all* of the meta-contacts' personas will
2389 for (l = personas; l != NULL; l = l->next)
2391 if (!TPF_IS_PERSONA (l->data))
2395 if (persona_count >= 2)
2399 if (persona_count < 2)
2401 /* Not a meta-contact */
2404 _("Do you really want to remove the contact '%s'?"),
2405 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2412 _("Do you really want to remove the linked contact '%s'? "
2413 "Note that this will remove all the contacts which make up "
2414 "this linked contact."),
2415 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2418 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2420 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2423 EmpathyIndividualManager *manager;
2425 manager = empathy_individual_manager_dup_singleton ();
2426 empathy_individual_manager_remove (manager, individual, "");
2427 g_object_unref (G_OBJECT (manager));
2431 g_object_unref (individual);
2436 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2437 EmpathyLinkingDialog *linking_dialog,
2438 EmpathyIndividualView *self)
2440 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2441 EmpathyIndividualLinker *linker;
2443 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2444 empathy_individual_linker_set_search_text (linker,
2445 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2449 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2451 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2452 FolksIndividual *individual;
2453 GtkWidget *menu = NULL;
2456 gboolean can_remove = FALSE;
2459 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2461 individual = empathy_individual_view_dup_selected (view);
2462 if (individual == NULL)
2465 /* If any of the Individual's personas can be removed, add an option to
2466 * remove. This will act as a best-effort option. If any Personas cannot be
2467 * removed from the server, then this option will just be inactive upon
2468 * subsequent menu openings */
2469 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2471 FolksPersona *persona = FOLKS_PERSONA (l->data);
2472 FolksPersonaStore *store = folks_persona_get_store (persona);
2473 FolksMaybeBool maybe_can_remove =
2474 folks_persona_store_get_can_remove_personas (store);
2476 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2483 menu = empathy_individual_menu_new (individual, priv->individual_features);
2485 /* Remove contact */
2486 if ((priv->view_features &
2487 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2490 /* create the menu if required, or just add a separator */
2492 menu = gtk_menu_new ();
2495 item = gtk_separator_menu_item_new ();
2496 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2497 gtk_widget_show (item);
2501 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2502 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2503 GTK_ICON_SIZE_MENU);
2504 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2505 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2506 gtk_widget_show (item);
2507 g_signal_connect (item, "activate",
2508 G_CALLBACK (individual_view_remove_activate_cb), view);
2511 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2512 * set the live search text on the new linking dialogue to be the same as
2514 g_signal_connect (menu, "link-contacts-activated",
2515 (GCallback) individual_menu_link_contacts_activated_cb, view);
2517 g_object_unref (individual);
2523 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2524 EmpathyLiveSearch *search)
2526 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2528 /* remove old handlers if old search was not null */
2529 if (priv->search_widget != NULL)
2531 g_signal_handlers_disconnect_by_func (view,
2532 individual_view_start_search_cb, NULL);
2534 g_signal_handlers_disconnect_by_func (priv->search_widget,
2535 individual_view_search_text_notify_cb, view);
2536 g_signal_handlers_disconnect_by_func (priv->search_widget,
2537 individual_view_search_activate_cb, view);
2538 g_signal_handlers_disconnect_by_func (priv->search_widget,
2539 individual_view_search_key_navigation_cb, view);
2540 g_signal_handlers_disconnect_by_func (priv->search_widget,
2541 individual_view_search_hide_cb, view);
2542 g_signal_handlers_disconnect_by_func (priv->search_widget,
2543 individual_view_search_show_cb, view);
2544 g_object_unref (priv->search_widget);
2545 priv->search_widget = NULL;
2548 /* connect handlers if new search is not null */
2551 priv->search_widget = g_object_ref (search);
2553 g_signal_connect (view, "start-interactive-search",
2554 G_CALLBACK (individual_view_start_search_cb), NULL);
2556 g_signal_connect (priv->search_widget, "notify::text",
2557 G_CALLBACK (individual_view_search_text_notify_cb), view);
2558 g_signal_connect (priv->search_widget, "activate",
2559 G_CALLBACK (individual_view_search_activate_cb), view);
2560 g_signal_connect (priv->search_widget, "key-navigation",
2561 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2562 g_signal_connect (priv->search_widget, "hide",
2563 G_CALLBACK (individual_view_search_hide_cb), view);
2564 g_signal_connect (priv->search_widget, "show",
2565 G_CALLBACK (individual_view_search_show_cb), view);
2570 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2572 EmpathyIndividualViewPriv *priv;
2574 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2576 priv = GET_PRIV (self);
2578 return (priv->search_widget != NULL &&
2579 gtk_widget_get_visible (priv->search_widget));
2583 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2585 EmpathyIndividualViewPriv *priv;
2587 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2589 priv = GET_PRIV (self);
2591 return priv->show_offline;
2595 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2596 gboolean show_offline)
2598 EmpathyIndividualViewPriv *priv;
2600 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2602 priv = GET_PRIV (self);
2604 priv->show_offline = show_offline;
2606 g_object_notify (G_OBJECT (self), "show-offline");
2607 gtk_tree_model_filter_refilter (priv->filter);
2611 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2613 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2615 return GET_PRIV (self)->show_untrusted;
2619 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2620 gboolean show_untrusted)
2622 EmpathyIndividualViewPriv *priv;
2624 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2626 priv = GET_PRIV (self);
2628 priv->show_untrusted = show_untrusted;
2630 g_object_notify (G_OBJECT (self), "show-untrusted");
2631 gtk_tree_model_filter_refilter (priv->filter);
2634 EmpathyIndividualStore *
2635 empathy_individual_view_get_store (EmpathyIndividualView *self)
2637 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2639 return GET_PRIV (self)->store;
2643 empathy_individual_view_set_store (EmpathyIndividualView *self,
2644 EmpathyIndividualStore *store)
2646 EmpathyIndividualViewPriv *priv;
2648 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2649 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2651 priv = GET_PRIV (self);
2653 /* Destroy the old filter and remove the old store */
2654 if (priv->store != NULL)
2656 g_signal_handlers_disconnect_by_func (priv->store,
2657 individual_view_store_row_changed_cb, self);
2658 g_signal_handlers_disconnect_by_func (priv->store,
2659 individual_view_store_row_deleted_cb, self);
2661 g_signal_handlers_disconnect_by_func (priv->filter,
2662 individual_view_row_has_child_toggled_cb, self);
2664 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2667 tp_clear_object (&priv->filter);
2668 tp_clear_object (&priv->store);
2670 /* Set the new store */
2671 priv->store = store;
2675 g_object_ref (store);
2677 /* Create a new filter */
2678 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2679 GTK_TREE_MODEL (priv->store), NULL));
2680 gtk_tree_model_filter_set_visible_func (priv->filter,
2681 individual_view_filter_visible_func, self, NULL);
2683 g_signal_connect (priv->filter, "row-has-child-toggled",
2684 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2685 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2686 GTK_TREE_MODEL (priv->filter));
2688 tp_g_signal_connect_object (priv->store, "row-changed",
2689 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2690 tp_g_signal_connect_object (priv->store, "row-inserted",
2691 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2692 tp_g_signal_connect_object (priv->store, "row-deleted",
2693 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2698 empathy_individual_view_start_search (EmpathyIndividualView *self)
2700 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2702 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2703 g_return_if_fail (priv->search_widget != NULL);
2705 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2706 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2708 gtk_widget_show (GTK_WIDGET (priv->search_widget));