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-request-util.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-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
91 } EmpathyIndividualViewPriv;
95 EmpathyIndividualView *view;
102 EmpathyIndividualView *view;
103 FolksIndividual *individual;
112 PROP_INDIVIDUAL_FEATURES,
117 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
118 * specific EmpathyContacts (between/in/out of Individuals) */
121 DND_DRAG_TYPE_INDIVIDUAL_ID,
122 DND_DRAG_TYPE_PERSONA_ID,
123 DND_DRAG_TYPE_URI_LIST,
124 DND_DRAG_TYPE_STRING,
127 #define DRAG_TYPE(T,I) \
128 { (gchar *) T, 0, I }
130 static const GtkTargetEntry drag_types_dest[] = {
131 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
132 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
133 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
135 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
136 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
139 static const GtkTargetEntry drag_types_source[] = {
140 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
145 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
146 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
150 DRAG_INDIVIDUAL_RECEIVED,
151 DRAG_PERSONA_RECEIVED,
155 static guint signals[LAST_SIGNAL];
157 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
161 individual_view_tooltip_destroy_cb (GtkWidget *widget,
162 EmpathyIndividualView *view)
164 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
166 tp_clear_object (&priv->tooltip_widget);
170 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
173 gboolean keyboard_mode,
177 EmpathyIndividualViewPriv *priv;
178 FolksIndividual *individual;
182 static gint running = 0;
183 gboolean ret = FALSE;
185 priv = GET_PRIV (view);
187 /* Avoid an infinite loop. See GNOME bug #574377 */
193 /* Don't show the tooltip if there's already a popup menu */
194 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
197 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
198 keyboard_mode, &model, &path, &iter))
201 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
202 gtk_tree_path_free (path);
204 gtk_tree_model_get (model, &iter,
205 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
207 if (individual == NULL)
210 if (priv->tooltip_widget == NULL)
212 priv->tooltip_widget = empathy_individual_widget_new (individual,
213 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
214 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
215 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
216 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
217 g_object_ref (priv->tooltip_widget);
218 g_signal_connect (priv->tooltip_widget, "destroy",
219 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
220 gtk_widget_show (priv->tooltip_widget);
224 empathy_individual_widget_set_individual (
225 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
228 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
231 g_object_unref (individual);
239 groups_change_group_cb (GObject *source,
240 GAsyncResult *result,
243 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
244 GError *error = NULL;
246 folks_group_details_change_group_finish (group_details, result, &error);
249 g_warning ("failed to change group: %s", error->message);
250 g_clear_error (&error);
255 group_can_be_modified (const gchar *name,
256 gboolean is_fake_group,
259 /* Real groups can always be modified */
263 /* The favorite fake group can be modified so users can
264 * add/remove favorites using DnD */
265 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
268 /* We can remove contacts from the 'ungrouped' fake group */
269 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
276 individual_view_individual_drag_received (GtkWidget *self,
277 GdkDragContext *context,
280 GtkSelectionData *selection)
282 EmpathyIndividualViewPriv *priv;
283 EmpathyIndividualManager *manager = NULL;
284 FolksIndividual *individual;
285 GtkTreePath *source_path;
286 const gchar *sel_data;
287 gchar *new_group = NULL;
288 gchar *old_group = NULL;
289 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
291 priv = GET_PRIV (self);
293 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
294 new_group = empathy_individual_store_get_parent_group (model, path,
295 NULL, &new_group_is_fake);
297 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
300 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
301 * feature. Otherwise, we just add the dropped contact to whichever group
302 * they were dropped in, and don't remove them from their old group. This
303 * allows for Individual views which shouldn't allow Individuals to have
304 * their groups changed, and also for dragging Individuals between Individual
306 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
307 priv->drag_row != NULL)
309 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
313 empathy_individual_store_get_parent_group (model, source_path,
314 NULL, &old_group_is_fake);
315 gtk_tree_path_free (source_path);
318 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
321 if (!tp_strdiff (old_group, new_group))
324 else if (priv->drag_row != NULL)
326 /* We don't allow changing Individuals' groups, and this Individual was
327 * dragged from another group in *this* Individual view, so we disallow
332 /* XXX: for contacts, we used to ensure the account, create the contact
333 * factory, and then wait on the contacts. But they should already be
334 * created by this point */
336 manager = empathy_individual_manager_dup_singleton ();
337 individual = empathy_individual_manager_lookup_member (manager, sel_data);
339 if (individual == NULL)
341 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
345 /* FIXME: We should probably wait for the cb before calling
348 /* Emit a signal notifying of the drag. We change the Individual's groups in
349 * the default signal handler. */
350 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
351 gdk_drag_context_get_selected_action (context), individual, new_group,
357 tp_clear_object (&manager);
365 real_drag_individual_received_cb (EmpathyIndividualView *self,
366 GdkDragAction action,
367 FolksIndividual *individual,
368 const gchar *new_group,
369 const gchar *old_group)
371 DEBUG ("individual %s dragged from '%s' to '%s'",
372 folks_individual_get_id (individual), old_group, new_group);
374 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
376 /* Mark contact as favourite */
377 folks_favourite_details_set_is_favourite (
378 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
382 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
384 /* Remove contact as favourite */
385 folks_favourite_details_set_is_favourite (
386 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
388 /* Don't try to remove it */
392 if (new_group != NULL)
394 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
395 new_group, TRUE, groups_change_group_cb, NULL);
398 if (old_group != NULL && action == GDK_ACTION_MOVE)
400 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
401 old_group, FALSE, groups_change_group_cb, NULL);
406 individual_view_persona_drag_received (GtkWidget *self,
407 GdkDragContext *context,
410 GtkSelectionData *selection)
412 EmpathyIndividualManager *manager = NULL;
413 FolksIndividual *individual = NULL;
414 FolksPersona *persona = NULL;
415 const gchar *persona_uid;
416 GList *individuals, *l;
417 gboolean retval = FALSE;
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_details_is_online (
681 FOLKS_PRESENCE_DETAILS (individual)) &&
682 (caps & EMPATHY_CAPABILITIES_FT))
684 gdk_drag_status (context, GDK_ACTION_COPY, time_);
685 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
686 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
690 gdk_drag_status (context, 0, time_);
691 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
695 if (individual != NULL)
696 g_object_unref (individual);
698 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
699 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
700 priv->drag_row == NULL)) ||
701 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
702 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
704 /* If target != GDK_NONE, then we have a contact (individual or persona)
705 drag. If we're pointing to a group, highlight it. Otherwise, if the
706 contact we're pointing to is in a group, highlight that. Otherwise,
707 set the drag position to before the first row for a drag into
708 the "non-group" at the top.
709 If it's an Individual:
710 We only highlight things if the contact is from a different
711 Individual view, or if this Individual view has
712 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
713 which don't have FEATURE_GROUPS_CHANGE, but do have
714 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
716 We only highlight things if we have FEATURE_PERSONA_DROP.
718 GtkTreeIter group_iter;
720 GtkTreePath *group_path;
721 gtk_tree_model_get (model, &iter,
722 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
729 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
730 gtk_tree_model_get (model, &group_iter,
731 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
735 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
736 group_path = gtk_tree_model_get_path (model, &group_iter);
737 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
738 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
739 gtk_tree_path_free (group_path);
743 group_path = gtk_tree_path_new_first ();
744 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
745 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
746 group_path, GTK_TREE_VIEW_DROP_BEFORE);
750 if (!is_different && !cleanup)
755 gtk_tree_path_free (dm->path);
758 g_source_remove (dm->timeout_id);
766 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
768 dm = g_new0 (DragMotionData, 1);
770 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
771 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
772 dm->path = gtk_tree_path_copy (path);
774 dm->timeout_id = g_timeout_add_seconds (1,
775 (GSourceFunc) individual_view_drag_motion_cb, dm);
782 individual_view_drag_begin (GtkWidget *widget,
783 GdkDragContext *context)
785 EmpathyIndividualViewPriv *priv;
786 GtkTreeSelection *selection;
791 priv = GET_PRIV (widget);
793 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
796 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
797 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
800 path = gtk_tree_model_get_path (model, &iter);
801 priv->drag_row = gtk_tree_row_reference_new (model, path);
802 gtk_tree_path_free (path);
806 individual_view_drag_data_get (GtkWidget *widget,
807 GdkDragContext *context,
808 GtkSelectionData *selection,
812 EmpathyIndividualViewPriv *priv;
813 GtkTreePath *src_path;
816 FolksIndividual *individual;
817 const gchar *individual_id;
819 priv = GET_PRIV (widget);
821 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
822 if (priv->drag_row == NULL)
825 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
826 if (src_path == NULL)
829 if (!gtk_tree_model_get_iter (model, &iter, src_path))
831 gtk_tree_path_free (src_path);
835 gtk_tree_path_free (src_path);
838 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
839 if (individual == NULL)
842 individual_id = folks_individual_get_id (individual);
844 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
846 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
847 (guchar *) individual_id, strlen (individual_id) + 1);
850 g_object_unref (individual);
854 individual_view_drag_end (GtkWidget *widget,
855 GdkDragContext *context)
857 EmpathyIndividualViewPriv *priv;
859 priv = GET_PRIV (widget);
861 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
866 gtk_tree_row_reference_free (priv->drag_row);
867 priv->drag_row = NULL;
872 individual_view_drag_drop (GtkWidget *widget,
873 GdkDragContext *drag_context,
883 EmpathyIndividualView *view;
889 menu_deactivate_cb (GtkMenuShell *menushell,
892 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
893 g_signal_handlers_disconnect_by_func (menushell,
894 menu_deactivate_cb, user_data);
896 gtk_menu_detach (GTK_MENU (menushell));
900 individual_view_popup_menu_idle_cb (gpointer user_data)
902 MenuPopupData *data = user_data;
905 menu = empathy_individual_view_get_individual_menu (data->view);
907 menu = empathy_individual_view_get_group_menu (data->view);
911 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
913 gtk_widget_show (menu);
914 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
917 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
918 * floating ref. We can either wait that the treeview releases its ref
919 * when it will be destroyed (when leaving Empathy) or explicitely
920 * detach the menu when it's not displayed any more.
921 * We go for the latter as we don't want to keep useless menus in memory
922 * during the whole lifetime of Empathy. */
923 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
927 g_slice_free (MenuPopupData, data);
933 individual_view_button_press_event_cb (EmpathyIndividualView *view,
934 GdkEventButton *event,
937 if (event->button == 3)
941 data = g_slice_new (MenuPopupData);
943 data->button = event->button;
944 data->time = event->time;
945 g_idle_add (individual_view_popup_menu_idle_cb, data);
952 individual_view_key_press_event_cb (EmpathyIndividualView *view,
956 if (event->keyval == GDK_KEY_Menu)
960 data = g_slice_new (MenuPopupData);
963 data->time = event->time;
964 g_idle_add (individual_view_popup_menu_idle_cb, data);
965 } else if (event->keyval == GDK_KEY_F2) {
966 FolksIndividual *individual;
967 EmpathyContact *contact;
969 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
971 individual = empathy_individual_view_dup_selected (view);
972 if (individual == NULL)
975 contact = empathy_contact_dup_from_folks_individual (individual);
976 if (contact == NULL) {
977 g_object_unref (individual);
980 empathy_contact_edit_dialog_show (contact, NULL);
982 g_object_unref (individual);
983 g_object_unref (contact);
990 individual_view_row_activated (GtkTreeView *view,
992 GtkTreeViewColumn *column)
994 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
995 FolksIndividual *individual;
996 EmpathyContact *contact;
1000 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1003 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1004 gtk_tree_model_get_iter (model, &iter, path);
1005 gtk_tree_model_get (model, &iter,
1006 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1008 if (individual == NULL)
1011 /* Determine which Persona to chat to, by choosing the most available one. */
1012 contact = empathy_contact_dup_best_for_action (individual,
1013 EMPATHY_ACTION_CHAT);
1015 if (contact != NULL)
1017 DEBUG ("Starting a chat");
1019 empathy_chat_with_contact (contact,
1020 gtk_get_current_event_time ());
1023 g_object_unref (individual);
1024 tp_clear_object (&contact);
1028 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1029 const gchar *path_string,
1030 EmpathyIndividualView *view)
1032 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1034 GtkTreeModel *model;
1036 FolksIndividual *individual;
1037 GdkEventButton *event;
1038 GtkMenuShell *shell;
1041 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1044 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1045 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1048 gtk_tree_model_get (model, &iter,
1049 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1050 if (individual == NULL)
1053 event = (GdkEventButton *) gtk_get_current_event ();
1055 menu = empathy_context_menu_new (GTK_WIDGET (view));
1056 shell = GTK_MENU_SHELL (menu);
1059 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1060 gtk_menu_shell_append (shell, item);
1061 gtk_widget_show (item);
1064 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1065 gtk_menu_shell_append (shell, item);
1066 gtk_widget_show (item);
1068 gtk_widget_show (menu);
1069 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1070 event->button, event->time);
1072 g_object_unref (individual);
1076 individual_view_cell_set_background (EmpathyIndividualView *view,
1077 GtkCellRenderer *cell,
1081 if (!is_group && is_active)
1083 GtkStyleContext *style;
1086 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1088 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1091 /* Here we take the current theme colour and add it to
1092 * the colour for white and average the two. This
1093 * gives a colour which is inline with the theme but
1096 empathy_make_color_whiter (&color);
1098 g_object_set (cell, "cell-background-rgba", &color, NULL);
1101 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1105 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1106 GtkCellRenderer *cell,
1107 GtkTreeModel *model,
1109 EmpathyIndividualView *view)
1115 gtk_tree_model_get (model, iter,
1116 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1117 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1118 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1121 "visible", !is_group,
1125 tp_clear_object (&pixbuf);
1127 individual_view_cell_set_background (view, cell, is_group, is_active);
1131 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1132 GtkCellRenderer *cell,
1133 GtkTreeModel *model,
1135 EmpathyIndividualView *view)
1137 GdkPixbuf *pixbuf = NULL;
1141 gtk_tree_model_get (model, iter,
1142 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1143 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1148 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1150 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1151 GTK_ICON_SIZE_MENU);
1153 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1155 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1156 GTK_ICON_SIZE_MENU);
1161 "visible", pixbuf != NULL,
1165 tp_clear_object (&pixbuf);
1171 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1172 GtkCellRenderer *cell,
1173 GtkTreeModel *model,
1175 EmpathyIndividualView *view)
1179 gboolean can_audio, can_video;
1181 gtk_tree_model_get (model, iter,
1182 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1183 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1184 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1185 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1188 "visible", !is_group && (can_audio || can_video),
1189 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1192 individual_view_cell_set_background (view, cell, is_group, is_active);
1196 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1197 GtkCellRenderer *cell,
1198 GtkTreeModel *model,
1200 EmpathyIndividualView *view)
1203 gboolean show_avatar;
1207 gtk_tree_model_get (model, iter,
1208 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1209 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1210 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1211 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1214 "visible", !is_group && show_avatar,
1218 tp_clear_object (&pixbuf);
1220 individual_view_cell_set_background (view, cell, is_group, is_active);
1224 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1225 GtkCellRenderer *cell,
1226 GtkTreeModel *model,
1228 EmpathyIndividualView *view)
1233 gtk_tree_model_get (model, iter,
1234 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1235 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1237 individual_view_cell_set_background (view, cell, is_group, is_active);
1241 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1242 GtkCellRenderer *cell,
1243 GtkTreeModel *model,
1245 EmpathyIndividualView *view)
1250 gtk_tree_model_get (model, iter,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1252 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1254 if (gtk_tree_model_iter_has_child (model, iter))
1257 gboolean row_expanded;
1259 path = gtk_tree_model_get_path (model, iter);
1261 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1262 (gtk_tree_view_column_get_tree_view (column)), path);
1263 gtk_tree_path_free (path);
1268 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1272 g_object_set (cell, "visible", FALSE, NULL);
1274 individual_view_cell_set_background (view, cell, is_group, is_active);
1278 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1283 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1284 GtkTreeModel *model;
1288 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1291 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1293 gtk_tree_model_get (model, iter,
1294 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1296 expanded = GPOINTER_TO_INT (user_data);
1297 empathy_contact_group_set_expanded (name, expanded);
1303 individual_view_start_search_cb (EmpathyIndividualView *view,
1306 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1308 if (priv->search_widget == NULL)
1311 empathy_individual_view_start_search (view);
1317 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1319 EmpathyIndividualView *view)
1321 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1323 GtkTreeViewColumn *focus_column;
1324 GtkTreeModel *model;
1326 gboolean set_cursor = FALSE;
1328 gtk_tree_model_filter_refilter (priv->filter);
1330 /* Set cursor on the first contact. If it is already set on a group,
1331 * set it on its first child contact. Note that first child of a group
1332 * is its separator, that's why we actually set to the 2nd
1335 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1336 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1340 path = gtk_tree_path_new_from_string ("0:1");
1343 else if (gtk_tree_path_get_depth (path) < 2)
1347 gtk_tree_model_get_iter (model, &iter, path);
1348 gtk_tree_model_get (model, &iter,
1349 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1354 gtk_tree_path_down (path);
1355 gtk_tree_path_next (path);
1362 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1364 if (gtk_tree_model_get_iter (model, &iter, path))
1366 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1371 gtk_tree_path_free (path);
1375 individual_view_search_activate_cb (GtkWidget *search,
1376 EmpathyIndividualView *view)
1379 GtkTreeViewColumn *focus_column;
1381 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1384 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1385 gtk_tree_path_free (path);
1387 gtk_widget_hide (search);
1392 individual_view_search_key_navigation_cb (GtkWidget *search,
1394 EmpathyIndividualView *view)
1396 GdkEventKey *eventkey = ((GdkEventKey *) event);
1397 gboolean ret = FALSE;
1399 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1400 || eventkey->keyval == GDK_KEY_F2)
1402 GdkEvent *new_event;
1404 new_event = gdk_event_copy (event);
1405 gtk_widget_grab_focus (GTK_WIDGET (view));
1406 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1407 gtk_widget_grab_focus (search);
1409 gdk_event_free (new_event);
1416 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1417 EmpathyIndividualView *view)
1419 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1420 GtkTreeModel *model;
1421 GtkTreePath *cursor_path;
1423 gboolean valid = FALSE;
1425 /* block expand or collapse handlers, they would write the
1426 * expand or collapsed setting to file otherwise */
1427 g_signal_handlers_block_by_func (view,
1428 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1429 g_signal_handlers_block_by_func (view,
1430 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1432 /* restore which groups are expanded and which are not */
1433 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1434 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1435 valid; valid = gtk_tree_model_iter_next (model, &iter))
1441 gtk_tree_model_get (model, &iter,
1442 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1443 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1452 path = gtk_tree_model_get_path (model, &iter);
1453 if ((priv->view_features &
1454 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1455 empathy_contact_group_get_expanded (name))
1457 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1461 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1464 gtk_tree_path_free (path);
1468 /* unblock expand or collapse handlers */
1469 g_signal_handlers_unblock_by_func (view,
1470 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1471 g_signal_handlers_unblock_by_func (view,
1472 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1474 /* keep the selected contact visible */
1475 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1477 if (cursor_path != NULL)
1478 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1481 gtk_tree_path_free (cursor_path);
1485 individual_view_search_show_cb (EmpathyLiveSearch *search,
1486 EmpathyIndividualView *view)
1488 /* block expand or collapse handlers during expand all, they would
1489 * write the expand or collapsed setting to file otherwise */
1490 g_signal_handlers_block_by_func (view,
1491 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1493 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1495 g_signal_handlers_unblock_by_func (view,
1496 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1500 expand_idle_foreach_cb (GtkTreeModel *model,
1503 EmpathyIndividualView *self)
1505 EmpathyIndividualViewPriv *priv;
1507 gpointer should_expand;
1510 /* We only want groups */
1511 if (gtk_tree_path_get_depth (path) > 1)
1514 gtk_tree_model_get (model, iter,
1515 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1516 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1519 if (is_group == FALSE)
1525 priv = GET_PRIV (self);
1527 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1528 &should_expand) == TRUE)
1530 if (GPOINTER_TO_INT (should_expand) == TRUE)
1531 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1533 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1535 g_hash_table_remove (priv->expand_groups, name);
1544 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1546 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1548 DEBUG ("individual_view_expand_idle_cb");
1550 g_signal_handlers_block_by_func (self,
1551 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1552 g_signal_handlers_block_by_func (self,
1553 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1555 /* The store/filter could've been removed while we were in the idle queue */
1556 if (priv->filter != NULL)
1558 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1559 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1562 g_signal_handlers_unblock_by_func (self,
1563 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1564 g_signal_handlers_unblock_by_func (self,
1565 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1567 /* Empty the table of groups to expand/contract, since it may contain groups
1568 * which no longer exist in the tree view. This can happen after going
1569 * offline, for example. */
1570 g_hash_table_remove_all (priv->expand_groups);
1571 priv->expand_groups_idle_handler = 0;
1572 g_object_unref (self);
1578 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1581 EmpathyIndividualView *view)
1583 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1584 gboolean should_expand, is_group = FALSE;
1586 gpointer will_expand;
1588 gtk_tree_model_get (model, iter,
1589 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1590 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1593 if (!is_group || EMP_STR_EMPTY (name))
1599 should_expand = (priv->view_features &
1600 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1601 (priv->search_widget != NULL &&
1602 gtk_widget_get_visible (priv->search_widget)) ||
1603 empathy_contact_group_get_expanded (name);
1605 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1606 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1607 * a hash table, and expand or contract them as appropriate all at once in
1608 * an idle handler which iterates over all the group rows. */
1609 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1610 &will_expand) == FALSE ||
1611 GPOINTER_TO_INT (will_expand) != should_expand)
1613 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1614 GINT_TO_POINTER (should_expand));
1616 if (priv->expand_groups_idle_handler == 0)
1618 priv->expand_groups_idle_handler =
1619 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1620 g_object_ref (view));
1627 /* FIXME: This is a workaround for bgo#621076 */
1629 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1632 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1633 GtkTreeModel *model;
1634 GtkTreePath *parent_path;
1635 GtkTreeIter parent_iter;
1637 if (gtk_tree_path_get_depth (path) < 2)
1640 /* A group row is visible if and only if at least one if its child is visible.
1641 * So when a row is inserted/deleted/changed in the base model, that could
1642 * modify the visibility of its parent in the filter model.
1645 model = GTK_TREE_MODEL (priv->store);
1646 parent_path = gtk_tree_path_copy (path);
1647 gtk_tree_path_up (parent_path);
1648 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1650 /* This tells the filter to verify the visibility of that row, and
1651 * show/hide it if necessary */
1652 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1653 parent_path, &parent_iter);
1655 gtk_tree_path_free (parent_path);
1659 individual_view_store_row_changed_cb (GtkTreeModel *model,
1662 EmpathyIndividualView *view)
1664 individual_view_verify_group_visibility (view, path);
1668 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1670 EmpathyIndividualView *view)
1672 individual_view_verify_group_visibility (view, path);
1676 individual_view_is_visible_individual (EmpathyIndividualView *self,
1677 FolksIndividual *individual,
1679 gboolean is_searching)
1681 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1682 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1684 GList *personas, *l;
1685 gboolean is_favorite, contains_interesting_persona = FALSE;
1687 /* We're only giving the visibility wrt filtering here, not things like
1689 if (priv->show_untrusted == FALSE &&
1690 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1695 /* Hide all individuals which consist entirely of uninteresting personas */
1696 personas = folks_individual_get_personas (individual);
1697 for (l = personas; l; l = l->next)
1699 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1701 contains_interesting_persona = TRUE;
1706 if (contains_interesting_persona == FALSE)
1709 is_favorite = folks_favourite_details_get_is_favourite (
1710 FOLKS_FAVOURITE_DETAILS (individual));
1711 if (is_searching == FALSE)
1712 return (priv->show_offline || is_online || is_favorite);
1714 /* check alias name */
1715 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1717 if (empathy_live_search_match (live, str))
1720 /* check contact id, remove the @server.com part */
1721 for (l = personas; l; l = l->next)
1724 gchar *dup_str = NULL;
1727 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1730 str = folks_persona_get_display_id (l->data);
1731 p = strstr (str, "@");
1733 str = dup_str = g_strndup (str, p - str);
1735 visible = empathy_live_search_match (live, str);
1741 /* FIXME: Add more rules here, we could check phone numbers in
1742 * contact's vCard for example. */
1748 individual_view_filter_visible_func (GtkTreeModel *model,
1752 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1753 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1754 FolksIndividual *individual = NULL;
1755 gboolean is_group, is_separator, valid;
1756 GtkTreeIter child_iter;
1757 gboolean visible, is_online;
1758 gboolean is_searching = TRUE;
1760 if (priv->search_widget == NULL ||
1761 !gtk_widget_get_visible (priv->search_widget))
1762 is_searching = FALSE;
1764 gtk_tree_model_get (model, iter,
1765 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1766 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1767 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1768 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1771 if (individual != NULL)
1773 visible = individual_view_is_visible_individual (self, individual,
1774 is_online, is_searching);
1776 g_object_unref (individual);
1778 /* FIXME: Work around bgo#626552/bgo#621076 */
1779 if (visible == TRUE)
1781 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1782 individual_view_verify_group_visibility (self, path);
1783 gtk_tree_path_free (path);
1792 /* Not a contact, not a separator, must be a group */
1793 g_return_val_if_fail (is_group, FALSE);
1795 /* only show groups which are not empty */
1796 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1797 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1799 gtk_tree_model_get (model, &child_iter,
1800 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1801 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1804 if (individual == NULL)
1807 visible = individual_view_is_visible_individual (self, individual,
1808 is_online, is_searching);
1809 g_object_unref (individual);
1811 /* show group if it has at least one visible contact in it */
1812 if (visible == TRUE)
1820 individual_view_constructed (GObject *object)
1822 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1823 GtkCellRenderer *cell;
1824 GtkTreeViewColumn *col;
1829 "headers-visible", FALSE,
1830 "show-expanders", FALSE,
1833 col = gtk_tree_view_column_new ();
1836 cell = gtk_cell_renderer_pixbuf_new ();
1837 gtk_tree_view_column_pack_start (col, cell, FALSE);
1838 gtk_tree_view_column_set_cell_data_func (col, cell,
1839 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1849 cell = gtk_cell_renderer_pixbuf_new ();
1850 gtk_tree_view_column_pack_start (col, cell, FALSE);
1851 gtk_tree_view_column_set_cell_data_func (col, cell,
1852 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1864 cell = empathy_cell_renderer_text_new ();
1865 gtk_tree_view_column_pack_start (col, cell, TRUE);
1866 gtk_tree_view_column_set_cell_data_func (col, cell,
1867 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1869 gtk_tree_view_column_add_attribute (col, cell,
1870 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1871 gtk_tree_view_column_add_attribute (col, cell,
1872 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1873 gtk_tree_view_column_add_attribute (col, cell,
1874 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1875 gtk_tree_view_column_add_attribute (col, cell,
1876 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1877 gtk_tree_view_column_add_attribute (col, cell,
1878 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1879 gtk_tree_view_column_add_attribute (col, cell,
1880 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1881 gtk_tree_view_column_add_attribute (col, cell,
1882 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1884 /* Audio Call Icon */
1885 cell = empathy_cell_renderer_activatable_new ();
1886 gtk_tree_view_column_pack_start (col, cell, FALSE);
1887 gtk_tree_view_column_set_cell_data_func (col, cell,
1888 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1891 g_object_set (cell, "visible", FALSE, NULL);
1893 g_signal_connect (cell, "path-activated",
1894 G_CALLBACK (individual_view_call_activated_cb), view);
1897 cell = gtk_cell_renderer_pixbuf_new ();
1898 gtk_tree_view_column_pack_start (col, cell, FALSE);
1899 gtk_tree_view_column_set_cell_data_func (col, cell,
1900 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1912 cell = empathy_cell_renderer_expander_new ();
1913 gtk_tree_view_column_pack_end (col, cell, FALSE);
1914 gtk_tree_view_column_set_cell_data_func (col, cell,
1915 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1918 /* Actually add the column now we have added all cell renderers */
1919 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1922 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1924 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1927 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1929 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1935 individual_view_set_view_features (EmpathyIndividualView *view,
1936 EmpathyIndividualFeatureFlags features)
1938 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1939 gboolean has_tooltip;
1941 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1943 priv->view_features = features;
1945 /* Setting reorderable is a hack that gets us row previews as drag icons
1946 for free. We override all the drag handlers. It's tricky to get the
1947 position of the drag icon right in drag_begin. GtkTreeView has special
1948 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1951 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1952 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1954 /* Update DnD source/dest */
1955 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1957 gtk_drag_source_set (GTK_WIDGET (view),
1960 G_N_ELEMENTS (drag_types_source),
1961 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1965 gtk_drag_source_unset (GTK_WIDGET (view));
1969 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1971 gtk_drag_dest_set (GTK_WIDGET (view),
1972 GTK_DEST_DEFAULT_ALL,
1974 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1978 /* FIXME: URI could still be droped depending on FT feature */
1979 gtk_drag_dest_unset (GTK_WIDGET (view));
1982 /* Update has-tooltip */
1984 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1985 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1989 individual_view_dispose (GObject *object)
1991 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1992 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1994 tp_clear_object (&priv->store);
1995 tp_clear_object (&priv->filter);
1996 tp_clear_object (&priv->tooltip_widget);
1998 empathy_individual_view_set_live_search (view, NULL);
2000 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2004 individual_view_finalize (GObject *object)
2006 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2008 if (priv->expand_groups_idle_handler != 0)
2009 g_source_remove (priv->expand_groups_idle_handler);
2010 g_hash_table_destroy (priv->expand_groups);
2012 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2016 individual_view_get_property (GObject *object,
2021 EmpathyIndividualViewPriv *priv;
2023 priv = GET_PRIV (object);
2028 g_value_set_object (value, priv->store);
2030 case PROP_VIEW_FEATURES:
2031 g_value_set_flags (value, priv->view_features);
2033 case PROP_INDIVIDUAL_FEATURES:
2034 g_value_set_flags (value, priv->individual_features);
2036 case PROP_SHOW_OFFLINE:
2037 g_value_set_boolean (value, priv->show_offline);
2039 case PROP_SHOW_UNTRUSTED:
2040 g_value_set_boolean (value, priv->show_untrusted);
2043 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2049 individual_view_set_property (GObject *object,
2051 const GValue *value,
2054 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2055 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2060 empathy_individual_view_set_store (view, g_value_get_object (value));
2062 case PROP_VIEW_FEATURES:
2063 individual_view_set_view_features (view, g_value_get_flags (value));
2065 case PROP_INDIVIDUAL_FEATURES:
2066 priv->individual_features = g_value_get_flags (value);
2068 case PROP_SHOW_OFFLINE:
2069 empathy_individual_view_set_show_offline (view,
2070 g_value_get_boolean (value));
2072 case PROP_SHOW_UNTRUSTED:
2073 empathy_individual_view_set_show_untrusted (view,
2074 g_value_get_boolean (value));
2077 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2083 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2085 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2086 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2087 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2089 object_class->constructed = individual_view_constructed;
2090 object_class->dispose = individual_view_dispose;
2091 object_class->finalize = individual_view_finalize;
2092 object_class->get_property = individual_view_get_property;
2093 object_class->set_property = individual_view_set_property;
2095 widget_class->drag_data_received = individual_view_drag_data_received;
2096 widget_class->drag_drop = individual_view_drag_drop;
2097 widget_class->drag_begin = individual_view_drag_begin;
2098 widget_class->drag_data_get = individual_view_drag_data_get;
2099 widget_class->drag_end = individual_view_drag_end;
2100 widget_class->drag_motion = individual_view_drag_motion;
2102 /* We use the class method to let user of this widget to connect to
2103 * the signal and stop emission of the signal so the default handler
2104 * won't be called. */
2105 tree_view_class->row_activated = individual_view_row_activated;
2107 klass->drag_individual_received = real_drag_individual_received_cb;
2109 signals[DRAG_INDIVIDUAL_RECEIVED] =
2110 g_signal_new ("drag-individual-received",
2111 G_OBJECT_CLASS_TYPE (klass),
2113 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2115 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2116 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2117 G_TYPE_STRING, G_TYPE_STRING);
2119 signals[DRAG_PERSONA_RECEIVED] =
2120 g_signal_new ("drag-persona-received",
2121 G_OBJECT_CLASS_TYPE (klass),
2123 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2125 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2126 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2128 g_object_class_install_property (object_class,
2130 g_param_spec_object ("store",
2131 "The store of the view",
2132 "The store of the view",
2133 EMPATHY_TYPE_INDIVIDUAL_STORE,
2134 G_PARAM_READWRITE));
2135 g_object_class_install_property (object_class,
2137 g_param_spec_flags ("view-features",
2138 "Features of the view",
2139 "Flags for all enabled features",
2140 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2141 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2142 g_object_class_install_property (object_class,
2143 PROP_INDIVIDUAL_FEATURES,
2144 g_param_spec_flags ("individual-features",
2145 "Features of the individual menu",
2146 "Flags for all enabled features for the menu",
2147 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2148 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2149 g_object_class_install_property (object_class,
2151 g_param_spec_boolean ("show-offline",
2153 "Whether contact list should display "
2154 "offline contacts", FALSE, G_PARAM_READWRITE));
2155 g_object_class_install_property (object_class,
2156 PROP_SHOW_UNTRUSTED,
2157 g_param_spec_boolean ("show-untrusted",
2158 "Show Untrusted Individuals",
2159 "Whether the view should display untrusted individuals; "
2160 "those who could not be who they say they are.",
2161 TRUE, G_PARAM_READWRITE));
2163 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2167 empathy_individual_view_init (EmpathyIndividualView *view)
2169 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2170 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2174 priv->show_untrusted = TRUE;
2176 /* Get saved group states. */
2177 empathy_contact_groups_get_all ();
2179 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2180 (GDestroyNotify) g_free, NULL);
2182 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2183 empathy_individual_store_row_separator_func, NULL, NULL);
2185 /* Connect to tree view signals rather than override. */
2186 g_signal_connect (view, "button-press-event",
2187 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2188 g_signal_connect (view, "key-press-event",
2189 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2190 g_signal_connect (view, "row-expanded",
2191 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2192 GINT_TO_POINTER (TRUE));
2193 g_signal_connect (view, "row-collapsed",
2194 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2195 GINT_TO_POINTER (FALSE));
2196 g_signal_connect (view, "query-tooltip",
2197 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2200 EmpathyIndividualView *
2201 empathy_individual_view_new (EmpathyIndividualStore *store,
2202 EmpathyIndividualViewFeatureFlags view_features,
2203 EmpathyIndividualFeatureFlags individual_features)
2205 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2207 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2209 "individual-features", individual_features,
2210 "view-features", view_features, NULL);
2214 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2216 GtkTreeSelection *selection;
2218 GtkTreeModel *model;
2219 FolksIndividual *individual;
2221 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
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_INDIVIDUAL, &individual, -1);
2234 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2235 gboolean *is_fake_group)
2237 GtkTreeSelection *selection;
2239 GtkTreeModel *model;
2244 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2246 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2247 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2250 gtk_tree_model_get (model, &iter,
2251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2252 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2253 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2261 if (is_fake_group != NULL)
2262 *is_fake_group = fake;
2269 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2270 REMOVE_DIALOG_RESPONSE_DELETE,
2271 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2275 individual_view_remove_dialog_show (GtkWindow *parent,
2276 const gchar *message,
2277 const gchar *secondary_text,
2278 gboolean block_button)
2283 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2284 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2290 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2291 * mnemonic so we have to create the button manually. */
2292 button = gtk_button_new_with_mnemonic (
2293 _("Delete and _Block"));
2295 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2296 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2298 gtk_widget_show (button);
2301 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2302 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2303 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2304 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2305 "%s", secondary_text);
2307 gtk_widget_show (dialog);
2309 res = gtk_dialog_run (GTK_DIALOG (dialog));
2310 gtk_widget_destroy (dialog);
2316 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2317 EmpathyIndividualView *view)
2321 group = empathy_individual_view_dup_selected_group (view, NULL);
2328 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2330 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2331 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2332 text, FALSE) == REMOVE_DIALOG_RESPONSE_DELETE)
2334 EmpathyIndividualManager *manager =
2335 empathy_individual_manager_dup_singleton ();
2336 empathy_individual_manager_remove_group (manager, group);
2337 g_object_unref (G_OBJECT (manager));
2347 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2349 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2354 gboolean is_fake_group;
2356 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2358 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2359 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2362 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2363 if (!group || is_fake_group)
2365 /* We can't alter fake groups */
2370 menu = gtk_menu_new ();
2373 if (priv->view_features &
2374 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2375 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2376 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2377 gtk_widget_show (item);
2378 g_signal_connect (item, "activate",
2379 G_CALLBACK (individual_view_group_rename_activate_cb),
2384 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2386 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2387 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2388 GTK_ICON_SIZE_MENU);
2389 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2390 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2391 gtk_widget_show (item);
2392 g_signal_connect (item, "activate",
2393 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2402 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2403 EmpathyIndividualView *view)
2405 FolksIndividual *individual;
2407 individual = empathy_individual_view_dup_selected (view);
2409 if (individual != NULL)
2411 EmpathyIndividualManager *manager;
2414 GList *l, *personas;
2415 guint persona_count = 0;
2419 personas = folks_individual_get_personas (individual);
2421 /* If we have more than one TpfPersona, display a different message
2422 * ensuring the user knows that *all* of the meta-contacts' personas will
2424 for (l = personas; l != NULL; l = l->next)
2426 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2430 if (persona_count >= 2)
2434 if (persona_count < 2)
2436 /* Not a meta-contact */
2439 _("Do you really want to remove the contact '%s'?"),
2440 folks_alias_details_get_alias (
2441 FOLKS_ALIAS_DETAILS (individual)));
2448 _("Do you really want to remove the linked contact '%s'? "
2449 "Note that this will remove all the contacts which make up "
2450 "this linked contact."),
2451 folks_alias_details_get_alias (
2452 FOLKS_ALIAS_DETAILS (individual)));
2456 manager = empathy_individual_manager_dup_singleton ();
2457 can_block = empathy_individual_manager_supports_blocking (manager,
2459 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2460 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2463 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2464 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2468 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2470 if (!empathy_block_individual_dialog_show (parent, individual,
2474 empathy_individual_manager_set_blocked (manager, individual,
2478 empathy_individual_manager_remove (manager, individual, "");
2483 g_object_unref (individual);
2484 g_object_unref (manager);
2489 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2490 EmpathyLinkingDialog *linking_dialog,
2491 EmpathyIndividualView *self)
2493 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2494 EmpathyIndividualLinker *linker;
2496 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2497 empathy_individual_linker_set_search_text (linker,
2498 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2502 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2504 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2505 FolksIndividual *individual;
2506 GtkWidget *menu = NULL;
2509 gboolean can_remove = FALSE;
2512 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2514 individual = empathy_individual_view_dup_selected (view);
2515 if (individual == NULL)
2518 /* If any of the Individual's personas can be removed, add an option to
2519 * remove. This will act as a best-effort option. If any Personas cannot be
2520 * removed from the server, then this option will just be inactive upon
2521 * subsequent menu openings */
2522 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2524 FolksPersona *persona = FOLKS_PERSONA (l->data);
2525 FolksPersonaStore *store = folks_persona_get_store (persona);
2526 FolksMaybeBool maybe_can_remove =
2527 folks_persona_store_get_can_remove_personas (store);
2529 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2536 menu = empathy_individual_menu_new (individual, priv->individual_features);
2538 /* Remove contact */
2539 if ((priv->view_features &
2540 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2543 /* create the menu if required, or just add a separator */
2545 menu = gtk_menu_new ();
2548 item = gtk_separator_menu_item_new ();
2549 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2550 gtk_widget_show (item);
2554 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2555 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2556 GTK_ICON_SIZE_MENU);
2557 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2558 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2559 gtk_widget_show (item);
2560 g_signal_connect (item, "activate",
2561 G_CALLBACK (individual_view_remove_activate_cb), view);
2564 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2565 * set the live search text on the new linking dialogue to be the same as
2567 g_signal_connect (menu, "link-contacts-activated",
2568 (GCallback) individual_menu_link_contacts_activated_cb, view);
2570 g_object_unref (individual);
2576 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2577 EmpathyLiveSearch *search)
2579 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2581 /* remove old handlers if old search was not null */
2582 if (priv->search_widget != NULL)
2584 g_signal_handlers_disconnect_by_func (view,
2585 individual_view_start_search_cb, NULL);
2587 g_signal_handlers_disconnect_by_func (priv->search_widget,
2588 individual_view_search_text_notify_cb, view);
2589 g_signal_handlers_disconnect_by_func (priv->search_widget,
2590 individual_view_search_activate_cb, view);
2591 g_signal_handlers_disconnect_by_func (priv->search_widget,
2592 individual_view_search_key_navigation_cb, view);
2593 g_signal_handlers_disconnect_by_func (priv->search_widget,
2594 individual_view_search_hide_cb, view);
2595 g_signal_handlers_disconnect_by_func (priv->search_widget,
2596 individual_view_search_show_cb, view);
2597 g_object_unref (priv->search_widget);
2598 priv->search_widget = NULL;
2601 /* connect handlers if new search is not null */
2604 priv->search_widget = g_object_ref (search);
2606 g_signal_connect (view, "start-interactive-search",
2607 G_CALLBACK (individual_view_start_search_cb), NULL);
2609 g_signal_connect (priv->search_widget, "notify::text",
2610 G_CALLBACK (individual_view_search_text_notify_cb), view);
2611 g_signal_connect (priv->search_widget, "activate",
2612 G_CALLBACK (individual_view_search_activate_cb), view);
2613 g_signal_connect (priv->search_widget, "key-navigation",
2614 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2615 g_signal_connect (priv->search_widget, "hide",
2616 G_CALLBACK (individual_view_search_hide_cb), view);
2617 g_signal_connect (priv->search_widget, "show",
2618 G_CALLBACK (individual_view_search_show_cb), view);
2623 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2625 EmpathyIndividualViewPriv *priv;
2627 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2629 priv = GET_PRIV (self);
2631 return (priv->search_widget != NULL &&
2632 gtk_widget_get_visible (priv->search_widget));
2636 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2638 EmpathyIndividualViewPriv *priv;
2640 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2642 priv = GET_PRIV (self);
2644 return priv->show_offline;
2648 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2649 gboolean show_offline)
2651 EmpathyIndividualViewPriv *priv;
2653 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2655 priv = GET_PRIV (self);
2657 priv->show_offline = show_offline;
2659 g_object_notify (G_OBJECT (self), "show-offline");
2660 gtk_tree_model_filter_refilter (priv->filter);
2664 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2666 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2668 return GET_PRIV (self)->show_untrusted;
2672 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2673 gboolean show_untrusted)
2675 EmpathyIndividualViewPriv *priv;
2677 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2679 priv = GET_PRIV (self);
2681 priv->show_untrusted = show_untrusted;
2683 g_object_notify (G_OBJECT (self), "show-untrusted");
2684 gtk_tree_model_filter_refilter (priv->filter);
2687 EmpathyIndividualStore *
2688 empathy_individual_view_get_store (EmpathyIndividualView *self)
2690 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2692 return GET_PRIV (self)->store;
2696 empathy_individual_view_set_store (EmpathyIndividualView *self,
2697 EmpathyIndividualStore *store)
2699 EmpathyIndividualViewPriv *priv;
2701 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2702 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2704 priv = GET_PRIV (self);
2706 /* Destroy the old filter and remove the old store */
2707 if (priv->store != NULL)
2709 g_signal_handlers_disconnect_by_func (priv->store,
2710 individual_view_store_row_changed_cb, self);
2711 g_signal_handlers_disconnect_by_func (priv->store,
2712 individual_view_store_row_deleted_cb, self);
2714 g_signal_handlers_disconnect_by_func (priv->filter,
2715 individual_view_row_has_child_toggled_cb, self);
2717 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2720 tp_clear_object (&priv->filter);
2721 tp_clear_object (&priv->store);
2723 /* Set the new store */
2724 priv->store = store;
2728 g_object_ref (store);
2730 /* Create a new filter */
2731 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2732 GTK_TREE_MODEL (priv->store), NULL));
2733 gtk_tree_model_filter_set_visible_func (priv->filter,
2734 individual_view_filter_visible_func, self, NULL);
2736 g_signal_connect (priv->filter, "row-has-child-toggled",
2737 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2738 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2739 GTK_TREE_MODEL (priv->filter));
2741 tp_g_signal_connect_object (priv->store, "row-changed",
2742 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2743 tp_g_signal_connect_object (priv->store, "row-inserted",
2744 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2745 tp_g_signal_connect_object (priv->store, "row-deleted",
2746 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2751 empathy_individual_view_start_search (EmpathyIndividualView *self)
2753 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2755 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2756 g_return_if_fail (priv->search_widget != NULL);
2758 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2759 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2761 gtk_widget_show (GTK_WIDGET (priv->search_widget));