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_presence_owner_is_online (FOLKS_PRESENCE_OWNER (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,
911 g_slice_free (MenuPopupData, data);
917 individual_view_button_press_event_cb (EmpathyIndividualView *view,
918 GdkEventButton *event,
921 if (event->button == 3)
925 data = g_slice_new (MenuPopupData);
927 data->button = event->button;
928 data->time = event->time;
929 g_idle_add (individual_view_popup_menu_idle_cb, data);
936 individual_view_key_press_event_cb (EmpathyIndividualView *view,
940 if (event->keyval == GDK_KEY_Menu)
944 data = g_slice_new (MenuPopupData);
947 data->time = event->time;
948 g_idle_add (individual_view_popup_menu_idle_cb, data);
949 } else if (event->keyval == GDK_KEY_F2) {
950 FolksIndividual *individual;
951 EmpathyContact *contact;
953 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
955 individual = empathy_individual_view_dup_selected (view);
956 if (individual == NULL)
959 contact = empathy_contact_dup_from_folks_individual (individual);
960 if (contact == NULL) {
961 g_object_unref (individual);
964 empathy_contact_edit_dialog_show (contact, NULL);
966 g_object_unref (individual);
967 g_object_unref (contact);
974 individual_view_row_activated (GtkTreeView *view,
976 GtkTreeViewColumn *column)
978 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
979 FolksIndividual *individual;
980 EmpathyContact *contact;
984 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
987 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
988 gtk_tree_model_get_iter (model, &iter, path);
989 gtk_tree_model_get (model, &iter,
990 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
992 if (individual == NULL)
995 /* Determine which Persona to chat to, by choosing the most available one. */
996 contact = empathy_contact_dup_best_for_action (individual,
997 EMPATHY_ACTION_CHAT);
1001 DEBUG ("Starting a chat");
1003 empathy_dispatcher_chat_with_contact (contact,
1004 gtk_get_current_event_time ());
1007 g_object_unref (individual);
1008 tp_clear_object (&contact);
1012 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1013 const gchar *path_string,
1014 EmpathyIndividualView *view)
1016 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1018 GtkTreeModel *model;
1020 FolksIndividual *individual;
1021 GdkEventButton *event;
1022 GtkMenuShell *shell;
1025 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1028 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1029 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1032 gtk_tree_model_get (model, &iter,
1033 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1034 if (individual == NULL)
1037 event = (GdkEventButton *) gtk_get_current_event ();
1039 menu = gtk_menu_new ();
1040 shell = GTK_MENU_SHELL (menu);
1043 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1044 gtk_menu_shell_append (shell, item);
1045 gtk_widget_show (item);
1048 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1049 gtk_menu_shell_append (shell, item);
1050 gtk_widget_show (item);
1052 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1053 gtk_widget_show (menu);
1054 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1055 event->button, event->time);
1056 g_object_ref_sink (menu);
1057 g_object_unref (menu);
1059 g_object_unref (individual);
1063 individual_view_cell_set_background (EmpathyIndividualView *view,
1064 GtkCellRenderer *cell,
1068 if (!is_group && is_active)
1070 GtkStyleContext *style;
1073 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1075 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1078 /* Here we take the current theme colour and add it to
1079 * the colour for white and average the two. This
1080 * gives a colour which is inline with the theme but
1083 empathy_make_color_whiter (&color);
1085 g_object_set (cell, "cell-background-rgba", &color, NULL);
1088 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1092 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1093 GtkCellRenderer *cell,
1094 GtkTreeModel *model,
1096 EmpathyIndividualView *view)
1102 gtk_tree_model_get (model, iter,
1103 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1104 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1105 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1108 "visible", !is_group,
1112 tp_clear_object (&pixbuf);
1114 individual_view_cell_set_background (view, cell, is_group, is_active);
1118 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1119 GtkCellRenderer *cell,
1120 GtkTreeModel *model,
1122 EmpathyIndividualView *view)
1124 GdkPixbuf *pixbuf = NULL;
1128 gtk_tree_model_get (model, iter,
1129 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1130 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1135 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1137 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1138 GTK_ICON_SIZE_MENU);
1140 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1142 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1143 GTK_ICON_SIZE_MENU);
1148 "visible", pixbuf != NULL,
1152 tp_clear_object (&pixbuf);
1158 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1159 GtkCellRenderer *cell,
1160 GtkTreeModel *model,
1162 EmpathyIndividualView *view)
1166 gboolean can_audio, can_video;
1168 gtk_tree_model_get (model, iter,
1169 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1170 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1171 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1172 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1175 "visible", !is_group && (can_audio || can_video),
1176 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1179 individual_view_cell_set_background (view, cell, is_group, is_active);
1183 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1184 GtkCellRenderer *cell,
1185 GtkTreeModel *model,
1187 EmpathyIndividualView *view)
1190 gboolean show_avatar;
1194 gtk_tree_model_get (model, iter,
1195 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1196 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1197 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1198 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1201 "visible", !is_group && show_avatar,
1205 tp_clear_object (&pixbuf);
1207 individual_view_cell_set_background (view, cell, is_group, is_active);
1211 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1212 GtkCellRenderer *cell,
1213 GtkTreeModel *model,
1215 EmpathyIndividualView *view)
1220 gtk_tree_model_get (model, iter,
1221 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1222 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1224 individual_view_cell_set_background (view, cell, is_group, is_active);
1228 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1229 GtkCellRenderer *cell,
1230 GtkTreeModel *model,
1232 EmpathyIndividualView *view)
1237 gtk_tree_model_get (model, iter,
1238 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1239 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1241 if (gtk_tree_model_iter_has_child (model, iter))
1244 gboolean row_expanded;
1246 path = gtk_tree_model_get_path (model, iter);
1248 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1249 (gtk_tree_view_column_get_tree_view (column)), path);
1250 gtk_tree_path_free (path);
1255 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1259 g_object_set (cell, "visible", FALSE, NULL);
1261 individual_view_cell_set_background (view, cell, is_group, is_active);
1265 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1270 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1271 GtkTreeModel *model;
1275 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1278 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1280 gtk_tree_model_get (model, iter,
1281 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1283 expanded = GPOINTER_TO_INT (user_data);
1284 empathy_contact_group_set_expanded (name, expanded);
1290 individual_view_start_search_cb (EmpathyIndividualView *view,
1293 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1295 if (priv->search_widget == NULL)
1298 empathy_individual_view_start_search (view);
1304 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1306 EmpathyIndividualView *view)
1308 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1310 GtkTreeViewColumn *focus_column;
1311 GtkTreeModel *model;
1313 gboolean set_cursor = FALSE;
1315 gtk_tree_model_filter_refilter (priv->filter);
1317 /* Set cursor on the first contact. If it is already set on a group,
1318 * set it on its first child contact. Note that first child of a group
1319 * is its separator, that's why we actually set to the 2nd
1322 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1323 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1327 path = gtk_tree_path_new_from_string ("0:1");
1330 else if (gtk_tree_path_get_depth (path) < 2)
1334 gtk_tree_model_get_iter (model, &iter, path);
1335 gtk_tree_model_get (model, &iter,
1336 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1341 gtk_tree_path_down (path);
1342 gtk_tree_path_next (path);
1349 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1351 if (gtk_tree_model_get_iter (model, &iter, path))
1353 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1358 gtk_tree_path_free (path);
1362 individual_view_search_activate_cb (GtkWidget *search,
1363 EmpathyIndividualView *view)
1366 GtkTreeViewColumn *focus_column;
1368 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1371 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1372 gtk_tree_path_free (path);
1374 gtk_widget_hide (search);
1379 individual_view_search_key_navigation_cb (GtkWidget *search,
1381 EmpathyIndividualView *view)
1383 GdkEventKey *eventkey = ((GdkEventKey *) event);
1384 gboolean ret = FALSE;
1386 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1387 || eventkey->keyval == GDK_KEY_F2)
1389 GdkEvent *new_event;
1391 new_event = gdk_event_copy (event);
1392 gtk_widget_grab_focus (GTK_WIDGET (view));
1393 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1394 gtk_widget_grab_focus (search);
1396 gdk_event_free (new_event);
1403 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1404 EmpathyIndividualView *view)
1406 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1407 GtkTreeModel *model;
1408 GtkTreePath *cursor_path;
1410 gboolean valid = FALSE;
1412 /* block expand or collapse handlers, they would write the
1413 * expand or collapsed setting to file otherwise */
1414 g_signal_handlers_block_by_func (view,
1415 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1416 g_signal_handlers_block_by_func (view,
1417 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1419 /* restore which groups are expanded and which are not */
1420 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1421 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1422 valid; valid = gtk_tree_model_iter_next (model, &iter))
1428 gtk_tree_model_get (model, &iter,
1429 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1430 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1439 path = gtk_tree_model_get_path (model, &iter);
1440 if ((priv->view_features &
1441 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1442 empathy_contact_group_get_expanded (name))
1444 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1448 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1451 gtk_tree_path_free (path);
1455 /* unblock expand or collapse handlers */
1456 g_signal_handlers_unblock_by_func (view,
1457 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1458 g_signal_handlers_unblock_by_func (view,
1459 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1461 /* keep the selected contact visible */
1462 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1464 if (cursor_path != NULL)
1465 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1468 gtk_tree_path_free (cursor_path);
1472 individual_view_search_show_cb (EmpathyLiveSearch *search,
1473 EmpathyIndividualView *view)
1475 /* block expand or collapse handlers during expand all, they would
1476 * write the expand or collapsed setting to file otherwise */
1477 g_signal_handlers_block_by_func (view,
1478 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1480 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1482 g_signal_handlers_unblock_by_func (view,
1483 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1487 expand_idle_foreach_cb (GtkTreeModel *model,
1490 EmpathyIndividualView *self)
1492 EmpathyIndividualViewPriv *priv;
1494 gpointer should_expand;
1497 /* We only want groups */
1498 if (gtk_tree_path_get_depth (path) > 1)
1501 gtk_tree_model_get (model, iter,
1502 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1503 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1506 if (is_group == FALSE)
1512 priv = GET_PRIV (self);
1514 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1515 &should_expand) == TRUE)
1517 if (GPOINTER_TO_INT (should_expand) == TRUE)
1518 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1520 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1522 g_hash_table_remove (priv->expand_groups, name);
1531 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1533 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1535 DEBUG ("individual_view_expand_idle_cb");
1537 g_signal_handlers_block_by_func (self,
1538 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1539 g_signal_handlers_block_by_func (self,
1540 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1542 /* The store/filter could've been removed while we were in the idle queue */
1543 if (priv->filter != NULL)
1545 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1546 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1549 g_signal_handlers_unblock_by_func (self,
1550 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1551 g_signal_handlers_unblock_by_func (self,
1552 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1554 /* Empty the table of groups to expand/contract, since it may contain groups
1555 * which no longer exist in the tree view. This can happen after going
1556 * offline, for example. */
1557 g_hash_table_remove_all (priv->expand_groups);
1558 priv->expand_groups_idle_handler = 0;
1559 g_object_unref (self);
1565 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1568 EmpathyIndividualView *view)
1570 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1571 gboolean should_expand, is_group = FALSE;
1573 gpointer will_expand;
1575 gtk_tree_model_get (model, iter,
1576 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1577 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1580 if (!is_group || EMP_STR_EMPTY (name))
1586 should_expand = (priv->view_features &
1587 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1588 (priv->search_widget != NULL &&
1589 gtk_widget_get_visible (priv->search_widget)) ||
1590 empathy_contact_group_get_expanded (name);
1592 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1593 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1594 * a hash table, and expand or contract them as appropriate all at once in
1595 * an idle handler which iterates over all the group rows. */
1596 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1597 &will_expand) == FALSE ||
1598 GPOINTER_TO_INT (will_expand) != should_expand)
1600 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1601 GINT_TO_POINTER (should_expand));
1603 if (priv->expand_groups_idle_handler == 0)
1605 priv->expand_groups_idle_handler =
1606 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1607 g_object_ref (view));
1614 /* FIXME: This is a workaround for bgo#621076 */
1616 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1619 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1620 GtkTreeModel *model;
1621 GtkTreePath *parent_path;
1622 GtkTreeIter parent_iter;
1624 if (gtk_tree_path_get_depth (path) < 2)
1627 /* A group row is visible if and only if at least one if its child is visible.
1628 * So when a row is inserted/deleted/changed in the base model, that could
1629 * modify the visibility of its parent in the filter model.
1632 model = GTK_TREE_MODEL (priv->store);
1633 parent_path = gtk_tree_path_copy (path);
1634 gtk_tree_path_up (parent_path);
1635 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1637 /* This tells the filter to verify the visibility of that row, and
1638 * show/hide it if necessary */
1639 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1640 parent_path, &parent_iter);
1642 gtk_tree_path_free (parent_path);
1646 individual_view_store_row_changed_cb (GtkTreeModel *model,
1649 EmpathyIndividualView *view)
1651 individual_view_verify_group_visibility (view, path);
1655 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1657 EmpathyIndividualView *view)
1659 individual_view_verify_group_visibility (view, path);
1663 individual_view_is_visible_individual (EmpathyIndividualView *self,
1664 FolksIndividual *individual,
1666 gboolean is_searching)
1668 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1669 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1671 GList *personas, *l;
1673 /* We're only giving the visibility wrt filtering here, not things like
1675 if (priv->show_untrusted == FALSE &&
1676 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1681 if (is_searching == FALSE)
1682 return (priv->show_offline || is_online);
1684 /* check alias name */
1685 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1687 if (empathy_live_search_match (live, str))
1690 /* check contact id, remove the @server.com part */
1691 personas = folks_individual_get_personas (individual);
1692 for (l = personas; l; l = l->next)
1695 gchar *dup_str = NULL;
1698 if (!TPF_IS_PERSONA (l->data))
1701 str = folks_persona_get_display_id (l->data);
1702 p = strstr (str, "@");
1704 str = dup_str = g_strndup (str, p - str);
1706 visible = empathy_live_search_match (live, str);
1712 /* FIXME: Add more rules here, we could check phone numbers in
1713 * contact's vCard for example. */
1719 individual_view_filter_visible_func (GtkTreeModel *model,
1723 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1724 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1725 FolksIndividual *individual = NULL;
1726 gboolean is_group, is_separator, valid;
1727 GtkTreeIter child_iter;
1728 gboolean visible, is_online;
1729 gboolean is_searching = TRUE;
1731 if (priv->search_widget == NULL ||
1732 !gtk_widget_get_visible (priv->search_widget))
1733 is_searching = FALSE;
1735 gtk_tree_model_get (model, iter,
1736 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1737 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1738 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1739 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1742 if (individual != NULL)
1744 visible = individual_view_is_visible_individual (self, individual,
1745 is_online, is_searching);
1747 g_object_unref (individual);
1749 /* FIXME: Work around bgo#626552/bgo#621076 */
1750 if (visible == TRUE)
1752 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1753 individual_view_verify_group_visibility (self, path);
1754 gtk_tree_path_free (path);
1763 /* Not a contact, not a separator, must be a group */
1764 g_return_val_if_fail (is_group, FALSE);
1766 /* only show groups which are not empty */
1767 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1768 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1770 gtk_tree_model_get (model, &child_iter,
1771 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1772 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1775 if (individual == NULL)
1778 visible = individual_view_is_visible_individual (self, individual,
1779 is_online, is_searching);
1780 g_object_unref (individual);
1782 /* show group if it has at least one visible contact in it */
1783 if (visible == TRUE)
1791 individual_view_constructed (GObject *object)
1793 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1794 GtkCellRenderer *cell;
1795 GtkTreeViewColumn *col;
1800 "headers-visible", FALSE,
1801 "show-expanders", FALSE,
1804 col = gtk_tree_view_column_new ();
1807 cell = gtk_cell_renderer_pixbuf_new ();
1808 gtk_tree_view_column_pack_start (col, cell, FALSE);
1809 gtk_tree_view_column_set_cell_data_func (col, cell,
1810 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1820 cell = gtk_cell_renderer_pixbuf_new ();
1821 gtk_tree_view_column_pack_start (col, cell, FALSE);
1822 gtk_tree_view_column_set_cell_data_func (col, cell,
1823 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1835 cell = empathy_cell_renderer_text_new ();
1836 gtk_tree_view_column_pack_start (col, cell, TRUE);
1837 gtk_tree_view_column_set_cell_data_func (col, cell,
1838 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1840 gtk_tree_view_column_add_attribute (col, cell,
1841 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1842 gtk_tree_view_column_add_attribute (col, cell,
1843 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1844 gtk_tree_view_column_add_attribute (col, cell,
1845 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1846 gtk_tree_view_column_add_attribute (col, cell,
1847 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1848 gtk_tree_view_column_add_attribute (col, cell,
1849 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1850 gtk_tree_view_column_add_attribute (col, cell,
1851 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1852 gtk_tree_view_column_add_attribute (col, cell,
1853 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1855 /* Audio Call Icon */
1856 cell = empathy_cell_renderer_activatable_new ();
1857 gtk_tree_view_column_pack_start (col, cell, FALSE);
1858 gtk_tree_view_column_set_cell_data_func (col, cell,
1859 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1862 g_object_set (cell, "visible", FALSE, NULL);
1864 g_signal_connect (cell, "path-activated",
1865 G_CALLBACK (individual_view_call_activated_cb), view);
1868 cell = gtk_cell_renderer_pixbuf_new ();
1869 gtk_tree_view_column_pack_start (col, cell, FALSE);
1870 gtk_tree_view_column_set_cell_data_func (col, cell,
1871 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1883 cell = empathy_cell_renderer_expander_new ();
1884 gtk_tree_view_column_pack_end (col, cell, FALSE);
1885 gtk_tree_view_column_set_cell_data_func (col, cell,
1886 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1889 /* Actually add the column now we have added all cell renderers */
1890 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1893 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1895 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1898 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1900 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1906 individual_view_set_view_features (EmpathyIndividualView *view,
1907 EmpathyIndividualFeatureFlags features)
1909 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1910 gboolean has_tooltip;
1912 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1914 priv->view_features = features;
1916 /* Setting reorderable is a hack that gets us row previews as drag icons
1917 for free. We override all the drag handlers. It's tricky to get the
1918 position of the drag icon right in drag_begin. GtkTreeView has special
1919 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1922 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1923 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1925 /* Update DnD source/dest */
1926 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1928 gtk_drag_source_set (GTK_WIDGET (view),
1931 G_N_ELEMENTS (drag_types_source),
1932 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1936 gtk_drag_source_unset (GTK_WIDGET (view));
1940 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1942 gtk_drag_dest_set (GTK_WIDGET (view),
1943 GTK_DEST_DEFAULT_ALL,
1945 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1949 /* FIXME: URI could still be droped depending on FT feature */
1950 gtk_drag_dest_unset (GTK_WIDGET (view));
1953 /* Update has-tooltip */
1955 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1956 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1960 individual_view_dispose (GObject *object)
1962 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1963 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1965 tp_clear_object (&priv->store);
1966 tp_clear_object (&priv->filter);
1967 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1969 empathy_individual_view_set_live_search (view, NULL);
1971 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1975 individual_view_finalize (GObject *object)
1977 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1979 if (priv->expand_groups_idle_handler != 0)
1980 g_source_remove (priv->expand_groups_idle_handler);
1981 g_hash_table_destroy (priv->expand_groups);
1983 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1987 individual_view_get_property (GObject *object,
1992 EmpathyIndividualViewPriv *priv;
1994 priv = GET_PRIV (object);
1999 g_value_set_object (value, priv->store);
2001 case PROP_VIEW_FEATURES:
2002 g_value_set_flags (value, priv->view_features);
2004 case PROP_INDIVIDUAL_FEATURES:
2005 g_value_set_flags (value, priv->individual_features);
2007 case PROP_SHOW_OFFLINE:
2008 g_value_set_boolean (value, priv->show_offline);
2010 case PROP_SHOW_UNTRUSTED:
2011 g_value_set_boolean (value, priv->show_untrusted);
2014 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2020 individual_view_set_property (GObject *object,
2022 const GValue *value,
2025 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2026 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2031 empathy_individual_view_set_store (view, g_value_get_object (value));
2033 case PROP_VIEW_FEATURES:
2034 individual_view_set_view_features (view, g_value_get_flags (value));
2036 case PROP_INDIVIDUAL_FEATURES:
2037 priv->individual_features = g_value_get_flags (value);
2039 case PROP_SHOW_OFFLINE:
2040 empathy_individual_view_set_show_offline (view,
2041 g_value_get_boolean (value));
2043 case PROP_SHOW_UNTRUSTED:
2044 empathy_individual_view_set_show_untrusted (view,
2045 g_value_get_boolean (value));
2048 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2054 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2056 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2057 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2058 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2060 object_class->constructed = individual_view_constructed;
2061 object_class->dispose = individual_view_dispose;
2062 object_class->finalize = individual_view_finalize;
2063 object_class->get_property = individual_view_get_property;
2064 object_class->set_property = individual_view_set_property;
2066 widget_class->drag_data_received = individual_view_drag_data_received;
2067 widget_class->drag_drop = individual_view_drag_drop;
2068 widget_class->drag_begin = individual_view_drag_begin;
2069 widget_class->drag_data_get = individual_view_drag_data_get;
2070 widget_class->drag_end = individual_view_drag_end;
2071 widget_class->drag_motion = individual_view_drag_motion;
2073 /* We use the class method to let user of this widget to connect to
2074 * the signal and stop emission of the signal so the default handler
2075 * won't be called. */
2076 tree_view_class->row_activated = individual_view_row_activated;
2078 klass->drag_individual_received = real_drag_individual_received_cb;
2080 signals[DRAG_INDIVIDUAL_RECEIVED] =
2081 g_signal_new ("drag-individual-received",
2082 G_OBJECT_CLASS_TYPE (klass),
2084 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2086 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2087 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2088 G_TYPE_STRING, G_TYPE_STRING);
2090 signals[DRAG_PERSONA_RECEIVED] =
2091 g_signal_new ("drag-persona-received",
2092 G_OBJECT_CLASS_TYPE (klass),
2094 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2096 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2097 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2099 g_object_class_install_property (object_class,
2101 g_param_spec_object ("store",
2102 "The store of the view",
2103 "The store of the view",
2104 EMPATHY_TYPE_INDIVIDUAL_STORE,
2105 G_PARAM_READWRITE));
2106 g_object_class_install_property (object_class,
2108 g_param_spec_flags ("view-features",
2109 "Features of the view",
2110 "Flags for all enabled features",
2111 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2112 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2113 g_object_class_install_property (object_class,
2114 PROP_INDIVIDUAL_FEATURES,
2115 g_param_spec_flags ("individual-features",
2116 "Features of the individual menu",
2117 "Flags for all enabled features for the menu",
2118 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2119 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2120 g_object_class_install_property (object_class,
2122 g_param_spec_boolean ("show-offline",
2124 "Whether contact list should display "
2125 "offline contacts", FALSE, G_PARAM_READWRITE));
2126 g_object_class_install_property (object_class,
2127 PROP_SHOW_UNTRUSTED,
2128 g_param_spec_boolean ("show-untrusted",
2129 "Show Untrusted Individuals",
2130 "Whether the view should display untrusted individuals; "
2131 "those who could not be who they say they are.",
2132 TRUE, G_PARAM_READWRITE));
2134 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2138 empathy_individual_view_init (EmpathyIndividualView *view)
2140 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2141 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2145 priv->show_untrusted = TRUE;
2147 /* Get saved group states. */
2148 empathy_contact_groups_get_all ();
2150 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2151 (GDestroyNotify) g_free, NULL);
2153 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2154 empathy_individual_store_row_separator_func, NULL, NULL);
2156 /* Connect to tree view signals rather than override. */
2157 g_signal_connect (view, "button-press-event",
2158 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2159 g_signal_connect (view, "key-press-event",
2160 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2161 g_signal_connect (view, "row-expanded",
2162 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2163 GINT_TO_POINTER (TRUE));
2164 g_signal_connect (view, "row-collapsed",
2165 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2166 GINT_TO_POINTER (FALSE));
2167 g_signal_connect (view, "query-tooltip",
2168 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2171 EmpathyIndividualView *
2172 empathy_individual_view_new (EmpathyIndividualStore *store,
2173 EmpathyIndividualViewFeatureFlags view_features,
2174 EmpathyIndividualFeatureFlags individual_features)
2176 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2178 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2180 "individual-features", individual_features,
2181 "view-features", view_features, NULL);
2185 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2187 EmpathyIndividualViewPriv *priv;
2188 GtkTreeSelection *selection;
2190 GtkTreeModel *model;
2191 FolksIndividual *individual;
2193 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2195 priv = GET_PRIV (view);
2197 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2198 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2201 gtk_tree_model_get (model, &iter,
2202 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2208 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2209 gboolean *is_fake_group)
2211 EmpathyIndividualViewPriv *priv;
2212 GtkTreeSelection *selection;
2214 GtkTreeModel *model;
2219 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2221 priv = GET_PRIV (view);
2223 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2224 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2227 gtk_tree_model_get (model, &iter,
2228 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2229 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2230 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2238 if (is_fake_group != NULL)
2239 *is_fake_group = fake;
2245 individual_view_remove_dialog_show (GtkWindow *parent,
2246 const gchar *message,
2247 const gchar *secondary_text)
2252 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2253 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2254 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2255 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2256 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2257 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2258 "%s", secondary_text);
2260 gtk_widget_show (dialog);
2262 res = gtk_dialog_run (GTK_DIALOG (dialog));
2263 gtk_widget_destroy (dialog);
2265 return (res == GTK_RESPONSE_YES);
2269 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2270 EmpathyIndividualView *view)
2274 group = empathy_individual_view_dup_selected_group (view, NULL);
2281 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2283 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2284 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2287 EmpathyIndividualManager *manager =
2288 empathy_individual_manager_dup_singleton ();
2289 empathy_individual_manager_remove_group (manager, group);
2290 g_object_unref (G_OBJECT (manager));
2300 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2302 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2307 gboolean is_fake_group;
2309 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2311 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2312 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2315 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2316 if (!group || is_fake_group)
2318 /* We can't alter fake groups */
2323 menu = gtk_menu_new ();
2326 if (priv->view_features &
2327 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2328 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2329 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2330 gtk_widget_show (item);
2331 g_signal_connect (item, "activate",
2332 G_CALLBACK (individual_view_group_rename_activate_cb),
2337 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2339 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2340 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2341 GTK_ICON_SIZE_MENU);
2342 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2343 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2344 gtk_widget_show (item);
2345 g_signal_connect (item, "activate",
2346 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2355 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2356 EmpathyIndividualView *view)
2358 FolksIndividual *individual;
2360 individual = empathy_individual_view_dup_selected (view);
2362 if (individual != NULL)
2366 GList *l, *personas;
2367 guint persona_count = 0;
2369 personas = folks_individual_get_personas (individual);
2371 /* If we have more than one TpfPersona, display a different message
2372 * ensuring the user knows that *all* of the meta-contacts' personas will
2374 for (l = personas; l != NULL; l = l->next)
2376 if (!TPF_IS_PERSONA (l->data))
2380 if (persona_count >= 2)
2384 if (persona_count < 2)
2386 /* Not a meta-contact */
2389 _("Do you really want to remove the contact '%s'?"),
2390 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2397 _("Do you really want to remove the linked contact '%s'? "
2398 "Note that this will remove all the contacts which make up "
2399 "this linked contact."),
2400 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2403 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2405 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2408 EmpathyIndividualManager *manager;
2410 manager = empathy_individual_manager_dup_singleton ();
2411 empathy_individual_manager_remove (manager, individual, "");
2412 g_object_unref (G_OBJECT (manager));
2416 g_object_unref (individual);
2421 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2422 EmpathyLinkingDialog *linking_dialog,
2423 EmpathyIndividualView *self)
2425 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2426 EmpathyIndividualLinker *linker;
2428 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2429 empathy_individual_linker_set_search_text (linker,
2430 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2434 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2436 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2437 FolksIndividual *individual;
2438 GtkWidget *menu = NULL;
2441 gboolean can_remove = FALSE;
2444 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2446 individual = empathy_individual_view_dup_selected (view);
2447 if (individual == NULL)
2450 /* If any of the Individual's personas can be removed, add an option to
2451 * remove. This will act as a best-effort option. If any Personas cannot be
2452 * removed from the server, then this option will just be inactive upon
2453 * subsequent menu openings */
2454 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2456 FolksPersona *persona = FOLKS_PERSONA (l->data);
2457 FolksPersonaStore *store = folks_persona_get_store (persona);
2458 FolksMaybeBool maybe_can_remove =
2459 folks_persona_store_get_can_remove_personas (store);
2461 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2468 menu = empathy_individual_menu_new (individual, priv->individual_features);
2470 /* Remove contact */
2471 if ((priv->view_features &
2472 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2475 /* create the menu if required, or just add a separator */
2477 menu = gtk_menu_new ();
2480 item = gtk_separator_menu_item_new ();
2481 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2482 gtk_widget_show (item);
2486 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2487 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2488 GTK_ICON_SIZE_MENU);
2489 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2490 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2491 gtk_widget_show (item);
2492 g_signal_connect (item, "activate",
2493 G_CALLBACK (individual_view_remove_activate_cb), view);
2496 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2497 * set the live search text on the new linking dialogue to be the same as
2499 g_signal_connect (menu, "link-contacts-activated",
2500 (GCallback) individual_menu_link_contacts_activated_cb, view);
2502 g_object_unref (individual);
2508 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2509 EmpathyLiveSearch *search)
2511 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2513 /* remove old handlers if old search was not null */
2514 if (priv->search_widget != NULL)
2516 g_signal_handlers_disconnect_by_func (view,
2517 individual_view_start_search_cb, NULL);
2519 g_signal_handlers_disconnect_by_func (priv->search_widget,
2520 individual_view_search_text_notify_cb, view);
2521 g_signal_handlers_disconnect_by_func (priv->search_widget,
2522 individual_view_search_activate_cb, view);
2523 g_signal_handlers_disconnect_by_func (priv->search_widget,
2524 individual_view_search_key_navigation_cb, view);
2525 g_signal_handlers_disconnect_by_func (priv->search_widget,
2526 individual_view_search_hide_cb, view);
2527 g_signal_handlers_disconnect_by_func (priv->search_widget,
2528 individual_view_search_show_cb, view);
2529 g_object_unref (priv->search_widget);
2530 priv->search_widget = NULL;
2533 /* connect handlers if new search is not null */
2536 priv->search_widget = g_object_ref (search);
2538 g_signal_connect (view, "start-interactive-search",
2539 G_CALLBACK (individual_view_start_search_cb), NULL);
2541 g_signal_connect (priv->search_widget, "notify::text",
2542 G_CALLBACK (individual_view_search_text_notify_cb), view);
2543 g_signal_connect (priv->search_widget, "activate",
2544 G_CALLBACK (individual_view_search_activate_cb), view);
2545 g_signal_connect (priv->search_widget, "key-navigation",
2546 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2547 g_signal_connect (priv->search_widget, "hide",
2548 G_CALLBACK (individual_view_search_hide_cb), view);
2549 g_signal_connect (priv->search_widget, "show",
2550 G_CALLBACK (individual_view_search_show_cb), view);
2555 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2557 EmpathyIndividualViewPriv *priv;
2559 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2561 priv = GET_PRIV (self);
2563 return (priv->search_widget != NULL &&
2564 gtk_widget_get_visible (priv->search_widget));
2568 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2570 EmpathyIndividualViewPriv *priv;
2572 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2574 priv = GET_PRIV (self);
2576 return priv->show_offline;
2580 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2581 gboolean show_offline)
2583 EmpathyIndividualViewPriv *priv;
2585 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2587 priv = GET_PRIV (self);
2589 priv->show_offline = show_offline;
2591 g_object_notify (G_OBJECT (self), "show-offline");
2592 gtk_tree_model_filter_refilter (priv->filter);
2596 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2598 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2600 return GET_PRIV (self)->show_untrusted;
2604 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2605 gboolean show_untrusted)
2607 EmpathyIndividualViewPriv *priv;
2609 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2611 priv = GET_PRIV (self);
2613 priv->show_untrusted = show_untrusted;
2615 g_object_notify (G_OBJECT (self), "show-untrusted");
2616 gtk_tree_model_filter_refilter (priv->filter);
2619 EmpathyIndividualStore *
2620 empathy_individual_view_get_store (EmpathyIndividualView *self)
2622 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2624 return GET_PRIV (self)->store;
2628 empathy_individual_view_set_store (EmpathyIndividualView *self,
2629 EmpathyIndividualStore *store)
2631 EmpathyIndividualViewPriv *priv;
2633 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2634 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2636 priv = GET_PRIV (self);
2638 /* Destroy the old filter and remove the old store */
2639 if (priv->store != NULL)
2641 g_signal_handlers_disconnect_by_func (priv->store,
2642 individual_view_store_row_changed_cb, self);
2643 g_signal_handlers_disconnect_by_func (priv->store,
2644 individual_view_store_row_deleted_cb, self);
2646 g_signal_handlers_disconnect_by_func (priv->filter,
2647 individual_view_row_has_child_toggled_cb, self);
2649 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2652 tp_clear_object (&priv->filter);
2653 tp_clear_object (&priv->store);
2655 /* Set the new store */
2656 priv->store = store;
2660 g_object_ref (store);
2662 /* Create a new filter */
2663 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2664 GTK_TREE_MODEL (priv->store), NULL));
2665 gtk_tree_model_filter_set_visible_func (priv->filter,
2666 individual_view_filter_visible_func, self, NULL);
2668 g_signal_connect (priv->filter, "row-has-child-toggled",
2669 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2670 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2671 GTK_TREE_MODEL (priv->filter));
2673 tp_g_signal_connect_object (priv->store, "row-changed",
2674 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2675 tp_g_signal_connect_object (priv->store, "row-inserted",
2676 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2677 tp_g_signal_connect_object (priv->store, "row-deleted",
2678 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2683 empathy_individual_view_start_search (EmpathyIndividualView *self)
2685 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2687 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2688 g_return_if_fail (priv->search_widget != NULL);
2690 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2691 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2693 gtk_widget_show (GTK_WIDGET (priv->search_widget));