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 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
892 g_signal_handlers_disconnect_by_func (menushell,
893 menu_deactivate_cb, user_data);
895 gtk_menu_detach (GTK_MENU (menushell));
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;
1684 gboolean is_favorite;
1686 /* We're only giving the visibility wrt filtering here, not things like
1688 if (priv->show_untrusted == FALSE &&
1689 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1694 is_favorite = folks_favouritable_get_is_favourite (
1695 FOLKS_FAVOURITABLE (individual));
1696 if (is_searching == FALSE)
1697 return (priv->show_offline || is_online || is_favorite);
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));