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 if (priv->tooltip_widget != NULL)
168 DEBUG ("Tooltip destroyed");
169 tp_clear_object (&priv->tooltip_widget);
174 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
177 gboolean keyboard_mode,
181 EmpathyIndividualViewPriv *priv;
182 FolksIndividual *individual;
186 static gint running = 0;
187 gboolean ret = FALSE;
189 priv = GET_PRIV (view);
191 /* Avoid an infinite loop. See GNOME bug #574377 */
197 /* Don't show the tooltip if there's already a popup menu */
198 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
201 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
202 keyboard_mode, &model, &path, &iter))
205 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
206 gtk_tree_path_free (path);
208 gtk_tree_model_get (model, &iter,
209 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
211 if (individual == NULL)
214 if (priv->tooltip_widget == NULL)
216 priv->tooltip_widget = empathy_individual_widget_new (individual,
217 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
219 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
220 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
221 g_object_ref (priv->tooltip_widget);
222 g_signal_connect (priv->tooltip_widget, "destroy",
223 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
224 gtk_widget_show (priv->tooltip_widget);
228 empathy_individual_widget_set_individual (
229 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
232 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
235 g_object_unref (individual);
243 groups_change_group_cb (GObject *source,
244 GAsyncResult *result,
247 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
248 GError *error = NULL;
250 folks_groupable_change_group_finish (groupable, result, &error);
253 g_warning ("failed to change group: %s", error->message);
254 g_clear_error (&error);
259 group_can_be_modified (const gchar *name,
260 gboolean is_fake_group,
263 /* Real groups can always be modified */
267 /* The favorite fake group can be modified so users can
268 * add/remove favorites using DnD */
269 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
272 /* We can remove contacts from the 'ungrouped' fake group */
273 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
280 individual_view_individual_drag_received (GtkWidget *self,
281 GdkDragContext *context,
284 GtkSelectionData *selection)
286 EmpathyIndividualViewPriv *priv;
287 EmpathyIndividualManager *manager = NULL;
288 FolksIndividual *individual;
289 GtkTreePath *source_path;
290 const gchar *sel_data;
291 gchar *new_group = NULL;
292 gchar *old_group = NULL;
293 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
295 priv = GET_PRIV (self);
297 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
298 new_group = empathy_individual_store_get_parent_group (model, path,
299 NULL, &new_group_is_fake);
301 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
304 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
305 * feature. Otherwise, we just add the dropped contact to whichever group
306 * they were dropped in, and don't remove them from their old group. This
307 * allows for Individual views which shouldn't allow Individuals to have
308 * their groups changed, and also for dragging Individuals between Individual
310 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
311 priv->drag_row != NULL)
313 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
317 empathy_individual_store_get_parent_group (model, source_path,
318 NULL, &old_group_is_fake);
319 gtk_tree_path_free (source_path);
322 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
325 if (!tp_strdiff (old_group, new_group))
328 else if (priv->drag_row != NULL)
330 /* We don't allow changing Individuals' groups, and this Individual was
331 * dragged from another group in *this* Individual view, so we disallow
336 /* XXX: for contacts, we used to ensure the account, create the contact
337 * factory, and then wait on the contacts. But they should already be
338 * created by this point */
340 manager = empathy_individual_manager_dup_singleton ();
341 individual = empathy_individual_manager_lookup_member (manager, sel_data);
343 if (individual == NULL)
345 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
349 /* FIXME: We should probably wait for the cb before calling
352 /* Emit a signal notifying of the drag. We change the Individual's groups in
353 * the default signal handler. */
354 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
355 gdk_drag_context_get_selected_action (context), individual, new_group,
361 tp_clear_object (&manager);
369 real_drag_individual_received_cb (EmpathyIndividualView *self,
370 GdkDragAction action,
371 FolksIndividual *individual,
372 const gchar *new_group,
373 const gchar *old_group)
375 DEBUG ("individual %s dragged from '%s' to '%s'",
376 folks_individual_get_id (individual), old_group, new_group);
378 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
380 /* Mark contact as favourite */
381 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
385 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
387 /* Remove contact as favourite */
388 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
390 /* Don't try to remove it */
394 if (new_group != NULL)
396 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
397 groups_change_group_cb, NULL);
400 if (old_group != NULL && action == GDK_ACTION_MOVE)
402 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
403 FALSE, groups_change_group_cb, NULL);
408 individual_view_persona_drag_received (GtkWidget *self,
409 GdkDragContext *context,
412 GtkSelectionData *selection)
414 EmpathyIndividualViewPriv *priv;
415 EmpathyIndividualManager *manager = NULL;
416 FolksIndividual *individual = NULL;
417 FolksPersona *persona = NULL;
418 const gchar *persona_uid;
419 GList *individuals, *l;
420 gboolean retval = FALSE;
422 priv = GET_PRIV (self);
424 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
426 /* FIXME: This is slow, but the only way to find the Persona we're having
428 manager = empathy_individual_manager_dup_singleton ();
429 individuals = empathy_individual_manager_get_members (manager);
431 for (l = individuals; l != NULL; l = l->next)
435 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
437 for (p = personas; p != NULL; p = p->next)
439 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
442 persona = g_object_ref (p->data);
443 individual = g_object_ref (l->data);
450 g_list_free (individuals);
452 if (persona == NULL || individual == NULL)
454 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
458 /* Emit a signal notifying of the drag. We change the Individual's groups in
459 * the default signal handler. */
460 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
461 gdk_drag_context_get_selected_action (context), persona, individual,
465 tp_clear_object (&manager);
466 tp_clear_object (&persona);
467 tp_clear_object (&individual);
473 individual_view_file_drag_received (GtkWidget *view,
474 GdkDragContext *context,
477 GtkSelectionData *selection)
480 const gchar *sel_data;
481 FolksIndividual *individual;
482 EmpathyContact *contact;
484 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
486 gtk_tree_model_get_iter (model, &iter, path);
487 gtk_tree_model_get (model, &iter,
488 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
489 if (individual == NULL)
492 contact = empathy_contact_dup_from_folks_individual (individual);
493 empathy_send_file_from_uri_list (contact, sel_data);
495 g_object_unref (individual);
496 tp_clear_object (&contact);
502 individual_view_drag_data_received (GtkWidget *view,
503 GdkDragContext *context,
506 GtkSelectionData *selection,
512 GtkTreeViewDropPosition position;
514 gboolean success = TRUE;
516 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
518 /* Get destination group information. */
519 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
520 x, y, &path, &position);
525 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
527 success = individual_view_individual_drag_received (view,
528 context, model, path, selection);
530 else if (info == DND_DRAG_TYPE_PERSONA_ID)
532 success = individual_view_persona_drag_received (view, context, model,
535 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
537 success = individual_view_file_drag_received (view,
538 context, model, path, selection);
541 gtk_tree_path_free (path);
542 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
546 individual_view_drag_motion_cb (DragMotionData *data)
548 if (data->view != NULL)
550 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
551 g_object_remove_weak_pointer (G_OBJECT (data->view),
552 (gpointer *) &data->view);
555 data->timeout_id = 0;
560 /* Minimum distance between the mouse pointer and a horizontal border when we
561 start auto scrolling. */
562 #define AUTO_SCROLL_MARGIN_SIZE 20
563 /* How far to scroll per one tick. */
564 #define AUTO_SCROLL_PITCH 10
567 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
569 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
573 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
575 if (priv->distance < 0)
576 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
578 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
580 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
581 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
583 gtk_adjustment_set_value (adj, new_value);
589 individual_view_drag_motion (GtkWidget *widget,
590 GdkDragContext *context,
595 EmpathyIndividualViewPriv *priv;
599 static DragMotionData *dm = NULL;
602 gboolean is_different = FALSE;
603 gboolean cleanup = TRUE;
604 gboolean retval = TRUE;
605 GtkAllocation allocation;
607 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
608 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
611 if (priv->auto_scroll_timeout_id != 0)
613 g_source_remove (priv->auto_scroll_timeout_id);
614 priv->auto_scroll_timeout_id = 0;
617 gtk_widget_get_allocation (widget, &allocation);
619 if (y < AUTO_SCROLL_MARGIN_SIZE ||
620 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
622 if (y < AUTO_SCROLL_MARGIN_SIZE)
623 priv->distance = MIN (-y, -1);
625 priv->distance = MAX (allocation.height - y, 1);
627 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
628 (GSourceFunc) individual_view_auto_scroll_cb, widget);
631 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
632 x, y, &path, NULL, NULL, NULL);
634 cleanup &= (dm == NULL);
638 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
639 is_different = ((dm == NULL) || ((dm != NULL)
640 && gtk_tree_path_compare (dm->path, path) != 0));
647 /* Coordinates don't point to an actual row, so make sure the pointer
648 and highlighting don't indicate that a drag is possible.
650 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
651 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
654 target = gtk_drag_dest_find_target (widget, context, NULL);
655 gtk_tree_model_get_iter (model, &iter, path);
657 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
658 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
660 /* This is a file drag, and it can only be dropped on contacts,
662 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
663 * even if we have a valid target. */
664 FolksIndividual *individual = NULL;
665 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
667 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
669 gtk_tree_model_get (model, &iter,
670 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
674 if (individual != NULL)
676 EmpathyContact *contact = NULL;
678 contact = empathy_contact_dup_from_folks_individual (individual);
679 caps = empathy_contact_get_capabilities (contact);
681 tp_clear_object (&contact);
684 if (individual != NULL &&
685 folks_has_presence_is_online (FOLKS_HAS_PRESENCE (individual)) &&
686 (caps & EMPATHY_CAPABILITIES_FT))
688 gdk_drag_status (context, GDK_ACTION_COPY, time_);
689 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
690 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
694 gdk_drag_status (context, 0, time_);
695 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
699 if (individual != NULL)
700 g_object_unref (individual);
702 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
703 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
704 priv->drag_row == NULL)) ||
705 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
706 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
708 /* If target != GDK_NONE, then we have a contact (individual or persona)
709 drag. If we're pointing to a group, highlight it. Otherwise, if the
710 contact we're pointing to is in a group, highlight that. Otherwise,
711 set the drag position to before the first row for a drag into
712 the "non-group" at the top.
713 If it's an Individual:
714 We only highlight things if the contact is from a different
715 Individual view, or if this Individual view has
716 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
717 which don't have FEATURE_GROUPS_CHANGE, but do have
718 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
720 We only highlight things if we have FEATURE_PERSONA_DROP.
722 GtkTreeIter group_iter;
724 GtkTreePath *group_path;
725 gtk_tree_model_get (model, &iter,
726 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
733 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
734 gtk_tree_model_get (model, &group_iter,
735 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
739 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
740 group_path = gtk_tree_model_get_path (model, &group_iter);
741 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
742 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
743 gtk_tree_path_free (group_path);
747 group_path = gtk_tree_path_new_first ();
748 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
749 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
750 group_path, GTK_TREE_VIEW_DROP_BEFORE);
754 if (!is_different && !cleanup)
759 gtk_tree_path_free (dm->path);
762 g_source_remove (dm->timeout_id);
770 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
772 dm = g_new0 (DragMotionData, 1);
774 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
775 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
776 dm->path = gtk_tree_path_copy (path);
778 dm->timeout_id = g_timeout_add_seconds (1,
779 (GSourceFunc) individual_view_drag_motion_cb, dm);
786 individual_view_drag_begin (GtkWidget *widget,
787 GdkDragContext *context)
789 EmpathyIndividualViewPriv *priv;
790 GtkTreeSelection *selection;
795 priv = GET_PRIV (widget);
797 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
800 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
801 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
804 path = gtk_tree_model_get_path (model, &iter);
805 priv->drag_row = gtk_tree_row_reference_new (model, path);
806 gtk_tree_path_free (path);
810 individual_view_drag_data_get (GtkWidget *widget,
811 GdkDragContext *context,
812 GtkSelectionData *selection,
816 EmpathyIndividualViewPriv *priv;
817 GtkTreePath *src_path;
820 FolksIndividual *individual;
821 const gchar *individual_id;
823 priv = GET_PRIV (widget);
825 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
826 if (priv->drag_row == NULL)
829 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
830 if (src_path == NULL)
833 if (!gtk_tree_model_get_iter (model, &iter, src_path))
835 gtk_tree_path_free (src_path);
839 gtk_tree_path_free (src_path);
842 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
843 if (individual == NULL)
846 individual_id = folks_individual_get_id (individual);
848 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
850 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
851 (guchar *) individual_id, strlen (individual_id) + 1);
854 g_object_unref (individual);
858 individual_view_drag_end (GtkWidget *widget,
859 GdkDragContext *context)
861 EmpathyIndividualViewPriv *priv;
863 priv = GET_PRIV (widget);
865 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
870 gtk_tree_row_reference_free (priv->drag_row);
871 priv->drag_row = NULL;
876 individual_view_drag_drop (GtkWidget *widget,
877 GdkDragContext *drag_context,
887 EmpathyIndividualView *view;
893 individual_view_popup_menu_idle_cb (gpointer user_data)
895 MenuPopupData *data = user_data;
898 menu = empathy_individual_view_get_individual_menu (data->view);
900 menu = empathy_individual_view_get_group_menu (data->view);
904 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
906 gtk_widget_show (menu);
907 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
909 g_object_ref_sink (menu);
910 g_object_unref (menu);
913 g_slice_free (MenuPopupData, data);
919 individual_view_button_press_event_cb (EmpathyIndividualView *view,
920 GdkEventButton *event,
923 if (event->button == 3)
927 data = g_slice_new (MenuPopupData);
929 data->button = event->button;
930 data->time = event->time;
931 g_idle_add (individual_view_popup_menu_idle_cb, data);
938 individual_view_key_press_event_cb (EmpathyIndividualView *view,
942 if (event->keyval == GDK_KEY_Menu)
946 data = g_slice_new (MenuPopupData);
949 data->time = event->time;
950 g_idle_add (individual_view_popup_menu_idle_cb, data);
951 } else if (event->keyval == GDK_KEY_F2) {
952 FolksIndividual *individual;
953 EmpathyContact *contact;
955 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
957 individual = empathy_individual_view_dup_selected (view);
958 if (individual == NULL)
961 contact = empathy_contact_dup_from_folks_individual (individual);
962 if (contact == NULL) {
963 g_object_unref (individual);
966 empathy_contact_edit_dialog_show (contact, NULL);
968 g_object_unref (individual);
969 g_object_unref (contact);
976 individual_view_row_activated (GtkTreeView *view,
978 GtkTreeViewColumn *column)
980 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
981 FolksIndividual *individual;
982 EmpathyContact *contact;
986 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
989 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
990 gtk_tree_model_get_iter (model, &iter, path);
991 gtk_tree_model_get (model, &iter,
992 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
994 if (individual == NULL)
997 /* Determine which Persona to chat to, by choosing the most available one. */
998 contact = empathy_contact_dup_best_for_action (individual,
999 EMPATHY_ACTION_CHAT);
1001 if (contact != NULL)
1003 DEBUG ("Starting a chat");
1005 empathy_dispatcher_chat_with_contact (contact,
1006 gtk_get_current_event_time ());
1009 g_object_unref (individual);
1010 tp_clear_object (&contact);
1014 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1015 const gchar *path_string,
1016 EmpathyIndividualView *view)
1018 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1020 GtkTreeModel *model;
1022 FolksIndividual *individual;
1023 GdkEventButton *event;
1024 GtkMenuShell *shell;
1027 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1030 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1031 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1034 gtk_tree_model_get (model, &iter,
1035 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1036 if (individual == NULL)
1039 event = (GdkEventButton *) gtk_get_current_event ();
1041 menu = gtk_menu_new ();
1042 shell = GTK_MENU_SHELL (menu);
1045 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1046 gtk_menu_shell_append (shell, item);
1047 gtk_widget_show (item);
1050 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1051 gtk_menu_shell_append (shell, item);
1052 gtk_widget_show (item);
1054 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1055 gtk_widget_show (menu);
1056 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1057 event->button, event->time);
1058 g_object_ref_sink (menu);
1059 g_object_unref (menu);
1061 g_object_unref (individual);
1065 individual_view_cell_set_background (EmpathyIndividualView *view,
1066 GtkCellRenderer *cell,
1070 if (!is_group && is_active)
1072 GtkStyleContext *style;
1075 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1077 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1080 /* Here we take the current theme colour and add it to
1081 * the colour for white and average the two. This
1082 * gives a colour which is inline with the theme but
1085 empathy_make_color_whiter (&color);
1087 g_object_set (cell, "cell-background-rgba", &color, NULL);
1090 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1094 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1095 GtkCellRenderer *cell,
1096 GtkTreeModel *model,
1098 EmpathyIndividualView *view)
1104 gtk_tree_model_get (model, iter,
1105 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1106 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1107 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1110 "visible", !is_group,
1114 tp_clear_object (&pixbuf);
1116 individual_view_cell_set_background (view, cell, is_group, is_active);
1120 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1121 GtkCellRenderer *cell,
1122 GtkTreeModel *model,
1124 EmpathyIndividualView *view)
1126 GdkPixbuf *pixbuf = NULL;
1130 gtk_tree_model_get (model, iter,
1131 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1132 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1137 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1139 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1140 GTK_ICON_SIZE_MENU);
1142 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1144 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1145 GTK_ICON_SIZE_MENU);
1150 "visible", pixbuf != NULL,
1154 tp_clear_object (&pixbuf);
1160 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1161 GtkCellRenderer *cell,
1162 GtkTreeModel *model,
1164 EmpathyIndividualView *view)
1168 gboolean can_audio, can_video;
1170 gtk_tree_model_get (model, iter,
1171 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1172 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1173 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1174 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1177 "visible", !is_group && (can_audio || can_video),
1178 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1181 individual_view_cell_set_background (view, cell, is_group, is_active);
1185 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1186 GtkCellRenderer *cell,
1187 GtkTreeModel *model,
1189 EmpathyIndividualView *view)
1192 gboolean show_avatar;
1196 gtk_tree_model_get (model, iter,
1197 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1198 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1199 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1200 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1203 "visible", !is_group && show_avatar,
1207 tp_clear_object (&pixbuf);
1209 individual_view_cell_set_background (view, cell, is_group, is_active);
1213 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1214 GtkCellRenderer *cell,
1215 GtkTreeModel *model,
1217 EmpathyIndividualView *view)
1222 gtk_tree_model_get (model, iter,
1223 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1224 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1226 individual_view_cell_set_background (view, cell, is_group, is_active);
1230 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1231 GtkCellRenderer *cell,
1232 GtkTreeModel *model,
1234 EmpathyIndividualView *view)
1239 gtk_tree_model_get (model, iter,
1240 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1241 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1243 if (gtk_tree_model_iter_has_child (model, iter))
1246 gboolean row_expanded;
1248 path = gtk_tree_model_get_path (model, iter);
1250 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1251 (gtk_tree_view_column_get_tree_view (column)), path);
1252 gtk_tree_path_free (path);
1257 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1261 g_object_set (cell, "visible", FALSE, NULL);
1263 individual_view_cell_set_background (view, cell, is_group, is_active);
1267 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1272 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1273 GtkTreeModel *model;
1277 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1280 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1282 gtk_tree_model_get (model, iter,
1283 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1285 expanded = GPOINTER_TO_INT (user_data);
1286 empathy_contact_group_set_expanded (name, expanded);
1292 individual_view_start_search_cb (EmpathyIndividualView *view,
1295 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1297 if (priv->search_widget == NULL)
1300 empathy_individual_view_start_search (view);
1306 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1308 EmpathyIndividualView *view)
1310 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1312 GtkTreeViewColumn *focus_column;
1313 GtkTreeModel *model;
1315 gboolean set_cursor = FALSE;
1317 gtk_tree_model_filter_refilter (priv->filter);
1319 /* Set cursor on the first contact. If it is already set on a group,
1320 * set it on its first child contact. Note that first child of a group
1321 * is its separator, that's why we actually set to the 2nd
1324 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1325 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1329 path = gtk_tree_path_new_from_string ("0:1");
1332 else if (gtk_tree_path_get_depth (path) < 2)
1336 gtk_tree_model_get_iter (model, &iter, path);
1337 gtk_tree_model_get (model, &iter,
1338 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1343 gtk_tree_path_down (path);
1344 gtk_tree_path_next (path);
1351 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1353 if (gtk_tree_model_get_iter (model, &iter, path))
1355 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1360 gtk_tree_path_free (path);
1364 individual_view_search_activate_cb (GtkWidget *search,
1365 EmpathyIndividualView *view)
1368 GtkTreeViewColumn *focus_column;
1370 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1373 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1374 gtk_tree_path_free (path);
1376 gtk_widget_hide (search);
1381 individual_view_search_key_navigation_cb (GtkWidget *search,
1383 EmpathyIndividualView *view)
1385 GdkEventKey *eventkey = ((GdkEventKey *) event);
1386 gboolean ret = FALSE;
1388 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1389 || eventkey->keyval == GDK_KEY_F2)
1391 GdkEvent *new_event;
1393 new_event = gdk_event_copy (event);
1394 gtk_widget_grab_focus (GTK_WIDGET (view));
1395 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1396 gtk_widget_grab_focus (search);
1398 gdk_event_free (new_event);
1405 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1406 EmpathyIndividualView *view)
1408 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1409 GtkTreeModel *model;
1410 GtkTreePath *cursor_path;
1412 gboolean valid = FALSE;
1414 /* block expand or collapse handlers, they would write the
1415 * expand or collapsed setting to file otherwise */
1416 g_signal_handlers_block_by_func (view,
1417 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1418 g_signal_handlers_block_by_func (view,
1419 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1421 /* restore which groups are expanded and which are not */
1422 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1423 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1424 valid; valid = gtk_tree_model_iter_next (model, &iter))
1430 gtk_tree_model_get (model, &iter,
1431 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1432 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1441 path = gtk_tree_model_get_path (model, &iter);
1442 if ((priv->view_features &
1443 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1444 empathy_contact_group_get_expanded (name))
1446 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1450 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1453 gtk_tree_path_free (path);
1457 /* unblock expand or collapse handlers */
1458 g_signal_handlers_unblock_by_func (view,
1459 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1460 g_signal_handlers_unblock_by_func (view,
1461 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1463 /* keep the selected contact visible */
1464 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1466 if (cursor_path != NULL)
1467 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1470 gtk_tree_path_free (cursor_path);
1474 individual_view_search_show_cb (EmpathyLiveSearch *search,
1475 EmpathyIndividualView *view)
1477 /* block expand or collapse handlers during expand all, they would
1478 * write the expand or collapsed setting to file otherwise */
1479 g_signal_handlers_block_by_func (view,
1480 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1482 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1484 g_signal_handlers_unblock_by_func (view,
1485 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1489 expand_idle_foreach_cb (GtkTreeModel *model,
1492 EmpathyIndividualView *self)
1494 EmpathyIndividualViewPriv *priv;
1496 gpointer should_expand;
1499 /* We only want groups */
1500 if (gtk_tree_path_get_depth (path) > 1)
1503 gtk_tree_model_get (model, iter,
1504 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1505 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1508 if (is_group == FALSE)
1514 priv = GET_PRIV (self);
1516 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1517 &should_expand) == TRUE)
1519 if (GPOINTER_TO_INT (should_expand) == TRUE)
1520 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1522 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1524 g_hash_table_remove (priv->expand_groups, name);
1533 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1535 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1537 DEBUG ("individual_view_expand_idle_cb");
1539 g_signal_handlers_block_by_func (self,
1540 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1541 g_signal_handlers_block_by_func (self,
1542 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1544 /* The store/filter could've been removed while we were in the idle queue */
1545 if (priv->filter != NULL)
1547 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1548 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1551 g_signal_handlers_unblock_by_func (self,
1552 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1553 g_signal_handlers_unblock_by_func (self,
1554 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1556 /* Empty the table of groups to expand/contract, since it may contain groups
1557 * which no longer exist in the tree view. This can happen after going
1558 * offline, for example. */
1559 g_hash_table_remove_all (priv->expand_groups);
1560 priv->expand_groups_idle_handler = 0;
1561 g_object_unref (self);
1567 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1570 EmpathyIndividualView *view)
1572 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1573 gboolean should_expand, is_group = FALSE;
1575 gpointer will_expand;
1577 gtk_tree_model_get (model, iter,
1578 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1579 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1582 if (!is_group || EMP_STR_EMPTY (name))
1588 should_expand = (priv->view_features &
1589 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1590 (priv->search_widget != NULL &&
1591 gtk_widget_get_visible (priv->search_widget)) ||
1592 empathy_contact_group_get_expanded (name);
1594 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1595 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1596 * a hash table, and expand or contract them as appropriate all at once in
1597 * an idle handler which iterates over all the group rows. */
1598 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1599 &will_expand) == FALSE ||
1600 GPOINTER_TO_INT (will_expand) != should_expand)
1602 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1603 GINT_TO_POINTER (should_expand));
1605 if (priv->expand_groups_idle_handler == 0)
1607 priv->expand_groups_idle_handler =
1608 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1609 g_object_ref (view));
1616 /* FIXME: This is a workaround for bgo#621076 */
1618 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1621 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1622 GtkTreeModel *model;
1623 GtkTreePath *parent_path;
1624 GtkTreeIter parent_iter;
1626 if (gtk_tree_path_get_depth (path) < 2)
1629 /* A group row is visible if and only if at least one if its child is visible.
1630 * So when a row is inserted/deleted/changed in the base model, that could
1631 * modify the visibility of its parent in the filter model.
1634 model = GTK_TREE_MODEL (priv->store);
1635 parent_path = gtk_tree_path_copy (path);
1636 gtk_tree_path_up (parent_path);
1637 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1639 /* This tells the filter to verify the visibility of that row, and
1640 * show/hide it if necessary */
1641 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1642 parent_path, &parent_iter);
1644 gtk_tree_path_free (parent_path);
1648 individual_view_store_row_changed_cb (GtkTreeModel *model,
1651 EmpathyIndividualView *view)
1653 individual_view_verify_group_visibility (view, path);
1657 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1659 EmpathyIndividualView *view)
1661 individual_view_verify_group_visibility (view, path);
1665 individual_view_is_visible_individual (EmpathyIndividualView *self,
1666 FolksIndividual *individual,
1668 gboolean is_searching)
1670 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1671 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1673 GList *personas, *l;
1675 /* We're only giving the visibility wrt filtering here, not things like
1677 if (priv->show_untrusted == FALSE &&
1678 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1683 if (is_searching == FALSE)
1684 return (priv->show_offline || is_online);
1686 /* check alias name */
1687 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1689 if (empathy_live_search_match (live, str))
1692 /* check contact id, remove the @server.com part */
1693 personas = folks_individual_get_personas (individual);
1694 for (l = personas; l; l = l->next)
1697 gchar *dup_str = NULL;
1700 if (!TPF_IS_PERSONA (l->data))
1703 str = folks_persona_get_display_id (l->data);
1704 p = strstr (str, "@");
1706 str = dup_str = g_strndup (str, p - str);
1708 visible = empathy_live_search_match (live, str);
1714 /* FIXME: Add more rules here, we could check phone numbers in
1715 * contact's vCard for example. */
1721 individual_view_filter_visible_func (GtkTreeModel *model,
1725 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1726 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1727 FolksIndividual *individual = NULL;
1728 gboolean is_group, is_separator, valid;
1729 GtkTreeIter child_iter;
1730 gboolean visible, is_online;
1731 gboolean is_searching = TRUE;
1733 if (priv->search_widget == NULL ||
1734 !gtk_widget_get_visible (priv->search_widget))
1735 is_searching = FALSE;
1737 gtk_tree_model_get (model, iter,
1738 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1739 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1740 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1741 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1744 if (individual != NULL)
1746 visible = individual_view_is_visible_individual (self, individual,
1747 is_online, is_searching);
1749 g_object_unref (individual);
1751 /* FIXME: Work around bgo#626552/bgo#621076 */
1752 if (visible == TRUE)
1754 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1755 individual_view_verify_group_visibility (self, path);
1756 gtk_tree_path_free (path);
1765 /* Not a contact, not a separator, must be a group */
1766 g_return_val_if_fail (is_group, FALSE);
1768 /* only show groups which are not empty */
1769 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1770 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1772 gtk_tree_model_get (model, &child_iter,
1773 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1774 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1777 if (individual == NULL)
1780 visible = individual_view_is_visible_individual (self, individual,
1781 is_online, is_searching);
1782 g_object_unref (individual);
1784 /* show group if it has at least one visible contact in it */
1785 if (visible == TRUE)
1793 individual_view_constructed (GObject *object)
1795 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1796 GtkCellRenderer *cell;
1797 GtkTreeViewColumn *col;
1802 "headers-visible", FALSE,
1803 "show-expanders", FALSE,
1806 col = gtk_tree_view_column_new ();
1809 cell = gtk_cell_renderer_pixbuf_new ();
1810 gtk_tree_view_column_pack_start (col, cell, FALSE);
1811 gtk_tree_view_column_set_cell_data_func (col, cell,
1812 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
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_group_icon_cell_data_func,
1837 cell = empathy_cell_renderer_text_new ();
1838 gtk_tree_view_column_pack_start (col, cell, TRUE);
1839 gtk_tree_view_column_set_cell_data_func (col, cell,
1840 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1842 gtk_tree_view_column_add_attribute (col, cell,
1843 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1844 gtk_tree_view_column_add_attribute (col, cell,
1845 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1846 gtk_tree_view_column_add_attribute (col, cell,
1847 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1848 gtk_tree_view_column_add_attribute (col, cell,
1849 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1850 gtk_tree_view_column_add_attribute (col, cell,
1851 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1852 gtk_tree_view_column_add_attribute (col, cell,
1853 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1854 gtk_tree_view_column_add_attribute (col, cell,
1855 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1857 /* Audio Call Icon */
1858 cell = empathy_cell_renderer_activatable_new ();
1859 gtk_tree_view_column_pack_start (col, cell, FALSE);
1860 gtk_tree_view_column_set_cell_data_func (col, cell,
1861 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1864 g_object_set (cell, "visible", FALSE, NULL);
1866 g_signal_connect (cell, "path-activated",
1867 G_CALLBACK (individual_view_call_activated_cb), view);
1870 cell = gtk_cell_renderer_pixbuf_new ();
1871 gtk_tree_view_column_pack_start (col, cell, FALSE);
1872 gtk_tree_view_column_set_cell_data_func (col, cell,
1873 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1885 cell = empathy_cell_renderer_expander_new ();
1886 gtk_tree_view_column_pack_end (col, cell, FALSE);
1887 gtk_tree_view_column_set_cell_data_func (col, cell,
1888 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1891 /* Actually add the column now we have added all cell renderers */
1892 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1895 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1897 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1900 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1902 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1908 individual_view_set_view_features (EmpathyIndividualView *view,
1909 EmpathyIndividualFeatureFlags features)
1911 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1912 gboolean has_tooltip;
1914 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1916 priv->view_features = features;
1918 /* Setting reorderable is a hack that gets us row previews as drag icons
1919 for free. We override all the drag handlers. It's tricky to get the
1920 position of the drag icon right in drag_begin. GtkTreeView has special
1921 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1924 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1925 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1927 /* Update DnD source/dest */
1928 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1930 gtk_drag_source_set (GTK_WIDGET (view),
1933 G_N_ELEMENTS (drag_types_source),
1934 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1938 gtk_drag_source_unset (GTK_WIDGET (view));
1942 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1944 gtk_drag_dest_set (GTK_WIDGET (view),
1945 GTK_DEST_DEFAULT_ALL,
1947 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1951 /* FIXME: URI could still be droped depending on FT feature */
1952 gtk_drag_dest_unset (GTK_WIDGET (view));
1955 /* Update has-tooltip */
1957 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1958 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1962 individual_view_dispose (GObject *object)
1964 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1965 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1967 tp_clear_object (&priv->store);
1968 tp_clear_object (&priv->filter);
1969 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1971 empathy_individual_view_set_live_search (view, NULL);
1973 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1977 individual_view_finalize (GObject *object)
1979 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1981 if (priv->expand_groups_idle_handler != 0)
1982 g_source_remove (priv->expand_groups_idle_handler);
1983 g_hash_table_destroy (priv->expand_groups);
1985 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1989 individual_view_get_property (GObject *object,
1994 EmpathyIndividualViewPriv *priv;
1996 priv = GET_PRIV (object);
2001 g_value_set_object (value, priv->store);
2003 case PROP_VIEW_FEATURES:
2004 g_value_set_flags (value, priv->view_features);
2006 case PROP_INDIVIDUAL_FEATURES:
2007 g_value_set_flags (value, priv->individual_features);
2009 case PROP_SHOW_OFFLINE:
2010 g_value_set_boolean (value, priv->show_offline);
2012 case PROP_SHOW_UNTRUSTED:
2013 g_value_set_boolean (value, priv->show_untrusted);
2016 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2022 individual_view_set_property (GObject *object,
2024 const GValue *value,
2027 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2028 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2033 empathy_individual_view_set_store (view, g_value_get_object (value));
2035 case PROP_VIEW_FEATURES:
2036 individual_view_set_view_features (view, g_value_get_flags (value));
2038 case PROP_INDIVIDUAL_FEATURES:
2039 priv->individual_features = g_value_get_flags (value);
2041 case PROP_SHOW_OFFLINE:
2042 empathy_individual_view_set_show_offline (view,
2043 g_value_get_boolean (value));
2045 case PROP_SHOW_UNTRUSTED:
2046 empathy_individual_view_set_show_untrusted (view,
2047 g_value_get_boolean (value));
2050 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2056 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2058 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2059 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2060 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2062 object_class->constructed = individual_view_constructed;
2063 object_class->dispose = individual_view_dispose;
2064 object_class->finalize = individual_view_finalize;
2065 object_class->get_property = individual_view_get_property;
2066 object_class->set_property = individual_view_set_property;
2068 widget_class->drag_data_received = individual_view_drag_data_received;
2069 widget_class->drag_drop = individual_view_drag_drop;
2070 widget_class->drag_begin = individual_view_drag_begin;
2071 widget_class->drag_data_get = individual_view_drag_data_get;
2072 widget_class->drag_end = individual_view_drag_end;
2073 widget_class->drag_motion = individual_view_drag_motion;
2075 /* We use the class method to let user of this widget to connect to
2076 * the signal and stop emission of the signal so the default handler
2077 * won't be called. */
2078 tree_view_class->row_activated = individual_view_row_activated;
2080 klass->drag_individual_received = real_drag_individual_received_cb;
2082 signals[DRAG_INDIVIDUAL_RECEIVED] =
2083 g_signal_new ("drag-individual-received",
2084 G_OBJECT_CLASS_TYPE (klass),
2086 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2088 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2089 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2090 G_TYPE_STRING, G_TYPE_STRING);
2092 signals[DRAG_PERSONA_RECEIVED] =
2093 g_signal_new ("drag-persona-received",
2094 G_OBJECT_CLASS_TYPE (klass),
2096 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2098 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2099 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2101 g_object_class_install_property (object_class,
2103 g_param_spec_object ("store",
2104 "The store of the view",
2105 "The store of the view",
2106 EMPATHY_TYPE_INDIVIDUAL_STORE,
2107 G_PARAM_READWRITE));
2108 g_object_class_install_property (object_class,
2110 g_param_spec_flags ("view-features",
2111 "Features of the view",
2112 "Flags for all enabled features",
2113 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2114 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2115 g_object_class_install_property (object_class,
2116 PROP_INDIVIDUAL_FEATURES,
2117 g_param_spec_flags ("individual-features",
2118 "Features of the individual menu",
2119 "Flags for all enabled features for the menu",
2120 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2121 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2122 g_object_class_install_property (object_class,
2124 g_param_spec_boolean ("show-offline",
2126 "Whether contact list should display "
2127 "offline contacts", FALSE, G_PARAM_READWRITE));
2128 g_object_class_install_property (object_class,
2129 PROP_SHOW_UNTRUSTED,
2130 g_param_spec_boolean ("show-untrusted",
2131 "Show Untrusted Individuals",
2132 "Whether the view should display untrusted individuals; "
2133 "those who could not be who they say they are.",
2134 TRUE, G_PARAM_READWRITE));
2136 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2140 empathy_individual_view_init (EmpathyIndividualView *view)
2142 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2143 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2147 priv->show_untrusted = TRUE;
2149 /* Get saved group states. */
2150 empathy_contact_groups_get_all ();
2152 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2153 (GDestroyNotify) g_free, NULL);
2155 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2156 empathy_individual_store_row_separator_func, NULL, NULL);
2158 /* Connect to tree view signals rather than override. */
2159 g_signal_connect (view, "button-press-event",
2160 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2161 g_signal_connect (view, "key-press-event",
2162 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2163 g_signal_connect (view, "row-expanded",
2164 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2165 GINT_TO_POINTER (TRUE));
2166 g_signal_connect (view, "row-collapsed",
2167 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2168 GINT_TO_POINTER (FALSE));
2169 g_signal_connect (view, "query-tooltip",
2170 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2173 EmpathyIndividualView *
2174 empathy_individual_view_new (EmpathyIndividualStore *store,
2175 EmpathyIndividualViewFeatureFlags view_features,
2176 EmpathyIndividualFeatureFlags individual_features)
2178 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2180 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2182 "individual-features", individual_features,
2183 "view-features", view_features, NULL);
2187 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2189 EmpathyIndividualViewPriv *priv;
2190 GtkTreeSelection *selection;
2192 GtkTreeModel *model;
2193 FolksIndividual *individual;
2195 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2197 priv = GET_PRIV (view);
2199 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2200 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2203 gtk_tree_model_get (model, &iter,
2204 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2210 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2211 gboolean *is_fake_group)
2213 EmpathyIndividualViewPriv *priv;
2214 GtkTreeSelection *selection;
2216 GtkTreeModel *model;
2221 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2223 priv = GET_PRIV (view);
2225 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2226 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2229 gtk_tree_model_get (model, &iter,
2230 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2231 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2232 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2240 if (is_fake_group != NULL)
2241 *is_fake_group = fake;
2247 individual_view_remove_dialog_show (GtkWindow *parent,
2248 const gchar *message,
2249 const gchar *secondary_text)
2254 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2255 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2256 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2257 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2258 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2259 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2260 "%s", secondary_text);
2262 gtk_widget_show (dialog);
2264 res = gtk_dialog_run (GTK_DIALOG (dialog));
2265 gtk_widget_destroy (dialog);
2267 return (res == GTK_RESPONSE_YES);
2271 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2272 EmpathyIndividualView *view)
2276 group = empathy_individual_view_dup_selected_group (view, NULL);
2283 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2285 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2286 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2289 EmpathyIndividualManager *manager =
2290 empathy_individual_manager_dup_singleton ();
2291 empathy_individual_manager_remove_group (manager, group);
2292 g_object_unref (G_OBJECT (manager));
2302 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2304 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2309 gboolean is_fake_group;
2311 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2313 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2314 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2317 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2318 if (!group || is_fake_group)
2320 /* We can't alter fake groups */
2325 menu = gtk_menu_new ();
2328 if (priv->view_features &
2329 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2330 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2331 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2332 gtk_widget_show (item);
2333 g_signal_connect (item, "activate",
2334 G_CALLBACK (individual_view_group_rename_activate_cb),
2339 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2341 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2342 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2343 GTK_ICON_SIZE_MENU);
2344 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
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_remove_activate_cb), view);
2357 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2358 EmpathyIndividualView *view)
2360 FolksIndividual *individual;
2362 individual = empathy_individual_view_dup_selected (view);
2364 if (individual != NULL)
2368 GList *l, *personas;
2369 guint persona_count = 0;
2371 personas = folks_individual_get_personas (individual);
2373 /* If we have more than one TpfPersona, display a different message
2374 * ensuring the user knows that *all* of the meta-contacts' personas will
2376 for (l = personas; l != NULL; l = l->next)
2378 if (!TPF_IS_PERSONA (l->data))
2382 if (persona_count >= 2)
2386 if (persona_count < 2)
2388 /* Not a meta-contact */
2391 _("Do you really want to remove the contact '%s'?"),
2392 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2399 _("Do you really want to remove the linked contact '%s'? "
2400 "Note that this will remove all the contacts which make up "
2401 "this linked contact."),
2402 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2405 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2407 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2410 EmpathyIndividualManager *manager;
2412 manager = empathy_individual_manager_dup_singleton ();
2413 empathy_individual_manager_remove (manager, individual, "");
2414 g_object_unref (G_OBJECT (manager));
2418 g_object_unref (individual);
2423 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2424 EmpathyLinkingDialog *linking_dialog,
2425 EmpathyIndividualView *self)
2427 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2428 EmpathyIndividualLinker *linker;
2430 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2431 empathy_individual_linker_set_search_text (linker,
2432 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2436 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2438 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2439 FolksIndividual *individual;
2440 GtkWidget *menu = NULL;
2443 gboolean can_remove = FALSE;
2446 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2448 individual = empathy_individual_view_dup_selected (view);
2449 if (individual == NULL)
2452 /* If any of the Individual's personas can be removed, add an option to
2453 * remove. This will act as a best-effort option. If any Personas cannot be
2454 * removed from the server, then this option will just be inactive upon
2455 * subsequent menu openings */
2456 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2458 FolksPersona *persona = FOLKS_PERSONA (l->data);
2459 FolksPersonaStore *store = folks_persona_get_store (persona);
2460 FolksMaybeBool maybe_can_remove =
2461 folks_persona_store_get_can_remove_personas (store);
2463 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2470 menu = empathy_individual_menu_new (individual, priv->individual_features);
2472 /* Remove contact */
2473 if ((priv->view_features &
2474 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2477 /* create the menu if required, or just add a separator */
2479 menu = gtk_menu_new ();
2482 item = gtk_separator_menu_item_new ();
2483 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2484 gtk_widget_show (item);
2488 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2489 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2490 GTK_ICON_SIZE_MENU);
2491 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2492 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2493 gtk_widget_show (item);
2494 g_signal_connect (item, "activate",
2495 G_CALLBACK (individual_view_remove_activate_cb), view);
2498 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2499 * set the live search text on the new linking dialogue to be the same as
2501 g_signal_connect (menu, "link-contacts-activated",
2502 (GCallback) individual_menu_link_contacts_activated_cb, view);
2504 g_object_unref (individual);
2510 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2511 EmpathyLiveSearch *search)
2513 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2515 /* remove old handlers if old search was not null */
2516 if (priv->search_widget != NULL)
2518 g_signal_handlers_disconnect_by_func (view,
2519 individual_view_start_search_cb, NULL);
2521 g_signal_handlers_disconnect_by_func (priv->search_widget,
2522 individual_view_search_text_notify_cb, view);
2523 g_signal_handlers_disconnect_by_func (priv->search_widget,
2524 individual_view_search_activate_cb, view);
2525 g_signal_handlers_disconnect_by_func (priv->search_widget,
2526 individual_view_search_key_navigation_cb, view);
2527 g_signal_handlers_disconnect_by_func (priv->search_widget,
2528 individual_view_search_hide_cb, view);
2529 g_signal_handlers_disconnect_by_func (priv->search_widget,
2530 individual_view_search_show_cb, view);
2531 g_object_unref (priv->search_widget);
2532 priv->search_widget = NULL;
2535 /* connect handlers if new search is not null */
2538 priv->search_widget = g_object_ref (search);
2540 g_signal_connect (view, "start-interactive-search",
2541 G_CALLBACK (individual_view_start_search_cb), NULL);
2543 g_signal_connect (priv->search_widget, "notify::text",
2544 G_CALLBACK (individual_view_search_text_notify_cb), view);
2545 g_signal_connect (priv->search_widget, "activate",
2546 G_CALLBACK (individual_view_search_activate_cb), view);
2547 g_signal_connect (priv->search_widget, "key-navigation",
2548 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2549 g_signal_connect (priv->search_widget, "hide",
2550 G_CALLBACK (individual_view_search_hide_cb), view);
2551 g_signal_connect (priv->search_widget, "show",
2552 G_CALLBACK (individual_view_search_show_cb), view);
2557 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2559 EmpathyIndividualViewPriv *priv;
2561 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2563 priv = GET_PRIV (self);
2565 return (priv->search_widget != NULL &&
2566 gtk_widget_get_visible (priv->search_widget));
2570 empathy_individual_view_get_show_offline (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->show_offline;
2582 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2583 gboolean show_offline)
2585 EmpathyIndividualViewPriv *priv;
2587 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2589 priv = GET_PRIV (self);
2591 priv->show_offline = show_offline;
2593 g_object_notify (G_OBJECT (self), "show-offline");
2594 gtk_tree_model_filter_refilter (priv->filter);
2598 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2600 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2602 return GET_PRIV (self)->show_untrusted;
2606 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2607 gboolean show_untrusted)
2609 EmpathyIndividualViewPriv *priv;
2611 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2613 priv = GET_PRIV (self);
2615 priv->show_untrusted = show_untrusted;
2617 g_object_notify (G_OBJECT (self), "show-untrusted");
2618 gtk_tree_model_filter_refilter (priv->filter);
2621 EmpathyIndividualStore *
2622 empathy_individual_view_get_store (EmpathyIndividualView *self)
2624 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2626 return GET_PRIV (self)->store;
2630 empathy_individual_view_set_store (EmpathyIndividualView *self,
2631 EmpathyIndividualStore *store)
2633 EmpathyIndividualViewPriv *priv;
2635 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2636 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2638 priv = GET_PRIV (self);
2640 /* Destroy the old filter and remove the old store */
2641 if (priv->store != NULL)
2643 g_signal_handlers_disconnect_by_func (priv->store,
2644 individual_view_store_row_changed_cb, self);
2645 g_signal_handlers_disconnect_by_func (priv->store,
2646 individual_view_store_row_deleted_cb, self);
2648 g_signal_handlers_disconnect_by_func (priv->filter,
2649 individual_view_row_has_child_toggled_cb, self);
2651 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2654 tp_clear_object (&priv->filter);
2655 tp_clear_object (&priv->store);
2657 /* Set the new store */
2658 priv->store = store;
2662 g_object_ref (store);
2664 /* Create a new filter */
2665 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2666 GTK_TREE_MODEL (priv->store), NULL));
2667 gtk_tree_model_filter_set_visible_func (priv->filter,
2668 individual_view_filter_visible_func, self, NULL);
2670 g_signal_connect (priv->filter, "row-has-child-toggled",
2671 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2672 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2673 GTK_TREE_MODEL (priv->filter));
2675 tp_g_signal_connect_object (priv->store, "row-changed",
2676 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2677 tp_g_signal_connect_object (priv->store, "row-inserted",
2678 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2679 tp_g_signal_connect_object (priv->store, "row-deleted",
2680 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2685 empathy_individual_view_start_search (EmpathyIndividualView *self)
2687 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2689 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2690 g_return_if_fail (priv->search_widget != NULL);
2692 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2693 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2695 gtk_widget_show (GTK_WIDGET (priv->search_widget));