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>
29 #include <glib/gi18n-lib.h>
31 #include "libempathy/empathy-connection-aggregator.h"
32 #include "libempathy/empathy-individual-manager.h"
33 #include "libempathy/empathy-contact-groups.h"
34 #include "libempathy/empathy-request-util.h"
35 #include "libempathy/empathy-utils.h"
37 #include "empathy-individual-edit-dialog.h"
38 #include "empathy-images.h"
39 #include "empathy-cell-renderer-expander.h"
40 #include "empathy-cell-renderer-text.h"
41 #include "empathy-cell-renderer-activatable.h"
42 #include "empathy-ui-utils.h"
43 #include "empathy-gtk-enum-types.h"
45 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
46 #include "libempathy/empathy-debug.h"
48 /* Active users are those which have recently changed state
49 * (e.g. online, offline or from normal to a busy state).
52 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
55 EmpathyIndividualStore *store;
56 GtkTreeRowReference *drag_row;
57 EmpathyIndividualViewFeatureFlags view_features;
58 EmpathyIndividualFeatureFlags individual_features;
59 GtkWidget *tooltip_widget;
61 gboolean show_offline;
62 gboolean show_untrusted;
63 gboolean show_uninteresting;
65 GtkTreeModelFilter *filter;
66 GtkWidget *search_widget;
68 guint expand_groups_idle_handler;
69 /* owned string (group name) -> bool (whether to expand/contract) */
70 GHashTable *expand_groups;
73 guint auto_scroll_timeout_id;
74 /* Distance between mouse pointer and the nearby border. Negative when
78 GtkTreeModelFilterVisibleFunc custom_filter;
79 gpointer custom_filter_data;
81 GtkCellRenderer *text_renderer;
82 } EmpathyIndividualViewPriv;
86 EmpathyIndividualView *view;
93 EmpathyIndividualView *view;
94 FolksIndividual *individual;
103 PROP_INDIVIDUAL_FEATURES,
106 PROP_SHOW_UNINTERESTING,
109 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
110 * specific EmpathyContacts (between/in/out of Individuals) */
113 DND_DRAG_TYPE_UNKNOWN = -1,
114 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
115 DND_DRAG_TYPE_PERSONA_ID,
116 DND_DRAG_TYPE_URI_LIST,
117 DND_DRAG_TYPE_STRING,
120 #define DRAG_TYPE(T,I) \
121 { (gchar *) T, 0, I }
123 static const GtkTargetEntry drag_types_dest[] = {
124 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
125 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
126 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
127 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
128 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
129 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
132 static const GtkTargetEntry drag_types_source[] = {
133 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
138 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
142 DRAG_INDIVIDUAL_RECEIVED,
143 DRAG_PERSONA_RECEIVED,
147 static guint signals[LAST_SIGNAL];
149 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
153 individual_view_tooltip_destroy_cb (GtkWidget *widget,
154 EmpathyIndividualView *view)
156 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
158 tp_clear_object (&priv->tooltip_widget);
162 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
165 gboolean keyboard_mode,
169 EmpathyIndividualViewPriv *priv;
170 FolksIndividual *individual;
174 static gint running = 0;
175 gboolean ret = FALSE;
177 priv = GET_PRIV (view);
179 /* Avoid an infinite loop. See GNOME bug #574377 */
185 /* Don't show the tooltip if there's already a popup menu */
186 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
189 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
190 keyboard_mode, &model, &path, &iter))
193 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
194 gtk_tree_path_free (path);
196 gtk_tree_model_get (model, &iter,
197 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
199 if (individual == NULL)
202 if (priv->tooltip_widget == NULL)
204 priv->tooltip_widget = empathy_individual_widget_new (individual,
205 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
206 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
207 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
208 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
209 g_object_ref (priv->tooltip_widget);
211 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
212 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
214 gtk_widget_show (priv->tooltip_widget);
218 empathy_individual_widget_set_individual (
219 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
222 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
225 g_object_unref (individual);
233 groups_change_group_cb (GObject *source,
234 GAsyncResult *result,
237 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
238 GError *error = NULL;
240 folks_group_details_change_group_finish (group_details, result, &error);
243 g_warning ("failed to change group: %s", error->message);
244 g_clear_error (&error);
249 group_can_be_modified (const gchar *name,
250 gboolean is_fake_group,
253 /* Real groups can always be modified */
257 /* The favorite fake group can be modified so users can
258 * add/remove favorites using DnD */
259 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
262 /* We can remove contacts from the 'ungrouped' fake group */
263 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
270 individual_view_individual_drag_received (GtkWidget *self,
271 GdkDragContext *context,
274 GtkSelectionData *selection)
276 EmpathyIndividualViewPriv *priv;
277 EmpathyIndividualManager *manager = NULL;
278 FolksIndividual *individual;
279 GtkTreePath *source_path;
280 const gchar *sel_data;
281 gchar *new_group = NULL;
282 gchar *old_group = NULL;
283 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
285 priv = GET_PRIV (self);
287 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
288 new_group = empathy_individual_store_get_parent_group (model, path,
289 NULL, &new_group_is_fake);
291 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
294 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
295 * feature. Otherwise, we just add the dropped contact to whichever group
296 * they were dropped in, and don't remove them from their old group. This
297 * allows for Individual views which shouldn't allow Individuals to have
298 * their groups changed, and also for dragging Individuals between Individual
300 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
301 priv->drag_row != NULL)
303 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
307 empathy_individual_store_get_parent_group (model, source_path,
308 NULL, &old_group_is_fake);
309 gtk_tree_path_free (source_path);
312 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
315 if (!tp_strdiff (old_group, new_group))
318 else if (priv->drag_row != NULL)
320 /* We don't allow changing Individuals' groups, and this Individual was
321 * dragged from another group in *this* Individual view, so we disallow
326 /* XXX: for contacts, we used to ensure the account, create the contact
327 * factory, and then wait on the contacts. But they should already be
328 * created by this point */
330 manager = empathy_individual_manager_dup_singleton ();
331 individual = empathy_individual_manager_lookup_member (manager, sel_data);
333 if (individual == NULL)
335 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
339 /* FIXME: We should probably wait for the cb before calling
342 /* Emit a signal notifying of the drag. We change the Individual's groups in
343 * the default signal handler. */
344 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
345 gdk_drag_context_get_selected_action (context), individual, new_group,
351 tp_clear_object (&manager);
359 real_drag_individual_received_cb (EmpathyIndividualView *self,
360 GdkDragAction action,
361 FolksIndividual *individual,
362 const gchar *new_group,
363 const gchar *old_group)
365 DEBUG ("individual %s dragged from '%s' to '%s'",
366 folks_individual_get_id (individual), old_group, new_group);
368 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
370 /* Mark contact as favourite */
371 folks_favourite_details_set_is_favourite (
372 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
376 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
378 /* Remove contact as favourite */
379 folks_favourite_details_set_is_favourite (
380 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
382 /* Don't try to remove it */
386 if (new_group != NULL)
388 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
389 new_group, TRUE, groups_change_group_cb, NULL);
392 if (old_group != NULL && action == GDK_ACTION_MOVE)
394 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
395 old_group, FALSE, groups_change_group_cb, NULL);
400 individual_view_persona_drag_received (GtkWidget *self,
401 GdkDragContext *context,
404 GtkSelectionData *selection)
406 EmpathyIndividualManager *manager = NULL;
407 FolksIndividual *individual = NULL;
408 FolksPersona *persona = NULL;
409 const gchar *persona_uid;
410 GList *individuals, *l;
411 GeeIterator *iter = NULL;
412 gboolean retval = FALSE;
414 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
416 /* FIXME: This is slow, but the only way to find the Persona we're having
418 manager = empathy_individual_manager_dup_singleton ();
419 individuals = empathy_individual_manager_get_members (manager);
421 for (l = individuals; l != NULL; l = l->next)
425 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
426 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
427 while (gee_iterator_next (iter))
429 FolksPersona *persona_cur = gee_iterator_get (iter);
431 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
433 /* takes ownership of the ref */
434 persona = persona_cur;
435 individual = g_object_ref (l->data);
438 g_clear_object (&persona_cur);
440 g_clear_object (&iter);
444 g_clear_object (&iter);
445 g_list_free (individuals);
447 if (persona == NULL || individual == NULL)
449 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
453 /* Emit a signal notifying of the drag. We change the Individual's groups in
454 * the default signal handler. */
455 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
456 gdk_drag_context_get_selected_action (context), persona, individual,
460 tp_clear_object (&manager);
461 tp_clear_object (&persona);
462 tp_clear_object (&individual);
468 individual_view_file_drag_received (GtkWidget *view,
469 GdkDragContext *context,
472 GtkSelectionData *selection)
475 const gchar *sel_data;
476 FolksIndividual *individual;
477 EmpathyContact *contact;
479 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
481 gtk_tree_model_get_iter (model, &iter, path);
482 gtk_tree_model_get (model, &iter,
483 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
484 if (individual == NULL)
487 contact = empathy_contact_dup_from_folks_individual (individual);
488 empathy_send_file_from_uri_list (contact, sel_data);
490 g_object_unref (individual);
491 tp_clear_object (&contact);
497 individual_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
501 GtkSelectionData *selection,
507 GtkTreeViewDropPosition position;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
515 x, y, &path, &position);
520 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
522 success = individual_view_individual_drag_received (view,
523 context, model, path, selection);
525 else if (info == DND_DRAG_TYPE_PERSONA_ID)
527 success = individual_view_persona_drag_received (view, context, model,
530 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
532 success = individual_view_file_drag_received (view,
533 context, model, path, selection);
536 gtk_tree_path_free (path);
537 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
541 individual_view_drag_motion_cb (DragMotionData *data)
543 if (data->view != NULL)
545 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
546 g_object_remove_weak_pointer (G_OBJECT (data->view),
547 (gpointer *) &data->view);
550 data->timeout_id = 0;
555 /* Minimum distance between the mouse pointer and a horizontal border when we
556 start auto scrolling. */
557 #define AUTO_SCROLL_MARGIN_SIZE 20
558 /* How far to scroll per one tick. */
559 #define AUTO_SCROLL_PITCH 10
562 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
564 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
568 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
570 if (priv->distance < 0)
571 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
573 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
575 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
576 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
578 gtk_adjustment_set_value (adj, new_value);
584 individual_view_drag_motion (GtkWidget *widget,
585 GdkDragContext *context,
590 EmpathyIndividualViewPriv *priv;
594 static DragMotionData *dm = NULL;
597 gboolean is_different = FALSE;
598 gboolean cleanup = TRUE;
599 gboolean retval = TRUE;
600 GtkAllocation allocation;
602 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
604 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
605 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
608 if (priv->auto_scroll_timeout_id != 0)
610 g_source_remove (priv->auto_scroll_timeout_id);
611 priv->auto_scroll_timeout_id = 0;
614 gtk_widget_get_allocation (widget, &allocation);
616 if (y < AUTO_SCROLL_MARGIN_SIZE ||
617 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
619 if (y < AUTO_SCROLL_MARGIN_SIZE)
620 priv->distance = MIN (-y, -1);
622 priv->distance = MAX (allocation.height - y, 1);
624 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
625 (GSourceFunc) individual_view_auto_scroll_cb, widget);
628 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
629 x, y, &path, NULL, NULL, NULL);
631 cleanup &= (dm == NULL);
635 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
636 is_different = ((dm == NULL) || ((dm != NULL)
637 && gtk_tree_path_compare (dm->path, path) != 0));
644 /* Coordinates don't point to an actual row, so make sure the pointer
645 and highlighting don't indicate that a drag is possible.
647 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
648 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
651 target = gtk_drag_dest_find_target (widget, context, NULL);
652 gtk_tree_model_get_iter (model, &iter, path);
654 /* Determine the DndDragType of the data */
655 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
657 if (target == drag_atoms_dest[i])
659 drag_type = drag_types_dest[i].info;
664 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
665 drag_type == DND_DRAG_TYPE_STRING)
667 /* This is a file drag, and it can only be dropped on contacts,
669 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
670 * even if we have a valid target. */
671 FolksIndividual *individual = NULL;
672 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
674 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
676 gtk_tree_model_get (model, &iter,
677 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
681 if (individual != NULL)
683 EmpathyContact *contact = NULL;
685 contact = empathy_contact_dup_from_folks_individual (individual);
687 caps = empathy_contact_get_capabilities (contact);
689 tp_clear_object (&contact);
692 if (individual != NULL &&
693 folks_presence_details_is_online (
694 FOLKS_PRESENCE_DETAILS (individual)) &&
695 (caps & EMPATHY_CAPABILITIES_FT))
697 gdk_drag_status (context, GDK_ACTION_COPY, time_);
698 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
699 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
703 gdk_drag_status (context, 0, time_);
704 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
708 if (individual != NULL)
709 g_object_unref (individual);
711 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
712 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
713 priv->drag_row == NULL)) ||
714 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
715 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
717 /* If target != GDK_NONE, then we have a contact (individual or persona)
718 drag. If we're pointing to a group, highlight it. Otherwise, if the
719 contact we're pointing to is in a group, highlight that. Otherwise,
720 set the drag position to before the first row for a drag into
721 the "non-group" at the top.
722 If it's an Individual:
723 We only highlight things if the contact is from a different
724 Individual view, or if this Individual view has
725 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
726 which don't have FEATURE_GROUPS_CHANGE, but do have
727 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
729 We only highlight things if we have FEATURE_PERSONA_DROP.
731 GtkTreeIter group_iter;
733 GtkTreePath *group_path;
734 gtk_tree_model_get (model, &iter,
735 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
742 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
743 gtk_tree_model_get (model, &group_iter,
744 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
748 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
749 group_path = gtk_tree_model_get_path (model, &group_iter);
750 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
751 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
752 gtk_tree_path_free (group_path);
756 group_path = gtk_tree_path_new_first ();
757 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
758 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
759 group_path, GTK_TREE_VIEW_DROP_BEFORE);
763 if (!is_different && !cleanup)
768 gtk_tree_path_free (dm->path);
771 g_source_remove (dm->timeout_id);
779 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
781 dm = g_new0 (DragMotionData, 1);
783 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
784 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
785 dm->path = gtk_tree_path_copy (path);
787 dm->timeout_id = g_timeout_add_seconds (1,
788 (GSourceFunc) individual_view_drag_motion_cb, dm);
795 individual_view_drag_begin (GtkWidget *widget,
796 GdkDragContext *context)
798 EmpathyIndividualViewPriv *priv;
799 GtkTreeSelection *selection;
804 priv = GET_PRIV (widget);
806 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
807 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
810 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
813 path = gtk_tree_model_get_path (model, &iter);
814 priv->drag_row = gtk_tree_row_reference_new (model, path);
815 gtk_tree_path_free (path);
819 individual_view_drag_data_get (GtkWidget *widget,
820 GdkDragContext *context,
821 GtkSelectionData *selection,
825 EmpathyIndividualViewPriv *priv;
826 GtkTreePath *src_path;
829 FolksIndividual *individual;
830 const gchar *individual_id;
832 priv = GET_PRIV (widget);
834 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
835 if (priv->drag_row == NULL)
838 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
839 if (src_path == NULL)
842 if (!gtk_tree_model_get_iter (model, &iter, src_path))
844 gtk_tree_path_free (src_path);
848 gtk_tree_path_free (src_path);
851 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
852 if (individual == NULL)
855 individual_id = folks_individual_get_id (individual);
857 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
859 gtk_selection_data_set (selection,
860 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
861 (guchar *) individual_id, strlen (individual_id) + 1);
864 g_object_unref (individual);
868 individual_view_drag_end (GtkWidget *widget,
869 GdkDragContext *context)
871 EmpathyIndividualViewPriv *priv;
873 priv = GET_PRIV (widget);
875 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
880 gtk_tree_row_reference_free (priv->drag_row);
881 priv->drag_row = NULL;
884 if (priv->auto_scroll_timeout_id != 0)
886 g_source_remove (priv->auto_scroll_timeout_id);
887 priv->auto_scroll_timeout_id = 0;
892 individual_view_drag_drop (GtkWidget *widget,
893 GdkDragContext *drag_context,
903 EmpathyIndividualView *view;
909 menu_deactivate_cb (GtkMenuShell *menushell,
912 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
913 g_signal_handlers_disconnect_by_func (menushell,
914 menu_deactivate_cb, user_data);
916 gtk_menu_detach (GTK_MENU (menushell));
920 individual_view_popup_menu_idle_cb (gpointer user_data)
922 MenuPopupData *data = user_data;
925 menu = empathy_individual_view_get_individual_menu (data->view);
927 menu = empathy_individual_view_get_group_menu (data->view);
931 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
933 gtk_widget_show (menu);
934 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
937 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
938 * floating ref. We can either wait that the treeview releases its ref
939 * when it will be destroyed (when leaving Empathy) or explicitely
940 * detach the menu when it's not displayed any more.
941 * We go for the latter as we don't want to keep useless menus in memory
942 * during the whole lifetime of Empathy. */
943 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
947 g_slice_free (MenuPopupData, data);
953 individual_view_button_press_event_cb (EmpathyIndividualView *view,
954 GdkEventButton *event,
957 if (event->button == 3)
961 data = g_slice_new (MenuPopupData);
963 data->button = event->button;
964 data->time = event->time;
965 g_idle_add (individual_view_popup_menu_idle_cb, data);
972 individual_view_key_press_event_cb (EmpathyIndividualView *view,
976 if (event->keyval == GDK_KEY_Menu)
980 data = g_slice_new (MenuPopupData);
983 data->time = event->time;
984 g_idle_add (individual_view_popup_menu_idle_cb, data);
985 } else if (event->keyval == GDK_KEY_F2) {
986 FolksIndividual *individual;
988 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
990 individual = empathy_individual_view_dup_selected (view);
991 if (individual == NULL)
994 empathy_individual_edit_dialog_show (individual, NULL);
996 g_object_unref (individual);
1003 individual_view_row_activated (GtkTreeView *view,
1005 GtkTreeViewColumn *column)
1007 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1008 FolksIndividual *individual;
1009 EmpathyContact *contact;
1010 GtkTreeModel *model;
1013 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1016 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1017 gtk_tree_model_get_iter (model, &iter, path);
1018 gtk_tree_model_get (model, &iter,
1019 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1021 if (individual == NULL)
1024 /* Determine which Persona to chat to, by choosing the most available one. */
1025 contact = empathy_contact_dup_best_for_action (individual,
1026 EMPATHY_ACTION_CHAT);
1028 if (contact != NULL)
1030 DEBUG ("Starting a chat");
1032 empathy_chat_with_contact (contact,
1033 gtk_get_current_event_time ());
1036 g_object_unref (individual);
1037 tp_clear_object (&contact);
1041 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1042 const gchar *path_string,
1043 EmpathyIndividualView *view)
1045 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1047 GtkTreeModel *model;
1049 FolksIndividual *individual;
1050 GdkEventButton *event;
1051 GtkMenuShell *shell;
1054 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1057 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1058 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1061 gtk_tree_model_get (model, &iter,
1062 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1063 if (individual == NULL)
1066 event = (GdkEventButton *) gtk_get_current_event ();
1068 menu = empathy_context_menu_new (GTK_WIDGET (view));
1069 shell = GTK_MENU_SHELL (menu);
1072 item = empathy_individual_audio_call_menu_item_new (individual);
1073 gtk_menu_shell_append (shell, item);
1074 gtk_widget_show (item);
1077 item = empathy_individual_video_call_menu_item_new (individual);
1078 gtk_menu_shell_append (shell, item);
1079 gtk_widget_show (item);
1081 gtk_widget_show (menu);
1082 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1083 event->button, event->time);
1085 g_object_unref (individual);
1089 individual_view_cell_set_background (EmpathyIndividualView *view,
1090 GtkCellRenderer *cell,
1094 if (!is_group && is_active)
1096 GtkStyleContext *style;
1099 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1101 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1104 /* Here we take the current theme colour and add it to
1105 * the colour for white and average the two. This
1106 * gives a colour which is inline with the theme but
1109 empathy_make_color_whiter (&color);
1111 g_object_set (cell, "cell-background-rgba", &color, NULL);
1114 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1118 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1119 GtkCellRenderer *cell,
1120 GtkTreeModel *model,
1122 EmpathyIndividualView *view)
1128 gtk_tree_model_get (model, iter,
1129 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1130 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1131 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1134 "visible", !is_group,
1138 tp_clear_object (&pixbuf);
1140 individual_view_cell_set_background (view, cell, is_group, is_active);
1144 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1145 GtkCellRenderer *cell,
1146 GtkTreeModel *model,
1148 EmpathyIndividualView *view)
1150 GdkPixbuf *pixbuf = NULL;
1154 gtk_tree_model_get (model, iter,
1155 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1156 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1161 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1163 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1164 GTK_ICON_SIZE_MENU);
1166 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1168 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1169 GTK_ICON_SIZE_MENU);
1174 "visible", pixbuf != NULL,
1178 tp_clear_object (&pixbuf);
1184 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1185 GtkCellRenderer *cell,
1186 GtkTreeModel *model,
1188 EmpathyIndividualView *view)
1192 gboolean can_audio, can_video;
1194 gtk_tree_model_get (model, iter,
1195 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1196 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1197 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1198 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1201 "visible", !is_group && (can_audio || can_video),
1202 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1205 individual_view_cell_set_background (view, cell, is_group, is_active);
1209 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1210 GtkCellRenderer *cell,
1211 GtkTreeModel *model,
1213 EmpathyIndividualView *view)
1216 gboolean show_avatar;
1220 gtk_tree_model_get (model, iter,
1221 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1222 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1223 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1224 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1227 "visible", !is_group && show_avatar,
1231 tp_clear_object (&pixbuf);
1233 individual_view_cell_set_background (view, cell, is_group, is_active);
1237 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1238 GtkCellRenderer *cell,
1239 GtkTreeModel *model,
1241 EmpathyIndividualView *view)
1246 gtk_tree_model_get (model, iter,
1247 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1248 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1250 individual_view_cell_set_background (view, cell, is_group, is_active);
1254 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1255 GtkCellRenderer *cell,
1256 GtkTreeModel *model,
1258 EmpathyIndividualView *view)
1263 gtk_tree_model_get (model, iter,
1264 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1265 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1267 if (gtk_tree_model_iter_has_child (model, iter))
1270 gboolean row_expanded;
1272 path = gtk_tree_model_get_path (model, iter);
1274 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1275 (gtk_tree_view_column_get_tree_view (column)), path);
1276 gtk_tree_path_free (path);
1281 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1285 g_object_set (cell, "visible", FALSE, NULL);
1287 individual_view_cell_set_background (view, cell, is_group, is_active);
1291 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1296 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1297 GtkTreeModel *model;
1301 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1304 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1306 gtk_tree_model_get (model, iter,
1307 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1309 expanded = GPOINTER_TO_INT (user_data);
1310 empathy_contact_group_set_expanded (name, expanded);
1316 individual_view_start_search_cb (EmpathyIndividualView *view,
1319 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1321 if (priv->search_widget == NULL)
1324 empathy_individual_view_start_search (view);
1330 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1332 EmpathyIndividualView *view)
1334 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1336 GtkTreeViewColumn *focus_column;
1337 GtkTreeModel *model;
1339 gboolean set_cursor = FALSE;
1341 gtk_tree_model_filter_refilter (priv->filter);
1343 /* Set cursor on the first contact. If it is already set on a group,
1344 * set it on its first child contact. Note that first child of a group
1345 * is its separator, that's why we actually set to the 2nd
1348 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1349 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1353 path = gtk_tree_path_new_from_string ("0:1");
1356 else if (gtk_tree_path_get_depth (path) < 2)
1360 gtk_tree_model_get_iter (model, &iter, path);
1361 gtk_tree_model_get (model, &iter,
1362 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1367 gtk_tree_path_down (path);
1368 gtk_tree_path_next (path);
1375 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1377 if (gtk_tree_model_get_iter (model, &iter, path))
1379 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1384 gtk_tree_path_free (path);
1388 individual_view_search_activate_cb (GtkWidget *search,
1389 EmpathyIndividualView *view)
1392 GtkTreeViewColumn *focus_column;
1394 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1397 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1398 gtk_tree_path_free (path);
1400 gtk_widget_hide (search);
1405 individual_view_search_key_navigation_cb (GtkWidget *search,
1407 EmpathyIndividualView *view)
1409 GdkEvent *new_event;
1410 gboolean ret = FALSE;
1412 new_event = gdk_event_copy (event);
1413 gtk_widget_grab_focus (GTK_WIDGET (view));
1414 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1415 gtk_widget_grab_focus (search);
1417 gdk_event_free (new_event);
1423 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1424 EmpathyIndividualView *view)
1426 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1427 GtkTreeModel *model;
1428 GtkTreePath *cursor_path;
1430 gboolean valid = FALSE;
1432 /* block expand or collapse handlers, they would write the
1433 * expand or collapsed setting to file otherwise */
1434 g_signal_handlers_block_by_func (view,
1435 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1436 g_signal_handlers_block_by_func (view,
1437 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1439 /* restore which groups are expanded and which are not */
1440 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1441 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1442 valid; valid = gtk_tree_model_iter_next (model, &iter))
1448 gtk_tree_model_get (model, &iter,
1449 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1450 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1459 path = gtk_tree_model_get_path (model, &iter);
1460 if ((priv->view_features &
1461 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1462 empathy_contact_group_get_expanded (name))
1464 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1468 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1471 gtk_tree_path_free (path);
1475 /* unblock expand or collapse handlers */
1476 g_signal_handlers_unblock_by_func (view,
1477 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1478 g_signal_handlers_unblock_by_func (view,
1479 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1481 /* keep the selected contact visible */
1482 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1484 if (cursor_path != NULL)
1485 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1488 gtk_tree_path_free (cursor_path);
1492 individual_view_search_show_cb (EmpathyLiveSearch *search,
1493 EmpathyIndividualView *view)
1495 /* block expand or collapse handlers during expand all, they would
1496 * write the expand or collapsed setting to file otherwise */
1497 g_signal_handlers_block_by_func (view,
1498 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1500 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1502 g_signal_handlers_unblock_by_func (view,
1503 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1507 expand_idle_foreach_cb (GtkTreeModel *model,
1510 EmpathyIndividualView *self)
1512 EmpathyIndividualViewPriv *priv;
1514 gpointer should_expand;
1517 /* We only want groups */
1518 if (gtk_tree_path_get_depth (path) > 1)
1521 gtk_tree_model_get (model, iter,
1522 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1523 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1532 priv = GET_PRIV (self);
1534 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1537 if (GPOINTER_TO_INT (should_expand))
1538 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1540 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1542 g_hash_table_remove (priv->expand_groups, name);
1551 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1553 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1555 g_signal_handlers_block_by_func (self,
1556 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1557 g_signal_handlers_block_by_func (self,
1558 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1560 /* The store/filter could've been removed while we were in the idle queue */
1561 if (priv->filter != NULL)
1563 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1564 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1567 g_signal_handlers_unblock_by_func (self,
1568 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1569 g_signal_handlers_unblock_by_func (self,
1570 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1572 /* Empty the table of groups to expand/contract, since it may contain groups
1573 * which no longer exist in the tree view. This can happen after going
1574 * offline, for example. */
1575 g_hash_table_remove_all (priv->expand_groups);
1576 priv->expand_groups_idle_handler = 0;
1577 g_object_unref (self);
1583 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1586 EmpathyIndividualView *view)
1588 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1589 gboolean should_expand, is_group = FALSE;
1591 gpointer will_expand;
1593 gtk_tree_model_get (model, iter,
1594 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1595 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1598 if (!is_group || EMP_STR_EMPTY (name))
1604 should_expand = (priv->view_features &
1605 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1606 (priv->search_widget != NULL &&
1607 gtk_widget_get_visible (priv->search_widget)) ||
1608 empathy_contact_group_get_expanded (name);
1610 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1611 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1612 * a hash table, and expand or contract them as appropriate all at once in
1613 * an idle handler which iterates over all the group rows. */
1614 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1616 GPOINTER_TO_INT (will_expand) != should_expand)
1618 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1619 GINT_TO_POINTER (should_expand));
1621 if (priv->expand_groups_idle_handler == 0)
1623 priv->expand_groups_idle_handler =
1624 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1625 g_object_ref (view));
1633 individual_view_is_visible_individual (EmpathyIndividualView *self,
1634 FolksIndividual *individual,
1636 gboolean is_searching,
1638 gboolean is_fake_group,
1641 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1642 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1645 gboolean is_favorite;
1647 /* Always display individuals having pending events */
1648 if (event_count > 0)
1651 /* We're only giving the visibility wrt filtering here, not things like
1653 if (!priv->show_untrusted &&
1654 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1659 if (!priv->show_uninteresting)
1661 gboolean contains_interesting_persona = FALSE;
1663 /* Hide all individuals which consist entirely of uninteresting
1665 personas = folks_individual_get_personas (individual);
1666 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1667 while (!contains_interesting_persona && gee_iterator_next (iter))
1669 FolksPersona *persona = gee_iterator_get (iter);
1671 if (empathy_folks_persona_is_interesting (persona))
1672 contains_interesting_persona = TRUE;
1674 g_clear_object (&persona);
1676 g_clear_object (&iter);
1678 if (!contains_interesting_persona)
1682 is_favorite = folks_favourite_details_get_is_favourite (
1683 FOLKS_FAVOURITE_DETAILS (individual));
1684 if (!is_searching) {
1685 if (is_favorite && is_fake_group &&
1686 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1687 /* Always display favorite contacts in the favorite group */
1690 return (priv->show_offline || is_online);
1693 return empathy_individual_match_string (individual,
1694 empathy_live_search_get_text (live),
1695 empathy_live_search_get_words (live));
1699 get_group (GtkTreeModel *model,
1703 GtkTreeIter parent_iter;
1708 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1711 gtk_tree_model_get (model, &parent_iter,
1712 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1713 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1721 individual_view_filter_visible_func (GtkTreeModel *model,
1725 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1726 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1727 FolksIndividual *individual = NULL;
1728 gboolean is_group, is_separator, valid;
1729 GtkTreeIter child_iter;
1730 gboolean visible, is_online;
1731 gboolean is_searching = TRUE;
1734 if (priv->custom_filter != NULL)
1735 return priv->custom_filter (model, iter, priv->custom_filter_data);
1737 if (priv->search_widget == NULL ||
1738 !gtk_widget_get_visible (priv->search_widget))
1739 is_searching = FALSE;
1741 gtk_tree_model_get (model, iter,
1742 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1743 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1744 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1745 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1746 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1749 if (individual != NULL)
1752 gboolean is_fake_group;
1754 group = get_group (model, iter, &is_fake_group);
1756 visible = individual_view_is_visible_individual (self, individual,
1757 is_online, is_searching, group, is_fake_group, event_count);
1759 g_object_unref (individual);
1768 /* Not a contact, not a separator, must be a group */
1769 g_return_val_if_fail (is_group, FALSE);
1771 /* only show groups which are not empty */
1772 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1773 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1776 gboolean is_fake_group;
1778 gtk_tree_model_get (model, &child_iter,
1779 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1780 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1781 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1784 if (individual == NULL)
1787 group = get_group (model, &child_iter, &is_fake_group);
1789 visible = individual_view_is_visible_individual (self, individual,
1790 is_online, is_searching, group, is_fake_group, event_count);
1792 g_object_unref (individual);
1795 /* show group if it has at least one visible contact in it */
1803 static gchar * empathy_individual_view_dup_selected_group (
1804 EmpathyIndividualView *view,
1805 gboolean *is_fake_group);
1808 text_edited_cb (GtkCellRendererText *cellrenderertext,
1811 EmpathyIndividualView *self)
1813 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1814 gchar *old_name, *new_name;
1816 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1818 new_name = g_strdup (name);
1819 g_strstrip (new_name);
1821 if (tp_str_empty (new_name))
1824 old_name = empathy_individual_view_dup_selected_group (self, NULL);
1825 g_return_if_fail (old_name != NULL);
1827 if (tp_strdiff (old_name, new_name))
1829 EmpathyConnectionAggregator *aggregator;
1831 DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1833 aggregator = empathy_connection_aggregator_dup_singleton ();
1835 empathy_connection_aggregator_rename_group (aggregator, old_name,
1837 g_object_unref (aggregator);
1846 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1847 EmpathyIndividualView *self)
1849 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1851 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1855 individual_view_constructed (GObject *object)
1857 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1858 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1859 GtkCellRenderer *cell;
1860 GtkTreeViewColumn *col;
1865 "headers-visible", FALSE,
1866 "show-expanders", FALSE,
1869 col = gtk_tree_view_column_new ();
1872 cell = gtk_cell_renderer_pixbuf_new ();
1873 gtk_tree_view_column_pack_start (col, cell, FALSE);
1874 gtk_tree_view_column_set_cell_data_func (col, cell,
1875 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1885 cell = gtk_cell_renderer_pixbuf_new ();
1886 gtk_tree_view_column_pack_start (col, cell, FALSE);
1887 gtk_tree_view_column_set_cell_data_func (col, cell,
1888 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1900 priv->text_renderer = empathy_cell_renderer_text_new ();
1901 gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1902 gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1903 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1905 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1906 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1907 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1908 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1909 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1910 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1911 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1912 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1913 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1914 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1915 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1916 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1917 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1918 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1920 g_signal_connect (priv->text_renderer, "editing-canceled",
1921 G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1922 g_signal_connect (priv->text_renderer, "edited",
1923 G_CALLBACK (text_edited_cb), view);
1925 /* Audio Call Icon */
1926 cell = empathy_cell_renderer_activatable_new ();
1927 gtk_tree_view_column_pack_start (col, cell, FALSE);
1928 gtk_tree_view_column_set_cell_data_func (col, cell,
1929 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1932 g_object_set (cell, "visible", FALSE, NULL);
1934 g_signal_connect (cell, "path-activated",
1935 G_CALLBACK (individual_view_call_activated_cb), view);
1938 cell = gtk_cell_renderer_pixbuf_new ();
1939 gtk_tree_view_column_pack_start (col, cell, FALSE);
1940 gtk_tree_view_column_set_cell_data_func (col, cell,
1941 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1953 cell = empathy_cell_renderer_expander_new ();
1954 gtk_tree_view_column_pack_end (col, cell, FALSE);
1955 gtk_tree_view_column_set_cell_data_func (col, cell,
1956 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1959 /* Actually add the column now we have added all cell renderers */
1960 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1963 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1965 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1970 individual_view_set_view_features (EmpathyIndividualView *view,
1971 EmpathyIndividualFeatureFlags features)
1973 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1974 gboolean has_tooltip;
1976 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1978 priv->view_features = features;
1980 /* Setting reorderable is a hack that gets us row previews as drag icons
1981 for free. We override all the drag handlers. It's tricky to get the
1982 position of the drag icon right in drag_begin. GtkTreeView has special
1983 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1986 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1987 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1989 /* Update DnD source/dest */
1990 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1992 gtk_drag_source_set (GTK_WIDGET (view),
1995 G_N_ELEMENTS (drag_types_source),
1996 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2000 gtk_drag_source_unset (GTK_WIDGET (view));
2004 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2006 gtk_drag_dest_set (GTK_WIDGET (view),
2007 GTK_DEST_DEFAULT_ALL,
2009 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2013 /* FIXME: URI could still be droped depending on FT feature */
2014 gtk_drag_dest_unset (GTK_WIDGET (view));
2017 /* Update has-tooltip */
2019 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2020 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2024 individual_view_dispose (GObject *object)
2026 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2027 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2029 tp_clear_object (&priv->store);
2030 tp_clear_object (&priv->filter);
2031 tp_clear_object (&priv->tooltip_widget);
2033 empathy_individual_view_set_live_search (view, NULL);
2035 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2039 individual_view_finalize (GObject *object)
2041 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2043 if (priv->expand_groups_idle_handler != 0)
2044 g_source_remove (priv->expand_groups_idle_handler);
2045 g_hash_table_unref (priv->expand_groups);
2047 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2051 individual_view_get_property (GObject *object,
2056 EmpathyIndividualViewPriv *priv;
2058 priv = GET_PRIV (object);
2063 g_value_set_object (value, priv->store);
2065 case PROP_VIEW_FEATURES:
2066 g_value_set_flags (value, priv->view_features);
2068 case PROP_INDIVIDUAL_FEATURES:
2069 g_value_set_flags (value, priv->individual_features);
2071 case PROP_SHOW_OFFLINE:
2072 g_value_set_boolean (value, priv->show_offline);
2074 case PROP_SHOW_UNTRUSTED:
2075 g_value_set_boolean (value, priv->show_untrusted);
2077 case PROP_SHOW_UNINTERESTING:
2078 g_value_set_boolean (value, priv->show_uninteresting);
2081 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2087 individual_view_set_property (GObject *object,
2089 const GValue *value,
2092 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2093 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2098 empathy_individual_view_set_store (view, g_value_get_object (value));
2100 case PROP_VIEW_FEATURES:
2101 individual_view_set_view_features (view, g_value_get_flags (value));
2103 case PROP_INDIVIDUAL_FEATURES:
2104 priv->individual_features = g_value_get_flags (value);
2106 case PROP_SHOW_OFFLINE:
2107 empathy_individual_view_set_show_offline (view,
2108 g_value_get_boolean (value));
2110 case PROP_SHOW_UNTRUSTED:
2111 empathy_individual_view_set_show_untrusted (view,
2112 g_value_get_boolean (value));
2114 case PROP_SHOW_UNINTERESTING:
2115 empathy_individual_view_set_show_uninteresting (view,
2116 g_value_get_boolean (value));
2118 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2124 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2126 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2127 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2128 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2130 object_class->constructed = individual_view_constructed;
2131 object_class->dispose = individual_view_dispose;
2132 object_class->finalize = individual_view_finalize;
2133 object_class->get_property = individual_view_get_property;
2134 object_class->set_property = individual_view_set_property;
2136 widget_class->drag_data_received = individual_view_drag_data_received;
2137 widget_class->drag_drop = individual_view_drag_drop;
2138 widget_class->drag_begin = individual_view_drag_begin;
2139 widget_class->drag_data_get = individual_view_drag_data_get;
2140 widget_class->drag_end = individual_view_drag_end;
2141 widget_class->drag_motion = individual_view_drag_motion;
2143 /* We use the class method to let user of this widget to connect to
2144 * the signal and stop emission of the signal so the default handler
2145 * won't be called. */
2146 tree_view_class->row_activated = individual_view_row_activated;
2148 klass->drag_individual_received = real_drag_individual_received_cb;
2150 signals[DRAG_INDIVIDUAL_RECEIVED] =
2151 g_signal_new ("drag-individual-received",
2152 G_OBJECT_CLASS_TYPE (klass),
2154 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2156 g_cclosure_marshal_generic,
2157 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2158 G_TYPE_STRING, G_TYPE_STRING);
2160 signals[DRAG_PERSONA_RECEIVED] =
2161 g_signal_new ("drag-persona-received",
2162 G_OBJECT_CLASS_TYPE (klass),
2164 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2166 g_cclosure_marshal_generic,
2167 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2169 g_object_class_install_property (object_class,
2171 g_param_spec_object ("store",
2172 "The store of the view",
2173 "The store of the view",
2174 EMPATHY_TYPE_INDIVIDUAL_STORE,
2175 G_PARAM_READWRITE));
2176 g_object_class_install_property (object_class,
2178 g_param_spec_flags ("view-features",
2179 "Features of the view",
2180 "Flags for all enabled features",
2181 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2182 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2183 g_object_class_install_property (object_class,
2184 PROP_INDIVIDUAL_FEATURES,
2185 g_param_spec_flags ("individual-features",
2186 "Features of the individual menu",
2187 "Flags for all enabled features for the menu",
2188 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2189 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2190 g_object_class_install_property (object_class,
2192 g_param_spec_boolean ("show-offline",
2194 "Whether contact list should display "
2195 "offline contacts", FALSE, G_PARAM_READWRITE));
2196 g_object_class_install_property (object_class,
2197 PROP_SHOW_UNTRUSTED,
2198 g_param_spec_boolean ("show-untrusted",
2199 "Show Untrusted Individuals",
2200 "Whether the view should display untrusted individuals; "
2201 "those who could not be who they say they are.",
2202 TRUE, G_PARAM_READWRITE));
2203 g_object_class_install_property (object_class,
2204 PROP_SHOW_UNINTERESTING,
2205 g_param_spec_boolean ("show-uninteresting",
2206 "Show Uninteresting Individuals",
2207 "Whether the view should not filter out individuals using "
2208 "empathy_folks_persona_is_interesting.",
2209 FALSE, G_PARAM_READWRITE));
2211 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2215 empathy_individual_view_init (EmpathyIndividualView *view)
2217 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2218 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2222 priv->show_untrusted = TRUE;
2223 priv->show_uninteresting = FALSE;
2225 /* Get saved group states. */
2226 empathy_contact_groups_get_all ();
2228 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2229 (GDestroyNotify) g_free, NULL);
2231 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2232 empathy_individual_store_row_separator_func, NULL, NULL);
2234 /* Connect to tree view signals rather than override. */
2235 g_signal_connect (view, "button-press-event",
2236 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2237 g_signal_connect (view, "key-press-event",
2238 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2239 g_signal_connect (view, "row-expanded",
2240 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2241 GINT_TO_POINTER (TRUE));
2242 g_signal_connect (view, "row-collapsed",
2243 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2244 GINT_TO_POINTER (FALSE));
2245 g_signal_connect (view, "query-tooltip",
2246 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2249 EmpathyIndividualView *
2250 empathy_individual_view_new (EmpathyIndividualStore *store,
2251 EmpathyIndividualViewFeatureFlags view_features,
2252 EmpathyIndividualFeatureFlags individual_features)
2254 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2256 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2258 "individual-features", individual_features,
2259 "view-features", view_features, NULL);
2263 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2265 GtkTreeSelection *selection;
2267 GtkTreeModel *model;
2268 FolksIndividual *individual;
2270 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2272 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2273 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2276 gtk_tree_model_get (model, &iter,
2277 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2283 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2284 gboolean *is_fake_group)
2286 GtkTreeSelection *selection;
2288 GtkTreeModel *model;
2293 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2295 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2296 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2299 gtk_tree_model_get (model, &iter,
2300 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2301 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2302 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2310 if (is_fake_group != NULL)
2311 *is_fake_group = fake;
2318 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2319 REMOVE_DIALOG_RESPONSE_DELETE,
2323 individual_view_remove_dialog_show (GtkWindow *parent,
2324 const gchar *message,
2325 const gchar *secondary_text)
2330 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2331 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2333 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2334 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2335 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2336 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2337 "%s", secondary_text);
2339 gtk_widget_show (dialog);
2341 res = gtk_dialog_run (GTK_DIALOG (dialog));
2342 gtk_widget_destroy (dialog);
2348 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2349 EmpathyIndividualView *view)
2353 group = empathy_individual_view_dup_selected_group (view, NULL);
2360 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2362 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2363 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2364 text) == REMOVE_DIALOG_RESPONSE_DELETE)
2366 EmpathyIndividualManager *manager =
2367 empathy_individual_manager_dup_singleton ();
2368 empathy_individual_manager_remove_group (manager, group);
2369 g_object_unref (G_OBJECT (manager));
2379 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2380 EmpathyIndividualView *self)
2382 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2385 GtkTreeSelection *selection;
2386 GtkTreeModel *model;
2388 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2389 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2391 path = gtk_tree_model_get_path (model, &iter);
2393 g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2395 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2396 gtk_widget_grab_focus (GTK_WIDGET (self));
2397 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2398 gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2400 gtk_tree_path_free (path);
2404 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2406 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2411 gboolean is_fake_group;
2413 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2415 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2416 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2419 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2420 if (!group || is_fake_group)
2422 /* We can't alter fake groups */
2427 menu = gtk_menu_new ();
2429 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2431 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2432 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2433 gtk_widget_show (item);
2434 g_signal_connect (item, "activate",
2435 G_CALLBACK (individual_view_group_rename_activate_cb), view);
2438 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2440 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2441 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2442 GTK_ICON_SIZE_MENU);
2443 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2444 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2445 gtk_widget_show (item);
2446 g_signal_connect (item, "activate",
2447 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2456 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2458 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2459 FolksIndividual *individual;
2460 GtkWidget *menu = NULL;
2462 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2464 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2465 /* No need to create a context menu */
2468 individual = empathy_individual_view_dup_selected (view);
2469 if (individual == NULL)
2472 if (!empathy_folks_individual_contains_contact (individual))
2475 menu = empathy_individual_menu_new (individual, priv->individual_features,
2479 g_object_unref (individual);
2485 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2486 EmpathyLiveSearch *search)
2488 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2490 /* remove old handlers if old search was not null */
2491 if (priv->search_widget != NULL)
2493 g_signal_handlers_disconnect_by_func (view,
2494 individual_view_start_search_cb, NULL);
2496 g_signal_handlers_disconnect_by_func (priv->search_widget,
2497 individual_view_search_text_notify_cb, view);
2498 g_signal_handlers_disconnect_by_func (priv->search_widget,
2499 individual_view_search_activate_cb, view);
2500 g_signal_handlers_disconnect_by_func (priv->search_widget,
2501 individual_view_search_key_navigation_cb, view);
2502 g_signal_handlers_disconnect_by_func (priv->search_widget,
2503 individual_view_search_hide_cb, view);
2504 g_signal_handlers_disconnect_by_func (priv->search_widget,
2505 individual_view_search_show_cb, view);
2506 g_object_unref (priv->search_widget);
2507 priv->search_widget = NULL;
2510 /* connect handlers if new search is not null */
2513 priv->search_widget = g_object_ref (search);
2515 g_signal_connect (view, "start-interactive-search",
2516 G_CALLBACK (individual_view_start_search_cb), NULL);
2518 g_signal_connect (priv->search_widget, "notify::text",
2519 G_CALLBACK (individual_view_search_text_notify_cb), view);
2520 g_signal_connect (priv->search_widget, "activate",
2521 G_CALLBACK (individual_view_search_activate_cb), view);
2522 g_signal_connect (priv->search_widget, "key-navigation",
2523 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2524 g_signal_connect (priv->search_widget, "hide",
2525 G_CALLBACK (individual_view_search_hide_cb), view);
2526 g_signal_connect (priv->search_widget, "show",
2527 G_CALLBACK (individual_view_search_show_cb), view);
2532 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2534 EmpathyIndividualViewPriv *priv;
2536 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2538 priv = GET_PRIV (self);
2540 return (priv->search_widget != NULL &&
2541 gtk_widget_get_visible (priv->search_widget));
2545 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2547 EmpathyIndividualViewPriv *priv;
2549 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2551 priv = GET_PRIV (self);
2553 return priv->show_offline;
2557 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2558 gboolean show_offline)
2560 EmpathyIndividualViewPriv *priv;
2562 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2564 priv = GET_PRIV (self);
2566 priv->show_offline = show_offline;
2568 g_object_notify (G_OBJECT (self), "show-offline");
2569 gtk_tree_model_filter_refilter (priv->filter);
2573 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2575 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2577 return GET_PRIV (self)->show_untrusted;
2581 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2582 gboolean show_untrusted)
2584 EmpathyIndividualViewPriv *priv;
2586 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2588 priv = GET_PRIV (self);
2590 priv->show_untrusted = show_untrusted;
2592 g_object_notify (G_OBJECT (self), "show-untrusted");
2593 gtk_tree_model_filter_refilter (priv->filter);
2596 EmpathyIndividualStore *
2597 empathy_individual_view_get_store (EmpathyIndividualView *self)
2599 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2601 return GET_PRIV (self)->store;
2605 empathy_individual_view_set_store (EmpathyIndividualView *self,
2606 EmpathyIndividualStore *store)
2608 EmpathyIndividualViewPriv *priv;
2610 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2611 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2613 priv = GET_PRIV (self);
2615 /* Destroy the old filter and remove the old store */
2616 if (priv->store != NULL)
2618 g_signal_handlers_disconnect_by_func (priv->filter,
2619 individual_view_row_has_child_toggled_cb, self);
2621 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2624 tp_clear_object (&priv->filter);
2625 tp_clear_object (&priv->store);
2627 /* Set the new store */
2628 priv->store = store;
2632 g_object_ref (store);
2634 /* Create a new filter */
2635 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2636 GTK_TREE_MODEL (priv->store), NULL));
2637 gtk_tree_model_filter_set_visible_func (priv->filter,
2638 individual_view_filter_visible_func, self, NULL);
2640 g_signal_connect (priv->filter, "row-has-child-toggled",
2641 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2642 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2643 GTK_TREE_MODEL (priv->filter));
2648 empathy_individual_view_start_search (EmpathyIndividualView *self)
2650 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2652 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2653 g_return_if_fail (priv->search_widget != NULL);
2655 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2656 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2658 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2662 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2663 GtkTreeModelFilterVisibleFunc filter,
2666 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2668 priv->custom_filter = filter;
2669 priv->custom_filter_data = data;
2673 empathy_individual_view_refilter (EmpathyIndividualView *self)
2675 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2677 gtk_tree_model_filter_refilter (priv->filter);
2681 empathy_individual_view_select_first (EmpathyIndividualView *self)
2683 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2686 gtk_tree_model_filter_refilter (priv->filter);
2688 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2690 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2691 GTK_TREE_VIEW (self));
2693 gtk_tree_selection_select_iter (selection, &iter);
2698 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2699 gboolean show_uninteresting)
2701 EmpathyIndividualViewPriv *priv;
2703 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2705 priv = GET_PRIV (self);
2707 priv->show_uninteresting = show_uninteresting;
2709 g_object_notify (G_OBJECT (self), "show-uninteresting");
2710 gtk_tree_model_filter_refilter (priv->filter);