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 = empathy_context_menu_new (GTK_WIDGET (view));
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_widget_show (menu);
1068 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1069 event->button, event->time);
1071 g_object_unref (individual);
1075 individual_view_cell_set_background (EmpathyIndividualView *view,
1076 GtkCellRenderer *cell,
1080 if (!is_group && is_active)
1082 GtkStyleContext *style;
1085 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1087 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1090 /* Here we take the current theme colour and add it to
1091 * the colour for white and average the two. This
1092 * gives a colour which is inline with the theme but
1095 empathy_make_color_whiter (&color);
1097 g_object_set (cell, "cell-background-rgba", &color, NULL);
1100 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1104 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1105 GtkCellRenderer *cell,
1106 GtkTreeModel *model,
1108 EmpathyIndividualView *view)
1114 gtk_tree_model_get (model, iter,
1115 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1116 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1117 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1120 "visible", !is_group,
1124 tp_clear_object (&pixbuf);
1126 individual_view_cell_set_background (view, cell, is_group, is_active);
1130 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1131 GtkCellRenderer *cell,
1132 GtkTreeModel *model,
1134 EmpathyIndividualView *view)
1136 GdkPixbuf *pixbuf = NULL;
1140 gtk_tree_model_get (model, iter,
1141 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1142 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1147 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1149 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1150 GTK_ICON_SIZE_MENU);
1152 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1154 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1155 GTK_ICON_SIZE_MENU);
1160 "visible", pixbuf != NULL,
1164 tp_clear_object (&pixbuf);
1170 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1171 GtkCellRenderer *cell,
1172 GtkTreeModel *model,
1174 EmpathyIndividualView *view)
1178 gboolean can_audio, can_video;
1180 gtk_tree_model_get (model, iter,
1181 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1182 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1183 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1184 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1187 "visible", !is_group && (can_audio || can_video),
1188 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1191 individual_view_cell_set_background (view, cell, is_group, is_active);
1195 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1196 GtkCellRenderer *cell,
1197 GtkTreeModel *model,
1199 EmpathyIndividualView *view)
1202 gboolean show_avatar;
1206 gtk_tree_model_get (model, iter,
1207 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1208 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1209 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1210 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1213 "visible", !is_group && show_avatar,
1217 tp_clear_object (&pixbuf);
1219 individual_view_cell_set_background (view, cell, is_group, is_active);
1223 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1224 GtkCellRenderer *cell,
1225 GtkTreeModel *model,
1227 EmpathyIndividualView *view)
1232 gtk_tree_model_get (model, iter,
1233 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1234 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1236 individual_view_cell_set_background (view, cell, is_group, is_active);
1240 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1241 GtkCellRenderer *cell,
1242 GtkTreeModel *model,
1244 EmpathyIndividualView *view)
1249 gtk_tree_model_get (model, iter,
1250 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1253 if (gtk_tree_model_iter_has_child (model, iter))
1256 gboolean row_expanded;
1258 path = gtk_tree_model_get_path (model, iter);
1260 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1261 (gtk_tree_view_column_get_tree_view (column)), path);
1262 gtk_tree_path_free (path);
1267 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1271 g_object_set (cell, "visible", FALSE, NULL);
1273 individual_view_cell_set_background (view, cell, is_group, is_active);
1277 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1282 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1283 GtkTreeModel *model;
1287 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1290 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1292 gtk_tree_model_get (model, iter,
1293 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1295 expanded = GPOINTER_TO_INT (user_data);
1296 empathy_contact_group_set_expanded (name, expanded);
1302 individual_view_start_search_cb (EmpathyIndividualView *view,
1305 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1307 if (priv->search_widget == NULL)
1310 empathy_individual_view_start_search (view);
1316 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1318 EmpathyIndividualView *view)
1320 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1322 GtkTreeViewColumn *focus_column;
1323 GtkTreeModel *model;
1325 gboolean set_cursor = FALSE;
1327 gtk_tree_model_filter_refilter (priv->filter);
1329 /* Set cursor on the first contact. If it is already set on a group,
1330 * set it on its first child contact. Note that first child of a group
1331 * is its separator, that's why we actually set to the 2nd
1334 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1335 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1339 path = gtk_tree_path_new_from_string ("0:1");
1342 else if (gtk_tree_path_get_depth (path) < 2)
1346 gtk_tree_model_get_iter (model, &iter, path);
1347 gtk_tree_model_get (model, &iter,
1348 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1353 gtk_tree_path_down (path);
1354 gtk_tree_path_next (path);
1361 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1363 if (gtk_tree_model_get_iter (model, &iter, path))
1365 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1370 gtk_tree_path_free (path);
1374 individual_view_search_activate_cb (GtkWidget *search,
1375 EmpathyIndividualView *view)
1378 GtkTreeViewColumn *focus_column;
1380 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1383 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1384 gtk_tree_path_free (path);
1386 gtk_widget_hide (search);
1391 individual_view_search_key_navigation_cb (GtkWidget *search,
1393 EmpathyIndividualView *view)
1395 GdkEventKey *eventkey = ((GdkEventKey *) event);
1396 gboolean ret = FALSE;
1398 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1399 || eventkey->keyval == GDK_KEY_F2)
1401 GdkEvent *new_event;
1403 new_event = gdk_event_copy (event);
1404 gtk_widget_grab_focus (GTK_WIDGET (view));
1405 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1406 gtk_widget_grab_focus (search);
1408 gdk_event_free (new_event);
1415 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1416 EmpathyIndividualView *view)
1418 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1419 GtkTreeModel *model;
1420 GtkTreePath *cursor_path;
1422 gboolean valid = FALSE;
1424 /* block expand or collapse handlers, they would write the
1425 * expand or collapsed setting to file otherwise */
1426 g_signal_handlers_block_by_func (view,
1427 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1428 g_signal_handlers_block_by_func (view,
1429 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1431 /* restore which groups are expanded and which are not */
1432 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1433 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1434 valid; valid = gtk_tree_model_iter_next (model, &iter))
1440 gtk_tree_model_get (model, &iter,
1441 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1442 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1451 path = gtk_tree_model_get_path (model, &iter);
1452 if ((priv->view_features &
1453 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1454 empathy_contact_group_get_expanded (name))
1456 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1460 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1463 gtk_tree_path_free (path);
1467 /* unblock expand or collapse handlers */
1468 g_signal_handlers_unblock_by_func (view,
1469 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1470 g_signal_handlers_unblock_by_func (view,
1471 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1473 /* keep the selected contact visible */
1474 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1476 if (cursor_path != NULL)
1477 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1480 gtk_tree_path_free (cursor_path);
1484 individual_view_search_show_cb (EmpathyLiveSearch *search,
1485 EmpathyIndividualView *view)
1487 /* block expand or collapse handlers during expand all, they would
1488 * write the expand or collapsed setting to file otherwise */
1489 g_signal_handlers_block_by_func (view,
1490 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1492 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1494 g_signal_handlers_unblock_by_func (view,
1495 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1499 expand_idle_foreach_cb (GtkTreeModel *model,
1502 EmpathyIndividualView *self)
1504 EmpathyIndividualViewPriv *priv;
1506 gpointer should_expand;
1509 /* We only want groups */
1510 if (gtk_tree_path_get_depth (path) > 1)
1513 gtk_tree_model_get (model, iter,
1514 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1515 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1518 if (is_group == FALSE)
1524 priv = GET_PRIV (self);
1526 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1527 &should_expand) == TRUE)
1529 if (GPOINTER_TO_INT (should_expand) == TRUE)
1530 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1532 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1534 g_hash_table_remove (priv->expand_groups, name);
1543 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1545 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1547 DEBUG ("individual_view_expand_idle_cb");
1549 g_signal_handlers_block_by_func (self,
1550 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1551 g_signal_handlers_block_by_func (self,
1552 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1554 /* The store/filter could've been removed while we were in the idle queue */
1555 if (priv->filter != NULL)
1557 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1558 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1561 g_signal_handlers_unblock_by_func (self,
1562 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1563 g_signal_handlers_unblock_by_func (self,
1564 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1566 /* Empty the table of groups to expand/contract, since it may contain groups
1567 * which no longer exist in the tree view. This can happen after going
1568 * offline, for example. */
1569 g_hash_table_remove_all (priv->expand_groups);
1570 priv->expand_groups_idle_handler = 0;
1571 g_object_unref (self);
1577 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1580 EmpathyIndividualView *view)
1582 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1583 gboolean should_expand, is_group = FALSE;
1585 gpointer will_expand;
1587 gtk_tree_model_get (model, iter,
1588 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1589 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1592 if (!is_group || EMP_STR_EMPTY (name))
1598 should_expand = (priv->view_features &
1599 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1600 (priv->search_widget != NULL &&
1601 gtk_widget_get_visible (priv->search_widget)) ||
1602 empathy_contact_group_get_expanded (name);
1604 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1605 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1606 * a hash table, and expand or contract them as appropriate all at once in
1607 * an idle handler which iterates over all the group rows. */
1608 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1609 &will_expand) == FALSE ||
1610 GPOINTER_TO_INT (will_expand) != should_expand)
1612 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1613 GINT_TO_POINTER (should_expand));
1615 if (priv->expand_groups_idle_handler == 0)
1617 priv->expand_groups_idle_handler =
1618 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1619 g_object_ref (view));
1626 /* FIXME: This is a workaround for bgo#621076 */
1628 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1631 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1632 GtkTreeModel *model;
1633 GtkTreePath *parent_path;
1634 GtkTreeIter parent_iter;
1636 if (gtk_tree_path_get_depth (path) < 2)
1639 /* A group row is visible if and only if at least one if its child is visible.
1640 * So when a row is inserted/deleted/changed in the base model, that could
1641 * modify the visibility of its parent in the filter model.
1644 model = GTK_TREE_MODEL (priv->store);
1645 parent_path = gtk_tree_path_copy (path);
1646 gtk_tree_path_up (parent_path);
1647 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1649 /* This tells the filter to verify the visibility of that row, and
1650 * show/hide it if necessary */
1651 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1652 parent_path, &parent_iter);
1654 gtk_tree_path_free (parent_path);
1658 individual_view_store_row_changed_cb (GtkTreeModel *model,
1661 EmpathyIndividualView *view)
1663 individual_view_verify_group_visibility (view, path);
1667 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1669 EmpathyIndividualView *view)
1671 individual_view_verify_group_visibility (view, path);
1675 individual_view_is_visible_individual (EmpathyIndividualView *self,
1676 FolksIndividual *individual,
1678 gboolean is_searching)
1680 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1681 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1683 GList *personas, *l;
1685 /* We're only giving the visibility wrt filtering here, not things like
1687 if (priv->show_untrusted == FALSE &&
1688 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1693 if (is_searching == FALSE)
1694 return (priv->show_offline || is_online);
1696 /* check alias name */
1697 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1699 if (empathy_live_search_match (live, str))
1702 /* check contact id, remove the @server.com part */
1703 personas = folks_individual_get_personas (individual);
1704 for (l = personas; l; l = l->next)
1707 gchar *dup_str = NULL;
1710 if (!TPF_IS_PERSONA (l->data))
1713 str = folks_persona_get_display_id (l->data);
1714 p = strstr (str, "@");
1716 str = dup_str = g_strndup (str, p - str);
1718 visible = empathy_live_search_match (live, str);
1724 /* FIXME: Add more rules here, we could check phone numbers in
1725 * contact's vCard for example. */
1731 individual_view_filter_visible_func (GtkTreeModel *model,
1735 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1736 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1737 FolksIndividual *individual = NULL;
1738 gboolean is_group, is_separator, valid;
1739 GtkTreeIter child_iter;
1740 gboolean visible, is_online;
1741 gboolean is_searching = TRUE;
1743 if (priv->search_widget == NULL ||
1744 !gtk_widget_get_visible (priv->search_widget))
1745 is_searching = FALSE;
1747 gtk_tree_model_get (model, iter,
1748 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1749 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1750 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1751 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1754 if (individual != NULL)
1756 visible = individual_view_is_visible_individual (self, individual,
1757 is_online, is_searching);
1759 g_object_unref (individual);
1761 /* FIXME: Work around bgo#626552/bgo#621076 */
1762 if (visible == TRUE)
1764 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1765 individual_view_verify_group_visibility (self, path);
1766 gtk_tree_path_free (path);
1775 /* Not a contact, not a separator, must be a group */
1776 g_return_val_if_fail (is_group, FALSE);
1778 /* only show groups which are not empty */
1779 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1780 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1782 gtk_tree_model_get (model, &child_iter,
1783 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1784 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1787 if (individual == NULL)
1790 visible = individual_view_is_visible_individual (self, individual,
1791 is_online, is_searching);
1792 g_object_unref (individual);
1794 /* show group if it has at least one visible contact in it */
1795 if (visible == TRUE)
1803 individual_view_constructed (GObject *object)
1805 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1806 GtkCellRenderer *cell;
1807 GtkTreeViewColumn *col;
1812 "headers-visible", FALSE,
1813 "show-expanders", FALSE,
1816 col = gtk_tree_view_column_new ();
1819 cell = gtk_cell_renderer_pixbuf_new ();
1820 gtk_tree_view_column_pack_start (col, cell, FALSE);
1821 gtk_tree_view_column_set_cell_data_func (col, cell,
1822 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1832 cell = gtk_cell_renderer_pixbuf_new ();
1833 gtk_tree_view_column_pack_start (col, cell, FALSE);
1834 gtk_tree_view_column_set_cell_data_func (col, cell,
1835 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1847 cell = empathy_cell_renderer_text_new ();
1848 gtk_tree_view_column_pack_start (col, cell, TRUE);
1849 gtk_tree_view_column_set_cell_data_func (col, cell,
1850 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1852 gtk_tree_view_column_add_attribute (col, cell,
1853 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1854 gtk_tree_view_column_add_attribute (col, cell,
1855 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1856 gtk_tree_view_column_add_attribute (col, cell,
1857 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1858 gtk_tree_view_column_add_attribute (col, cell,
1859 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1860 gtk_tree_view_column_add_attribute (col, cell,
1861 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1862 gtk_tree_view_column_add_attribute (col, cell,
1863 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1864 gtk_tree_view_column_add_attribute (col, cell,
1865 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1867 /* Audio Call Icon */
1868 cell = empathy_cell_renderer_activatable_new ();
1869 gtk_tree_view_column_pack_start (col, cell, FALSE);
1870 gtk_tree_view_column_set_cell_data_func (col, cell,
1871 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1874 g_object_set (cell, "visible", FALSE, NULL);
1876 g_signal_connect (cell, "path-activated",
1877 G_CALLBACK (individual_view_call_activated_cb), view);
1880 cell = gtk_cell_renderer_pixbuf_new ();
1881 gtk_tree_view_column_pack_start (col, cell, FALSE);
1882 gtk_tree_view_column_set_cell_data_func (col, cell,
1883 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1895 cell = empathy_cell_renderer_expander_new ();
1896 gtk_tree_view_column_pack_end (col, cell, FALSE);
1897 gtk_tree_view_column_set_cell_data_func (col, cell,
1898 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1901 /* Actually add the column now we have added all cell renderers */
1902 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1905 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1907 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1910 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1912 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1918 individual_view_set_view_features (EmpathyIndividualView *view,
1919 EmpathyIndividualFeatureFlags features)
1921 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1922 gboolean has_tooltip;
1924 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1926 priv->view_features = features;
1928 /* Setting reorderable is a hack that gets us row previews as drag icons
1929 for free. We override all the drag handlers. It's tricky to get the
1930 position of the drag icon right in drag_begin. GtkTreeView has special
1931 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1934 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1935 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1937 /* Update DnD source/dest */
1938 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1940 gtk_drag_source_set (GTK_WIDGET (view),
1943 G_N_ELEMENTS (drag_types_source),
1944 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1948 gtk_drag_source_unset (GTK_WIDGET (view));
1952 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1954 gtk_drag_dest_set (GTK_WIDGET (view),
1955 GTK_DEST_DEFAULT_ALL,
1957 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1961 /* FIXME: URI could still be droped depending on FT feature */
1962 gtk_drag_dest_unset (GTK_WIDGET (view));
1965 /* Update has-tooltip */
1967 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1968 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1972 individual_view_dispose (GObject *object)
1974 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1975 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1977 tp_clear_object (&priv->store);
1978 tp_clear_object (&priv->filter);
1979 tp_clear_object (&priv->tooltip_widget);
1981 empathy_individual_view_set_live_search (view, NULL);
1983 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1987 individual_view_finalize (GObject *object)
1989 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1991 if (priv->expand_groups_idle_handler != 0)
1992 g_source_remove (priv->expand_groups_idle_handler);
1993 g_hash_table_destroy (priv->expand_groups);
1995 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1999 individual_view_get_property (GObject *object,
2004 EmpathyIndividualViewPriv *priv;
2006 priv = GET_PRIV (object);
2011 g_value_set_object (value, priv->store);
2013 case PROP_VIEW_FEATURES:
2014 g_value_set_flags (value, priv->view_features);
2016 case PROP_INDIVIDUAL_FEATURES:
2017 g_value_set_flags (value, priv->individual_features);
2019 case PROP_SHOW_OFFLINE:
2020 g_value_set_boolean (value, priv->show_offline);
2022 case PROP_SHOW_UNTRUSTED:
2023 g_value_set_boolean (value, priv->show_untrusted);
2026 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2032 individual_view_set_property (GObject *object,
2034 const GValue *value,
2037 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2038 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2043 empathy_individual_view_set_store (view, g_value_get_object (value));
2045 case PROP_VIEW_FEATURES:
2046 individual_view_set_view_features (view, g_value_get_flags (value));
2048 case PROP_INDIVIDUAL_FEATURES:
2049 priv->individual_features = g_value_get_flags (value);
2051 case PROP_SHOW_OFFLINE:
2052 empathy_individual_view_set_show_offline (view,
2053 g_value_get_boolean (value));
2055 case PROP_SHOW_UNTRUSTED:
2056 empathy_individual_view_set_show_untrusted (view,
2057 g_value_get_boolean (value));
2060 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2066 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2068 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2069 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2070 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2072 object_class->constructed = individual_view_constructed;
2073 object_class->dispose = individual_view_dispose;
2074 object_class->finalize = individual_view_finalize;
2075 object_class->get_property = individual_view_get_property;
2076 object_class->set_property = individual_view_set_property;
2078 widget_class->drag_data_received = individual_view_drag_data_received;
2079 widget_class->drag_drop = individual_view_drag_drop;
2080 widget_class->drag_begin = individual_view_drag_begin;
2081 widget_class->drag_data_get = individual_view_drag_data_get;
2082 widget_class->drag_end = individual_view_drag_end;
2083 widget_class->drag_motion = individual_view_drag_motion;
2085 /* We use the class method to let user of this widget to connect to
2086 * the signal and stop emission of the signal so the default handler
2087 * won't be called. */
2088 tree_view_class->row_activated = individual_view_row_activated;
2090 klass->drag_individual_received = real_drag_individual_received_cb;
2092 signals[DRAG_INDIVIDUAL_RECEIVED] =
2093 g_signal_new ("drag-individual-received",
2094 G_OBJECT_CLASS_TYPE (klass),
2096 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2098 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2099 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2100 G_TYPE_STRING, G_TYPE_STRING);
2102 signals[DRAG_PERSONA_RECEIVED] =
2103 g_signal_new ("drag-persona-received",
2104 G_OBJECT_CLASS_TYPE (klass),
2106 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2108 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2109 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2111 g_object_class_install_property (object_class,
2113 g_param_spec_object ("store",
2114 "The store of the view",
2115 "The store of the view",
2116 EMPATHY_TYPE_INDIVIDUAL_STORE,
2117 G_PARAM_READWRITE));
2118 g_object_class_install_property (object_class,
2120 g_param_spec_flags ("view-features",
2121 "Features of the view",
2122 "Flags for all enabled features",
2123 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2124 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2125 g_object_class_install_property (object_class,
2126 PROP_INDIVIDUAL_FEATURES,
2127 g_param_spec_flags ("individual-features",
2128 "Features of the individual menu",
2129 "Flags for all enabled features for the menu",
2130 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2131 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2132 g_object_class_install_property (object_class,
2134 g_param_spec_boolean ("show-offline",
2136 "Whether contact list should display "
2137 "offline contacts", FALSE, G_PARAM_READWRITE));
2138 g_object_class_install_property (object_class,
2139 PROP_SHOW_UNTRUSTED,
2140 g_param_spec_boolean ("show-untrusted",
2141 "Show Untrusted Individuals",
2142 "Whether the view should display untrusted individuals; "
2143 "those who could not be who they say they are.",
2144 TRUE, G_PARAM_READWRITE));
2146 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2150 empathy_individual_view_init (EmpathyIndividualView *view)
2152 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2153 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2157 priv->show_untrusted = TRUE;
2159 /* Get saved group states. */
2160 empathy_contact_groups_get_all ();
2162 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2163 (GDestroyNotify) g_free, NULL);
2165 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2166 empathy_individual_store_row_separator_func, NULL, NULL);
2168 /* Connect to tree view signals rather than override. */
2169 g_signal_connect (view, "button-press-event",
2170 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2171 g_signal_connect (view, "key-press-event",
2172 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2173 g_signal_connect (view, "row-expanded",
2174 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2175 GINT_TO_POINTER (TRUE));
2176 g_signal_connect (view, "row-collapsed",
2177 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2178 GINT_TO_POINTER (FALSE));
2179 g_signal_connect (view, "query-tooltip",
2180 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2183 EmpathyIndividualView *
2184 empathy_individual_view_new (EmpathyIndividualStore *store,
2185 EmpathyIndividualViewFeatureFlags view_features,
2186 EmpathyIndividualFeatureFlags individual_features)
2188 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2190 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2192 "individual-features", individual_features,
2193 "view-features", view_features, NULL);
2197 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2199 EmpathyIndividualViewPriv *priv;
2200 GtkTreeSelection *selection;
2202 GtkTreeModel *model;
2203 FolksIndividual *individual;
2205 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2207 priv = GET_PRIV (view);
2209 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2210 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2213 gtk_tree_model_get (model, &iter,
2214 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2220 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2221 gboolean *is_fake_group)
2223 EmpathyIndividualViewPriv *priv;
2224 GtkTreeSelection *selection;
2226 GtkTreeModel *model;
2231 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2233 priv = GET_PRIV (view);
2235 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2236 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2239 gtk_tree_model_get (model, &iter,
2240 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2241 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2242 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2250 if (is_fake_group != NULL)
2251 *is_fake_group = fake;
2257 individual_view_remove_dialog_show (GtkWindow *parent,
2258 const gchar *message,
2259 const gchar *secondary_text)
2264 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2265 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2266 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2267 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2268 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2269 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2270 "%s", secondary_text);
2272 gtk_widget_show (dialog);
2274 res = gtk_dialog_run (GTK_DIALOG (dialog));
2275 gtk_widget_destroy (dialog);
2277 return (res == GTK_RESPONSE_YES);
2281 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2282 EmpathyIndividualView *view)
2286 group = empathy_individual_view_dup_selected_group (view, NULL);
2293 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2295 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2296 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2299 EmpathyIndividualManager *manager =
2300 empathy_individual_manager_dup_singleton ();
2301 empathy_individual_manager_remove_group (manager, group);
2302 g_object_unref (G_OBJECT (manager));
2312 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2314 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2319 gboolean is_fake_group;
2321 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2323 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2324 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2327 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2328 if (!group || is_fake_group)
2330 /* We can't alter fake groups */
2335 menu = gtk_menu_new ();
2338 if (priv->view_features &
2339 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2340 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2341 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2342 gtk_widget_show (item);
2343 g_signal_connect (item, "activate",
2344 G_CALLBACK (individual_view_group_rename_activate_cb),
2349 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2351 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2352 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2353 GTK_ICON_SIZE_MENU);
2354 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2355 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2356 gtk_widget_show (item);
2357 g_signal_connect (item, "activate",
2358 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2367 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2368 EmpathyIndividualView *view)
2370 FolksIndividual *individual;
2372 individual = empathy_individual_view_dup_selected (view);
2374 if (individual != NULL)
2378 GList *l, *personas;
2379 guint persona_count = 0;
2381 personas = folks_individual_get_personas (individual);
2383 /* If we have more than one TpfPersona, display a different message
2384 * ensuring the user knows that *all* of the meta-contacts' personas will
2386 for (l = personas; l != NULL; l = l->next)
2388 if (!TPF_IS_PERSONA (l->data))
2392 if (persona_count >= 2)
2396 if (persona_count < 2)
2398 /* Not a meta-contact */
2401 _("Do you really want to remove the contact '%s'?"),
2402 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2409 _("Do you really want to remove the linked contact '%s'? "
2410 "Note that this will remove all the contacts which make up "
2411 "this linked contact."),
2412 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2415 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2417 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2420 EmpathyIndividualManager *manager;
2422 manager = empathy_individual_manager_dup_singleton ();
2423 empathy_individual_manager_remove (manager, individual, "");
2424 g_object_unref (G_OBJECT (manager));
2428 g_object_unref (individual);
2433 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2434 EmpathyLinkingDialog *linking_dialog,
2435 EmpathyIndividualView *self)
2437 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2438 EmpathyIndividualLinker *linker;
2440 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2441 empathy_individual_linker_set_search_text (linker,
2442 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2446 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2448 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2449 FolksIndividual *individual;
2450 GtkWidget *menu = NULL;
2453 gboolean can_remove = FALSE;
2456 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2458 individual = empathy_individual_view_dup_selected (view);
2459 if (individual == NULL)
2462 /* If any of the Individual's personas can be removed, add an option to
2463 * remove. This will act as a best-effort option. If any Personas cannot be
2464 * removed from the server, then this option will just be inactive upon
2465 * subsequent menu openings */
2466 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2468 FolksPersona *persona = FOLKS_PERSONA (l->data);
2469 FolksPersonaStore *store = folks_persona_get_store (persona);
2470 FolksMaybeBool maybe_can_remove =
2471 folks_persona_store_get_can_remove_personas (store);
2473 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2480 menu = empathy_individual_menu_new (individual, priv->individual_features);
2482 /* Remove contact */
2483 if ((priv->view_features &
2484 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2487 /* create the menu if required, or just add a separator */
2489 menu = gtk_menu_new ();
2492 item = gtk_separator_menu_item_new ();
2493 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2494 gtk_widget_show (item);
2498 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2499 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2500 GTK_ICON_SIZE_MENU);
2501 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2502 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2503 gtk_widget_show (item);
2504 g_signal_connect (item, "activate",
2505 G_CALLBACK (individual_view_remove_activate_cb), view);
2508 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2509 * set the live search text on the new linking dialogue to be the same as
2511 g_signal_connect (menu, "link-contacts-activated",
2512 (GCallback) individual_menu_link_contacts_activated_cb, view);
2514 g_object_unref (individual);
2520 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2521 EmpathyLiveSearch *search)
2523 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2525 /* remove old handlers if old search was not null */
2526 if (priv->search_widget != NULL)
2528 g_signal_handlers_disconnect_by_func (view,
2529 individual_view_start_search_cb, NULL);
2531 g_signal_handlers_disconnect_by_func (priv->search_widget,
2532 individual_view_search_text_notify_cb, view);
2533 g_signal_handlers_disconnect_by_func (priv->search_widget,
2534 individual_view_search_activate_cb, view);
2535 g_signal_handlers_disconnect_by_func (priv->search_widget,
2536 individual_view_search_key_navigation_cb, view);
2537 g_signal_handlers_disconnect_by_func (priv->search_widget,
2538 individual_view_search_hide_cb, view);
2539 g_signal_handlers_disconnect_by_func (priv->search_widget,
2540 individual_view_search_show_cb, view);
2541 g_object_unref (priv->search_widget);
2542 priv->search_widget = NULL;
2545 /* connect handlers if new search is not null */
2548 priv->search_widget = g_object_ref (search);
2550 g_signal_connect (view, "start-interactive-search",
2551 G_CALLBACK (individual_view_start_search_cb), NULL);
2553 g_signal_connect (priv->search_widget, "notify::text",
2554 G_CALLBACK (individual_view_search_text_notify_cb), view);
2555 g_signal_connect (priv->search_widget, "activate",
2556 G_CALLBACK (individual_view_search_activate_cb), view);
2557 g_signal_connect (priv->search_widget, "key-navigation",
2558 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2559 g_signal_connect (priv->search_widget, "hide",
2560 G_CALLBACK (individual_view_search_hide_cb), view);
2561 g_signal_connect (priv->search_widget, "show",
2562 G_CALLBACK (individual_view_search_show_cb), view);
2567 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2569 EmpathyIndividualViewPriv *priv;
2571 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2573 priv = GET_PRIV (self);
2575 return (priv->search_widget != NULL &&
2576 gtk_widget_get_visible (priv->search_widget));
2580 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2582 EmpathyIndividualViewPriv *priv;
2584 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2586 priv = GET_PRIV (self);
2588 return priv->show_offline;
2592 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2593 gboolean show_offline)
2595 EmpathyIndividualViewPriv *priv;
2597 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2599 priv = GET_PRIV (self);
2601 priv->show_offline = show_offline;
2603 g_object_notify (G_OBJECT (self), "show-offline");
2604 gtk_tree_model_filter_refilter (priv->filter);
2608 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2610 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2612 return GET_PRIV (self)->show_untrusted;
2616 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2617 gboolean show_untrusted)
2619 EmpathyIndividualViewPriv *priv;
2621 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2623 priv = GET_PRIV (self);
2625 priv->show_untrusted = show_untrusted;
2627 g_object_notify (G_OBJECT (self), "show-untrusted");
2628 gtk_tree_model_filter_refilter (priv->filter);
2631 EmpathyIndividualStore *
2632 empathy_individual_view_get_store (EmpathyIndividualView *self)
2634 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2636 return GET_PRIV (self)->store;
2640 empathy_individual_view_set_store (EmpathyIndividualView *self,
2641 EmpathyIndividualStore *store)
2643 EmpathyIndividualViewPriv *priv;
2645 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2646 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2648 priv = GET_PRIV (self);
2650 /* Destroy the old filter and remove the old store */
2651 if (priv->store != NULL)
2653 g_signal_handlers_disconnect_by_func (priv->store,
2654 individual_view_store_row_changed_cb, self);
2655 g_signal_handlers_disconnect_by_func (priv->store,
2656 individual_view_store_row_deleted_cb, self);
2658 g_signal_handlers_disconnect_by_func (priv->filter,
2659 individual_view_row_has_child_toggled_cb, self);
2661 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2664 tp_clear_object (&priv->filter);
2665 tp_clear_object (&priv->store);
2667 /* Set the new store */
2668 priv->store = store;
2672 g_object_ref (store);
2674 /* Create a new filter */
2675 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2676 GTK_TREE_MODEL (priv->store), NULL));
2677 gtk_tree_model_filter_set_visible_func (priv->filter,
2678 individual_view_filter_visible_func, self, NULL);
2680 g_signal_connect (priv->filter, "row-has-child-toggled",
2681 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2682 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2683 GTK_TREE_MODEL (priv->filter));
2685 tp_g_signal_connect_object (priv->store, "row-changed",
2686 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2687 tp_g_signal_connect_object (priv->store, "row-inserted",
2688 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2689 tp_g_signal_connect_object (priv->store, "row-deleted",
2690 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2695 empathy_individual_view_start_search (EmpathyIndividualView *self)
2697 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2699 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2700 g_return_if_fail (priv->search_widget != NULL);
2702 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2703 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2705 gtk_widget_show (GTK_WIDGET (priv->search_widget));