1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-individual-manager.h>
42 #include <libempathy/empathy-contact-groups.h>
43 #include <libempathy/empathy-dispatcher.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-contact-dialogs.h"
50 #include "empathy-images.h"
51 #include "empathy-linking-dialog.h"
52 #include "empathy-cell-renderer-expander.h"
53 #include "empathy-cell-renderer-text.h"
54 #include "empathy-cell-renderer-activatable.h"
55 #include "empathy-ui-utils.h"
56 #include "empathy-gtk-enum-types.h"
57 #include "empathy-gtk-marshal.h"
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
62 /* Active users are those which have recently changed state
63 * (e.g. online, offline or from normal to a busy state).
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
69 EmpathyIndividualStore *store;
70 GtkTreeRowReference *drag_row;
71 EmpathyIndividualViewFeatureFlags view_features;
72 EmpathyIndividualFeatureFlags individual_features;
73 GtkWidget *tooltip_widget;
75 gboolean show_offline;
76 gboolean show_untrusted;
78 GtkTreeModelFilter *filter;
79 GtkWidget *search_widget;
81 guint expand_groups_idle_handler;
82 /* owned string (group name) -> bool (whether to expand/contract) */
83 GHashTable *expand_groups;
86 guint auto_scroll_timeout_id;
87 /* Distance between mouse pointer and the nearby border. Negative when
90 } EmpathyIndividualViewPriv;
94 EmpathyIndividualView *view;
101 EmpathyIndividualView *view;
102 FolksIndividual *individual;
111 PROP_INDIVIDUAL_FEATURES,
116 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
117 * specific EmpathyContacts (between/in/out of Individuals) */
120 DND_DRAG_TYPE_INDIVIDUAL_ID,
121 DND_DRAG_TYPE_PERSONA_ID,
122 DND_DRAG_TYPE_URI_LIST,
123 DND_DRAG_TYPE_STRING,
126 #define DRAG_TYPE(T,I) \
127 { (gchar *) T, 0, I }
129 static const GtkTargetEntry drag_types_dest[] = {
130 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
131 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
132 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
133 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
135 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
138 static const GtkTargetEntry drag_types_source[] = {
139 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
144 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
145 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
149 DRAG_INDIVIDUAL_RECEIVED,
150 DRAG_PERSONA_RECEIVED,
154 static guint signals[LAST_SIGNAL];
156 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
160 individual_view_tooltip_destroy_cb (GtkWidget *widget,
161 EmpathyIndividualView *view)
163 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
165 tp_clear_object (&priv->tooltip_widget);
169 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
172 gboolean keyboard_mode,
176 EmpathyIndividualViewPriv *priv;
177 FolksIndividual *individual;
181 static gint running = 0;
182 gboolean ret = FALSE;
184 priv = GET_PRIV (view);
186 /* Avoid an infinite loop. See GNOME bug #574377 */
192 /* Don't show the tooltip if there's already a popup menu */
193 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
196 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
197 keyboard_mode, &model, &path, &iter))
200 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
201 gtk_tree_path_free (path);
203 gtk_tree_model_get (model, &iter,
204 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
206 if (individual == NULL)
209 if (priv->tooltip_widget == NULL)
211 priv->tooltip_widget = empathy_individual_widget_new (individual,
212 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
213 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
214 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
215 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
216 g_object_ref (priv->tooltip_widget);
217 g_signal_connect (priv->tooltip_widget, "destroy",
218 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
219 gtk_widget_show (priv->tooltip_widget);
223 empathy_individual_widget_set_individual (
224 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
227 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
230 g_object_unref (individual);
238 groups_change_group_cb (GObject *source,
239 GAsyncResult *result,
242 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
243 GError *error = NULL;
245 folks_groupable_change_group_finish (groupable, result, &error);
248 g_warning ("failed to change group: %s", error->message);
249 g_clear_error (&error);
254 group_can_be_modified (const gchar *name,
255 gboolean is_fake_group,
258 /* Real groups can always be modified */
262 /* The favorite fake group can be modified so users can
263 * add/remove favorites using DnD */
264 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
267 /* We can remove contacts from the 'ungrouped' fake group */
268 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
275 individual_view_individual_drag_received (GtkWidget *self,
276 GdkDragContext *context,
279 GtkSelectionData *selection)
281 EmpathyIndividualViewPriv *priv;
282 EmpathyIndividualManager *manager = NULL;
283 FolksIndividual *individual;
284 GtkTreePath *source_path;
285 const gchar *sel_data;
286 gchar *new_group = NULL;
287 gchar *old_group = NULL;
288 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
290 priv = GET_PRIV (self);
292 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
293 new_group = empathy_individual_store_get_parent_group (model, path,
294 NULL, &new_group_is_fake);
296 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
299 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
300 * feature. Otherwise, we just add the dropped contact to whichever group
301 * they were dropped in, and don't remove them from their old group. This
302 * allows for Individual views which shouldn't allow Individuals to have
303 * their groups changed, and also for dragging Individuals between Individual
305 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
306 priv->drag_row != NULL)
308 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
312 empathy_individual_store_get_parent_group (model, source_path,
313 NULL, &old_group_is_fake);
314 gtk_tree_path_free (source_path);
317 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
320 if (!tp_strdiff (old_group, new_group))
323 else if (priv->drag_row != NULL)
325 /* We don't allow changing Individuals' groups, and this Individual was
326 * dragged from another group in *this* Individual view, so we disallow
331 /* XXX: for contacts, we used to ensure the account, create the contact
332 * factory, and then wait on the contacts. But they should already be
333 * created by this point */
335 manager = empathy_individual_manager_dup_singleton ();
336 individual = empathy_individual_manager_lookup_member (manager, sel_data);
338 if (individual == NULL)
340 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
344 /* FIXME: We should probably wait for the cb before calling
347 /* Emit a signal notifying of the drag. We change the Individual's groups in
348 * the default signal handler. */
349 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
350 gdk_drag_context_get_selected_action (context), individual, new_group,
356 tp_clear_object (&manager);
364 real_drag_individual_received_cb (EmpathyIndividualView *self,
365 GdkDragAction action,
366 FolksIndividual *individual,
367 const gchar *new_group,
368 const gchar *old_group)
370 DEBUG ("individual %s dragged from '%s' to '%s'",
371 folks_individual_get_id (individual), old_group, new_group);
373 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
375 /* Mark contact as favourite */
376 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
380 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
382 /* Remove contact as favourite */
383 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
385 /* Don't try to remove it */
389 if (new_group != NULL)
391 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
392 groups_change_group_cb, NULL);
395 if (old_group != NULL && action == GDK_ACTION_MOVE)
397 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
398 FALSE, groups_change_group_cb, NULL);
403 individual_view_persona_drag_received (GtkWidget *self,
404 GdkDragContext *context,
407 GtkSelectionData *selection)
409 EmpathyIndividualViewPriv *priv;
410 EmpathyIndividualManager *manager = NULL;
411 FolksIndividual *individual = NULL;
412 FolksPersona *persona = NULL;
413 const gchar *persona_uid;
414 GList *individuals, *l;
415 gboolean retval = FALSE;
417 priv = GET_PRIV (self);
419 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
421 /* FIXME: This is slow, but the only way to find the Persona we're having
423 manager = empathy_individual_manager_dup_singleton ();
424 individuals = empathy_individual_manager_get_members (manager);
426 for (l = individuals; l != NULL; l = l->next)
430 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
432 for (p = personas; p != NULL; p = p->next)
434 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
437 persona = g_object_ref (p->data);
438 individual = g_object_ref (l->data);
445 g_list_free (individuals);
447 if (persona == NULL || individual == NULL)
449 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
453 /* Emit a signal notifying of the drag. We change the Individual's groups in
454 * the default signal handler. */
455 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
456 gdk_drag_context_get_selected_action (context), persona, individual,
460 tp_clear_object (&manager);
461 tp_clear_object (&persona);
462 tp_clear_object (&individual);
468 individual_view_file_drag_received (GtkWidget *view,
469 GdkDragContext *context,
472 GtkSelectionData *selection)
475 const gchar *sel_data;
476 FolksIndividual *individual;
477 EmpathyContact *contact;
479 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
481 gtk_tree_model_get_iter (model, &iter, path);
482 gtk_tree_model_get (model, &iter,
483 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
484 if (individual == NULL)
487 contact = empathy_contact_dup_from_folks_individual (individual);
488 empathy_send_file_from_uri_list (contact, sel_data);
490 g_object_unref (individual);
491 tp_clear_object (&contact);
497 individual_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
501 GtkSelectionData *selection,
507 GtkTreeViewDropPosition position;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
515 x, y, &path, &position);
520 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
522 success = individual_view_individual_drag_received (view,
523 context, model, path, selection);
525 else if (info == DND_DRAG_TYPE_PERSONA_ID)
527 success = individual_view_persona_drag_received (view, context, model,
530 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
532 success = individual_view_file_drag_received (view,
533 context, model, path, selection);
536 gtk_tree_path_free (path);
537 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
541 individual_view_drag_motion_cb (DragMotionData *data)
543 if (data->view != NULL)
545 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
546 g_object_remove_weak_pointer (G_OBJECT (data->view),
547 (gpointer *) &data->view);
550 data->timeout_id = 0;
555 /* Minimum distance between the mouse pointer and a horizontal border when we
556 start auto scrolling. */
557 #define AUTO_SCROLL_MARGIN_SIZE 20
558 /* How far to scroll per one tick. */
559 #define AUTO_SCROLL_PITCH 10
562 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
564 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
568 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
570 if (priv->distance < 0)
571 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
573 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
575 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
576 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
578 gtk_adjustment_set_value (adj, new_value);
584 individual_view_drag_motion (GtkWidget *widget,
585 GdkDragContext *context,
590 EmpathyIndividualViewPriv *priv;
594 static DragMotionData *dm = NULL;
597 gboolean is_different = FALSE;
598 gboolean cleanup = TRUE;
599 gboolean retval = TRUE;
600 GtkAllocation allocation;
602 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
603 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
606 if (priv->auto_scroll_timeout_id != 0)
608 g_source_remove (priv->auto_scroll_timeout_id);
609 priv->auto_scroll_timeout_id = 0;
612 gtk_widget_get_allocation (widget, &allocation);
614 if (y < AUTO_SCROLL_MARGIN_SIZE ||
615 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
617 if (y < AUTO_SCROLL_MARGIN_SIZE)
618 priv->distance = MIN (-y, -1);
620 priv->distance = MAX (allocation.height - y, 1);
622 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
623 (GSourceFunc) individual_view_auto_scroll_cb, widget);
626 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
627 x, y, &path, NULL, NULL, NULL);
629 cleanup &= (dm == NULL);
633 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
634 is_different = ((dm == NULL) || ((dm != NULL)
635 && gtk_tree_path_compare (dm->path, path) != 0));
642 /* Coordinates don't point to an actual row, so make sure the pointer
643 and highlighting don't indicate that a drag is possible.
645 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
646 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
649 target = gtk_drag_dest_find_target (widget, context, NULL);
650 gtk_tree_model_get_iter (model, &iter, path);
652 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
653 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
655 /* This is a file drag, and it can only be dropped on contacts,
657 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
658 * even if we have a valid target. */
659 FolksIndividual *individual = NULL;
660 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
662 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
664 gtk_tree_model_get (model, &iter,
665 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
669 if (individual != NULL)
671 EmpathyContact *contact = NULL;
673 contact = empathy_contact_dup_from_folks_individual (individual);
674 caps = empathy_contact_get_capabilities (contact);
676 tp_clear_object (&contact);
679 if (individual != NULL &&
680 folks_presence_owner_is_online (FOLKS_PRESENCE_OWNER (individual)) &&
681 (caps & EMPATHY_CAPABILITIES_FT))
683 gdk_drag_status (context, GDK_ACTION_COPY, time_);
684 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
685 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
689 gdk_drag_status (context, 0, time_);
690 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
694 if (individual != NULL)
695 g_object_unref (individual);
697 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
698 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
699 priv->drag_row == NULL)) ||
700 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
701 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
703 /* If target != GDK_NONE, then we have a contact (individual or persona)
704 drag. If we're pointing to a group, highlight it. Otherwise, if the
705 contact we're pointing to is in a group, highlight that. Otherwise,
706 set the drag position to before the first row for a drag into
707 the "non-group" at the top.
708 If it's an Individual:
709 We only highlight things if the contact is from a different
710 Individual view, or if this Individual view has
711 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
712 which don't have FEATURE_GROUPS_CHANGE, but do have
713 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
715 We only highlight things if we have FEATURE_PERSONA_DROP.
717 GtkTreeIter group_iter;
719 GtkTreePath *group_path;
720 gtk_tree_model_get (model, &iter,
721 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
728 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
729 gtk_tree_model_get (model, &group_iter,
730 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
734 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
735 group_path = gtk_tree_model_get_path (model, &group_iter);
736 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
737 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
738 gtk_tree_path_free (group_path);
742 group_path = gtk_tree_path_new_first ();
743 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
744 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
745 group_path, GTK_TREE_VIEW_DROP_BEFORE);
749 if (!is_different && !cleanup)
754 gtk_tree_path_free (dm->path);
757 g_source_remove (dm->timeout_id);
765 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
767 dm = g_new0 (DragMotionData, 1);
769 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
770 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
771 dm->path = gtk_tree_path_copy (path);
773 dm->timeout_id = g_timeout_add_seconds (1,
774 (GSourceFunc) individual_view_drag_motion_cb, dm);
781 individual_view_drag_begin (GtkWidget *widget,
782 GdkDragContext *context)
784 EmpathyIndividualViewPriv *priv;
785 GtkTreeSelection *selection;
790 priv = GET_PRIV (widget);
792 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
795 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
796 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
799 path = gtk_tree_model_get_path (model, &iter);
800 priv->drag_row = gtk_tree_row_reference_new (model, path);
801 gtk_tree_path_free (path);
805 individual_view_drag_data_get (GtkWidget *widget,
806 GdkDragContext *context,
807 GtkSelectionData *selection,
811 EmpathyIndividualViewPriv *priv;
812 GtkTreePath *src_path;
815 FolksIndividual *individual;
816 const gchar *individual_id;
818 priv = GET_PRIV (widget);
820 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
821 if (priv->drag_row == NULL)
824 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
825 if (src_path == NULL)
828 if (!gtk_tree_model_get_iter (model, &iter, src_path))
830 gtk_tree_path_free (src_path);
834 gtk_tree_path_free (src_path);
837 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
838 if (individual == NULL)
841 individual_id = folks_individual_get_id (individual);
843 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
845 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
846 (guchar *) individual_id, strlen (individual_id) + 1);
849 g_object_unref (individual);
853 individual_view_drag_end (GtkWidget *widget,
854 GdkDragContext *context)
856 EmpathyIndividualViewPriv *priv;
858 priv = GET_PRIV (widget);
860 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
865 gtk_tree_row_reference_free (priv->drag_row);
866 priv->drag_row = NULL;
871 individual_view_drag_drop (GtkWidget *widget,
872 GdkDragContext *drag_context,
882 EmpathyIndividualView *view;
888 menu_deactivate_cb (GtkMenuShell *menushell,
891 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
892 g_signal_handlers_disconnect_by_func (menushell,
893 menu_deactivate_cb, user_data);
895 gtk_menu_detach (GTK_MENU (menushell));
899 individual_view_popup_menu_idle_cb (gpointer user_data)
901 MenuPopupData *data = user_data;
904 menu = empathy_individual_view_get_individual_menu (data->view);
906 menu = empathy_individual_view_get_group_menu (data->view);
910 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
912 gtk_widget_show (menu);
913 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
916 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
917 * floating ref. We can either wait that the treeview releases its ref
918 * when it will be destroyed (when leaving Empathy) or explicitely
919 * detach the menu when it's not displayed any more.
920 * We go for the latter as we don't want to keep useless menus in memory
921 * during the whole lifetime of Empathy. */
922 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
926 g_slice_free (MenuPopupData, data);
932 individual_view_button_press_event_cb (EmpathyIndividualView *view,
933 GdkEventButton *event,
936 if (event->button == 3)
940 data = g_slice_new (MenuPopupData);
942 data->button = event->button;
943 data->time = event->time;
944 g_idle_add (individual_view_popup_menu_idle_cb, data);
951 individual_view_key_press_event_cb (EmpathyIndividualView *view,
955 if (event->keyval == GDK_KEY_Menu)
959 data = g_slice_new (MenuPopupData);
962 data->time = event->time;
963 g_idle_add (individual_view_popup_menu_idle_cb, data);
964 } else if (event->keyval == GDK_KEY_F2) {
965 FolksIndividual *individual;
966 EmpathyContact *contact;
968 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
970 individual = empathy_individual_view_dup_selected (view);
971 if (individual == NULL)
974 contact = empathy_contact_dup_from_folks_individual (individual);
975 if (contact == NULL) {
976 g_object_unref (individual);
979 empathy_contact_edit_dialog_show (contact, NULL);
981 g_object_unref (individual);
982 g_object_unref (contact);
989 individual_view_row_activated (GtkTreeView *view,
991 GtkTreeViewColumn *column)
993 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
994 FolksIndividual *individual;
995 EmpathyContact *contact;
999 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1002 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1003 gtk_tree_model_get_iter (model, &iter, path);
1004 gtk_tree_model_get (model, &iter,
1005 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1007 if (individual == NULL)
1010 /* Determine which Persona to chat to, by choosing the most available one. */
1011 contact = empathy_contact_dup_best_for_action (individual,
1012 EMPATHY_ACTION_CHAT);
1014 if (contact != NULL)
1016 DEBUG ("Starting a chat");
1018 empathy_dispatcher_chat_with_contact (contact,
1019 gtk_get_current_event_time ());
1022 g_object_unref (individual);
1023 tp_clear_object (&contact);
1027 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1028 const gchar *path_string,
1029 EmpathyIndividualView *view)
1031 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1033 GtkTreeModel *model;
1035 FolksIndividual *individual;
1036 GdkEventButton *event;
1037 GtkMenuShell *shell;
1040 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1043 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1044 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1047 gtk_tree_model_get (model, &iter,
1048 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1049 if (individual == NULL)
1052 event = (GdkEventButton *) gtk_get_current_event ();
1054 menu = empathy_context_menu_new (GTK_WIDGET (view));
1055 shell = GTK_MENU_SHELL (menu);
1058 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1059 gtk_menu_shell_append (shell, item);
1060 gtk_widget_show (item);
1063 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1064 gtk_menu_shell_append (shell, item);
1065 gtk_widget_show (item);
1067 gtk_widget_show (menu);
1068 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1069 event->button, event->time);
1071 g_object_unref (individual);
1075 individual_view_cell_set_background (EmpathyIndividualView *view,
1076 GtkCellRenderer *cell,
1080 if (!is_group && is_active)
1082 GtkStyleContext *style;
1085 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1087 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1090 /* Here we take the current theme colour and add it to
1091 * the colour for white and average the two. This
1092 * gives a colour which is inline with the theme but
1095 empathy_make_color_whiter (&color);
1097 g_object_set (cell, "cell-background-rgba", &color, NULL);
1100 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1104 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1105 GtkCellRenderer *cell,
1106 GtkTreeModel *model,
1108 EmpathyIndividualView *view)
1114 gtk_tree_model_get (model, iter,
1115 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1116 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1117 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1120 "visible", !is_group,
1124 tp_clear_object (&pixbuf);
1126 individual_view_cell_set_background (view, cell, is_group, is_active);
1130 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1131 GtkCellRenderer *cell,
1132 GtkTreeModel *model,
1134 EmpathyIndividualView *view)
1136 GdkPixbuf *pixbuf = NULL;
1140 gtk_tree_model_get (model, iter,
1141 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1142 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1147 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1149 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1150 GTK_ICON_SIZE_MENU);
1152 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1154 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1155 GTK_ICON_SIZE_MENU);
1160 "visible", pixbuf != NULL,
1164 tp_clear_object (&pixbuf);
1170 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1171 GtkCellRenderer *cell,
1172 GtkTreeModel *model,
1174 EmpathyIndividualView *view)
1178 gboolean can_audio, can_video;
1180 gtk_tree_model_get (model, iter,
1181 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1182 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1183 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1184 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1187 "visible", !is_group && (can_audio || can_video),
1188 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1191 individual_view_cell_set_background (view, cell, is_group, is_active);
1195 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1196 GtkCellRenderer *cell,
1197 GtkTreeModel *model,
1199 EmpathyIndividualView *view)
1202 gboolean show_avatar;
1206 gtk_tree_model_get (model, iter,
1207 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1208 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1209 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1210 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1213 "visible", !is_group && show_avatar,
1217 tp_clear_object (&pixbuf);
1219 individual_view_cell_set_background (view, cell, is_group, is_active);
1223 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1224 GtkCellRenderer *cell,
1225 GtkTreeModel *model,
1227 EmpathyIndividualView *view)
1232 gtk_tree_model_get (model, iter,
1233 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1234 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1236 individual_view_cell_set_background (view, cell, is_group, is_active);
1240 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1241 GtkCellRenderer *cell,
1242 GtkTreeModel *model,
1244 EmpathyIndividualView *view)
1249 gtk_tree_model_get (model, iter,
1250 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1253 if (gtk_tree_model_iter_has_child (model, iter))
1256 gboolean row_expanded;
1258 path = gtk_tree_model_get_path (model, iter);
1260 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1261 (gtk_tree_view_column_get_tree_view (column)), path);
1262 gtk_tree_path_free (path);
1267 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1271 g_object_set (cell, "visible", FALSE, NULL);
1273 individual_view_cell_set_background (view, cell, is_group, is_active);
1277 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1282 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1283 GtkTreeModel *model;
1287 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1290 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1292 gtk_tree_model_get (model, iter,
1293 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1295 expanded = GPOINTER_TO_INT (user_data);
1296 empathy_contact_group_set_expanded (name, expanded);
1302 individual_view_start_search_cb (EmpathyIndividualView *view,
1305 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1307 if (priv->search_widget == NULL)
1310 empathy_individual_view_start_search (view);
1316 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1318 EmpathyIndividualView *view)
1320 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1322 GtkTreeViewColumn *focus_column;
1323 GtkTreeModel *model;
1325 gboolean set_cursor = FALSE;
1327 gtk_tree_model_filter_refilter (priv->filter);
1329 /* Set cursor on the first contact. If it is already set on a group,
1330 * set it on its first child contact. Note that first child of a group
1331 * is its separator, that's why we actually set to the 2nd
1334 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1335 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1339 path = gtk_tree_path_new_from_string ("0:1");
1342 else if (gtk_tree_path_get_depth (path) < 2)
1346 gtk_tree_model_get_iter (model, &iter, path);
1347 gtk_tree_model_get (model, &iter,
1348 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1353 gtk_tree_path_down (path);
1354 gtk_tree_path_next (path);
1361 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1363 if (gtk_tree_model_get_iter (model, &iter, path))
1365 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1370 gtk_tree_path_free (path);
1374 individual_view_search_activate_cb (GtkWidget *search,
1375 EmpathyIndividualView *view)
1378 GtkTreeViewColumn *focus_column;
1380 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1383 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1384 gtk_tree_path_free (path);
1386 gtk_widget_hide (search);
1391 individual_view_search_key_navigation_cb (GtkWidget *search,
1393 EmpathyIndividualView *view)
1395 GdkEventKey *eventkey = ((GdkEventKey *) event);
1396 gboolean ret = FALSE;
1398 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1399 || eventkey->keyval == GDK_KEY_F2)
1401 GdkEvent *new_event;
1403 new_event = gdk_event_copy (event);
1404 gtk_widget_grab_focus (GTK_WIDGET (view));
1405 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1406 gtk_widget_grab_focus (search);
1408 gdk_event_free (new_event);
1415 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1416 EmpathyIndividualView *view)
1418 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1419 GtkTreeModel *model;
1420 GtkTreePath *cursor_path;
1422 gboolean valid = FALSE;
1424 /* block expand or collapse handlers, they would write the
1425 * expand or collapsed setting to file otherwise */
1426 g_signal_handlers_block_by_func (view,
1427 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1428 g_signal_handlers_block_by_func (view,
1429 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1431 /* restore which groups are expanded and which are not */
1432 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1433 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1434 valid; valid = gtk_tree_model_iter_next (model, &iter))
1440 gtk_tree_model_get (model, &iter,
1441 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1442 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1451 path = gtk_tree_model_get_path (model, &iter);
1452 if ((priv->view_features &
1453 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1454 empathy_contact_group_get_expanded (name))
1456 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1460 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1463 gtk_tree_path_free (path);
1467 /* unblock expand or collapse handlers */
1468 g_signal_handlers_unblock_by_func (view,
1469 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1470 g_signal_handlers_unblock_by_func (view,
1471 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1473 /* keep the selected contact visible */
1474 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1476 if (cursor_path != NULL)
1477 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1480 gtk_tree_path_free (cursor_path);
1484 individual_view_search_show_cb (EmpathyLiveSearch *search,
1485 EmpathyIndividualView *view)
1487 /* block expand or collapse handlers during expand all, they would
1488 * write the expand or collapsed setting to file otherwise */
1489 g_signal_handlers_block_by_func (view,
1490 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1492 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1494 g_signal_handlers_unblock_by_func (view,
1495 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1499 expand_idle_foreach_cb (GtkTreeModel *model,
1502 EmpathyIndividualView *self)
1504 EmpathyIndividualViewPriv *priv;
1506 gpointer should_expand;
1509 /* We only want groups */
1510 if (gtk_tree_path_get_depth (path) > 1)
1513 gtk_tree_model_get (model, iter,
1514 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1515 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1518 if (is_group == FALSE)
1524 priv = GET_PRIV (self);
1526 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1527 &should_expand) == TRUE)
1529 if (GPOINTER_TO_INT (should_expand) == TRUE)
1530 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1532 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1534 g_hash_table_remove (priv->expand_groups, name);
1543 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1545 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1547 DEBUG ("individual_view_expand_idle_cb");
1549 g_signal_handlers_block_by_func (self,
1550 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1551 g_signal_handlers_block_by_func (self,
1552 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1554 /* The store/filter could've been removed while we were in the idle queue */
1555 if (priv->filter != NULL)
1557 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1558 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1561 g_signal_handlers_unblock_by_func (self,
1562 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1563 g_signal_handlers_unblock_by_func (self,
1564 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1566 /* Empty the table of groups to expand/contract, since it may contain groups
1567 * which no longer exist in the tree view. This can happen after going
1568 * offline, for example. */
1569 g_hash_table_remove_all (priv->expand_groups);
1570 priv->expand_groups_idle_handler = 0;
1571 g_object_unref (self);
1577 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1580 EmpathyIndividualView *view)
1582 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1583 gboolean should_expand, is_group = FALSE;
1585 gpointer will_expand;
1587 gtk_tree_model_get (model, iter,
1588 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1589 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1592 if (!is_group || EMP_STR_EMPTY (name))
1598 should_expand = (priv->view_features &
1599 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1600 (priv->search_widget != NULL &&
1601 gtk_widget_get_visible (priv->search_widget)) ||
1602 empathy_contact_group_get_expanded (name);
1604 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1605 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1606 * a hash table, and expand or contract them as appropriate all at once in
1607 * an idle handler which iterates over all the group rows. */
1608 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1609 &will_expand) == FALSE ||
1610 GPOINTER_TO_INT (will_expand) != should_expand)
1612 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1613 GINT_TO_POINTER (should_expand));
1615 if (priv->expand_groups_idle_handler == 0)
1617 priv->expand_groups_idle_handler =
1618 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1619 g_object_ref (view));
1626 /* FIXME: This is a workaround for bgo#621076 */
1628 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1631 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1632 GtkTreeModel *model;
1633 GtkTreePath *parent_path;
1634 GtkTreeIter parent_iter;
1636 if (gtk_tree_path_get_depth (path) < 2)
1639 /* A group row is visible if and only if at least one if its child is visible.
1640 * So when a row is inserted/deleted/changed in the base model, that could
1641 * modify the visibility of its parent in the filter model.
1644 model = GTK_TREE_MODEL (priv->store);
1645 parent_path = gtk_tree_path_copy (path);
1646 gtk_tree_path_up (parent_path);
1647 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1649 /* This tells the filter to verify the visibility of that row, and
1650 * show/hide it if necessary */
1651 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1652 parent_path, &parent_iter);
1654 gtk_tree_path_free (parent_path);
1658 individual_view_store_row_changed_cb (GtkTreeModel *model,
1661 EmpathyIndividualView *view)
1663 individual_view_verify_group_visibility (view, path);
1667 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1669 EmpathyIndividualView *view)
1671 individual_view_verify_group_visibility (view, path);
1675 individual_view_is_visible_individual (EmpathyIndividualView *self,
1676 FolksIndividual *individual,
1678 gboolean is_searching)
1680 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1681 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1683 GList *personas, *l;
1684 gboolean is_favorite, contains_interesting_persona = FALSE;
1686 /* We're only giving the visibility wrt filtering here, not things like
1688 if (priv->show_untrusted == FALSE &&
1689 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1694 /* Hide all individuals which consist entirely of uninteresting personas */
1695 personas = folks_individual_get_personas (individual);
1696 for (l = personas; l; l = l->next)
1698 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1700 contains_interesting_persona = TRUE;
1705 if (contains_interesting_persona == FALSE)
1708 is_favorite = folks_favouritable_get_is_favourite (
1709 FOLKS_FAVOURITABLE (individual));
1710 if (is_searching == FALSE)
1711 return (priv->show_offline || is_online || is_favorite);
1713 /* check alias name */
1714 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1716 if (empathy_live_search_match (live, str))
1719 /* check contact id, remove the @server.com part */
1720 for (l = personas; l; l = l->next)
1723 gchar *dup_str = NULL;
1726 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1729 str = folks_persona_get_display_id (l->data);
1730 p = strstr (str, "@");
1732 str = dup_str = g_strndup (str, p - str);
1734 visible = empathy_live_search_match (live, str);
1740 /* FIXME: Add more rules here, we could check phone numbers in
1741 * contact's vCard for example. */
1747 individual_view_filter_visible_func (GtkTreeModel *model,
1751 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1752 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1753 FolksIndividual *individual = NULL;
1754 gboolean is_group, is_separator, valid;
1755 GtkTreeIter child_iter;
1756 gboolean visible, is_online;
1757 gboolean is_searching = TRUE;
1759 if (priv->search_widget == NULL ||
1760 !gtk_widget_get_visible (priv->search_widget))
1761 is_searching = FALSE;
1763 gtk_tree_model_get (model, iter,
1764 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1765 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1766 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1767 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1770 if (individual != NULL)
1772 visible = individual_view_is_visible_individual (self, individual,
1773 is_online, is_searching);
1775 g_object_unref (individual);
1777 /* FIXME: Work around bgo#626552/bgo#621076 */
1778 if (visible == TRUE)
1780 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1781 individual_view_verify_group_visibility (self, path);
1782 gtk_tree_path_free (path);
1791 /* Not a contact, not a separator, must be a group */
1792 g_return_val_if_fail (is_group, FALSE);
1794 /* only show groups which are not empty */
1795 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1796 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1798 gtk_tree_model_get (model, &child_iter,
1799 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1800 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1803 if (individual == NULL)
1806 visible = individual_view_is_visible_individual (self, individual,
1807 is_online, is_searching);
1808 g_object_unref (individual);
1810 /* show group if it has at least one visible contact in it */
1811 if (visible == TRUE)
1819 individual_view_constructed (GObject *object)
1821 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1822 GtkCellRenderer *cell;
1823 GtkTreeViewColumn *col;
1828 "headers-visible", FALSE,
1829 "show-expanders", FALSE,
1832 col = gtk_tree_view_column_new ();
1835 cell = gtk_cell_renderer_pixbuf_new ();
1836 gtk_tree_view_column_pack_start (col, cell, FALSE);
1837 gtk_tree_view_column_set_cell_data_func (col, cell,
1838 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1848 cell = gtk_cell_renderer_pixbuf_new ();
1849 gtk_tree_view_column_pack_start (col, cell, FALSE);
1850 gtk_tree_view_column_set_cell_data_func (col, cell,
1851 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1863 cell = empathy_cell_renderer_text_new ();
1864 gtk_tree_view_column_pack_start (col, cell, TRUE);
1865 gtk_tree_view_column_set_cell_data_func (col, cell,
1866 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1868 gtk_tree_view_column_add_attribute (col, cell,
1869 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1870 gtk_tree_view_column_add_attribute (col, cell,
1871 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1872 gtk_tree_view_column_add_attribute (col, cell,
1873 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1874 gtk_tree_view_column_add_attribute (col, cell,
1875 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1876 gtk_tree_view_column_add_attribute (col, cell,
1877 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1878 gtk_tree_view_column_add_attribute (col, cell,
1879 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1880 gtk_tree_view_column_add_attribute (col, cell,
1881 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1883 /* Audio Call Icon */
1884 cell = empathy_cell_renderer_activatable_new ();
1885 gtk_tree_view_column_pack_start (col, cell, FALSE);
1886 gtk_tree_view_column_set_cell_data_func (col, cell,
1887 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1890 g_object_set (cell, "visible", FALSE, NULL);
1892 g_signal_connect (cell, "path-activated",
1893 G_CALLBACK (individual_view_call_activated_cb), view);
1896 cell = gtk_cell_renderer_pixbuf_new ();
1897 gtk_tree_view_column_pack_start (col, cell, FALSE);
1898 gtk_tree_view_column_set_cell_data_func (col, cell,
1899 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1911 cell = empathy_cell_renderer_expander_new ();
1912 gtk_tree_view_column_pack_end (col, cell, FALSE);
1913 gtk_tree_view_column_set_cell_data_func (col, cell,
1914 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1917 /* Actually add the column now we have added all cell renderers */
1918 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1921 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1923 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1926 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1928 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1934 individual_view_set_view_features (EmpathyIndividualView *view,
1935 EmpathyIndividualFeatureFlags features)
1937 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1938 gboolean has_tooltip;
1940 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1942 priv->view_features = features;
1944 /* Setting reorderable is a hack that gets us row previews as drag icons
1945 for free. We override all the drag handlers. It's tricky to get the
1946 position of the drag icon right in drag_begin. GtkTreeView has special
1947 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1950 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1951 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1953 /* Update DnD source/dest */
1954 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1956 gtk_drag_source_set (GTK_WIDGET (view),
1959 G_N_ELEMENTS (drag_types_source),
1960 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1964 gtk_drag_source_unset (GTK_WIDGET (view));
1968 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1970 gtk_drag_dest_set (GTK_WIDGET (view),
1971 GTK_DEST_DEFAULT_ALL,
1973 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1977 /* FIXME: URI could still be droped depending on FT feature */
1978 gtk_drag_dest_unset (GTK_WIDGET (view));
1981 /* Update has-tooltip */
1983 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1984 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1988 individual_view_dispose (GObject *object)
1990 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1991 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1993 tp_clear_object (&priv->store);
1994 tp_clear_object (&priv->filter);
1995 tp_clear_object (&priv->tooltip_widget);
1997 empathy_individual_view_set_live_search (view, NULL);
1999 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2003 individual_view_finalize (GObject *object)
2005 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2007 if (priv->expand_groups_idle_handler != 0)
2008 g_source_remove (priv->expand_groups_idle_handler);
2009 g_hash_table_destroy (priv->expand_groups);
2011 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2015 individual_view_get_property (GObject *object,
2020 EmpathyIndividualViewPriv *priv;
2022 priv = GET_PRIV (object);
2027 g_value_set_object (value, priv->store);
2029 case PROP_VIEW_FEATURES:
2030 g_value_set_flags (value, priv->view_features);
2032 case PROP_INDIVIDUAL_FEATURES:
2033 g_value_set_flags (value, priv->individual_features);
2035 case PROP_SHOW_OFFLINE:
2036 g_value_set_boolean (value, priv->show_offline);
2038 case PROP_SHOW_UNTRUSTED:
2039 g_value_set_boolean (value, priv->show_untrusted);
2042 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2048 individual_view_set_property (GObject *object,
2050 const GValue *value,
2053 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2054 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2059 empathy_individual_view_set_store (view, g_value_get_object (value));
2061 case PROP_VIEW_FEATURES:
2062 individual_view_set_view_features (view, g_value_get_flags (value));
2064 case PROP_INDIVIDUAL_FEATURES:
2065 priv->individual_features = g_value_get_flags (value);
2067 case PROP_SHOW_OFFLINE:
2068 empathy_individual_view_set_show_offline (view,
2069 g_value_get_boolean (value));
2071 case PROP_SHOW_UNTRUSTED:
2072 empathy_individual_view_set_show_untrusted (view,
2073 g_value_get_boolean (value));
2076 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2082 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2084 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2085 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2086 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2088 object_class->constructed = individual_view_constructed;
2089 object_class->dispose = individual_view_dispose;
2090 object_class->finalize = individual_view_finalize;
2091 object_class->get_property = individual_view_get_property;
2092 object_class->set_property = individual_view_set_property;
2094 widget_class->drag_data_received = individual_view_drag_data_received;
2095 widget_class->drag_drop = individual_view_drag_drop;
2096 widget_class->drag_begin = individual_view_drag_begin;
2097 widget_class->drag_data_get = individual_view_drag_data_get;
2098 widget_class->drag_end = individual_view_drag_end;
2099 widget_class->drag_motion = individual_view_drag_motion;
2101 /* We use the class method to let user of this widget to connect to
2102 * the signal and stop emission of the signal so the default handler
2103 * won't be called. */
2104 tree_view_class->row_activated = individual_view_row_activated;
2106 klass->drag_individual_received = real_drag_individual_received_cb;
2108 signals[DRAG_INDIVIDUAL_RECEIVED] =
2109 g_signal_new ("drag-individual-received",
2110 G_OBJECT_CLASS_TYPE (klass),
2112 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2114 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2115 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2116 G_TYPE_STRING, G_TYPE_STRING);
2118 signals[DRAG_PERSONA_RECEIVED] =
2119 g_signal_new ("drag-persona-received",
2120 G_OBJECT_CLASS_TYPE (klass),
2122 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2124 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2125 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2127 g_object_class_install_property (object_class,
2129 g_param_spec_object ("store",
2130 "The store of the view",
2131 "The store of the view",
2132 EMPATHY_TYPE_INDIVIDUAL_STORE,
2133 G_PARAM_READWRITE));
2134 g_object_class_install_property (object_class,
2136 g_param_spec_flags ("view-features",
2137 "Features of the view",
2138 "Flags for all enabled features",
2139 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2140 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2141 g_object_class_install_property (object_class,
2142 PROP_INDIVIDUAL_FEATURES,
2143 g_param_spec_flags ("individual-features",
2144 "Features of the individual menu",
2145 "Flags for all enabled features for the menu",
2146 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2147 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2148 g_object_class_install_property (object_class,
2150 g_param_spec_boolean ("show-offline",
2152 "Whether contact list should display "
2153 "offline contacts", FALSE, G_PARAM_READWRITE));
2154 g_object_class_install_property (object_class,
2155 PROP_SHOW_UNTRUSTED,
2156 g_param_spec_boolean ("show-untrusted",
2157 "Show Untrusted Individuals",
2158 "Whether the view should display untrusted individuals; "
2159 "those who could not be who they say they are.",
2160 TRUE, G_PARAM_READWRITE));
2162 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2166 empathy_individual_view_init (EmpathyIndividualView *view)
2168 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2169 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2173 priv->show_untrusted = TRUE;
2175 /* Get saved group states. */
2176 empathy_contact_groups_get_all ();
2178 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2179 (GDestroyNotify) g_free, NULL);
2181 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2182 empathy_individual_store_row_separator_func, NULL, NULL);
2184 /* Connect to tree view signals rather than override. */
2185 g_signal_connect (view, "button-press-event",
2186 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2187 g_signal_connect (view, "key-press-event",
2188 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2189 g_signal_connect (view, "row-expanded",
2190 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2191 GINT_TO_POINTER (TRUE));
2192 g_signal_connect (view, "row-collapsed",
2193 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2194 GINT_TO_POINTER (FALSE));
2195 g_signal_connect (view, "query-tooltip",
2196 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2199 EmpathyIndividualView *
2200 empathy_individual_view_new (EmpathyIndividualStore *store,
2201 EmpathyIndividualViewFeatureFlags view_features,
2202 EmpathyIndividualFeatureFlags individual_features)
2204 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2206 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2208 "individual-features", individual_features,
2209 "view-features", view_features, NULL);
2213 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2215 EmpathyIndividualViewPriv *priv;
2216 GtkTreeSelection *selection;
2218 GtkTreeModel *model;
2219 FolksIndividual *individual;
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_INDIVIDUAL, &individual, -1);
2236 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2237 gboolean *is_fake_group)
2239 EmpathyIndividualViewPriv *priv;
2240 GtkTreeSelection *selection;
2242 GtkTreeModel *model;
2247 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2249 priv = GET_PRIV (view);
2251 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2252 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2255 gtk_tree_model_get (model, &iter,
2256 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2257 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2258 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2266 if (is_fake_group != NULL)
2267 *is_fake_group = fake;
2273 individual_view_remove_dialog_show (GtkWindow *parent,
2274 const gchar *message,
2275 const gchar *secondary_text,
2276 gboolean block_button)
2281 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2282 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2285 gtk_dialog_add_button (GTK_DIALOG (dialog),
2286 _("Delete and Block"), GTK_RESPONSE_REJECT);
2288 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2289 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2290 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2291 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2292 "%s", secondary_text);
2294 gtk_widget_show (dialog);
2296 res = gtk_dialog_run (GTK_DIALOG (dialog));
2297 gtk_widget_destroy (dialog);
2303 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2304 EmpathyIndividualView *view)
2308 group = empathy_individual_view_dup_selected_group (view, NULL);
2315 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2317 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2318 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2319 text, FALSE) == GTK_RESPONSE_YES)
2321 EmpathyIndividualManager *manager =
2322 empathy_individual_manager_dup_singleton ();
2323 empathy_individual_manager_remove_group (manager, group);
2324 g_object_unref (G_OBJECT (manager));
2334 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2336 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2341 gboolean is_fake_group;
2343 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2345 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2346 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2349 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2350 if (!group || is_fake_group)
2352 /* We can't alter fake groups */
2357 menu = gtk_menu_new ();
2360 if (priv->view_features &
2361 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2362 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2363 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2364 gtk_widget_show (item);
2365 g_signal_connect (item, "activate",
2366 G_CALLBACK (individual_view_group_rename_activate_cb),
2371 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2373 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2374 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2375 GTK_ICON_SIZE_MENU);
2376 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2377 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2378 gtk_widget_show (item);
2379 g_signal_connect (item, "activate",
2380 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2389 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2390 EmpathyIndividualView *view)
2392 FolksIndividual *individual;
2394 individual = empathy_individual_view_dup_selected (view);
2396 if (individual != NULL)
2398 EmpathyIndividualManager *manager;
2401 GList *l, *personas;
2402 guint persona_count = 0;
2406 personas = folks_individual_get_personas (individual);
2408 /* If we have more than one TpfPersona, display a different message
2409 * ensuring the user knows that *all* of the meta-contacts' personas will
2411 for (l = personas; l != NULL; l = l->next)
2413 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2417 if (persona_count >= 2)
2421 if (persona_count < 2)
2423 /* Not a meta-contact */
2426 _("Do you really want to remove the contact '%s'?"),
2427 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2434 _("Do you really want to remove the linked contact '%s'? "
2435 "Note that this will remove all the contacts which make up "
2436 "this linked contact."),
2437 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2441 manager = empathy_individual_manager_dup_singleton ();
2442 can_block = empathy_individual_manager_supports_blocking (manager,
2444 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2445 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2448 if (res == GTK_RESPONSE_YES || res == GTK_RESPONSE_REJECT)
2450 empathy_individual_manager_remove (manager, individual, "");
2452 if (res == GTK_RESPONSE_REJECT)
2453 empathy_individual_manager_set_blocked (manager, individual, TRUE);
2457 g_object_unref (individual);
2458 g_object_unref (manager);
2463 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2464 EmpathyLinkingDialog *linking_dialog,
2465 EmpathyIndividualView *self)
2467 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2468 EmpathyIndividualLinker *linker;
2470 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2471 empathy_individual_linker_set_search_text (linker,
2472 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2476 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2478 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2479 FolksIndividual *individual;
2480 GtkWidget *menu = NULL;
2483 gboolean can_remove = FALSE;
2486 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2488 individual = empathy_individual_view_dup_selected (view);
2489 if (individual == NULL)
2492 /* If any of the Individual's personas can be removed, add an option to
2493 * remove. This will act as a best-effort option. If any Personas cannot be
2494 * removed from the server, then this option will just be inactive upon
2495 * subsequent menu openings */
2496 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2498 FolksPersona *persona = FOLKS_PERSONA (l->data);
2499 FolksPersonaStore *store = folks_persona_get_store (persona);
2500 FolksMaybeBool maybe_can_remove =
2501 folks_persona_store_get_can_remove_personas (store);
2503 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2510 menu = empathy_individual_menu_new (individual, priv->individual_features);
2512 /* Remove contact */
2513 if ((priv->view_features &
2514 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2517 /* create the menu if required, or just add a separator */
2519 menu = gtk_menu_new ();
2522 item = gtk_separator_menu_item_new ();
2523 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2524 gtk_widget_show (item);
2528 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2529 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2530 GTK_ICON_SIZE_MENU);
2531 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2532 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2533 gtk_widget_show (item);
2534 g_signal_connect (item, "activate",
2535 G_CALLBACK (individual_view_remove_activate_cb), view);
2538 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2539 * set the live search text on the new linking dialogue to be the same as
2541 g_signal_connect (menu, "link-contacts-activated",
2542 (GCallback) individual_menu_link_contacts_activated_cb, view);
2544 g_object_unref (individual);
2550 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2551 EmpathyLiveSearch *search)
2553 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2555 /* remove old handlers if old search was not null */
2556 if (priv->search_widget != NULL)
2558 g_signal_handlers_disconnect_by_func (view,
2559 individual_view_start_search_cb, NULL);
2561 g_signal_handlers_disconnect_by_func (priv->search_widget,
2562 individual_view_search_text_notify_cb, view);
2563 g_signal_handlers_disconnect_by_func (priv->search_widget,
2564 individual_view_search_activate_cb, view);
2565 g_signal_handlers_disconnect_by_func (priv->search_widget,
2566 individual_view_search_key_navigation_cb, view);
2567 g_signal_handlers_disconnect_by_func (priv->search_widget,
2568 individual_view_search_hide_cb, view);
2569 g_signal_handlers_disconnect_by_func (priv->search_widget,
2570 individual_view_search_show_cb, view);
2571 g_object_unref (priv->search_widget);
2572 priv->search_widget = NULL;
2575 /* connect handlers if new search is not null */
2578 priv->search_widget = g_object_ref (search);
2580 g_signal_connect (view, "start-interactive-search",
2581 G_CALLBACK (individual_view_start_search_cb), NULL);
2583 g_signal_connect (priv->search_widget, "notify::text",
2584 G_CALLBACK (individual_view_search_text_notify_cb), view);
2585 g_signal_connect (priv->search_widget, "activate",
2586 G_CALLBACK (individual_view_search_activate_cb), view);
2587 g_signal_connect (priv->search_widget, "key-navigation",
2588 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2589 g_signal_connect (priv->search_widget, "hide",
2590 G_CALLBACK (individual_view_search_hide_cb), view);
2591 g_signal_connect (priv->search_widget, "show",
2592 G_CALLBACK (individual_view_search_show_cb), view);
2597 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2599 EmpathyIndividualViewPriv *priv;
2601 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2603 priv = GET_PRIV (self);
2605 return (priv->search_widget != NULL &&
2606 gtk_widget_get_visible (priv->search_widget));
2610 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2612 EmpathyIndividualViewPriv *priv;
2614 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2616 priv = GET_PRIV (self);
2618 return priv->show_offline;
2622 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2623 gboolean show_offline)
2625 EmpathyIndividualViewPriv *priv;
2627 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2629 priv = GET_PRIV (self);
2631 priv->show_offline = show_offline;
2633 g_object_notify (G_OBJECT (self), "show-offline");
2634 gtk_tree_model_filter_refilter (priv->filter);
2638 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2640 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2642 return GET_PRIV (self)->show_untrusted;
2646 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2647 gboolean show_untrusted)
2649 EmpathyIndividualViewPriv *priv;
2651 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2653 priv = GET_PRIV (self);
2655 priv->show_untrusted = show_untrusted;
2657 g_object_notify (G_OBJECT (self), "show-untrusted");
2658 gtk_tree_model_filter_refilter (priv->filter);
2661 EmpathyIndividualStore *
2662 empathy_individual_view_get_store (EmpathyIndividualView *self)
2664 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2666 return GET_PRIV (self)->store;
2670 empathy_individual_view_set_store (EmpathyIndividualView *self,
2671 EmpathyIndividualStore *store)
2673 EmpathyIndividualViewPriv *priv;
2675 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2676 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2678 priv = GET_PRIV (self);
2680 /* Destroy the old filter and remove the old store */
2681 if (priv->store != NULL)
2683 g_signal_handlers_disconnect_by_func (priv->store,
2684 individual_view_store_row_changed_cb, self);
2685 g_signal_handlers_disconnect_by_func (priv->store,
2686 individual_view_store_row_deleted_cb, self);
2688 g_signal_handlers_disconnect_by_func (priv->filter,
2689 individual_view_row_has_child_toggled_cb, self);
2691 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2694 tp_clear_object (&priv->filter);
2695 tp_clear_object (&priv->store);
2697 /* Set the new store */
2698 priv->store = store;
2702 g_object_ref (store);
2704 /* Create a new filter */
2705 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2706 GTK_TREE_MODEL (priv->store), NULL));
2707 gtk_tree_model_filter_set_visible_func (priv->filter,
2708 individual_view_filter_visible_func, self, NULL);
2710 g_signal_connect (priv->filter, "row-has-child-toggled",
2711 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2712 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2713 GTK_TREE_MODEL (priv->filter));
2715 tp_g_signal_connect_object (priv->store, "row-changed",
2716 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2717 tp_g_signal_connect_object (priv->store, "row-inserted",
2718 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2719 tp_g_signal_connect_object (priv->store, "row-deleted",
2720 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2725 empathy_individual_view_start_search (EmpathyIndividualView *self)
2727 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2729 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2730 g_return_if_fail (priv->search_widget != NULL);
2732 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2733 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2735 gtk_widget_show (GTK_WIDGET (priv->search_widget));