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>
28 #include "empathy-individual-view.h"
30 #include <glib/gi18n-lib.h>
31 #include <tp-account-widgets/tpaw-utils.h>
33 #include "empathy-cell-renderer-activatable.h"
34 #include "empathy-cell-renderer-expander.h"
35 #include "empathy-cell-renderer-text.h"
36 #include "empathy-connection-aggregator.h"
37 #include "empathy-contact-groups.h"
38 #include "empathy-gtk-enum-types.h"
39 #include "empathy-images.h"
40 #include "empathy-individual-edit-dialog.h"
41 #include "empathy-individual-manager.h"
42 #include "empathy-request-util.h"
43 #include "empathy-ui-utils.h"
44 #include "empathy-utils.h"
46 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
47 #include "empathy-debug.h"
49 /* Active users are those which have recently changed state
50 * (e.g. online, offline or from normal to a busy state).
53 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
56 EmpathyIndividualStore *store;
57 GtkTreeRowReference *drag_row;
58 EmpathyIndividualViewFeatureFlags view_features;
59 EmpathyIndividualFeatureFlags individual_features;
60 GtkWidget *tooltip_widget;
62 gboolean show_offline;
63 gboolean show_untrusted;
64 gboolean show_uninteresting;
66 GtkTreeModelFilter *filter;
67 GtkWidget *search_widget;
69 guint expand_groups_idle_handler;
70 /* owned string (group name) -> bool (whether to expand/contract) */
71 GHashTable *expand_groups;
74 guint auto_scroll_timeout_id;
75 /* Distance between mouse pointer and the nearby border. Negative when
79 GtkTreeModelFilterVisibleFunc custom_filter;
80 gpointer custom_filter_data;
82 GtkCellRenderer *text_renderer;
83 } EmpathyIndividualViewPriv;
87 EmpathyIndividualView *view;
94 EmpathyIndividualView *view;
95 FolksIndividual *individual;
104 PROP_INDIVIDUAL_FEATURES,
107 PROP_SHOW_UNINTERESTING,
110 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
111 * specific EmpathyContacts (between/in/out of Individuals) */
114 DND_DRAG_TYPE_UNKNOWN = -1,
115 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
116 DND_DRAG_TYPE_PERSONA_ID,
117 DND_DRAG_TYPE_URI_LIST,
118 DND_DRAG_TYPE_STRING,
121 #define DRAG_TYPE(T,I) \
122 { (gchar *) T, 0, I }
124 static const GtkTargetEntry drag_types_dest[] = {
125 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
126 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
127 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
128 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
129 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
130 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
133 static const GtkTargetEntry drag_types_source[] = {
134 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
139 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
143 DRAG_INDIVIDUAL_RECEIVED,
144 DRAG_PERSONA_RECEIVED,
148 static guint signals[LAST_SIGNAL];
150 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
154 individual_view_tooltip_destroy_cb (GtkWidget *widget,
155 EmpathyIndividualView *view)
157 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
159 tp_clear_object (&priv->tooltip_widget);
163 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
166 gboolean keyboard_mode,
170 EmpathyIndividualViewPriv *priv;
171 FolksIndividual *individual;
175 static gint running = 0;
176 gboolean ret = FALSE;
178 priv = GET_PRIV (view);
180 /* Avoid an infinite loop. See GNOME bug #574377 */
186 /* Don't show the tooltip if there's already a popup menu */
187 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
190 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
191 keyboard_mode, &model, &path, &iter))
194 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
195 gtk_tree_path_free (path);
197 gtk_tree_model_get (model, &iter,
198 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
200 if (individual == NULL)
203 if (priv->tooltip_widget == NULL)
205 priv->tooltip_widget = empathy_individual_widget_new (individual,
206 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
207 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
208 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
209 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
210 g_object_ref (priv->tooltip_widget);
212 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
213 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
215 gtk_widget_show (priv->tooltip_widget);
219 empathy_individual_widget_set_individual (
220 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
223 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
226 g_object_unref (individual);
234 groups_change_group_cb (GObject *source,
235 GAsyncResult *result,
238 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
239 GError *error = NULL;
241 folks_group_details_change_group_finish (group_details, result, &error);
244 g_warning ("failed to change group: %s", error->message);
245 g_clear_error (&error);
250 group_can_be_modified (const gchar *name,
251 gboolean is_fake_group,
254 /* Real groups can always be modified */
258 /* The favorite fake group can be modified so users can
259 * add/remove favorites using DnD */
260 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
263 /* We can remove contacts from the 'ungrouped' fake group */
264 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
271 individual_view_individual_drag_received (GtkWidget *self,
272 GdkDragContext *context,
275 GtkSelectionData *selection)
277 EmpathyIndividualViewPriv *priv;
278 EmpathyIndividualManager *manager = NULL;
279 FolksIndividual *individual;
280 GtkTreePath *source_path;
281 const gchar *sel_data;
282 gchar *new_group = NULL;
283 gchar *old_group = NULL;
284 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
286 priv = GET_PRIV (self);
288 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
289 new_group = empathy_individual_store_get_parent_group (model, path,
290 NULL, &new_group_is_fake);
292 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
295 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
296 * feature. Otherwise, we just add the dropped contact to whichever group
297 * they were dropped in, and don't remove them from their old group. This
298 * allows for Individual views which shouldn't allow Individuals to have
299 * their groups changed, and also for dragging Individuals between Individual
301 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
302 priv->drag_row != NULL)
304 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
308 empathy_individual_store_get_parent_group (model, source_path,
309 NULL, &old_group_is_fake);
310 gtk_tree_path_free (source_path);
313 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
316 if (!tp_strdiff (old_group, new_group))
319 else if (priv->drag_row != NULL)
321 /* We don't allow changing Individuals' groups, and this Individual was
322 * dragged from another group in *this* Individual view, so we disallow
327 /* XXX: for contacts, we used to ensure the account, create the contact
328 * factory, and then wait on the contacts. But they should already be
329 * created by this point */
331 manager = empathy_individual_manager_dup_singleton ();
332 individual = empathy_individual_manager_lookup_member (manager, sel_data);
334 if (individual == NULL)
336 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
340 /* FIXME: We should probably wait for the cb before calling
343 /* Emit a signal notifying of the drag. We change the Individual's groups in
344 * the default signal handler. */
345 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
346 gdk_drag_context_get_selected_action (context), individual, new_group,
352 tp_clear_object (&manager);
360 real_drag_individual_received_cb (EmpathyIndividualView *self,
361 GdkDragAction action,
362 FolksIndividual *individual,
363 const gchar *new_group,
364 const gchar *old_group)
366 DEBUG ("individual %s dragged from '%s' to '%s'",
367 folks_individual_get_id (individual), old_group, new_group);
369 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
371 /* Mark contact as favourite */
372 folks_favourite_details_set_is_favourite (
373 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
377 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379 /* Remove contact as favourite */
380 folks_favourite_details_set_is_favourite (
381 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
383 /* Don't try to remove it */
387 if (new_group != NULL)
389 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
390 new_group, TRUE, groups_change_group_cb, NULL);
393 if (old_group != NULL && action == GDK_ACTION_MOVE)
395 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
396 old_group, FALSE, groups_change_group_cb, NULL);
401 individual_view_persona_drag_received (GtkWidget *self,
402 GdkDragContext *context,
405 GtkSelectionData *selection)
407 EmpathyIndividualManager *manager = NULL;
408 FolksIndividual *individual = NULL;
409 FolksPersona *persona = NULL;
410 const gchar *persona_uid;
411 GList *individuals, *l;
412 GeeIterator *iter = NULL;
413 gboolean retval = FALSE;
415 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
417 /* FIXME: This is slow, but the only way to find the Persona we're having
419 manager = empathy_individual_manager_dup_singleton ();
420 individuals = empathy_individual_manager_get_members (manager);
422 for (l = individuals; l != NULL; l = l->next)
426 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
427 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
428 while (gee_iterator_next (iter))
430 FolksPersona *persona_cur = gee_iterator_get (iter);
432 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
434 /* takes ownership of the ref */
435 persona = persona_cur;
436 individual = g_object_ref (l->data);
439 g_clear_object (&persona_cur);
441 g_clear_object (&iter);
445 g_clear_object (&iter);
446 g_list_free (individuals);
448 if (persona == NULL || individual == NULL)
450 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
454 /* Emit a signal notifying of the drag. We change the Individual's groups in
455 * the default signal handler. */
456 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
457 gdk_drag_context_get_selected_action (context), persona, individual,
461 tp_clear_object (&manager);
462 tp_clear_object (&persona);
463 tp_clear_object (&individual);
469 individual_view_file_drag_received (GtkWidget *view,
470 GdkDragContext *context,
473 GtkSelectionData *selection)
476 const gchar *sel_data;
477 FolksIndividual *individual;
478 EmpathyContact *contact;
480 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
482 gtk_tree_model_get_iter (model, &iter, path);
483 gtk_tree_model_get (model, &iter,
484 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
485 if (individual == NULL)
488 contact = empathy_contact_dup_from_folks_individual (individual);
489 empathy_send_file_from_uri_list (contact, sel_data);
491 g_object_unref (individual);
492 tp_clear_object (&contact);
498 individual_view_drag_data_received (GtkWidget *view,
499 GdkDragContext *context,
502 GtkSelectionData *selection,
508 GtkTreeViewDropPosition position;
510 gboolean success = TRUE;
512 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
514 /* Get destination group information. */
515 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
516 x, y, &path, &position);
521 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
523 success = individual_view_individual_drag_received (view,
524 context, model, path, selection);
526 else if (info == DND_DRAG_TYPE_PERSONA_ID)
528 success = individual_view_persona_drag_received (view, context, model,
531 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
533 success = individual_view_file_drag_received (view,
534 context, model, path, selection);
537 gtk_tree_path_free (path);
538 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
542 individual_view_drag_motion_cb (DragMotionData *data)
544 if (data->view != NULL)
546 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
547 g_object_remove_weak_pointer (G_OBJECT (data->view),
548 (gpointer *) &data->view);
551 data->timeout_id = 0;
556 /* Minimum distance between the mouse pointer and a horizontal border when we
557 start auto scrolling. */
558 #define AUTO_SCROLL_MARGIN_SIZE 20
559 /* How far to scroll per one tick. */
560 #define AUTO_SCROLL_PITCH 10
563 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
565 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
569 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
571 if (priv->distance < 0)
572 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
574 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
576 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
577 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
579 gtk_adjustment_set_value (adj, new_value);
585 individual_view_drag_motion (GtkWidget *widget,
586 GdkDragContext *context,
591 EmpathyIndividualViewPriv *priv;
595 static DragMotionData *dm = NULL;
598 gboolean is_different = FALSE;
599 gboolean cleanup = TRUE;
600 gboolean retval = TRUE;
601 GtkAllocation allocation;
603 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
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 /* Determine the DndDragType of the data */
656 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
658 if (target == drag_atoms_dest[i])
660 drag_type = drag_types_dest[i].info;
665 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
666 drag_type == DND_DRAG_TYPE_STRING)
668 /* This is a file drag, and it can only be dropped on contacts,
670 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
671 * even if we have a valid target. */
672 FolksIndividual *individual = NULL;
673 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
675 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
677 gtk_tree_model_get (model, &iter,
678 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
682 if (individual != NULL)
684 EmpathyContact *contact = NULL;
686 contact = empathy_contact_dup_from_folks_individual (individual);
688 caps = empathy_contact_get_capabilities (contact);
690 tp_clear_object (&contact);
693 if (individual != NULL &&
694 folks_presence_details_is_online (
695 FOLKS_PRESENCE_DETAILS (individual)) &&
696 (caps & EMPATHY_CAPABILITIES_FT))
698 gdk_drag_status (context, GDK_ACTION_COPY, time_);
699 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
700 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
704 gdk_drag_status (context, 0, time_);
705 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
709 if (individual != NULL)
710 g_object_unref (individual);
712 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
713 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
714 priv->drag_row == NULL)) ||
715 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
716 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
718 /* If target != GDK_NONE, then we have a contact (individual or persona)
719 drag. If we're pointing to a group, highlight it. Otherwise, if the
720 contact we're pointing to is in a group, highlight that. Otherwise,
721 set the drag position to before the first row for a drag into
722 the "non-group" at the top.
723 If it's an Individual:
724 We only highlight things if the contact is from a different
725 Individual view, or if this Individual view has
726 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
727 which don't have FEATURE_GROUPS_CHANGE, but do have
728 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
730 We only highlight things if we have FEATURE_PERSONA_DROP.
732 GtkTreeIter group_iter;
734 GtkTreePath *group_path;
735 gtk_tree_model_get (model, &iter,
736 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
743 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
744 gtk_tree_model_get (model, &group_iter,
745 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
749 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
750 group_path = gtk_tree_model_get_path (model, &group_iter);
751 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
752 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
753 gtk_tree_path_free (group_path);
757 group_path = gtk_tree_path_new_first ();
758 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
759 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
760 group_path, GTK_TREE_VIEW_DROP_BEFORE);
764 if (!is_different && !cleanup)
769 gtk_tree_path_free (dm->path);
772 g_source_remove (dm->timeout_id);
780 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
782 dm = g_new0 (DragMotionData, 1);
784 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
785 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
786 dm->path = gtk_tree_path_copy (path);
788 dm->timeout_id = g_timeout_add_seconds (1,
789 (GSourceFunc) individual_view_drag_motion_cb, dm);
796 individual_view_drag_begin (GtkWidget *widget,
797 GdkDragContext *context)
799 EmpathyIndividualViewPriv *priv;
800 GtkTreeSelection *selection;
805 priv = GET_PRIV (widget);
807 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
808 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
811 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
814 path = gtk_tree_model_get_path (model, &iter);
815 priv->drag_row = gtk_tree_row_reference_new (model, path);
816 gtk_tree_path_free (path);
820 individual_view_drag_data_get (GtkWidget *widget,
821 GdkDragContext *context,
822 GtkSelectionData *selection,
826 EmpathyIndividualViewPriv *priv;
827 GtkTreePath *src_path;
830 FolksIndividual *individual;
831 const gchar *individual_id;
833 priv = GET_PRIV (widget);
835 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
836 if (priv->drag_row == NULL)
839 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
840 if (src_path == NULL)
843 if (!gtk_tree_model_get_iter (model, &iter, src_path))
845 gtk_tree_path_free (src_path);
849 gtk_tree_path_free (src_path);
852 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
853 if (individual == NULL)
856 individual_id = folks_individual_get_id (individual);
858 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
860 gtk_selection_data_set (selection,
861 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
862 (guchar *) individual_id, strlen (individual_id) + 1);
865 g_object_unref (individual);
869 individual_view_drag_end (GtkWidget *widget,
870 GdkDragContext *context)
872 EmpathyIndividualViewPriv *priv;
874 priv = GET_PRIV (widget);
876 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
881 gtk_tree_row_reference_free (priv->drag_row);
882 priv->drag_row = NULL;
885 if (priv->auto_scroll_timeout_id != 0)
887 g_source_remove (priv->auto_scroll_timeout_id);
888 priv->auto_scroll_timeout_id = 0;
893 individual_view_drag_drop (GtkWidget *widget,
894 GdkDragContext *drag_context,
904 EmpathyIndividualView *view;
910 menu_deactivate_cb (GtkMenuShell *menushell,
913 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
914 g_signal_handlers_disconnect_by_func (menushell,
915 menu_deactivate_cb, user_data);
917 gtk_menu_detach (GTK_MENU (menushell));
921 individual_view_popup_menu_idle_cb (gpointer user_data)
923 MenuPopupData *data = user_data;
926 menu = empathy_individual_view_get_individual_menu (data->view);
928 menu = empathy_individual_view_get_group_menu (data->view);
932 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
934 gtk_widget_show (menu);
935 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
938 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
939 * floating ref. We can either wait that the treeview releases its ref
940 * when it will be destroyed (when leaving Empathy) or explicitely
941 * detach the menu when it's not displayed any more.
942 * We go for the latter as we don't want to keep useless menus in memory
943 * during the whole lifetime of Empathy. */
944 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
948 g_slice_free (MenuPopupData, data);
954 individual_view_button_press_event_cb (EmpathyIndividualView *view,
955 GdkEventButton *event,
958 if (event->button == 3)
962 data = g_slice_new (MenuPopupData);
964 data->button = event->button;
965 data->time = event->time;
966 g_idle_add (individual_view_popup_menu_idle_cb, data);
973 individual_view_key_press_event_cb (EmpathyIndividualView *view,
977 if (event->keyval == GDK_KEY_Menu)
981 data = g_slice_new (MenuPopupData);
984 data->time = event->time;
985 g_idle_add (individual_view_popup_menu_idle_cb, data);
986 } else if (event->keyval == GDK_KEY_F2) {
987 FolksIndividual *individual;
989 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
991 individual = empathy_individual_view_dup_selected (view);
992 if (individual == NULL)
995 empathy_individual_edit_dialog_show (individual, NULL);
997 g_object_unref (individual);
1004 individual_view_row_activated (GtkTreeView *view,
1006 GtkTreeViewColumn *column)
1008 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1009 FolksIndividual *individual;
1010 EmpathyContact *contact;
1011 GtkTreeModel *model;
1014 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1017 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1018 gtk_tree_model_get_iter (model, &iter, path);
1019 gtk_tree_model_get (model, &iter,
1020 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1022 if (individual == NULL)
1025 /* Determine which Persona to chat to, by choosing the most available one. */
1026 contact = empathy_contact_dup_best_for_action (individual,
1027 EMPATHY_ACTION_CHAT);
1029 if (contact != NULL)
1031 DEBUG ("Starting a chat");
1033 empathy_chat_with_contact (contact,
1034 gtk_get_current_event_time ());
1037 g_object_unref (individual);
1038 tp_clear_object (&contact);
1042 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1043 const gchar *path_string,
1044 EmpathyIndividualView *view)
1046 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1048 GtkTreeModel *model;
1050 FolksIndividual *individual;
1051 GdkEventButton *event;
1052 GtkMenuShell *shell;
1055 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1058 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1059 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1062 gtk_tree_model_get (model, &iter,
1063 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1064 if (individual == NULL)
1067 event = (GdkEventButton *) gtk_get_current_event ();
1069 menu = empathy_context_menu_new (GTK_WIDGET (view));
1070 shell = GTK_MENU_SHELL (menu);
1073 item = empathy_individual_audio_call_menu_item_new (individual);
1074 gtk_menu_shell_append (shell, item);
1075 gtk_widget_show (item);
1078 item = empathy_individual_video_call_menu_item_new (individual);
1079 gtk_menu_shell_append (shell, item);
1080 gtk_widget_show (item);
1082 gtk_widget_show (menu);
1083 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1084 event->button, event->time);
1086 g_object_unref (individual);
1090 individual_view_cell_set_background (EmpathyIndividualView *view,
1091 GtkCellRenderer *cell,
1095 if (!is_group && is_active)
1097 GtkStyleContext *style;
1100 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1102 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1105 /* Here we take the current theme colour and add it to
1106 * the colour for white and average the two. This
1107 * gives a colour which is inline with the theme but
1110 empathy_make_color_whiter (&color);
1112 g_object_set (cell, "cell-background-rgba", &color, NULL);
1115 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1119 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1120 GtkCellRenderer *cell,
1121 GtkTreeModel *model,
1123 EmpathyIndividualView *view)
1129 gtk_tree_model_get (model, iter,
1130 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1131 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1132 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1135 "visible", !is_group,
1139 tp_clear_object (&pixbuf);
1141 individual_view_cell_set_background (view, cell, is_group, is_active);
1145 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1146 GtkCellRenderer *cell,
1147 GtkTreeModel *model,
1149 EmpathyIndividualView *view)
1151 GdkPixbuf *pixbuf = NULL;
1155 gtk_tree_model_get (model, iter,
1156 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1157 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1162 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1164 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1165 GTK_ICON_SIZE_MENU);
1167 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1169 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1170 GTK_ICON_SIZE_MENU);
1175 "visible", pixbuf != NULL,
1179 tp_clear_object (&pixbuf);
1185 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1186 GtkCellRenderer *cell,
1187 GtkTreeModel *model,
1189 EmpathyIndividualView *view)
1193 gboolean can_audio, can_video;
1195 gtk_tree_model_get (model, iter,
1196 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1197 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1198 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1199 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1202 "visible", !is_group && (can_audio || can_video),
1203 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1206 individual_view_cell_set_background (view, cell, is_group, is_active);
1210 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1211 GtkCellRenderer *cell,
1212 GtkTreeModel *model,
1214 EmpathyIndividualView *view)
1217 gboolean show_avatar;
1221 gtk_tree_model_get (model, iter,
1222 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1223 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1224 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1225 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1228 "visible", !is_group && show_avatar,
1232 tp_clear_object (&pixbuf);
1234 individual_view_cell_set_background (view, cell, is_group, is_active);
1238 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1239 GtkCellRenderer *cell,
1240 GtkTreeModel *model,
1242 EmpathyIndividualView *view)
1247 gtk_tree_model_get (model, iter,
1248 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1249 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1251 individual_view_cell_set_background (view, cell, is_group, is_active);
1255 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1256 GtkCellRenderer *cell,
1257 GtkTreeModel *model,
1259 EmpathyIndividualView *view)
1264 gtk_tree_model_get (model, iter,
1265 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1266 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1268 if (gtk_tree_model_iter_has_child (model, iter))
1271 gboolean row_expanded;
1273 path = gtk_tree_model_get_path (model, iter);
1275 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1276 (gtk_tree_view_column_get_tree_view (column)), path);
1277 gtk_tree_path_free (path);
1282 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1286 g_object_set (cell, "visible", FALSE, NULL);
1288 individual_view_cell_set_background (view, cell, is_group, is_active);
1292 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1297 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1298 GtkTreeModel *model;
1302 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1305 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1307 gtk_tree_model_get (model, iter,
1308 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1310 expanded = GPOINTER_TO_INT (user_data);
1311 empathy_contact_group_set_expanded (name, expanded);
1317 individual_view_start_search_cb (EmpathyIndividualView *view,
1320 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1322 if (priv->search_widget == NULL)
1325 empathy_individual_view_start_search (view);
1331 individual_view_search_text_notify_cb (TpawLiveSearch *search,
1333 EmpathyIndividualView *view)
1335 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1337 GtkTreeViewColumn *focus_column;
1338 GtkTreeModel *model;
1340 gboolean set_cursor = FALSE;
1342 gtk_tree_model_filter_refilter (priv->filter);
1344 /* Set cursor on the first contact. If it is already set on a group,
1345 * set it on its first child contact. Note that first child of a group
1346 * is its separator, that's why we actually set to the 2nd
1349 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1350 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1354 path = gtk_tree_path_new_from_string ("0:1");
1357 else if (gtk_tree_path_get_depth (path) < 2)
1361 gtk_tree_model_get_iter (model, &iter, path);
1362 gtk_tree_model_get (model, &iter,
1363 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1368 gtk_tree_path_down (path);
1369 gtk_tree_path_next (path);
1376 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1378 if (gtk_tree_model_get_iter (model, &iter, path))
1380 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1385 gtk_tree_path_free (path);
1389 individual_view_search_activate_cb (GtkWidget *search,
1390 EmpathyIndividualView *view)
1393 GtkTreeViewColumn *focus_column;
1395 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1398 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1399 gtk_tree_path_free (path);
1401 gtk_widget_hide (search);
1406 individual_view_search_key_navigation_cb (GtkWidget *search,
1408 EmpathyIndividualView *view)
1410 GdkEvent *new_event;
1411 gboolean ret = FALSE;
1413 new_event = gdk_event_copy (event);
1414 gtk_widget_grab_focus (GTK_WIDGET (view));
1415 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1416 gtk_widget_grab_focus (search);
1418 gdk_event_free (new_event);
1424 individual_view_search_hide_cb (TpawLiveSearch *search,
1425 EmpathyIndividualView *view)
1427 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1428 GtkTreeModel *model;
1429 GtkTreePath *cursor_path;
1431 gboolean valid = FALSE;
1433 /* block expand or collapse handlers, they would write the
1434 * expand or collapsed setting to file otherwise */
1435 g_signal_handlers_block_by_func (view,
1436 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1437 g_signal_handlers_block_by_func (view,
1438 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1440 /* restore which groups are expanded and which are not */
1441 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1442 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1443 valid; valid = gtk_tree_model_iter_next (model, &iter))
1449 gtk_tree_model_get (model, &iter,
1450 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1451 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1460 path = gtk_tree_model_get_path (model, &iter);
1461 if ((priv->view_features &
1462 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1463 empathy_contact_group_get_expanded (name))
1465 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1469 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1472 gtk_tree_path_free (path);
1476 /* unblock expand or collapse handlers */
1477 g_signal_handlers_unblock_by_func (view,
1478 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1479 g_signal_handlers_unblock_by_func (view,
1480 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1482 /* keep the selected contact visible */
1483 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1485 if (cursor_path != NULL)
1486 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1489 gtk_tree_path_free (cursor_path);
1493 individual_view_search_show_cb (TpawLiveSearch *search,
1494 EmpathyIndividualView *view)
1496 /* block expand or collapse handlers during expand all, they would
1497 * write the expand or collapsed setting to file otherwise */
1498 g_signal_handlers_block_by_func (view,
1499 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1501 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1503 g_signal_handlers_unblock_by_func (view,
1504 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1508 expand_idle_foreach_cb (GtkTreeModel *model,
1511 EmpathyIndividualView *self)
1513 EmpathyIndividualViewPriv *priv;
1515 gpointer should_expand;
1518 /* We only want groups */
1519 if (gtk_tree_path_get_depth (path) > 1)
1522 gtk_tree_model_get (model, iter,
1523 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1524 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1533 priv = GET_PRIV (self);
1535 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1538 if (GPOINTER_TO_INT (should_expand))
1539 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1541 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1543 g_hash_table_remove (priv->expand_groups, name);
1552 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1554 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1556 g_signal_handlers_block_by_func (self,
1557 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1558 g_signal_handlers_block_by_func (self,
1559 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1561 /* The store/filter could've been removed while we were in the idle queue */
1562 if (priv->filter != NULL)
1564 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1565 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1568 g_signal_handlers_unblock_by_func (self,
1569 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1570 g_signal_handlers_unblock_by_func (self,
1571 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1573 /* Empty the table of groups to expand/contract, since it may contain groups
1574 * which no longer exist in the tree view. This can happen after going
1575 * offline, for example. */
1576 g_hash_table_remove_all (priv->expand_groups);
1577 priv->expand_groups_idle_handler = 0;
1578 g_object_unref (self);
1584 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1587 EmpathyIndividualView *view)
1589 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1590 gboolean should_expand, is_group = FALSE;
1592 gpointer will_expand;
1594 gtk_tree_model_get (model, iter,
1595 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1596 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1599 if (!is_group || TPAW_STR_EMPTY (name))
1605 should_expand = (priv->view_features &
1606 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1607 (priv->search_widget != NULL &&
1608 gtk_widget_get_visible (priv->search_widget)) ||
1609 empathy_contact_group_get_expanded (name);
1611 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1612 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1613 * a hash table, and expand or contract them as appropriate all at once in
1614 * an idle handler which iterates over all the group rows. */
1615 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1617 GPOINTER_TO_INT (will_expand) != should_expand)
1619 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1620 GINT_TO_POINTER (should_expand));
1622 if (priv->expand_groups_idle_handler == 0)
1624 priv->expand_groups_idle_handler =
1625 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1626 g_object_ref (view));
1634 individual_view_is_visible_individual (EmpathyIndividualView *self,
1635 FolksIndividual *individual,
1637 gboolean is_searching,
1639 gboolean is_fake_group,
1642 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1643 TpawLiveSearch *live = TPAW_LIVE_SEARCH (priv->search_widget);
1646 gboolean is_favorite;
1648 /* Always display individuals having pending events */
1649 if (event_count > 0)
1652 /* We're only giving the visibility wrt filtering here, not things like
1654 if (!priv->show_untrusted &&
1655 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1660 if (!priv->show_uninteresting)
1662 gboolean contains_interesting_persona = FALSE;
1664 /* Hide all individuals which consist entirely of uninteresting
1666 personas = folks_individual_get_personas (individual);
1667 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1668 while (!contains_interesting_persona && gee_iterator_next (iter))
1670 FolksPersona *persona = gee_iterator_get (iter);
1672 if (empathy_folks_persona_is_interesting (persona))
1673 contains_interesting_persona = TRUE;
1675 g_clear_object (&persona);
1677 g_clear_object (&iter);
1679 if (!contains_interesting_persona)
1683 is_favorite = folks_favourite_details_get_is_favourite (
1684 FOLKS_FAVOURITE_DETAILS (individual));
1685 if (!is_searching) {
1686 if (is_favorite && is_fake_group &&
1687 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1688 /* Always display favorite contacts in the favorite group */
1691 return (priv->show_offline || is_online);
1694 return empathy_individual_match_string (individual,
1695 tpaw_live_search_get_text (live),
1696 tpaw_live_search_get_words (live));
1700 get_group (GtkTreeModel *model,
1704 GtkTreeIter parent_iter;
1709 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1712 gtk_tree_model_get (model, &parent_iter,
1713 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1714 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1722 individual_view_filter_visible_func (GtkTreeModel *model,
1726 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1727 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1728 FolksIndividual *individual = NULL;
1729 gboolean is_group, is_separator, valid;
1730 GtkTreeIter child_iter;
1731 gboolean visible, is_online;
1732 gboolean is_searching = TRUE;
1735 if (priv->custom_filter != NULL)
1736 return priv->custom_filter (model, iter, priv->custom_filter_data);
1738 if (priv->search_widget == NULL ||
1739 !gtk_widget_get_visible (priv->search_widget))
1740 is_searching = FALSE;
1742 gtk_tree_model_get (model, iter,
1743 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1744 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1745 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1746 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1747 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1750 if (individual != NULL)
1753 gboolean is_fake_group;
1755 group = get_group (model, iter, &is_fake_group);
1757 visible = individual_view_is_visible_individual (self, individual,
1758 is_online, is_searching, group, is_fake_group, event_count);
1760 g_object_unref (individual);
1769 /* Not a contact, not a separator, must be a group */
1770 g_return_val_if_fail (is_group, FALSE);
1772 /* only show groups which are not empty */
1773 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1774 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1777 gboolean is_fake_group;
1779 gtk_tree_model_get (model, &child_iter,
1780 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1781 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1782 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1785 if (individual == NULL)
1788 group = get_group (model, &child_iter, &is_fake_group);
1790 visible = individual_view_is_visible_individual (self, individual,
1791 is_online, is_searching, group, is_fake_group, event_count);
1793 g_object_unref (individual);
1796 /* show group if it has at least one visible contact in it */
1804 static gchar * empathy_individual_view_dup_selected_group (
1805 EmpathyIndividualView *view,
1806 gboolean *is_fake_group);
1809 text_edited_cb (GtkCellRendererText *cellrenderertext,
1812 EmpathyIndividualView *self)
1814 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1815 gchar *old_name, *new_name;
1817 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1819 new_name = g_strdup (name);
1820 g_strstrip (new_name);
1822 if (tp_str_empty (new_name))
1825 old_name = empathy_individual_view_dup_selected_group (self, NULL);
1826 g_return_if_fail (old_name != NULL);
1828 if (tp_strdiff (old_name, new_name))
1830 EmpathyConnectionAggregator *aggregator;
1832 DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1834 aggregator = empathy_connection_aggregator_dup_singleton ();
1836 empathy_connection_aggregator_rename_group (aggregator, old_name,
1838 g_object_unref (aggregator);
1847 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1848 EmpathyIndividualView *self)
1850 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1852 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1856 individual_view_constructed (GObject *object)
1858 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1859 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1860 GtkCellRenderer *cell;
1861 GtkTreeViewColumn *col;
1866 "headers-visible", FALSE,
1867 "show-expanders", FALSE,
1870 col = gtk_tree_view_column_new ();
1873 cell = gtk_cell_renderer_pixbuf_new ();
1874 gtk_tree_view_column_pack_start (col, cell, FALSE);
1875 gtk_tree_view_column_set_cell_data_func (col, cell,
1876 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1886 cell = gtk_cell_renderer_pixbuf_new ();
1887 gtk_tree_view_column_pack_start (col, cell, FALSE);
1888 gtk_tree_view_column_set_cell_data_func (col, cell,
1889 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1901 priv->text_renderer = empathy_cell_renderer_text_new ();
1902 gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1903 gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1904 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1906 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1907 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1908 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1909 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1910 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1911 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1912 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1913 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1914 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1915 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1916 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1917 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1918 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1919 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1921 g_signal_connect (priv->text_renderer, "editing-canceled",
1922 G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1923 g_signal_connect (priv->text_renderer, "edited",
1924 G_CALLBACK (text_edited_cb), view);
1926 /* Audio Call Icon */
1927 cell = empathy_cell_renderer_activatable_new ();
1928 gtk_tree_view_column_pack_start (col, cell, FALSE);
1929 gtk_tree_view_column_set_cell_data_func (col, cell,
1930 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1933 g_object_set (cell, "visible", FALSE, NULL);
1935 g_signal_connect (cell, "path-activated",
1936 G_CALLBACK (individual_view_call_activated_cb), view);
1939 cell = gtk_cell_renderer_pixbuf_new ();
1940 gtk_tree_view_column_pack_start (col, cell, FALSE);
1941 gtk_tree_view_column_set_cell_data_func (col, cell,
1942 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1954 cell = empathy_cell_renderer_expander_new ();
1955 gtk_tree_view_column_pack_end (col, cell, FALSE);
1956 gtk_tree_view_column_set_cell_data_func (col, cell,
1957 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1960 /* Actually add the column now we have added all cell renderers */
1961 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1964 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1966 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1971 individual_view_set_view_features (EmpathyIndividualView *view,
1972 EmpathyIndividualFeatureFlags features)
1974 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1975 gboolean has_tooltip;
1977 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1979 priv->view_features = features;
1981 /* Setting reorderable is a hack that gets us row previews as drag icons
1982 for free. We override all the drag handlers. It's tricky to get the
1983 position of the drag icon right in drag_begin. GtkTreeView has special
1984 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1987 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1988 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1990 /* Update DnD source/dest */
1991 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1993 gtk_drag_source_set (GTK_WIDGET (view),
1996 G_N_ELEMENTS (drag_types_source),
1997 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2001 gtk_drag_source_unset (GTK_WIDGET (view));
2005 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2007 gtk_drag_dest_set (GTK_WIDGET (view),
2008 GTK_DEST_DEFAULT_ALL,
2010 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2014 /* FIXME: URI could still be droped depending on FT feature */
2015 gtk_drag_dest_unset (GTK_WIDGET (view));
2018 /* Update has-tooltip */
2020 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2021 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2025 individual_view_dispose (GObject *object)
2027 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2028 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2030 tp_clear_object (&priv->store);
2031 tp_clear_object (&priv->filter);
2032 tp_clear_object (&priv->tooltip_widget);
2034 empathy_individual_view_set_live_search (view, NULL);
2036 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2040 individual_view_finalize (GObject *object)
2042 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2044 if (priv->expand_groups_idle_handler != 0)
2045 g_source_remove (priv->expand_groups_idle_handler);
2046 g_hash_table_unref (priv->expand_groups);
2048 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2052 individual_view_get_property (GObject *object,
2057 EmpathyIndividualViewPriv *priv;
2059 priv = GET_PRIV (object);
2064 g_value_set_object (value, priv->store);
2066 case PROP_VIEW_FEATURES:
2067 g_value_set_flags (value, priv->view_features);
2069 case PROP_INDIVIDUAL_FEATURES:
2070 g_value_set_flags (value, priv->individual_features);
2072 case PROP_SHOW_OFFLINE:
2073 g_value_set_boolean (value, priv->show_offline);
2075 case PROP_SHOW_UNTRUSTED:
2076 g_value_set_boolean (value, priv->show_untrusted);
2078 case PROP_SHOW_UNINTERESTING:
2079 g_value_set_boolean (value, priv->show_uninteresting);
2082 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2088 individual_view_set_property (GObject *object,
2090 const GValue *value,
2093 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2094 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2099 empathy_individual_view_set_store (view, g_value_get_object (value));
2101 case PROP_VIEW_FEATURES:
2102 individual_view_set_view_features (view, g_value_get_flags (value));
2104 case PROP_INDIVIDUAL_FEATURES:
2105 priv->individual_features = g_value_get_flags (value);
2107 case PROP_SHOW_OFFLINE:
2108 empathy_individual_view_set_show_offline (view,
2109 g_value_get_boolean (value));
2111 case PROP_SHOW_UNTRUSTED:
2112 empathy_individual_view_set_show_untrusted (view,
2113 g_value_get_boolean (value));
2115 case PROP_SHOW_UNINTERESTING:
2116 empathy_individual_view_set_show_uninteresting (view,
2117 g_value_get_boolean (value));
2119 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2125 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2127 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2128 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2129 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2131 object_class->constructed = individual_view_constructed;
2132 object_class->dispose = individual_view_dispose;
2133 object_class->finalize = individual_view_finalize;
2134 object_class->get_property = individual_view_get_property;
2135 object_class->set_property = individual_view_set_property;
2137 widget_class->drag_data_received = individual_view_drag_data_received;
2138 widget_class->drag_drop = individual_view_drag_drop;
2139 widget_class->drag_begin = individual_view_drag_begin;
2140 widget_class->drag_data_get = individual_view_drag_data_get;
2141 widget_class->drag_end = individual_view_drag_end;
2142 widget_class->drag_motion = individual_view_drag_motion;
2144 /* We use the class method to let user of this widget to connect to
2145 * the signal and stop emission of the signal so the default handler
2146 * won't be called. */
2147 tree_view_class->row_activated = individual_view_row_activated;
2149 klass->drag_individual_received = real_drag_individual_received_cb;
2151 signals[DRAG_INDIVIDUAL_RECEIVED] =
2152 g_signal_new ("drag-individual-received",
2153 G_OBJECT_CLASS_TYPE (klass),
2155 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2157 g_cclosure_marshal_generic,
2158 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2159 G_TYPE_STRING, G_TYPE_STRING);
2161 signals[DRAG_PERSONA_RECEIVED] =
2162 g_signal_new ("drag-persona-received",
2163 G_OBJECT_CLASS_TYPE (klass),
2165 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2167 g_cclosure_marshal_generic,
2168 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2170 g_object_class_install_property (object_class,
2172 g_param_spec_object ("store",
2173 "The store of the view",
2174 "The store of the view",
2175 EMPATHY_TYPE_INDIVIDUAL_STORE,
2176 G_PARAM_READWRITE));
2177 g_object_class_install_property (object_class,
2179 g_param_spec_flags ("view-features",
2180 "Features of the view",
2181 "Flags for all enabled features",
2182 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2183 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2184 g_object_class_install_property (object_class,
2185 PROP_INDIVIDUAL_FEATURES,
2186 g_param_spec_flags ("individual-features",
2187 "Features of the individual menu",
2188 "Flags for all enabled features for the menu",
2189 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2190 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2191 g_object_class_install_property (object_class,
2193 g_param_spec_boolean ("show-offline",
2195 "Whether contact list should display "
2196 "offline contacts", FALSE, G_PARAM_READWRITE));
2197 g_object_class_install_property (object_class,
2198 PROP_SHOW_UNTRUSTED,
2199 g_param_spec_boolean ("show-untrusted",
2200 "Show Untrusted Individuals",
2201 "Whether the view should display untrusted individuals; "
2202 "those who could not be who they say they are.",
2203 TRUE, G_PARAM_READWRITE));
2204 g_object_class_install_property (object_class,
2205 PROP_SHOW_UNINTERESTING,
2206 g_param_spec_boolean ("show-uninteresting",
2207 "Show Uninteresting Individuals",
2208 "Whether the view should not filter out individuals using "
2209 "empathy_folks_persona_is_interesting.",
2210 FALSE, G_PARAM_READWRITE));
2212 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2216 empathy_individual_view_init (EmpathyIndividualView *view)
2218 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2219 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2223 priv->show_untrusted = TRUE;
2224 priv->show_uninteresting = FALSE;
2226 /* Get saved group states. */
2227 empathy_contact_groups_get_all ();
2229 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2230 (GDestroyNotify) g_free, NULL);
2232 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2233 empathy_individual_store_row_separator_func, NULL, NULL);
2235 /* Connect to tree view signals rather than override. */
2236 g_signal_connect (view, "button-press-event",
2237 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2238 g_signal_connect (view, "key-press-event",
2239 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2240 g_signal_connect (view, "row-expanded",
2241 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2242 GINT_TO_POINTER (TRUE));
2243 g_signal_connect (view, "row-collapsed",
2244 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2245 GINT_TO_POINTER (FALSE));
2246 g_signal_connect (view, "query-tooltip",
2247 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2250 EmpathyIndividualView *
2251 empathy_individual_view_new (EmpathyIndividualStore *store,
2252 EmpathyIndividualViewFeatureFlags view_features,
2253 EmpathyIndividualFeatureFlags individual_features)
2255 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2257 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2259 "individual-features", individual_features,
2260 "view-features", view_features, NULL);
2264 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2266 GtkTreeSelection *selection;
2268 GtkTreeModel *model;
2269 FolksIndividual *individual;
2271 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2273 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2274 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2277 gtk_tree_model_get (model, &iter,
2278 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2284 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2285 gboolean *is_fake_group)
2287 GtkTreeSelection *selection;
2289 GtkTreeModel *model;
2294 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2296 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2297 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2300 gtk_tree_model_get (model, &iter,
2301 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2302 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2303 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2311 if (is_fake_group != NULL)
2312 *is_fake_group = fake;
2319 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2320 REMOVE_DIALOG_RESPONSE_DELETE,
2324 individual_view_remove_dialog_show (GtkWindow *parent,
2325 const gchar *message,
2326 const gchar *secondary_text)
2331 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2332 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2334 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2335 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2336 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2337 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2338 "%s", secondary_text);
2340 gtk_widget_show (dialog);
2342 res = gtk_dialog_run (GTK_DIALOG (dialog));
2343 gtk_widget_destroy (dialog);
2349 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2350 EmpathyIndividualView *view)
2354 group = empathy_individual_view_dup_selected_group (view, NULL);
2361 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2363 parent = tpaw_get_toplevel_window (GTK_WIDGET (view));
2364 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2365 text) == REMOVE_DIALOG_RESPONSE_DELETE)
2367 EmpathyIndividualManager *manager =
2368 empathy_individual_manager_dup_singleton ();
2369 empathy_individual_manager_remove_group (manager, group);
2370 g_object_unref (G_OBJECT (manager));
2380 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2381 EmpathyIndividualView *self)
2383 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2386 GtkTreeSelection *selection;
2387 GtkTreeModel *model;
2389 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2390 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2392 path = gtk_tree_model_get_path (model, &iter);
2394 g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2396 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2397 gtk_widget_grab_focus (GTK_WIDGET (self));
2398 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2399 gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2401 gtk_tree_path_free (path);
2405 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2407 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2412 gboolean is_fake_group;
2414 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2416 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2417 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2420 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2421 if (!group || is_fake_group)
2423 /* We can't alter fake groups */
2428 menu = gtk_menu_new ();
2430 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2432 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2433 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2434 gtk_widget_show (item);
2435 g_signal_connect (item, "activate",
2436 G_CALLBACK (individual_view_group_rename_activate_cb), view);
2439 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2441 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2442 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2443 GTK_ICON_SIZE_MENU);
2444 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2445 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2446 gtk_widget_show (item);
2447 g_signal_connect (item, "activate",
2448 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2457 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2459 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2460 FolksIndividual *individual;
2461 GtkWidget *menu = NULL;
2463 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2465 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2466 /* No need to create a context menu */
2469 individual = empathy_individual_view_dup_selected (view);
2470 if (individual == NULL)
2473 if (!empathy_folks_individual_contains_contact (individual))
2476 menu = empathy_individual_menu_new (individual, NULL,
2477 priv->individual_features, priv->store);
2480 g_object_unref (individual);
2486 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2487 TpawLiveSearch *search)
2489 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2491 /* remove old handlers if old search was not null */
2492 if (priv->search_widget != NULL)
2494 g_signal_handlers_disconnect_by_func (view,
2495 individual_view_start_search_cb, NULL);
2497 g_signal_handlers_disconnect_by_func (priv->search_widget,
2498 individual_view_search_text_notify_cb, view);
2499 g_signal_handlers_disconnect_by_func (priv->search_widget,
2500 individual_view_search_activate_cb, view);
2501 g_signal_handlers_disconnect_by_func (priv->search_widget,
2502 individual_view_search_key_navigation_cb, view);
2503 g_signal_handlers_disconnect_by_func (priv->search_widget,
2504 individual_view_search_hide_cb, view);
2505 g_signal_handlers_disconnect_by_func (priv->search_widget,
2506 individual_view_search_show_cb, view);
2507 g_object_unref (priv->search_widget);
2508 priv->search_widget = NULL;
2511 /* connect handlers if new search is not null */
2514 priv->search_widget = g_object_ref (search);
2516 g_signal_connect (view, "start-interactive-search",
2517 G_CALLBACK (individual_view_start_search_cb), NULL);
2519 g_signal_connect (priv->search_widget, "notify::text",
2520 G_CALLBACK (individual_view_search_text_notify_cb), view);
2521 g_signal_connect (priv->search_widget, "activate",
2522 G_CALLBACK (individual_view_search_activate_cb), view);
2523 g_signal_connect (priv->search_widget, "key-navigation",
2524 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2525 g_signal_connect (priv->search_widget, "hide",
2526 G_CALLBACK (individual_view_search_hide_cb), view);
2527 g_signal_connect (priv->search_widget, "show",
2528 G_CALLBACK (individual_view_search_show_cb), view);
2533 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2535 EmpathyIndividualViewPriv *priv;
2537 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2539 priv = GET_PRIV (self);
2541 return (priv->search_widget != NULL &&
2542 gtk_widget_get_visible (priv->search_widget));
2546 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2548 EmpathyIndividualViewPriv *priv;
2550 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2552 priv = GET_PRIV (self);
2554 return priv->show_offline;
2558 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2559 gboolean show_offline)
2561 EmpathyIndividualViewPriv *priv;
2563 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2565 priv = GET_PRIV (self);
2567 priv->show_offline = show_offline;
2569 g_object_notify (G_OBJECT (self), "show-offline");
2570 gtk_tree_model_filter_refilter (priv->filter);
2574 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2576 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2578 return GET_PRIV (self)->show_untrusted;
2582 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2583 gboolean show_untrusted)
2585 EmpathyIndividualViewPriv *priv;
2587 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2589 priv = GET_PRIV (self);
2591 priv->show_untrusted = show_untrusted;
2593 g_object_notify (G_OBJECT (self), "show-untrusted");
2594 gtk_tree_model_filter_refilter (priv->filter);
2597 EmpathyIndividualStore *
2598 empathy_individual_view_get_store (EmpathyIndividualView *self)
2600 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2602 return GET_PRIV (self)->store;
2606 empathy_individual_view_set_store (EmpathyIndividualView *self,
2607 EmpathyIndividualStore *store)
2609 EmpathyIndividualViewPriv *priv;
2611 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2612 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2614 priv = GET_PRIV (self);
2616 /* Destroy the old filter and remove the old store */
2617 if (priv->store != NULL)
2619 g_signal_handlers_disconnect_by_func (priv->filter,
2620 individual_view_row_has_child_toggled_cb, self);
2622 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2625 tp_clear_object (&priv->filter);
2626 tp_clear_object (&priv->store);
2628 /* Set the new store */
2629 priv->store = store;
2633 g_object_ref (store);
2635 /* Create a new filter */
2636 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2637 GTK_TREE_MODEL (priv->store), NULL));
2638 gtk_tree_model_filter_set_visible_func (priv->filter,
2639 individual_view_filter_visible_func, self, NULL);
2641 g_signal_connect (priv->filter, "row-has-child-toggled",
2642 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2643 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2644 GTK_TREE_MODEL (priv->filter));
2649 empathy_individual_view_start_search (EmpathyIndividualView *self)
2651 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2653 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2654 g_return_if_fail (priv->search_widget != NULL);
2656 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2657 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2659 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2663 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2664 GtkTreeModelFilterVisibleFunc filter,
2667 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2669 priv->custom_filter = filter;
2670 priv->custom_filter_data = data;
2674 empathy_individual_view_refilter (EmpathyIndividualView *self)
2676 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2678 gtk_tree_model_filter_refilter (priv->filter);
2682 empathy_individual_view_select_first (EmpathyIndividualView *self)
2684 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2687 gtk_tree_model_filter_refilter (priv->filter);
2689 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2691 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2692 GTK_TREE_VIEW (self));
2694 gtk_tree_selection_select_iter (selection, &iter);
2699 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2700 gboolean show_uninteresting)
2702 EmpathyIndividualViewPriv *priv;
2704 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2706 priv = GET_PRIV (self);
2708 priv->show_uninteresting = show_uninteresting;
2710 g_object_notify (G_OBJECT (self), "show-uninteresting");
2711 gtk_tree_model_filter_refilter (priv->filter);