1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-contact-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
91 } EmpathyIndividualViewPriv;
95 EmpathyIndividualView *view;
102 EmpathyIndividualView *view;
103 FolksIndividual *individual;
112 PROP_INDIVIDUAL_FEATURES,
117 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
118 * specific EmpathyContacts (between/in/out of Individuals) */
121 DND_DRAG_TYPE_INDIVIDUAL_ID,
122 DND_DRAG_TYPE_PERSONA_ID,
123 DND_DRAG_TYPE_URI_LIST,
124 DND_DRAG_TYPE_STRING,
127 #define DRAG_TYPE(T,I) \
128 { (gchar *) T, 0, I }
130 static const GtkTargetEntry drag_types_dest[] = {
131 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
132 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
133 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
135 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
136 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
139 static const GtkTargetEntry drag_types_source[] = {
140 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
145 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
146 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
150 DRAG_INDIVIDUAL_RECEIVED,
151 DRAG_PERSONA_RECEIVED,
155 static guint signals[LAST_SIGNAL];
157 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
161 individual_view_tooltip_destroy_cb (GtkWidget *widget,
162 EmpathyIndividualView *view)
164 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
166 tp_clear_object (&priv->tooltip_widget);
170 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
173 gboolean keyboard_mode,
177 EmpathyIndividualViewPriv *priv;
178 FolksIndividual *individual;
182 static gint running = 0;
183 gboolean ret = FALSE;
185 priv = GET_PRIV (view);
187 /* Avoid an infinite loop. See GNOME bug #574377 */
193 /* Don't show the tooltip if there's already a popup menu */
194 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
197 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
198 keyboard_mode, &model, &path, &iter))
201 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
202 gtk_tree_path_free (path);
204 gtk_tree_model_get (model, &iter,
205 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
207 if (individual == NULL)
210 if (priv->tooltip_widget == NULL)
212 priv->tooltip_widget = empathy_individual_widget_new (individual,
213 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
214 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
215 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
216 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
217 g_object_ref (priv->tooltip_widget);
218 g_signal_connect (priv->tooltip_widget, "destroy",
219 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
220 gtk_widget_show (priv->tooltip_widget);
224 empathy_individual_widget_set_individual (
225 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
228 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
231 g_object_unref (individual);
239 groups_change_group_cb (GObject *source,
240 GAsyncResult *result,
243 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
244 GError *error = NULL;
246 folks_groupable_change_group_finish (groupable, result, &error);
249 g_warning ("failed to change group: %s", error->message);
250 g_clear_error (&error);
255 group_can_be_modified (const gchar *name,
256 gboolean is_fake_group,
259 /* Real groups can always be modified */
263 /* The favorite fake group can be modified so users can
264 * add/remove favorites using DnD */
265 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
268 /* We can remove contacts from the 'ungrouped' fake group */
269 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
276 individual_view_individual_drag_received (GtkWidget *self,
277 GdkDragContext *context,
280 GtkSelectionData *selection)
282 EmpathyIndividualViewPriv *priv;
283 EmpathyIndividualManager *manager = NULL;
284 FolksIndividual *individual;
285 GtkTreePath *source_path;
286 const gchar *sel_data;
287 gchar *new_group = NULL;
288 gchar *old_group = NULL;
289 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
291 priv = GET_PRIV (self);
293 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
294 new_group = empathy_individual_store_get_parent_group (model, path,
295 NULL, &new_group_is_fake);
297 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
300 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
301 * feature. Otherwise, we just add the dropped contact to whichever group
302 * they were dropped in, and don't remove them from their old group. This
303 * allows for Individual views which shouldn't allow Individuals to have
304 * their groups changed, and also for dragging Individuals between Individual
306 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
307 priv->drag_row != NULL)
309 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
313 empathy_individual_store_get_parent_group (model, source_path,
314 NULL, &old_group_is_fake);
315 gtk_tree_path_free (source_path);
318 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
321 if (!tp_strdiff (old_group, new_group))
324 else if (priv->drag_row != NULL)
326 /* We don't allow changing Individuals' groups, and this Individual was
327 * dragged from another group in *this* Individual view, so we disallow
332 /* XXX: for contacts, we used to ensure the account, create the contact
333 * factory, and then wait on the contacts. But they should already be
334 * created by this point */
336 manager = empathy_individual_manager_dup_singleton ();
337 individual = empathy_individual_manager_lookup_member (manager, sel_data);
339 if (individual == NULL)
341 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
345 /* FIXME: We should probably wait for the cb before calling
348 /* Emit a signal notifying of the drag. We change the Individual's groups in
349 * the default signal handler. */
350 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
351 gdk_drag_context_get_selected_action (context), individual, new_group,
357 tp_clear_object (&manager);
365 real_drag_individual_received_cb (EmpathyIndividualView *self,
366 GdkDragAction action,
367 FolksIndividual *individual,
368 const gchar *new_group,
369 const gchar *old_group)
371 DEBUG ("individual %s dragged from '%s' to '%s'",
372 folks_individual_get_id (individual), old_group, new_group);
374 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
376 /* Mark contact as favourite */
377 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
381 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
383 /* Remove contact as favourite */
384 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
386 /* Don't try to remove it */
390 if (new_group != NULL)
392 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
393 groups_change_group_cb, NULL);
396 if (old_group != NULL && action == GDK_ACTION_MOVE)
398 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
399 FALSE, groups_change_group_cb, NULL);
404 individual_view_persona_drag_received (GtkWidget *self,
405 GdkDragContext *context,
408 GtkSelectionData *selection)
410 EmpathyIndividualViewPriv *priv;
411 EmpathyIndividualManager *manager = NULL;
412 FolksIndividual *individual = NULL;
413 FolksPersona *persona = NULL;
414 const gchar *persona_uid;
415 GList *individuals, *l;
416 gboolean retval = FALSE;
418 priv = GET_PRIV (self);
420 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
422 /* FIXME: This is slow, but the only way to find the Persona we're having
424 manager = empathy_individual_manager_dup_singleton ();
425 individuals = empathy_individual_manager_get_members (manager);
427 for (l = individuals; l != NULL; l = l->next)
431 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
433 for (p = personas; p != NULL; p = p->next)
435 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
438 persona = g_object_ref (p->data);
439 individual = g_object_ref (l->data);
446 g_list_free (individuals);
448 if (persona == NULL || individual == NULL)
450 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
454 /* Emit a signal notifying of the drag. We change the Individual's groups in
455 * the default signal handler. */
456 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
457 gdk_drag_context_get_selected_action (context), persona, individual,
461 tp_clear_object (&manager);
462 tp_clear_object (&persona);
463 tp_clear_object (&individual);
469 individual_view_file_drag_received (GtkWidget *view,
470 GdkDragContext *context,
473 GtkSelectionData *selection)
476 const gchar *sel_data;
477 FolksIndividual *individual;
478 EmpathyContact *contact;
480 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
482 gtk_tree_model_get_iter (model, &iter, path);
483 gtk_tree_model_get (model, &iter,
484 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
485 if (individual == NULL)
488 contact = empathy_contact_dup_from_folks_individual (individual);
489 empathy_send_file_from_uri_list (contact, sel_data);
491 g_object_unref (individual);
492 tp_clear_object (&contact);
498 individual_view_drag_data_received (GtkWidget *view,
499 GdkDragContext *context,
502 GtkSelectionData *selection,
508 GtkTreeViewDropPosition position;
510 gboolean success = TRUE;
512 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
514 /* Get destination group information. */
515 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
516 x, y, &path, &position);
521 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
523 success = individual_view_individual_drag_received (view,
524 context, model, path, selection);
526 else if (info == DND_DRAG_TYPE_PERSONA_ID)
528 success = individual_view_persona_drag_received (view, context, model,
531 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
533 success = individual_view_file_drag_received (view,
534 context, model, path, selection);
537 gtk_tree_path_free (path);
538 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
542 individual_view_drag_motion_cb (DragMotionData *data)
544 if (data->view != NULL)
546 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
547 g_object_remove_weak_pointer (G_OBJECT (data->view),
548 (gpointer *) &data->view);
551 data->timeout_id = 0;
556 /* Minimum distance between the mouse pointer and a horizontal border when we
557 start auto scrolling. */
558 #define AUTO_SCROLL_MARGIN_SIZE 20
559 /* How far to scroll per one tick. */
560 #define AUTO_SCROLL_PITCH 10
563 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
565 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
569 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
571 if (priv->distance < 0)
572 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
574 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
576 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
577 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
579 gtk_adjustment_set_value (adj, new_value);
585 individual_view_drag_motion (GtkWidget *widget,
586 GdkDragContext *context,
591 EmpathyIndividualViewPriv *priv;
595 static DragMotionData *dm = NULL;
598 gboolean is_different = FALSE;
599 gboolean cleanup = TRUE;
600 gboolean retval = TRUE;
601 GtkAllocation allocation;
603 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
604 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
607 if (priv->auto_scroll_timeout_id != 0)
609 g_source_remove (priv->auto_scroll_timeout_id);
610 priv->auto_scroll_timeout_id = 0;
613 gtk_widget_get_allocation (widget, &allocation);
615 if (y < AUTO_SCROLL_MARGIN_SIZE ||
616 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
618 if (y < AUTO_SCROLL_MARGIN_SIZE)
619 priv->distance = MIN (-y, -1);
621 priv->distance = MAX (allocation.height - y, 1);
623 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
624 (GSourceFunc) individual_view_auto_scroll_cb, widget);
627 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
628 x, y, &path, NULL, NULL, NULL);
630 cleanup &= (dm == NULL);
634 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
635 is_different = ((dm == NULL) || ((dm != NULL)
636 && gtk_tree_path_compare (dm->path, path) != 0));
643 /* Coordinates don't point to an actual row, so make sure the pointer
644 and highlighting don't indicate that a drag is possible.
646 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
647 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
650 target = gtk_drag_dest_find_target (widget, context, NULL);
651 gtk_tree_model_get_iter (model, &iter, path);
653 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
654 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
656 /* This is a file drag, and it can only be dropped on contacts,
658 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
659 * even if we have a valid target. */
660 FolksIndividual *individual = NULL;
661 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
663 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
665 gtk_tree_model_get (model, &iter,
666 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
670 if (individual != NULL)
672 EmpathyContact *contact = NULL;
674 contact = empathy_contact_dup_from_folks_individual (individual);
675 caps = empathy_contact_get_capabilities (contact);
677 tp_clear_object (&contact);
680 if (individual != NULL &&
681 folks_presence_owner_is_online (FOLKS_PRESENCE_OWNER (individual)) &&
682 (caps & EMPATHY_CAPABILITIES_FT))
684 gdk_drag_status (context, GDK_ACTION_COPY, time_);
685 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
686 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
690 gdk_drag_status (context, 0, time_);
691 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
695 if (individual != NULL)
696 g_object_unref (individual);
698 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
699 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
700 priv->drag_row == NULL)) ||
701 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
702 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
704 /* If target != GDK_NONE, then we have a contact (individual or persona)
705 drag. If we're pointing to a group, highlight it. Otherwise, if the
706 contact we're pointing to is in a group, highlight that. Otherwise,
707 set the drag position to before the first row for a drag into
708 the "non-group" at the top.
709 If it's an Individual:
710 We only highlight things if the contact is from a different
711 Individual view, or if this Individual view has
712 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
713 which don't have FEATURE_GROUPS_CHANGE, but do have
714 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
716 We only highlight things if we have FEATURE_PERSONA_DROP.
718 GtkTreeIter group_iter;
720 GtkTreePath *group_path;
721 gtk_tree_model_get (model, &iter,
722 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
729 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
730 gtk_tree_model_get (model, &group_iter,
731 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
735 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
736 group_path = gtk_tree_model_get_path (model, &group_iter);
737 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
738 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
739 gtk_tree_path_free (group_path);
743 group_path = gtk_tree_path_new_first ();
744 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
745 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
746 group_path, GTK_TREE_VIEW_DROP_BEFORE);
750 if (!is_different && !cleanup)
755 gtk_tree_path_free (dm->path);
758 g_source_remove (dm->timeout_id);
766 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
768 dm = g_new0 (DragMotionData, 1);
770 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
771 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
772 dm->path = gtk_tree_path_copy (path);
774 dm->timeout_id = g_timeout_add_seconds (1,
775 (GSourceFunc) individual_view_drag_motion_cb, dm);
782 individual_view_drag_begin (GtkWidget *widget,
783 GdkDragContext *context)
785 EmpathyIndividualViewPriv *priv;
786 GtkTreeSelection *selection;
791 priv = GET_PRIV (widget);
793 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
796 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
797 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
800 path = gtk_tree_model_get_path (model, &iter);
801 priv->drag_row = gtk_tree_row_reference_new (model, path);
802 gtk_tree_path_free (path);
806 individual_view_drag_data_get (GtkWidget *widget,
807 GdkDragContext *context,
808 GtkSelectionData *selection,
812 EmpathyIndividualViewPriv *priv;
813 GtkTreePath *src_path;
816 FolksIndividual *individual;
817 const gchar *individual_id;
819 priv = GET_PRIV (widget);
821 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
822 if (priv->drag_row == NULL)
825 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
826 if (src_path == NULL)
829 if (!gtk_tree_model_get_iter (model, &iter, src_path))
831 gtk_tree_path_free (src_path);
835 gtk_tree_path_free (src_path);
838 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
839 if (individual == NULL)
842 individual_id = folks_individual_get_id (individual);
844 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
846 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
847 (guchar *) individual_id, strlen (individual_id) + 1);
850 g_object_unref (individual);
854 individual_view_drag_end (GtkWidget *widget,
855 GdkDragContext *context)
857 EmpathyIndividualViewPriv *priv;
859 priv = GET_PRIV (widget);
861 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
866 gtk_tree_row_reference_free (priv->drag_row);
867 priv->drag_row = NULL;
872 individual_view_drag_drop (GtkWidget *widget,
873 GdkDragContext *drag_context,
883 EmpathyIndividualView *view;
889 menu_deactivate_cb (GtkMenuShell *menushell,
892 gtk_menu_detach (GTK_MENU (menushell));
894 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
895 g_signal_handlers_disconnect_by_func (menushell,
896 menu_deactivate_cb, user_data);
900 individual_view_popup_menu_idle_cb (gpointer user_data)
902 MenuPopupData *data = user_data;
905 menu = empathy_individual_view_get_individual_menu (data->view);
907 menu = empathy_individual_view_get_group_menu (data->view);
911 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
913 gtk_widget_show (menu);
914 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
917 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
918 * floating ref. We can either wait that the treeview releases its ref
919 * when it will be destroyed (when leaving Empathy) or explicitely
920 * detach the menu when it's not displayed any more.
921 * We go for the latter as we don't want to keep useless menus in memory
922 * during the whole lifetime of Empathy. */
923 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
927 g_slice_free (MenuPopupData, data);
933 individual_view_button_press_event_cb (EmpathyIndividualView *view,
934 GdkEventButton *event,
937 if (event->button == 3)
941 data = g_slice_new (MenuPopupData);
943 data->button = event->button;
944 data->time = event->time;
945 g_idle_add (individual_view_popup_menu_idle_cb, data);
952 individual_view_key_press_event_cb (EmpathyIndividualView *view,
956 if (event->keyval == GDK_KEY_Menu)
960 data = g_slice_new (MenuPopupData);
963 data->time = event->time;
964 g_idle_add (individual_view_popup_menu_idle_cb, data);
965 } else if (event->keyval == GDK_KEY_F2) {
966 FolksIndividual *individual;
967 EmpathyContact *contact;
969 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
971 individual = empathy_individual_view_dup_selected (view);
972 if (individual == NULL)
975 contact = empathy_contact_dup_from_folks_individual (individual);
976 if (contact == NULL) {
977 g_object_unref (individual);
980 empathy_contact_edit_dialog_show (contact, NULL);
982 g_object_unref (individual);
983 g_object_unref (contact);
990 individual_view_row_activated (GtkTreeView *view,
992 GtkTreeViewColumn *column)
994 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
995 FolksIndividual *individual;
996 EmpathyContact *contact;
1000 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1003 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1004 gtk_tree_model_get_iter (model, &iter, path);
1005 gtk_tree_model_get (model, &iter,
1006 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1008 if (individual == NULL)
1011 /* Determine which Persona to chat to, by choosing the most available one. */
1012 contact = empathy_contact_dup_best_for_action (individual,
1013 EMPATHY_ACTION_CHAT);
1015 if (contact != NULL)
1017 DEBUG ("Starting a chat");
1019 empathy_dispatcher_chat_with_contact (contact,
1020 gtk_get_current_event_time ());
1023 g_object_unref (individual);
1024 tp_clear_object (&contact);
1028 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1029 const gchar *path_string,
1030 EmpathyIndividualView *view)
1032 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1034 GtkTreeModel *model;
1036 FolksIndividual *individual;
1037 GdkEventButton *event;
1038 GtkMenuShell *shell;
1041 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1044 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1045 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1048 gtk_tree_model_get (model, &iter,
1049 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1050 if (individual == NULL)
1053 event = (GdkEventButton *) gtk_get_current_event ();
1055 menu = gtk_menu_new ();
1056 shell = GTK_MENU_SHELL (menu);
1059 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1060 gtk_menu_shell_append (shell, item);
1061 gtk_widget_show (item);
1064 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1065 gtk_menu_shell_append (shell, item);
1066 gtk_widget_show (item);
1068 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1069 gtk_widget_show (menu);
1070 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1071 event->button, event->time);
1072 g_object_ref_sink (menu);
1073 g_object_unref (menu);
1075 g_object_unref (individual);
1079 individual_view_cell_set_background (EmpathyIndividualView *view,
1080 GtkCellRenderer *cell,
1084 if (!is_group && is_active)
1086 GtkStyleContext *style;
1089 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1091 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1094 /* Here we take the current theme colour and add it to
1095 * the colour for white and average the two. This
1096 * gives a colour which is inline with the theme but
1099 empathy_make_color_whiter (&color);
1101 g_object_set (cell, "cell-background-rgba", &color, NULL);
1104 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1108 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1109 GtkCellRenderer *cell,
1110 GtkTreeModel *model,
1112 EmpathyIndividualView *view)
1118 gtk_tree_model_get (model, iter,
1119 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1120 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1121 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1124 "visible", !is_group,
1128 tp_clear_object (&pixbuf);
1130 individual_view_cell_set_background (view, cell, is_group, is_active);
1134 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1135 GtkCellRenderer *cell,
1136 GtkTreeModel *model,
1138 EmpathyIndividualView *view)
1140 GdkPixbuf *pixbuf = NULL;
1144 gtk_tree_model_get (model, iter,
1145 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1146 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1151 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1153 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1154 GTK_ICON_SIZE_MENU);
1156 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1158 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1159 GTK_ICON_SIZE_MENU);
1164 "visible", pixbuf != NULL,
1168 tp_clear_object (&pixbuf);
1174 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1175 GtkCellRenderer *cell,
1176 GtkTreeModel *model,
1178 EmpathyIndividualView *view)
1182 gboolean can_audio, can_video;
1184 gtk_tree_model_get (model, iter,
1185 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1186 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1187 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1188 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1191 "visible", !is_group && (can_audio || can_video),
1192 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1195 individual_view_cell_set_background (view, cell, is_group, is_active);
1199 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1200 GtkCellRenderer *cell,
1201 GtkTreeModel *model,
1203 EmpathyIndividualView *view)
1206 gboolean show_avatar;
1210 gtk_tree_model_get (model, iter,
1211 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1212 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1213 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1214 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1217 "visible", !is_group && show_avatar,
1221 tp_clear_object (&pixbuf);
1223 individual_view_cell_set_background (view, cell, is_group, is_active);
1227 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1228 GtkCellRenderer *cell,
1229 GtkTreeModel *model,
1231 EmpathyIndividualView *view)
1236 gtk_tree_model_get (model, iter,
1237 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1238 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1240 individual_view_cell_set_background (view, cell, is_group, is_active);
1244 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1245 GtkCellRenderer *cell,
1246 GtkTreeModel *model,
1248 EmpathyIndividualView *view)
1253 gtk_tree_model_get (model, iter,
1254 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1255 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1257 if (gtk_tree_model_iter_has_child (model, iter))
1260 gboolean row_expanded;
1262 path = gtk_tree_model_get_path (model, iter);
1264 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1265 (gtk_tree_view_column_get_tree_view (column)), path);
1266 gtk_tree_path_free (path);
1271 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1275 g_object_set (cell, "visible", FALSE, NULL);
1277 individual_view_cell_set_background (view, cell, is_group, is_active);
1281 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1286 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1287 GtkTreeModel *model;
1291 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1294 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1296 gtk_tree_model_get (model, iter,
1297 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1299 expanded = GPOINTER_TO_INT (user_data);
1300 empathy_contact_group_set_expanded (name, expanded);
1306 individual_view_start_search_cb (EmpathyIndividualView *view,
1309 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1311 if (priv->search_widget == NULL)
1314 empathy_individual_view_start_search (view);
1320 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1322 EmpathyIndividualView *view)
1324 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1326 GtkTreeViewColumn *focus_column;
1327 GtkTreeModel *model;
1329 gboolean set_cursor = FALSE;
1331 gtk_tree_model_filter_refilter (priv->filter);
1333 /* Set cursor on the first contact. If it is already set on a group,
1334 * set it on its first child contact. Note that first child of a group
1335 * is its separator, that's why we actually set to the 2nd
1338 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1339 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1343 path = gtk_tree_path_new_from_string ("0:1");
1346 else if (gtk_tree_path_get_depth (path) < 2)
1350 gtk_tree_model_get_iter (model, &iter, path);
1351 gtk_tree_model_get (model, &iter,
1352 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1357 gtk_tree_path_down (path);
1358 gtk_tree_path_next (path);
1365 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1367 if (gtk_tree_model_get_iter (model, &iter, path))
1369 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1374 gtk_tree_path_free (path);
1378 individual_view_search_activate_cb (GtkWidget *search,
1379 EmpathyIndividualView *view)
1382 GtkTreeViewColumn *focus_column;
1384 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1387 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1388 gtk_tree_path_free (path);
1390 gtk_widget_hide (search);
1395 individual_view_search_key_navigation_cb (GtkWidget *search,
1397 EmpathyIndividualView *view)
1399 GdkEventKey *eventkey = ((GdkEventKey *) event);
1400 gboolean ret = FALSE;
1402 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1403 || eventkey->keyval == GDK_KEY_F2)
1405 GdkEvent *new_event;
1407 new_event = gdk_event_copy (event);
1408 gtk_widget_grab_focus (GTK_WIDGET (view));
1409 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1410 gtk_widget_grab_focus (search);
1412 gdk_event_free (new_event);
1419 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1420 EmpathyIndividualView *view)
1422 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1423 GtkTreeModel *model;
1424 GtkTreePath *cursor_path;
1426 gboolean valid = FALSE;
1428 /* block expand or collapse handlers, they would write the
1429 * expand or collapsed setting to file otherwise */
1430 g_signal_handlers_block_by_func (view,
1431 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1432 g_signal_handlers_block_by_func (view,
1433 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1435 /* restore which groups are expanded and which are not */
1436 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1437 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1438 valid; valid = gtk_tree_model_iter_next (model, &iter))
1444 gtk_tree_model_get (model, &iter,
1445 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1446 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1455 path = gtk_tree_model_get_path (model, &iter);
1456 if ((priv->view_features &
1457 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1458 empathy_contact_group_get_expanded (name))
1460 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1464 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1467 gtk_tree_path_free (path);
1471 /* unblock expand or collapse handlers */
1472 g_signal_handlers_unblock_by_func (view,
1473 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1474 g_signal_handlers_unblock_by_func (view,
1475 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1477 /* keep the selected contact visible */
1478 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1480 if (cursor_path != NULL)
1481 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1484 gtk_tree_path_free (cursor_path);
1488 individual_view_search_show_cb (EmpathyLiveSearch *search,
1489 EmpathyIndividualView *view)
1491 /* block expand or collapse handlers during expand all, they would
1492 * write the expand or collapsed setting to file otherwise */
1493 g_signal_handlers_block_by_func (view,
1494 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1496 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1498 g_signal_handlers_unblock_by_func (view,
1499 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1503 expand_idle_foreach_cb (GtkTreeModel *model,
1506 EmpathyIndividualView *self)
1508 EmpathyIndividualViewPriv *priv;
1510 gpointer should_expand;
1513 /* We only want groups */
1514 if (gtk_tree_path_get_depth (path) > 1)
1517 gtk_tree_model_get (model, iter,
1518 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1519 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1522 if (is_group == FALSE)
1528 priv = GET_PRIV (self);
1530 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1531 &should_expand) == TRUE)
1533 if (GPOINTER_TO_INT (should_expand) == TRUE)
1534 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1536 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1538 g_hash_table_remove (priv->expand_groups, name);
1547 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1549 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1551 DEBUG ("individual_view_expand_idle_cb");
1553 g_signal_handlers_block_by_func (self,
1554 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1555 g_signal_handlers_block_by_func (self,
1556 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1558 /* The store/filter could've been removed while we were in the idle queue */
1559 if (priv->filter != NULL)
1561 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1562 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1565 g_signal_handlers_unblock_by_func (self,
1566 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1567 g_signal_handlers_unblock_by_func (self,
1568 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1570 /* Empty the table of groups to expand/contract, since it may contain groups
1571 * which no longer exist in the tree view. This can happen after going
1572 * offline, for example. */
1573 g_hash_table_remove_all (priv->expand_groups);
1574 priv->expand_groups_idle_handler = 0;
1575 g_object_unref (self);
1581 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1584 EmpathyIndividualView *view)
1586 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1587 gboolean should_expand, is_group = FALSE;
1589 gpointer will_expand;
1591 gtk_tree_model_get (model, iter,
1592 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1593 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1596 if (!is_group || EMP_STR_EMPTY (name))
1602 should_expand = (priv->view_features &
1603 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1604 (priv->search_widget != NULL &&
1605 gtk_widget_get_visible (priv->search_widget)) ||
1606 empathy_contact_group_get_expanded (name);
1608 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1609 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1610 * a hash table, and expand or contract them as appropriate all at once in
1611 * an idle handler which iterates over all the group rows. */
1612 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1613 &will_expand) == FALSE ||
1614 GPOINTER_TO_INT (will_expand) != should_expand)
1616 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1617 GINT_TO_POINTER (should_expand));
1619 if (priv->expand_groups_idle_handler == 0)
1621 priv->expand_groups_idle_handler =
1622 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1623 g_object_ref (view));
1630 /* FIXME: This is a workaround for bgo#621076 */
1632 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1635 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1636 GtkTreeModel *model;
1637 GtkTreePath *parent_path;
1638 GtkTreeIter parent_iter;
1640 if (gtk_tree_path_get_depth (path) < 2)
1643 /* A group row is visible if and only if at least one if its child is visible.
1644 * So when a row is inserted/deleted/changed in the base model, that could
1645 * modify the visibility of its parent in the filter model.
1648 model = GTK_TREE_MODEL (priv->store);
1649 parent_path = gtk_tree_path_copy (path);
1650 gtk_tree_path_up (parent_path);
1651 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1653 /* This tells the filter to verify the visibility of that row, and
1654 * show/hide it if necessary */
1655 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1656 parent_path, &parent_iter);
1658 gtk_tree_path_free (parent_path);
1662 individual_view_store_row_changed_cb (GtkTreeModel *model,
1665 EmpathyIndividualView *view)
1667 individual_view_verify_group_visibility (view, path);
1671 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1673 EmpathyIndividualView *view)
1675 individual_view_verify_group_visibility (view, path);
1679 individual_view_is_visible_individual (EmpathyIndividualView *self,
1680 FolksIndividual *individual,
1682 gboolean is_searching)
1684 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1685 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1687 GList *personas, *l;
1689 /* We're only giving the visibility wrt filtering here, not things like
1691 if (priv->show_untrusted == FALSE &&
1692 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1697 if (is_searching == FALSE)
1698 return (priv->show_offline || is_online);
1700 /* check alias name */
1701 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1703 if (empathy_live_search_match (live, str))
1706 /* check contact id, remove the @server.com part */
1707 personas = folks_individual_get_personas (individual);
1708 for (l = personas; l; l = l->next)
1711 gchar *dup_str = NULL;
1714 if (!TPF_IS_PERSONA (l->data))
1717 str = folks_persona_get_display_id (l->data);
1718 p = strstr (str, "@");
1720 str = dup_str = g_strndup (str, p - str);
1722 visible = empathy_live_search_match (live, str);
1728 /* FIXME: Add more rules here, we could check phone numbers in
1729 * contact's vCard for example. */
1735 individual_view_filter_visible_func (GtkTreeModel *model,
1739 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1740 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1741 FolksIndividual *individual = NULL;
1742 gboolean is_group, is_separator, valid;
1743 GtkTreeIter child_iter;
1744 gboolean visible, is_online;
1745 gboolean is_searching = TRUE;
1747 if (priv->search_widget == NULL ||
1748 !gtk_widget_get_visible (priv->search_widget))
1749 is_searching = FALSE;
1751 gtk_tree_model_get (model, iter,
1752 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1753 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1754 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1755 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1758 if (individual != NULL)
1760 visible = individual_view_is_visible_individual (self, individual,
1761 is_online, is_searching);
1763 g_object_unref (individual);
1765 /* FIXME: Work around bgo#626552/bgo#621076 */
1766 if (visible == TRUE)
1768 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1769 individual_view_verify_group_visibility (self, path);
1770 gtk_tree_path_free (path);
1779 /* Not a contact, not a separator, must be a group */
1780 g_return_val_if_fail (is_group, FALSE);
1782 /* only show groups which are not empty */
1783 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1784 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1786 gtk_tree_model_get (model, &child_iter,
1787 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1788 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1791 if (individual == NULL)
1794 visible = individual_view_is_visible_individual (self, individual,
1795 is_online, is_searching);
1796 g_object_unref (individual);
1798 /* show group if it has at least one visible contact in it */
1799 if (visible == TRUE)
1807 individual_view_constructed (GObject *object)
1809 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1810 GtkCellRenderer *cell;
1811 GtkTreeViewColumn *col;
1816 "headers-visible", FALSE,
1817 "show-expanders", FALSE,
1820 col = gtk_tree_view_column_new ();
1823 cell = gtk_cell_renderer_pixbuf_new ();
1824 gtk_tree_view_column_pack_start (col, cell, FALSE);
1825 gtk_tree_view_column_set_cell_data_func (col, cell,
1826 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1836 cell = gtk_cell_renderer_pixbuf_new ();
1837 gtk_tree_view_column_pack_start (col, cell, FALSE);
1838 gtk_tree_view_column_set_cell_data_func (col, cell,
1839 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1851 cell = empathy_cell_renderer_text_new ();
1852 gtk_tree_view_column_pack_start (col, cell, TRUE);
1853 gtk_tree_view_column_set_cell_data_func (col, cell,
1854 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1856 gtk_tree_view_column_add_attribute (col, cell,
1857 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1858 gtk_tree_view_column_add_attribute (col, cell,
1859 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1860 gtk_tree_view_column_add_attribute (col, cell,
1861 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1862 gtk_tree_view_column_add_attribute (col, cell,
1863 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1864 gtk_tree_view_column_add_attribute (col, cell,
1865 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1866 gtk_tree_view_column_add_attribute (col, cell,
1867 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1868 gtk_tree_view_column_add_attribute (col, cell,
1869 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1871 /* Audio Call Icon */
1872 cell = empathy_cell_renderer_activatable_new ();
1873 gtk_tree_view_column_pack_start (col, cell, FALSE);
1874 gtk_tree_view_column_set_cell_data_func (col, cell,
1875 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1878 g_object_set (cell, "visible", FALSE, NULL);
1880 g_signal_connect (cell, "path-activated",
1881 G_CALLBACK (individual_view_call_activated_cb), view);
1884 cell = gtk_cell_renderer_pixbuf_new ();
1885 gtk_tree_view_column_pack_start (col, cell, FALSE);
1886 gtk_tree_view_column_set_cell_data_func (col, cell,
1887 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1899 cell = empathy_cell_renderer_expander_new ();
1900 gtk_tree_view_column_pack_end (col, cell, FALSE);
1901 gtk_tree_view_column_set_cell_data_func (col, cell,
1902 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1905 /* Actually add the column now we have added all cell renderers */
1906 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1909 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1911 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1914 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1916 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1922 individual_view_set_view_features (EmpathyIndividualView *view,
1923 EmpathyIndividualFeatureFlags features)
1925 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1926 gboolean has_tooltip;
1928 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1930 priv->view_features = features;
1932 /* Setting reorderable is a hack that gets us row previews as drag icons
1933 for free. We override all the drag handlers. It's tricky to get the
1934 position of the drag icon right in drag_begin. GtkTreeView has special
1935 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1938 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1939 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1941 /* Update DnD source/dest */
1942 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1944 gtk_drag_source_set (GTK_WIDGET (view),
1947 G_N_ELEMENTS (drag_types_source),
1948 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1952 gtk_drag_source_unset (GTK_WIDGET (view));
1956 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1958 gtk_drag_dest_set (GTK_WIDGET (view),
1959 GTK_DEST_DEFAULT_ALL,
1961 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1965 /* FIXME: URI could still be droped depending on FT feature */
1966 gtk_drag_dest_unset (GTK_WIDGET (view));
1969 /* Update has-tooltip */
1971 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1972 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1976 individual_view_dispose (GObject *object)
1978 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1979 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1981 tp_clear_object (&priv->store);
1982 tp_clear_object (&priv->filter);
1983 tp_clear_object (&priv->tooltip_widget);
1985 empathy_individual_view_set_live_search (view, NULL);
1987 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1991 individual_view_finalize (GObject *object)
1993 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1995 if (priv->expand_groups_idle_handler != 0)
1996 g_source_remove (priv->expand_groups_idle_handler);
1997 g_hash_table_destroy (priv->expand_groups);
1999 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2003 individual_view_get_property (GObject *object,
2008 EmpathyIndividualViewPriv *priv;
2010 priv = GET_PRIV (object);
2015 g_value_set_object (value, priv->store);
2017 case PROP_VIEW_FEATURES:
2018 g_value_set_flags (value, priv->view_features);
2020 case PROP_INDIVIDUAL_FEATURES:
2021 g_value_set_flags (value, priv->individual_features);
2023 case PROP_SHOW_OFFLINE:
2024 g_value_set_boolean (value, priv->show_offline);
2026 case PROP_SHOW_UNTRUSTED:
2027 g_value_set_boolean (value, priv->show_untrusted);
2030 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2036 individual_view_set_property (GObject *object,
2038 const GValue *value,
2041 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2042 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2047 empathy_individual_view_set_store (view, g_value_get_object (value));
2049 case PROP_VIEW_FEATURES:
2050 individual_view_set_view_features (view, g_value_get_flags (value));
2052 case PROP_INDIVIDUAL_FEATURES:
2053 priv->individual_features = g_value_get_flags (value);
2055 case PROP_SHOW_OFFLINE:
2056 empathy_individual_view_set_show_offline (view,
2057 g_value_get_boolean (value));
2059 case PROP_SHOW_UNTRUSTED:
2060 empathy_individual_view_set_show_untrusted (view,
2061 g_value_get_boolean (value));
2064 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2070 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2072 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2073 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2074 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2076 object_class->constructed = individual_view_constructed;
2077 object_class->dispose = individual_view_dispose;
2078 object_class->finalize = individual_view_finalize;
2079 object_class->get_property = individual_view_get_property;
2080 object_class->set_property = individual_view_set_property;
2082 widget_class->drag_data_received = individual_view_drag_data_received;
2083 widget_class->drag_drop = individual_view_drag_drop;
2084 widget_class->drag_begin = individual_view_drag_begin;
2085 widget_class->drag_data_get = individual_view_drag_data_get;
2086 widget_class->drag_end = individual_view_drag_end;
2087 widget_class->drag_motion = individual_view_drag_motion;
2089 /* We use the class method to let user of this widget to connect to
2090 * the signal and stop emission of the signal so the default handler
2091 * won't be called. */
2092 tree_view_class->row_activated = individual_view_row_activated;
2094 klass->drag_individual_received = real_drag_individual_received_cb;
2096 signals[DRAG_INDIVIDUAL_RECEIVED] =
2097 g_signal_new ("drag-individual-received",
2098 G_OBJECT_CLASS_TYPE (klass),
2100 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2102 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2103 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2104 G_TYPE_STRING, G_TYPE_STRING);
2106 signals[DRAG_PERSONA_RECEIVED] =
2107 g_signal_new ("drag-persona-received",
2108 G_OBJECT_CLASS_TYPE (klass),
2110 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2112 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2113 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2115 g_object_class_install_property (object_class,
2117 g_param_spec_object ("store",
2118 "The store of the view",
2119 "The store of the view",
2120 EMPATHY_TYPE_INDIVIDUAL_STORE,
2121 G_PARAM_READWRITE));
2122 g_object_class_install_property (object_class,
2124 g_param_spec_flags ("view-features",
2125 "Features of the view",
2126 "Flags for all enabled features",
2127 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2128 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2129 g_object_class_install_property (object_class,
2130 PROP_INDIVIDUAL_FEATURES,
2131 g_param_spec_flags ("individual-features",
2132 "Features of the individual menu",
2133 "Flags for all enabled features for the menu",
2134 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2135 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2136 g_object_class_install_property (object_class,
2138 g_param_spec_boolean ("show-offline",
2140 "Whether contact list should display "
2141 "offline contacts", FALSE, G_PARAM_READWRITE));
2142 g_object_class_install_property (object_class,
2143 PROP_SHOW_UNTRUSTED,
2144 g_param_spec_boolean ("show-untrusted",
2145 "Show Untrusted Individuals",
2146 "Whether the view should display untrusted individuals; "
2147 "those who could not be who they say they are.",
2148 TRUE, G_PARAM_READWRITE));
2150 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2154 empathy_individual_view_init (EmpathyIndividualView *view)
2156 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2157 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2161 priv->show_untrusted = TRUE;
2163 /* Get saved group states. */
2164 empathy_contact_groups_get_all ();
2166 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2167 (GDestroyNotify) g_free, NULL);
2169 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2170 empathy_individual_store_row_separator_func, NULL, NULL);
2172 /* Connect to tree view signals rather than override. */
2173 g_signal_connect (view, "button-press-event",
2174 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2175 g_signal_connect (view, "key-press-event",
2176 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2177 g_signal_connect (view, "row-expanded",
2178 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2179 GINT_TO_POINTER (TRUE));
2180 g_signal_connect (view, "row-collapsed",
2181 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2182 GINT_TO_POINTER (FALSE));
2183 g_signal_connect (view, "query-tooltip",
2184 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2187 EmpathyIndividualView *
2188 empathy_individual_view_new (EmpathyIndividualStore *store,
2189 EmpathyIndividualViewFeatureFlags view_features,
2190 EmpathyIndividualFeatureFlags individual_features)
2192 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2194 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2196 "individual-features", individual_features,
2197 "view-features", view_features, NULL);
2201 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2203 EmpathyIndividualViewPriv *priv;
2204 GtkTreeSelection *selection;
2206 GtkTreeModel *model;
2207 FolksIndividual *individual;
2209 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2211 priv = GET_PRIV (view);
2213 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2214 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2217 gtk_tree_model_get (model, &iter,
2218 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2224 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2225 gboolean *is_fake_group)
2227 EmpathyIndividualViewPriv *priv;
2228 GtkTreeSelection *selection;
2230 GtkTreeModel *model;
2235 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2237 priv = GET_PRIV (view);
2239 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2240 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2243 gtk_tree_model_get (model, &iter,
2244 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2245 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2246 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2254 if (is_fake_group != NULL)
2255 *is_fake_group = fake;
2261 individual_view_remove_dialog_show (GtkWindow *parent,
2262 const gchar *message,
2263 const gchar *secondary_text)
2268 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2269 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2270 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2271 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2272 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2273 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2274 "%s", secondary_text);
2276 gtk_widget_show (dialog);
2278 res = gtk_dialog_run (GTK_DIALOG (dialog));
2279 gtk_widget_destroy (dialog);
2281 return (res == GTK_RESPONSE_YES);
2285 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2286 EmpathyIndividualView *view)
2290 group = empathy_individual_view_dup_selected_group (view, NULL);
2297 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2299 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2300 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2303 EmpathyIndividualManager *manager =
2304 empathy_individual_manager_dup_singleton ();
2305 empathy_individual_manager_remove_group (manager, group);
2306 g_object_unref (G_OBJECT (manager));
2316 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2318 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2323 gboolean is_fake_group;
2325 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2327 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2328 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2331 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2332 if (!group || is_fake_group)
2334 /* We can't alter fake groups */
2339 menu = gtk_menu_new ();
2342 if (priv->view_features &
2343 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2344 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2345 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2346 gtk_widget_show (item);
2347 g_signal_connect (item, "activate",
2348 G_CALLBACK (individual_view_group_rename_activate_cb),
2353 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2355 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2356 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2357 GTK_ICON_SIZE_MENU);
2358 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2359 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2360 gtk_widget_show (item);
2361 g_signal_connect (item, "activate",
2362 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2371 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2372 EmpathyIndividualView *view)
2374 FolksIndividual *individual;
2376 individual = empathy_individual_view_dup_selected (view);
2378 if (individual != NULL)
2382 GList *l, *personas;
2383 guint persona_count = 0;
2385 personas = folks_individual_get_personas (individual);
2387 /* If we have more than one TpfPersona, display a different message
2388 * ensuring the user knows that *all* of the meta-contacts' personas will
2390 for (l = personas; l != NULL; l = l->next)
2392 if (!TPF_IS_PERSONA (l->data))
2396 if (persona_count >= 2)
2400 if (persona_count < 2)
2402 /* Not a meta-contact */
2405 _("Do you really want to remove the contact '%s'?"),
2406 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2413 _("Do you really want to remove the linked contact '%s'? "
2414 "Note that this will remove all the contacts which make up "
2415 "this linked contact."),
2416 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2419 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2421 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2424 EmpathyIndividualManager *manager;
2426 manager = empathy_individual_manager_dup_singleton ();
2427 empathy_individual_manager_remove (manager, individual, "");
2428 g_object_unref (G_OBJECT (manager));
2432 g_object_unref (individual);
2437 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2438 EmpathyLinkingDialog *linking_dialog,
2439 EmpathyIndividualView *self)
2441 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2442 EmpathyIndividualLinker *linker;
2444 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2445 empathy_individual_linker_set_search_text (linker,
2446 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2450 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2452 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2453 FolksIndividual *individual;
2454 GtkWidget *menu = NULL;
2457 gboolean can_remove = FALSE;
2460 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2462 individual = empathy_individual_view_dup_selected (view);
2463 if (individual == NULL)
2466 /* If any of the Individual's personas can be removed, add an option to
2467 * remove. This will act as a best-effort option. If any Personas cannot be
2468 * removed from the server, then this option will just be inactive upon
2469 * subsequent menu openings */
2470 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2472 FolksPersona *persona = FOLKS_PERSONA (l->data);
2473 FolksPersonaStore *store = folks_persona_get_store (persona);
2474 FolksMaybeBool maybe_can_remove =
2475 folks_persona_store_get_can_remove_personas (store);
2477 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2484 menu = empathy_individual_menu_new (individual, priv->individual_features);
2486 /* Remove contact */
2487 if ((priv->view_features &
2488 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2491 /* create the menu if required, or just add a separator */
2493 menu = gtk_menu_new ();
2496 item = gtk_separator_menu_item_new ();
2497 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2498 gtk_widget_show (item);
2502 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2503 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2504 GTK_ICON_SIZE_MENU);
2505 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2506 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2507 gtk_widget_show (item);
2508 g_signal_connect (item, "activate",
2509 G_CALLBACK (individual_view_remove_activate_cb), view);
2512 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2513 * set the live search text on the new linking dialogue to be the same as
2515 g_signal_connect (menu, "link-contacts-activated",
2516 (GCallback) individual_menu_link_contacts_activated_cb, view);
2518 g_object_unref (individual);
2524 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2525 EmpathyLiveSearch *search)
2527 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2529 /* remove old handlers if old search was not null */
2530 if (priv->search_widget != NULL)
2532 g_signal_handlers_disconnect_by_func (view,
2533 individual_view_start_search_cb, NULL);
2535 g_signal_handlers_disconnect_by_func (priv->search_widget,
2536 individual_view_search_text_notify_cb, view);
2537 g_signal_handlers_disconnect_by_func (priv->search_widget,
2538 individual_view_search_activate_cb, view);
2539 g_signal_handlers_disconnect_by_func (priv->search_widget,
2540 individual_view_search_key_navigation_cb, view);
2541 g_signal_handlers_disconnect_by_func (priv->search_widget,
2542 individual_view_search_hide_cb, view);
2543 g_signal_handlers_disconnect_by_func (priv->search_widget,
2544 individual_view_search_show_cb, view);
2545 g_object_unref (priv->search_widget);
2546 priv->search_widget = NULL;
2549 /* connect handlers if new search is not null */
2552 priv->search_widget = g_object_ref (search);
2554 g_signal_connect (view, "start-interactive-search",
2555 G_CALLBACK (individual_view_start_search_cb), NULL);
2557 g_signal_connect (priv->search_widget, "notify::text",
2558 G_CALLBACK (individual_view_search_text_notify_cb), view);
2559 g_signal_connect (priv->search_widget, "activate",
2560 G_CALLBACK (individual_view_search_activate_cb), view);
2561 g_signal_connect (priv->search_widget, "key-navigation",
2562 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2563 g_signal_connect (priv->search_widget, "hide",
2564 G_CALLBACK (individual_view_search_hide_cb), view);
2565 g_signal_connect (priv->search_widget, "show",
2566 G_CALLBACK (individual_view_search_show_cb), view);
2571 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2573 EmpathyIndividualViewPriv *priv;
2575 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2577 priv = GET_PRIV (self);
2579 return (priv->search_widget != NULL &&
2580 gtk_widget_get_visible (priv->search_widget));
2584 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2586 EmpathyIndividualViewPriv *priv;
2588 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2590 priv = GET_PRIV (self);
2592 return priv->show_offline;
2596 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2597 gboolean show_offline)
2599 EmpathyIndividualViewPriv *priv;
2601 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2603 priv = GET_PRIV (self);
2605 priv->show_offline = show_offline;
2607 g_object_notify (G_OBJECT (self), "show-offline");
2608 gtk_tree_model_filter_refilter (priv->filter);
2612 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2614 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2616 return GET_PRIV (self)->show_untrusted;
2620 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2621 gboolean show_untrusted)
2623 EmpathyIndividualViewPriv *priv;
2625 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2627 priv = GET_PRIV (self);
2629 priv->show_untrusted = show_untrusted;
2631 g_object_notify (G_OBJECT (self), "show-untrusted");
2632 gtk_tree_model_filter_refilter (priv->filter);
2635 EmpathyIndividualStore *
2636 empathy_individual_view_get_store (EmpathyIndividualView *self)
2638 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2640 return GET_PRIV (self)->store;
2644 empathy_individual_view_set_store (EmpathyIndividualView *self,
2645 EmpathyIndividualStore *store)
2647 EmpathyIndividualViewPriv *priv;
2649 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2650 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2652 priv = GET_PRIV (self);
2654 /* Destroy the old filter and remove the old store */
2655 if (priv->store != NULL)
2657 g_signal_handlers_disconnect_by_func (priv->store,
2658 individual_view_store_row_changed_cb, self);
2659 g_signal_handlers_disconnect_by_func (priv->store,
2660 individual_view_store_row_deleted_cb, self);
2662 g_signal_handlers_disconnect_by_func (priv->filter,
2663 individual_view_row_has_child_toggled_cb, self);
2665 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2668 tp_clear_object (&priv->filter);
2669 tp_clear_object (&priv->store);
2671 /* Set the new store */
2672 priv->store = store;
2676 g_object_ref (store);
2678 /* Create a new filter */
2679 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2680 GTK_TREE_MODEL (priv->store), NULL));
2681 gtk_tree_model_filter_set_visible_func (priv->filter,
2682 individual_view_filter_visible_func, self, NULL);
2684 g_signal_connect (priv->filter, "row-has-child-toggled",
2685 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2686 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2687 GTK_TREE_MODEL (priv->filter));
2689 tp_g_signal_connect_object (priv->store, "row-changed",
2690 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2691 tp_g_signal_connect_object (priv->store, "row-inserted",
2692 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2693 tp_g_signal_connect_object (priv->store, "row-deleted",
2694 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2699 empathy_individual_view_start_search (EmpathyIndividualView *self)
2701 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2703 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2704 g_return_if_fail (priv->search_widget != NULL);
2706 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2707 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2709 gtk_widget_show (GTK_WIDGET (priv->search_widget));