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-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 EmpathyIndividualViewPriv *priv;
413 EmpathyIndividualManager *manager = NULL;
414 FolksIndividual *individual = NULL;
415 FolksPersona *persona = NULL;
416 const gchar *persona_uid;
417 GList *individuals, *l;
418 gboolean retval = FALSE;
420 priv = GET_PRIV (self);
422 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
424 /* FIXME: This is slow, but the only way to find the Persona we're having
426 manager = empathy_individual_manager_dup_singleton ();
427 individuals = empathy_individual_manager_get_members (manager);
429 for (l = individuals; l != NULL; l = l->next)
433 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435 for (p = personas; p != NULL; p = p->next)
437 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
440 persona = g_object_ref (p->data);
441 individual = g_object_ref (l->data);
448 g_list_free (individuals);
450 if (persona == NULL || individual == NULL)
452 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
456 /* Emit a signal notifying of the drag. We change the Individual's groups in
457 * the default signal handler. */
458 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
459 gdk_drag_context_get_selected_action (context), persona, individual,
463 tp_clear_object (&manager);
464 tp_clear_object (&persona);
465 tp_clear_object (&individual);
471 individual_view_file_drag_received (GtkWidget *view,
472 GdkDragContext *context,
475 GtkSelectionData *selection)
478 const gchar *sel_data;
479 FolksIndividual *individual;
480 EmpathyContact *contact;
482 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
484 gtk_tree_model_get_iter (model, &iter, path);
485 gtk_tree_model_get (model, &iter,
486 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
487 if (individual == NULL)
490 contact = empathy_contact_dup_from_folks_individual (individual);
491 empathy_send_file_from_uri_list (contact, sel_data);
493 g_object_unref (individual);
494 tp_clear_object (&contact);
500 individual_view_drag_data_received (GtkWidget *view,
501 GdkDragContext *context,
504 GtkSelectionData *selection,
510 GtkTreeViewDropPosition position;
512 gboolean success = TRUE;
514 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
516 /* Get destination group information. */
517 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
518 x, y, &path, &position);
523 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
525 success = individual_view_individual_drag_received (view,
526 context, model, path, selection);
528 else if (info == DND_DRAG_TYPE_PERSONA_ID)
530 success = individual_view_persona_drag_received (view, context, model,
533 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
535 success = individual_view_file_drag_received (view,
536 context, model, path, selection);
539 gtk_tree_path_free (path);
540 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
544 individual_view_drag_motion_cb (DragMotionData *data)
546 if (data->view != NULL)
548 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
549 g_object_remove_weak_pointer (G_OBJECT (data->view),
550 (gpointer *) &data->view);
553 data->timeout_id = 0;
558 /* Minimum distance between the mouse pointer and a horizontal border when we
559 start auto scrolling. */
560 #define AUTO_SCROLL_MARGIN_SIZE 20
561 /* How far to scroll per one tick. */
562 #define AUTO_SCROLL_PITCH 10
565 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
567 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
571 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
573 if (priv->distance < 0)
574 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
576 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
578 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
579 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
581 gtk_adjustment_set_value (adj, new_value);
587 individual_view_drag_motion (GtkWidget *widget,
588 GdkDragContext *context,
593 EmpathyIndividualViewPriv *priv;
597 static DragMotionData *dm = NULL;
600 gboolean is_different = FALSE;
601 gboolean cleanup = TRUE;
602 gboolean retval = TRUE;
603 GtkAllocation allocation;
605 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
606 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
609 if (priv->auto_scroll_timeout_id != 0)
611 g_source_remove (priv->auto_scroll_timeout_id);
612 priv->auto_scroll_timeout_id = 0;
615 gtk_widget_get_allocation (widget, &allocation);
617 if (y < AUTO_SCROLL_MARGIN_SIZE ||
618 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
620 if (y < AUTO_SCROLL_MARGIN_SIZE)
621 priv->distance = MIN (-y, -1);
623 priv->distance = MAX (allocation.height - y, 1);
625 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
626 (GSourceFunc) individual_view_auto_scroll_cb, widget);
629 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
630 x, y, &path, NULL, NULL, NULL);
632 cleanup &= (dm == NULL);
636 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
637 is_different = ((dm == NULL) || ((dm != NULL)
638 && gtk_tree_path_compare (dm->path, path) != 0));
645 /* Coordinates don't point to an actual row, so make sure the pointer
646 and highlighting don't indicate that a drag is possible.
648 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
649 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
652 target = gtk_drag_dest_find_target (widget, context, NULL);
653 gtk_tree_model_get_iter (model, &iter, path);
655 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
656 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
658 /* This is a file drag, and it can only be dropped on contacts,
660 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
661 * even if we have a valid target. */
662 FolksIndividual *individual = NULL;
663 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
665 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
667 gtk_tree_model_get (model, &iter,
668 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
672 if (individual != NULL)
674 EmpathyContact *contact = NULL;
676 contact = empathy_contact_dup_from_folks_individual (individual);
677 caps = empathy_contact_get_capabilities (contact);
679 tp_clear_object (&contact);
682 if (individual != NULL &&
683 folks_presence_details_is_online (
684 FOLKS_PRESENCE_DETAILS (individual)) &&
685 (caps & EMPATHY_CAPABILITIES_FT))
687 gdk_drag_status (context, GDK_ACTION_COPY, time_);
688 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
689 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
693 gdk_drag_status (context, 0, time_);
694 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
698 if (individual != NULL)
699 g_object_unref (individual);
701 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
702 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
703 priv->drag_row == NULL)) ||
704 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
705 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
707 /* If target != GDK_NONE, then we have a contact (individual or persona)
708 drag. If we're pointing to a group, highlight it. Otherwise, if the
709 contact we're pointing to is in a group, highlight that. Otherwise,
710 set the drag position to before the first row for a drag into
711 the "non-group" at the top.
712 If it's an Individual:
713 We only highlight things if the contact is from a different
714 Individual view, or if this Individual view has
715 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
716 which don't have FEATURE_GROUPS_CHANGE, but do have
717 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
719 We only highlight things if we have FEATURE_PERSONA_DROP.
721 GtkTreeIter group_iter;
723 GtkTreePath *group_path;
724 gtk_tree_model_get (model, &iter,
725 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
732 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
733 gtk_tree_model_get (model, &group_iter,
734 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
738 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
739 group_path = gtk_tree_model_get_path (model, &group_iter);
740 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
741 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
742 gtk_tree_path_free (group_path);
746 group_path = gtk_tree_path_new_first ();
747 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
748 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
749 group_path, GTK_TREE_VIEW_DROP_BEFORE);
753 if (!is_different && !cleanup)
758 gtk_tree_path_free (dm->path);
761 g_source_remove (dm->timeout_id);
769 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
771 dm = g_new0 (DragMotionData, 1);
773 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
774 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
775 dm->path = gtk_tree_path_copy (path);
777 dm->timeout_id = g_timeout_add_seconds (1,
778 (GSourceFunc) individual_view_drag_motion_cb, dm);
785 individual_view_drag_begin (GtkWidget *widget,
786 GdkDragContext *context)
788 EmpathyIndividualViewPriv *priv;
789 GtkTreeSelection *selection;
794 priv = GET_PRIV (widget);
796 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
799 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
800 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
803 path = gtk_tree_model_get_path (model, &iter);
804 priv->drag_row = gtk_tree_row_reference_new (model, path);
805 gtk_tree_path_free (path);
809 individual_view_drag_data_get (GtkWidget *widget,
810 GdkDragContext *context,
811 GtkSelectionData *selection,
815 EmpathyIndividualViewPriv *priv;
816 GtkTreePath *src_path;
819 FolksIndividual *individual;
820 const gchar *individual_id;
822 priv = GET_PRIV (widget);
824 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
825 if (priv->drag_row == NULL)
828 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
829 if (src_path == NULL)
832 if (!gtk_tree_model_get_iter (model, &iter, src_path))
834 gtk_tree_path_free (src_path);
838 gtk_tree_path_free (src_path);
841 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
842 if (individual == NULL)
845 individual_id = folks_individual_get_id (individual);
847 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
849 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
850 (guchar *) individual_id, strlen (individual_id) + 1);
853 g_object_unref (individual);
857 individual_view_drag_end (GtkWidget *widget,
858 GdkDragContext *context)
860 EmpathyIndividualViewPriv *priv;
862 priv = GET_PRIV (widget);
864 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
869 gtk_tree_row_reference_free (priv->drag_row);
870 priv->drag_row = NULL;
875 individual_view_drag_drop (GtkWidget *widget,
876 GdkDragContext *drag_context,
886 EmpathyIndividualView *view;
892 menu_deactivate_cb (GtkMenuShell *menushell,
895 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
896 g_signal_handlers_disconnect_by_func (menushell,
897 menu_deactivate_cb, user_data);
899 gtk_menu_detach (GTK_MENU (menushell));
903 individual_view_popup_menu_idle_cb (gpointer user_data)
905 MenuPopupData *data = user_data;
908 menu = empathy_individual_view_get_individual_menu (data->view);
910 menu = empathy_individual_view_get_group_menu (data->view);
914 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
916 gtk_widget_show (menu);
917 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
920 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
921 * floating ref. We can either wait that the treeview releases its ref
922 * when it will be destroyed (when leaving Empathy) or explicitely
923 * detach the menu when it's not displayed any more.
924 * We go for the latter as we don't want to keep useless menus in memory
925 * during the whole lifetime of Empathy. */
926 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
930 g_slice_free (MenuPopupData, data);
936 individual_view_button_press_event_cb (EmpathyIndividualView *view,
937 GdkEventButton *event,
940 if (event->button == 3)
944 data = g_slice_new (MenuPopupData);
946 data->button = event->button;
947 data->time = event->time;
948 g_idle_add (individual_view_popup_menu_idle_cb, data);
955 individual_view_key_press_event_cb (EmpathyIndividualView *view,
959 if (event->keyval == GDK_KEY_Menu)
963 data = g_slice_new (MenuPopupData);
966 data->time = event->time;
967 g_idle_add (individual_view_popup_menu_idle_cb, data);
968 } else if (event->keyval == GDK_KEY_F2) {
969 FolksIndividual *individual;
970 EmpathyContact *contact;
972 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
974 individual = empathy_individual_view_dup_selected (view);
975 if (individual == NULL)
978 contact = empathy_contact_dup_from_folks_individual (individual);
979 if (contact == NULL) {
980 g_object_unref (individual);
983 empathy_contact_edit_dialog_show (contact, NULL);
985 g_object_unref (individual);
986 g_object_unref (contact);
993 individual_view_row_activated (GtkTreeView *view,
995 GtkTreeViewColumn *column)
997 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
998 FolksIndividual *individual;
999 EmpathyContact *contact;
1000 GtkTreeModel *model;
1003 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1006 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1007 gtk_tree_model_get_iter (model, &iter, path);
1008 gtk_tree_model_get (model, &iter,
1009 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1011 if (individual == NULL)
1014 /* Determine which Persona to chat to, by choosing the most available one. */
1015 contact = empathy_contact_dup_best_for_action (individual,
1016 EMPATHY_ACTION_CHAT);
1018 if (contact != NULL)
1020 DEBUG ("Starting a chat");
1022 empathy_dispatcher_chat_with_contact (contact,
1023 gtk_get_current_event_time ());
1026 g_object_unref (individual);
1027 tp_clear_object (&contact);
1031 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1032 const gchar *path_string,
1033 EmpathyIndividualView *view)
1035 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1037 GtkTreeModel *model;
1039 FolksIndividual *individual;
1040 GdkEventButton *event;
1041 GtkMenuShell *shell;
1044 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1047 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1048 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1051 gtk_tree_model_get (model, &iter,
1052 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1053 if (individual == NULL)
1056 event = (GdkEventButton *) gtk_get_current_event ();
1058 menu = empathy_context_menu_new (GTK_WIDGET (view));
1059 shell = GTK_MENU_SHELL (menu);
1062 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1063 gtk_menu_shell_append (shell, item);
1064 gtk_widget_show (item);
1067 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1068 gtk_menu_shell_append (shell, item);
1069 gtk_widget_show (item);
1071 gtk_widget_show (menu);
1072 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1073 event->button, event->time);
1075 g_object_unref (individual);
1079 individual_view_cell_set_background (EmpathyIndividualView *view,
1080 GtkCellRenderer *cell,
1084 if (!is_group && is_active)
1086 GtkStyleContext *style;
1089 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1091 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1094 /* Here we take the current theme colour and add it to
1095 * the colour for white and average the two. This
1096 * gives a colour which is inline with the theme but
1099 empathy_make_color_whiter (&color);
1101 g_object_set (cell, "cell-background-rgba", &color, NULL);
1104 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1108 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1109 GtkCellRenderer *cell,
1110 GtkTreeModel *model,
1112 EmpathyIndividualView *view)
1118 gtk_tree_model_get (model, iter,
1119 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1120 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1121 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1124 "visible", !is_group,
1128 tp_clear_object (&pixbuf);
1130 individual_view_cell_set_background (view, cell, is_group, is_active);
1134 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1135 GtkCellRenderer *cell,
1136 GtkTreeModel *model,
1138 EmpathyIndividualView *view)
1140 GdkPixbuf *pixbuf = NULL;
1144 gtk_tree_model_get (model, iter,
1145 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1146 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1151 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1153 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1154 GTK_ICON_SIZE_MENU);
1156 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1158 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1159 GTK_ICON_SIZE_MENU);
1164 "visible", pixbuf != NULL,
1168 tp_clear_object (&pixbuf);
1174 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1175 GtkCellRenderer *cell,
1176 GtkTreeModel *model,
1178 EmpathyIndividualView *view)
1182 gboolean can_audio, can_video;
1184 gtk_tree_model_get (model, iter,
1185 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1186 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1187 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1188 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1191 "visible", !is_group && (can_audio || can_video),
1192 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1195 individual_view_cell_set_background (view, cell, is_group, is_active);
1199 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1200 GtkCellRenderer *cell,
1201 GtkTreeModel *model,
1203 EmpathyIndividualView *view)
1206 gboolean show_avatar;
1210 gtk_tree_model_get (model, iter,
1211 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1212 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1213 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1214 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1217 "visible", !is_group && show_avatar,
1221 tp_clear_object (&pixbuf);
1223 individual_view_cell_set_background (view, cell, is_group, is_active);
1227 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1228 GtkCellRenderer *cell,
1229 GtkTreeModel *model,
1231 EmpathyIndividualView *view)
1236 gtk_tree_model_get (model, iter,
1237 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1238 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1240 individual_view_cell_set_background (view, cell, is_group, is_active);
1244 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1245 GtkCellRenderer *cell,
1246 GtkTreeModel *model,
1248 EmpathyIndividualView *view)
1253 gtk_tree_model_get (model, iter,
1254 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1255 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1257 if (gtk_tree_model_iter_has_child (model, iter))
1260 gboolean row_expanded;
1262 path = gtk_tree_model_get_path (model, iter);
1264 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1265 (gtk_tree_view_column_get_tree_view (column)), path);
1266 gtk_tree_path_free (path);
1271 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1275 g_object_set (cell, "visible", FALSE, NULL);
1277 individual_view_cell_set_background (view, cell, is_group, is_active);
1281 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1286 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1287 GtkTreeModel *model;
1291 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1294 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1296 gtk_tree_model_get (model, iter,
1297 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1299 expanded = GPOINTER_TO_INT (user_data);
1300 empathy_contact_group_set_expanded (name, expanded);
1306 individual_view_start_search_cb (EmpathyIndividualView *view,
1309 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1311 if (priv->search_widget == NULL)
1314 empathy_individual_view_start_search (view);
1320 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1322 EmpathyIndividualView *view)
1324 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1326 GtkTreeViewColumn *focus_column;
1327 GtkTreeModel *model;
1329 gboolean set_cursor = FALSE;
1331 gtk_tree_model_filter_refilter (priv->filter);
1333 /* Set cursor on the first contact. If it is already set on a group,
1334 * set it on its first child contact. Note that first child of a group
1335 * is its separator, that's why we actually set to the 2nd
1338 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1339 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1343 path = gtk_tree_path_new_from_string ("0:1");
1346 else if (gtk_tree_path_get_depth (path) < 2)
1350 gtk_tree_model_get_iter (model, &iter, path);
1351 gtk_tree_model_get (model, &iter,
1352 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1357 gtk_tree_path_down (path);
1358 gtk_tree_path_next (path);
1365 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1367 if (gtk_tree_model_get_iter (model, &iter, path))
1369 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1374 gtk_tree_path_free (path);
1378 individual_view_search_activate_cb (GtkWidget *search,
1379 EmpathyIndividualView *view)
1382 GtkTreeViewColumn *focus_column;
1384 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1387 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1388 gtk_tree_path_free (path);
1390 gtk_widget_hide (search);
1395 individual_view_search_key_navigation_cb (GtkWidget *search,
1397 EmpathyIndividualView *view)
1399 GdkEventKey *eventkey = ((GdkEventKey *) event);
1400 gboolean ret = FALSE;
1402 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1403 || eventkey->keyval == GDK_KEY_F2)
1405 GdkEvent *new_event;
1407 new_event = gdk_event_copy (event);
1408 gtk_widget_grab_focus (GTK_WIDGET (view));
1409 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1410 gtk_widget_grab_focus (search);
1412 gdk_event_free (new_event);
1419 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1420 EmpathyIndividualView *view)
1422 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1423 GtkTreeModel *model;
1424 GtkTreePath *cursor_path;
1426 gboolean valid = FALSE;
1428 /* block expand or collapse handlers, they would write the
1429 * expand or collapsed setting to file otherwise */
1430 g_signal_handlers_block_by_func (view,
1431 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1432 g_signal_handlers_block_by_func (view,
1433 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1435 /* restore which groups are expanded and which are not */
1436 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1437 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1438 valid; valid = gtk_tree_model_iter_next (model, &iter))
1444 gtk_tree_model_get (model, &iter,
1445 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1446 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1455 path = gtk_tree_model_get_path (model, &iter);
1456 if ((priv->view_features &
1457 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1458 empathy_contact_group_get_expanded (name))
1460 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1464 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1467 gtk_tree_path_free (path);
1471 /* unblock expand or collapse handlers */
1472 g_signal_handlers_unblock_by_func (view,
1473 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1474 g_signal_handlers_unblock_by_func (view,
1475 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1477 /* keep the selected contact visible */
1478 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1480 if (cursor_path != NULL)
1481 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1484 gtk_tree_path_free (cursor_path);
1488 individual_view_search_show_cb (EmpathyLiveSearch *search,
1489 EmpathyIndividualView *view)
1491 /* block expand or collapse handlers during expand all, they would
1492 * write the expand or collapsed setting to file otherwise */
1493 g_signal_handlers_block_by_func (view,
1494 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1496 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1498 g_signal_handlers_unblock_by_func (view,
1499 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1503 expand_idle_foreach_cb (GtkTreeModel *model,
1506 EmpathyIndividualView *self)
1508 EmpathyIndividualViewPriv *priv;
1510 gpointer should_expand;
1513 /* We only want groups */
1514 if (gtk_tree_path_get_depth (path) > 1)
1517 gtk_tree_model_get (model, iter,
1518 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1519 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1522 if (is_group == FALSE)
1528 priv = GET_PRIV (self);
1530 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1531 &should_expand) == TRUE)
1533 if (GPOINTER_TO_INT (should_expand) == TRUE)
1534 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1536 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1538 g_hash_table_remove (priv->expand_groups, name);
1547 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1549 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1551 DEBUG ("individual_view_expand_idle_cb");
1553 g_signal_handlers_block_by_func (self,
1554 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1555 g_signal_handlers_block_by_func (self,
1556 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1558 /* The store/filter could've been removed while we were in the idle queue */
1559 if (priv->filter != NULL)
1561 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1562 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1565 g_signal_handlers_unblock_by_func (self,
1566 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1567 g_signal_handlers_unblock_by_func (self,
1568 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1570 /* Empty the table of groups to expand/contract, since it may contain groups
1571 * which no longer exist in the tree view. This can happen after going
1572 * offline, for example. */
1573 g_hash_table_remove_all (priv->expand_groups);
1574 priv->expand_groups_idle_handler = 0;
1575 g_object_unref (self);
1581 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1584 EmpathyIndividualView *view)
1586 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1587 gboolean should_expand, is_group = FALSE;
1589 gpointer will_expand;
1591 gtk_tree_model_get (model, iter,
1592 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1593 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1596 if (!is_group || EMP_STR_EMPTY (name))
1602 should_expand = (priv->view_features &
1603 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1604 (priv->search_widget != NULL &&
1605 gtk_widget_get_visible (priv->search_widget)) ||
1606 empathy_contact_group_get_expanded (name);
1608 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1609 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1610 * a hash table, and expand or contract them as appropriate all at once in
1611 * an idle handler which iterates over all the group rows. */
1612 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1613 &will_expand) == FALSE ||
1614 GPOINTER_TO_INT (will_expand) != should_expand)
1616 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1617 GINT_TO_POINTER (should_expand));
1619 if (priv->expand_groups_idle_handler == 0)
1621 priv->expand_groups_idle_handler =
1622 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1623 g_object_ref (view));
1630 /* FIXME: This is a workaround for bgo#621076 */
1632 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1635 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1636 GtkTreeModel *model;
1637 GtkTreePath *parent_path;
1638 GtkTreeIter parent_iter;
1640 if (gtk_tree_path_get_depth (path) < 2)
1643 /* A group row is visible if and only if at least one if its child is visible.
1644 * So when a row is inserted/deleted/changed in the base model, that could
1645 * modify the visibility of its parent in the filter model.
1648 model = GTK_TREE_MODEL (priv->store);
1649 parent_path = gtk_tree_path_copy (path);
1650 gtk_tree_path_up (parent_path);
1651 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1653 /* This tells the filter to verify the visibility of that row, and
1654 * show/hide it if necessary */
1655 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1656 parent_path, &parent_iter);
1658 gtk_tree_path_free (parent_path);
1662 individual_view_store_row_changed_cb (GtkTreeModel *model,
1665 EmpathyIndividualView *view)
1667 individual_view_verify_group_visibility (view, path);
1671 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1673 EmpathyIndividualView *view)
1675 individual_view_verify_group_visibility (view, path);
1679 individual_view_is_visible_individual (EmpathyIndividualView *self,
1680 FolksIndividual *individual,
1682 gboolean is_searching)
1684 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1685 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1687 GList *personas, *l;
1688 gboolean is_favorite, contains_interesting_persona = FALSE;
1690 /* We're only giving the visibility wrt filtering here, not things like
1692 if (priv->show_untrusted == FALSE &&
1693 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1698 /* Hide all individuals which consist entirely of uninteresting personas */
1699 personas = folks_individual_get_personas (individual);
1700 for (l = personas; l; l = l->next)
1702 if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1704 contains_interesting_persona = TRUE;
1709 if (contains_interesting_persona == FALSE)
1712 is_favorite = folks_favourite_details_get_is_favourite (
1713 FOLKS_FAVOURITE_DETAILS (individual));
1714 if (is_searching == FALSE)
1715 return (priv->show_offline || is_online || is_favorite);
1717 /* check alias name */
1718 str = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual));
1720 if (empathy_live_search_match (live, str))
1723 /* check contact id, remove the @server.com part */
1724 for (l = personas; l; l = l->next)
1727 gchar *dup_str = NULL;
1730 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
1733 str = folks_persona_get_display_id (l->data);
1734 p = strstr (str, "@");
1736 str = dup_str = g_strndup (str, p - str);
1738 visible = empathy_live_search_match (live, str);
1744 /* FIXME: Add more rules here, we could check phone numbers in
1745 * contact's vCard for example. */
1751 individual_view_filter_visible_func (GtkTreeModel *model,
1755 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1756 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1757 FolksIndividual *individual = NULL;
1758 gboolean is_group, is_separator, valid;
1759 GtkTreeIter child_iter;
1760 gboolean visible, is_online;
1761 gboolean is_searching = TRUE;
1763 if (priv->search_widget == NULL ||
1764 !gtk_widget_get_visible (priv->search_widget))
1765 is_searching = FALSE;
1767 gtk_tree_model_get (model, iter,
1768 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1769 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1770 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1771 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1774 if (individual != NULL)
1776 visible = individual_view_is_visible_individual (self, individual,
1777 is_online, is_searching);
1779 g_object_unref (individual);
1781 /* FIXME: Work around bgo#626552/bgo#621076 */
1782 if (visible == TRUE)
1784 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1785 individual_view_verify_group_visibility (self, path);
1786 gtk_tree_path_free (path);
1795 /* Not a contact, not a separator, must be a group */
1796 g_return_val_if_fail (is_group, FALSE);
1798 /* only show groups which are not empty */
1799 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1800 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1802 gtk_tree_model_get (model, &child_iter,
1803 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1804 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1807 if (individual == NULL)
1810 visible = individual_view_is_visible_individual (self, individual,
1811 is_online, is_searching);
1812 g_object_unref (individual);
1814 /* show group if it has at least one visible contact in it */
1815 if (visible == TRUE)
1823 individual_view_constructed (GObject *object)
1825 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1826 GtkCellRenderer *cell;
1827 GtkTreeViewColumn *col;
1832 "headers-visible", FALSE,
1833 "show-expanders", FALSE,
1836 col = gtk_tree_view_column_new ();
1839 cell = gtk_cell_renderer_pixbuf_new ();
1840 gtk_tree_view_column_pack_start (col, cell, FALSE);
1841 gtk_tree_view_column_set_cell_data_func (col, cell,
1842 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1852 cell = gtk_cell_renderer_pixbuf_new ();
1853 gtk_tree_view_column_pack_start (col, cell, FALSE);
1854 gtk_tree_view_column_set_cell_data_func (col, cell,
1855 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1867 cell = empathy_cell_renderer_text_new ();
1868 gtk_tree_view_column_pack_start (col, cell, TRUE);
1869 gtk_tree_view_column_set_cell_data_func (col, cell,
1870 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1872 gtk_tree_view_column_add_attribute (col, cell,
1873 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1874 gtk_tree_view_column_add_attribute (col, cell,
1875 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1876 gtk_tree_view_column_add_attribute (col, cell,
1877 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1878 gtk_tree_view_column_add_attribute (col, cell,
1879 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1880 gtk_tree_view_column_add_attribute (col, cell,
1881 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1882 gtk_tree_view_column_add_attribute (col, cell,
1883 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1884 gtk_tree_view_column_add_attribute (col, cell,
1885 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1887 /* Audio Call Icon */
1888 cell = empathy_cell_renderer_activatable_new ();
1889 gtk_tree_view_column_pack_start (col, cell, FALSE);
1890 gtk_tree_view_column_set_cell_data_func (col, cell,
1891 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1894 g_object_set (cell, "visible", FALSE, NULL);
1896 g_signal_connect (cell, "path-activated",
1897 G_CALLBACK (individual_view_call_activated_cb), view);
1900 cell = gtk_cell_renderer_pixbuf_new ();
1901 gtk_tree_view_column_pack_start (col, cell, FALSE);
1902 gtk_tree_view_column_set_cell_data_func (col, cell,
1903 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1915 cell = empathy_cell_renderer_expander_new ();
1916 gtk_tree_view_column_pack_end (col, cell, FALSE);
1917 gtk_tree_view_column_set_cell_data_func (col, cell,
1918 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1921 /* Actually add the column now we have added all cell renderers */
1922 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1925 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1927 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1930 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1932 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1938 individual_view_set_view_features (EmpathyIndividualView *view,
1939 EmpathyIndividualFeatureFlags features)
1941 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1942 gboolean has_tooltip;
1944 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1946 priv->view_features = features;
1948 /* Setting reorderable is a hack that gets us row previews as drag icons
1949 for free. We override all the drag handlers. It's tricky to get the
1950 position of the drag icon right in drag_begin. GtkTreeView has special
1951 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1954 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1955 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1957 /* Update DnD source/dest */
1958 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1960 gtk_drag_source_set (GTK_WIDGET (view),
1963 G_N_ELEMENTS (drag_types_source),
1964 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1968 gtk_drag_source_unset (GTK_WIDGET (view));
1972 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1974 gtk_drag_dest_set (GTK_WIDGET (view),
1975 GTK_DEST_DEFAULT_ALL,
1977 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1981 /* FIXME: URI could still be droped depending on FT feature */
1982 gtk_drag_dest_unset (GTK_WIDGET (view));
1985 /* Update has-tooltip */
1987 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1988 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1992 individual_view_dispose (GObject *object)
1994 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1995 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1997 tp_clear_object (&priv->store);
1998 tp_clear_object (&priv->filter);
1999 tp_clear_object (&priv->tooltip_widget);
2001 empathy_individual_view_set_live_search (view, NULL);
2003 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2007 individual_view_finalize (GObject *object)
2009 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2011 if (priv->expand_groups_idle_handler != 0)
2012 g_source_remove (priv->expand_groups_idle_handler);
2013 g_hash_table_destroy (priv->expand_groups);
2015 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2019 individual_view_get_property (GObject *object,
2024 EmpathyIndividualViewPriv *priv;
2026 priv = GET_PRIV (object);
2031 g_value_set_object (value, priv->store);
2033 case PROP_VIEW_FEATURES:
2034 g_value_set_flags (value, priv->view_features);
2036 case PROP_INDIVIDUAL_FEATURES:
2037 g_value_set_flags (value, priv->individual_features);
2039 case PROP_SHOW_OFFLINE:
2040 g_value_set_boolean (value, priv->show_offline);
2042 case PROP_SHOW_UNTRUSTED:
2043 g_value_set_boolean (value, priv->show_untrusted);
2046 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2052 individual_view_set_property (GObject *object,
2054 const GValue *value,
2057 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2058 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2063 empathy_individual_view_set_store (view, g_value_get_object (value));
2065 case PROP_VIEW_FEATURES:
2066 individual_view_set_view_features (view, g_value_get_flags (value));
2068 case PROP_INDIVIDUAL_FEATURES:
2069 priv->individual_features = g_value_get_flags (value);
2071 case PROP_SHOW_OFFLINE:
2072 empathy_individual_view_set_show_offline (view,
2073 g_value_get_boolean (value));
2075 case PROP_SHOW_UNTRUSTED:
2076 empathy_individual_view_set_show_untrusted (view,
2077 g_value_get_boolean (value));
2080 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2086 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2088 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2089 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2090 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2092 object_class->constructed = individual_view_constructed;
2093 object_class->dispose = individual_view_dispose;
2094 object_class->finalize = individual_view_finalize;
2095 object_class->get_property = individual_view_get_property;
2096 object_class->set_property = individual_view_set_property;
2098 widget_class->drag_data_received = individual_view_drag_data_received;
2099 widget_class->drag_drop = individual_view_drag_drop;
2100 widget_class->drag_begin = individual_view_drag_begin;
2101 widget_class->drag_data_get = individual_view_drag_data_get;
2102 widget_class->drag_end = individual_view_drag_end;
2103 widget_class->drag_motion = individual_view_drag_motion;
2105 /* We use the class method to let user of this widget to connect to
2106 * the signal and stop emission of the signal so the default handler
2107 * won't be called. */
2108 tree_view_class->row_activated = individual_view_row_activated;
2110 klass->drag_individual_received = real_drag_individual_received_cb;
2112 signals[DRAG_INDIVIDUAL_RECEIVED] =
2113 g_signal_new ("drag-individual-received",
2114 G_OBJECT_CLASS_TYPE (klass),
2116 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2118 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2119 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2120 G_TYPE_STRING, G_TYPE_STRING);
2122 signals[DRAG_PERSONA_RECEIVED] =
2123 g_signal_new ("drag-persona-received",
2124 G_OBJECT_CLASS_TYPE (klass),
2126 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2128 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2129 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2131 g_object_class_install_property (object_class,
2133 g_param_spec_object ("store",
2134 "The store of the view",
2135 "The store of the view",
2136 EMPATHY_TYPE_INDIVIDUAL_STORE,
2137 G_PARAM_READWRITE));
2138 g_object_class_install_property (object_class,
2140 g_param_spec_flags ("view-features",
2141 "Features of the view",
2142 "Flags for all enabled features",
2143 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2144 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2145 g_object_class_install_property (object_class,
2146 PROP_INDIVIDUAL_FEATURES,
2147 g_param_spec_flags ("individual-features",
2148 "Features of the individual menu",
2149 "Flags for all enabled features for the menu",
2150 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2151 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2152 g_object_class_install_property (object_class,
2154 g_param_spec_boolean ("show-offline",
2156 "Whether contact list should display "
2157 "offline contacts", FALSE, G_PARAM_READWRITE));
2158 g_object_class_install_property (object_class,
2159 PROP_SHOW_UNTRUSTED,
2160 g_param_spec_boolean ("show-untrusted",
2161 "Show Untrusted Individuals",
2162 "Whether the view should display untrusted individuals; "
2163 "those who could not be who they say they are.",
2164 TRUE, G_PARAM_READWRITE));
2166 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2170 empathy_individual_view_init (EmpathyIndividualView *view)
2172 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2173 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2177 priv->show_untrusted = TRUE;
2179 /* Get saved group states. */
2180 empathy_contact_groups_get_all ();
2182 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2183 (GDestroyNotify) g_free, NULL);
2185 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2186 empathy_individual_store_row_separator_func, NULL, NULL);
2188 /* Connect to tree view signals rather than override. */
2189 g_signal_connect (view, "button-press-event",
2190 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2191 g_signal_connect (view, "key-press-event",
2192 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2193 g_signal_connect (view, "row-expanded",
2194 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2195 GINT_TO_POINTER (TRUE));
2196 g_signal_connect (view, "row-collapsed",
2197 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2198 GINT_TO_POINTER (FALSE));
2199 g_signal_connect (view, "query-tooltip",
2200 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2203 EmpathyIndividualView *
2204 empathy_individual_view_new (EmpathyIndividualStore *store,
2205 EmpathyIndividualViewFeatureFlags view_features,
2206 EmpathyIndividualFeatureFlags individual_features)
2208 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2210 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2212 "individual-features", individual_features,
2213 "view-features", view_features, NULL);
2217 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2219 EmpathyIndividualViewPriv *priv;
2220 GtkTreeSelection *selection;
2222 GtkTreeModel *model;
2223 FolksIndividual *individual;
2225 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2227 priv = GET_PRIV (view);
2229 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2230 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2233 gtk_tree_model_get (model, &iter,
2234 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2240 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2241 gboolean *is_fake_group)
2243 EmpathyIndividualViewPriv *priv;
2244 GtkTreeSelection *selection;
2246 GtkTreeModel *model;
2251 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2253 priv = GET_PRIV (view);
2255 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2256 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2259 gtk_tree_model_get (model, &iter,
2260 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2261 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2262 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2270 if (is_fake_group != NULL)
2271 *is_fake_group = fake;
2278 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2279 REMOVE_DIALOG_RESPONSE_DELETE,
2280 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2284 individual_view_remove_dialog_show (GtkWindow *parent,
2285 const gchar *message,
2286 const gchar *secondary_text,
2287 gboolean block_button)
2292 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2293 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2299 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2300 * mnemonic so we have to create the button manually. */
2301 button = gtk_button_new_with_mnemonic (
2302 _("Delete and _Block"));
2304 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2305 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2307 gtk_widget_show (button);
2310 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2311 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2312 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2313 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2314 "%s", secondary_text);
2316 gtk_widget_show (dialog);
2318 res = gtk_dialog_run (GTK_DIALOG (dialog));
2319 gtk_widget_destroy (dialog);
2325 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2326 EmpathyIndividualView *view)
2330 group = empathy_individual_view_dup_selected_group (view, NULL);
2337 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2339 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2340 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2341 text, FALSE) == REMOVE_DIALOG_RESPONSE_DELETE)
2343 EmpathyIndividualManager *manager =
2344 empathy_individual_manager_dup_singleton ();
2345 empathy_individual_manager_remove_group (manager, group);
2346 g_object_unref (G_OBJECT (manager));
2356 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2358 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2363 gboolean is_fake_group;
2365 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2367 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2368 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2371 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2372 if (!group || is_fake_group)
2374 /* We can't alter fake groups */
2379 menu = gtk_menu_new ();
2382 if (priv->view_features &
2383 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2384 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2385 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2386 gtk_widget_show (item);
2387 g_signal_connect (item, "activate",
2388 G_CALLBACK (individual_view_group_rename_activate_cb),
2393 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2395 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2396 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2397 GTK_ICON_SIZE_MENU);
2398 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2399 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2400 gtk_widget_show (item);
2401 g_signal_connect (item, "activate",
2402 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2411 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2412 EmpathyIndividualView *view)
2414 FolksIndividual *individual;
2416 individual = empathy_individual_view_dup_selected (view);
2418 if (individual != NULL)
2420 EmpathyIndividualManager *manager;
2423 GList *l, *personas;
2424 guint persona_count = 0;
2428 personas = folks_individual_get_personas (individual);
2430 /* If we have more than one TpfPersona, display a different message
2431 * ensuring the user knows that *all* of the meta-contacts' personas will
2433 for (l = personas; l != NULL; l = l->next)
2435 if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (l->data)))
2439 if (persona_count >= 2)
2443 if (persona_count < 2)
2445 /* Not a meta-contact */
2448 _("Do you really want to remove the contact '%s'?"),
2449 folks_alias_details_get_alias (
2450 FOLKS_ALIAS_DETAILS (individual)));
2457 _("Do you really want to remove the linked contact '%s'? "
2458 "Note that this will remove all the contacts which make up "
2459 "this linked contact."),
2460 folks_alias_details_get_alias (
2461 FOLKS_ALIAS_DETAILS (individual)));
2465 manager = empathy_individual_manager_dup_singleton ();
2466 can_block = empathy_individual_manager_supports_blocking (manager,
2468 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2469 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2472 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2473 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2477 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2479 if (!empathy_block_individual_dialog_show (parent, individual,
2483 empathy_individual_manager_set_blocked (manager, individual,
2487 empathy_individual_manager_remove (manager, individual, "");
2492 g_object_unref (individual);
2493 g_object_unref (manager);
2498 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2499 EmpathyLinkingDialog *linking_dialog,
2500 EmpathyIndividualView *self)
2502 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2503 EmpathyIndividualLinker *linker;
2505 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2506 empathy_individual_linker_set_search_text (linker,
2507 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2511 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2513 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2514 FolksIndividual *individual;
2515 GtkWidget *menu = NULL;
2518 gboolean can_remove = FALSE;
2521 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2523 individual = empathy_individual_view_dup_selected (view);
2524 if (individual == NULL)
2527 /* If any of the Individual's personas can be removed, add an option to
2528 * remove. This will act as a best-effort option. If any Personas cannot be
2529 * removed from the server, then this option will just be inactive upon
2530 * subsequent menu openings */
2531 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2533 FolksPersona *persona = FOLKS_PERSONA (l->data);
2534 FolksPersonaStore *store = folks_persona_get_store (persona);
2535 FolksMaybeBool maybe_can_remove =
2536 folks_persona_store_get_can_remove_personas (store);
2538 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2545 menu = empathy_individual_menu_new (individual, priv->individual_features);
2547 /* Remove contact */
2548 if ((priv->view_features &
2549 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2552 /* create the menu if required, or just add a separator */
2554 menu = gtk_menu_new ();
2557 item = gtk_separator_menu_item_new ();
2558 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2559 gtk_widget_show (item);
2563 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2564 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2565 GTK_ICON_SIZE_MENU);
2566 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2567 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2568 gtk_widget_show (item);
2569 g_signal_connect (item, "activate",
2570 G_CALLBACK (individual_view_remove_activate_cb), view);
2573 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2574 * set the live search text on the new linking dialogue to be the same as
2576 g_signal_connect (menu, "link-contacts-activated",
2577 (GCallback) individual_menu_link_contacts_activated_cb, view);
2579 g_object_unref (individual);
2585 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2586 EmpathyLiveSearch *search)
2588 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2590 /* remove old handlers if old search was not null */
2591 if (priv->search_widget != NULL)
2593 g_signal_handlers_disconnect_by_func (view,
2594 individual_view_start_search_cb, NULL);
2596 g_signal_handlers_disconnect_by_func (priv->search_widget,
2597 individual_view_search_text_notify_cb, view);
2598 g_signal_handlers_disconnect_by_func (priv->search_widget,
2599 individual_view_search_activate_cb, view);
2600 g_signal_handlers_disconnect_by_func (priv->search_widget,
2601 individual_view_search_key_navigation_cb, view);
2602 g_signal_handlers_disconnect_by_func (priv->search_widget,
2603 individual_view_search_hide_cb, view);
2604 g_signal_handlers_disconnect_by_func (priv->search_widget,
2605 individual_view_search_show_cb, view);
2606 g_object_unref (priv->search_widget);
2607 priv->search_widget = NULL;
2610 /* connect handlers if new search is not null */
2613 priv->search_widget = g_object_ref (search);
2615 g_signal_connect (view, "start-interactive-search",
2616 G_CALLBACK (individual_view_start_search_cb), NULL);
2618 g_signal_connect (priv->search_widget, "notify::text",
2619 G_CALLBACK (individual_view_search_text_notify_cb), view);
2620 g_signal_connect (priv->search_widget, "activate",
2621 G_CALLBACK (individual_view_search_activate_cb), view);
2622 g_signal_connect (priv->search_widget, "key-navigation",
2623 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2624 g_signal_connect (priv->search_widget, "hide",
2625 G_CALLBACK (individual_view_search_hide_cb), view);
2626 g_signal_connect (priv->search_widget, "show",
2627 G_CALLBACK (individual_view_search_show_cb), view);
2632 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2634 EmpathyIndividualViewPriv *priv;
2636 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2638 priv = GET_PRIV (self);
2640 return (priv->search_widget != NULL &&
2641 gtk_widget_get_visible (priv->search_widget));
2645 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2647 EmpathyIndividualViewPriv *priv;
2649 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2651 priv = GET_PRIV (self);
2653 return priv->show_offline;
2657 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2658 gboolean show_offline)
2660 EmpathyIndividualViewPriv *priv;
2662 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2664 priv = GET_PRIV (self);
2666 priv->show_offline = show_offline;
2668 g_object_notify (G_OBJECT (self), "show-offline");
2669 gtk_tree_model_filter_refilter (priv->filter);
2673 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2675 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2677 return GET_PRIV (self)->show_untrusted;
2681 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2682 gboolean show_untrusted)
2684 EmpathyIndividualViewPriv *priv;
2686 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2688 priv = GET_PRIV (self);
2690 priv->show_untrusted = show_untrusted;
2692 g_object_notify (G_OBJECT (self), "show-untrusted");
2693 gtk_tree_model_filter_refilter (priv->filter);
2696 EmpathyIndividualStore *
2697 empathy_individual_view_get_store (EmpathyIndividualView *self)
2699 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2701 return GET_PRIV (self)->store;
2705 empathy_individual_view_set_store (EmpathyIndividualView *self,
2706 EmpathyIndividualStore *store)
2708 EmpathyIndividualViewPriv *priv;
2710 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2711 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2713 priv = GET_PRIV (self);
2715 /* Destroy the old filter and remove the old store */
2716 if (priv->store != NULL)
2718 g_signal_handlers_disconnect_by_func (priv->store,
2719 individual_view_store_row_changed_cb, self);
2720 g_signal_handlers_disconnect_by_func (priv->store,
2721 individual_view_store_row_deleted_cb, self);
2723 g_signal_handlers_disconnect_by_func (priv->filter,
2724 individual_view_row_has_child_toggled_cb, self);
2726 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2729 tp_clear_object (&priv->filter);
2730 tp_clear_object (&priv->store);
2732 /* Set the new store */
2733 priv->store = store;
2737 g_object_ref (store);
2739 /* Create a new filter */
2740 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2741 GTK_TREE_MODEL (priv->store), NULL));
2742 gtk_tree_model_filter_set_visible_func (priv->filter,
2743 individual_view_filter_visible_func, self, NULL);
2745 g_signal_connect (priv->filter, "row-has-child-toggled",
2746 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2747 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2748 GTK_TREE_MODEL (priv->filter));
2750 tp_g_signal_connect_object (priv->store, "row-changed",
2751 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2752 tp_g_signal_connect_object (priv->store, "row-inserted",
2753 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2754 tp_g_signal_connect_object (priv->store, "row-deleted",
2755 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2760 empathy_individual_view_start_search (EmpathyIndividualView *self)
2762 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2764 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2765 g_return_if_fail (priv->search_widget != NULL);
2767 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2768 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2770 gtk_widget_show (GTK_WIDGET (priv->search_widget));