1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/telepathy-glib.h>
37 #include <folks/folks.h>
38 #include <folks/folks-telepathy.h>
40 #include <libempathy/empathy-connection-aggregator.h>
41 #include <libempathy/empathy-individual-manager.h>
42 #include <libempathy/empathy-contact-groups.h>
43 #include <libempathy/empathy-request-util.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-individual-edit-dialog.h"
50 #include "empathy-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-cell-renderer-expander.h"
53 #include "empathy-cell-renderer-text.h"
54 #include "empathy-cell-renderer-activatable.h"
55 #include "empathy-ui-utils.h"
56 #include "empathy-gtk-enum-types.h"
58 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
59 #include <libempathy/empathy-debug.h>
61 /* Active users are those which have recently changed state
62 * (e.g. online, offline or from normal to a busy state).
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
68 EmpathyIndividualStore *store;
69 GtkTreeRowReference *drag_row;
70 EmpathyIndividualViewFeatureFlags view_features;
71 EmpathyIndividualFeatureFlags individual_features;
72 GtkWidget *tooltip_widget;
74 gboolean show_offline;
75 gboolean show_untrusted;
76 gboolean show_uninteresting;
78 GtkTreeModelFilter *filter;
79 GtkWidget *search_widget;
81 guint expand_groups_idle_handler;
82 /* owned string (group name) -> bool (whether to expand/contract) */
83 GHashTable *expand_groups;
86 guint auto_scroll_timeout_id;
87 /* Distance between mouse pointer and the nearby border. Negative when
91 GtkTreeModelFilterVisibleFunc custom_filter;
92 gpointer custom_filter_data;
94 GtkCellRenderer *text_renderer;
95 } EmpathyIndividualViewPriv;
99 EmpathyIndividualView *view;
106 EmpathyIndividualView *view;
107 FolksIndividual *individual;
116 PROP_INDIVIDUAL_FEATURES,
119 PROP_SHOW_UNINTERESTING,
122 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
123 * specific EmpathyContacts (between/in/out of Individuals) */
126 DND_DRAG_TYPE_UNKNOWN = -1,
127 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
128 DND_DRAG_TYPE_PERSONA_ID,
129 DND_DRAG_TYPE_URI_LIST,
130 DND_DRAG_TYPE_STRING,
133 #define DRAG_TYPE(T,I) \
134 { (gchar *) T, 0, I }
136 static const GtkTargetEntry drag_types_dest[] = {
137 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
138 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
139 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
140 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
141 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
142 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
145 static const GtkTargetEntry drag_types_source[] = {
146 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
151 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
155 DRAG_INDIVIDUAL_RECEIVED,
156 DRAG_PERSONA_RECEIVED,
160 static guint signals[LAST_SIGNAL];
162 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
166 individual_view_tooltip_destroy_cb (GtkWidget *widget,
167 EmpathyIndividualView *view)
169 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
171 tp_clear_object (&priv->tooltip_widget);
175 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
178 gboolean keyboard_mode,
182 EmpathyIndividualViewPriv *priv;
183 FolksIndividual *individual;
187 static gint running = 0;
188 gboolean ret = FALSE;
190 priv = GET_PRIV (view);
192 /* Avoid an infinite loop. See GNOME bug #574377 */
198 /* Don't show the tooltip if there's already a popup menu */
199 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
202 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
203 keyboard_mode, &model, &path, &iter))
206 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
207 gtk_tree_path_free (path);
209 gtk_tree_model_get (model, &iter,
210 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
212 if (individual == NULL)
215 if (priv->tooltip_widget == NULL)
217 priv->tooltip_widget = empathy_individual_widget_new (individual,
218 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
219 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
220 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
221 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
222 g_object_ref (priv->tooltip_widget);
224 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
225 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
227 gtk_widget_show (priv->tooltip_widget);
231 empathy_individual_widget_set_individual (
232 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
235 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
238 g_object_unref (individual);
246 groups_change_group_cb (GObject *source,
247 GAsyncResult *result,
250 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
251 GError *error = NULL;
253 folks_group_details_change_group_finish (group_details, result, &error);
256 g_warning ("failed to change group: %s", error->message);
257 g_clear_error (&error);
262 group_can_be_modified (const gchar *name,
263 gboolean is_fake_group,
266 /* Real groups can always be modified */
270 /* The favorite fake group can be modified so users can
271 * add/remove favorites using DnD */
272 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
275 /* We can remove contacts from the 'ungrouped' fake group */
276 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
283 individual_view_individual_drag_received (GtkWidget *self,
284 GdkDragContext *context,
287 GtkSelectionData *selection)
289 EmpathyIndividualViewPriv *priv;
290 EmpathyIndividualManager *manager = NULL;
291 FolksIndividual *individual;
292 GtkTreePath *source_path;
293 const gchar *sel_data;
294 gchar *new_group = NULL;
295 gchar *old_group = NULL;
296 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
298 priv = GET_PRIV (self);
300 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
301 new_group = empathy_individual_store_get_parent_group (model, path,
302 NULL, &new_group_is_fake);
304 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
307 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
308 * feature. Otherwise, we just add the dropped contact to whichever group
309 * they were dropped in, and don't remove them from their old group. This
310 * allows for Individual views which shouldn't allow Individuals to have
311 * their groups changed, and also for dragging Individuals between Individual
313 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
314 priv->drag_row != NULL)
316 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
320 empathy_individual_store_get_parent_group (model, source_path,
321 NULL, &old_group_is_fake);
322 gtk_tree_path_free (source_path);
325 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
328 if (!tp_strdiff (old_group, new_group))
331 else if (priv->drag_row != NULL)
333 /* We don't allow changing Individuals' groups, and this Individual was
334 * dragged from another group in *this* Individual view, so we disallow
339 /* XXX: for contacts, we used to ensure the account, create the contact
340 * factory, and then wait on the contacts. But they should already be
341 * created by this point */
343 manager = empathy_individual_manager_dup_singleton ();
344 individual = empathy_individual_manager_lookup_member (manager, sel_data);
346 if (individual == NULL)
348 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
352 /* FIXME: We should probably wait for the cb before calling
355 /* Emit a signal notifying of the drag. We change the Individual's groups in
356 * the default signal handler. */
357 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
358 gdk_drag_context_get_selected_action (context), individual, new_group,
364 tp_clear_object (&manager);
372 real_drag_individual_received_cb (EmpathyIndividualView *self,
373 GdkDragAction action,
374 FolksIndividual *individual,
375 const gchar *new_group,
376 const gchar *old_group)
378 DEBUG ("individual %s dragged from '%s' to '%s'",
379 folks_individual_get_id (individual), old_group, new_group);
381 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
383 /* Mark contact as favourite */
384 folks_favourite_details_set_is_favourite (
385 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
389 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
391 /* Remove contact as favourite */
392 folks_favourite_details_set_is_favourite (
393 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
395 /* Don't try to remove it */
399 if (new_group != NULL)
401 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
402 new_group, TRUE, groups_change_group_cb, NULL);
405 if (old_group != NULL && action == GDK_ACTION_MOVE)
407 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
408 old_group, FALSE, groups_change_group_cb, NULL);
413 individual_view_persona_drag_received (GtkWidget *self,
414 GdkDragContext *context,
417 GtkSelectionData *selection)
419 EmpathyIndividualManager *manager = NULL;
420 FolksIndividual *individual = NULL;
421 FolksPersona *persona = NULL;
422 const gchar *persona_uid;
423 GList *individuals, *l;
424 GeeIterator *iter = NULL;
425 gboolean retval = FALSE;
427 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
429 /* FIXME: This is slow, but the only way to find the Persona we're having
431 manager = empathy_individual_manager_dup_singleton ();
432 individuals = empathy_individual_manager_get_members (manager);
434 for (l = individuals; l != NULL; l = l->next)
438 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
439 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
440 while (gee_iterator_next (iter))
442 FolksPersona *persona_cur = gee_iterator_get (iter);
444 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
446 /* takes ownership of the ref */
447 persona = persona_cur;
448 individual = g_object_ref (l->data);
451 g_clear_object (&persona_cur);
453 g_clear_object (&iter);
457 g_clear_object (&iter);
458 g_list_free (individuals);
460 if (persona == NULL || individual == NULL)
462 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
466 /* Emit a signal notifying of the drag. We change the Individual's groups in
467 * the default signal handler. */
468 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
469 gdk_drag_context_get_selected_action (context), persona, individual,
473 tp_clear_object (&manager);
474 tp_clear_object (&persona);
475 tp_clear_object (&individual);
481 individual_view_file_drag_received (GtkWidget *view,
482 GdkDragContext *context,
485 GtkSelectionData *selection)
488 const gchar *sel_data;
489 FolksIndividual *individual;
490 EmpathyContact *contact;
492 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
494 gtk_tree_model_get_iter (model, &iter, path);
495 gtk_tree_model_get (model, &iter,
496 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
497 if (individual == NULL)
500 contact = empathy_contact_dup_from_folks_individual (individual);
501 empathy_send_file_from_uri_list (contact, sel_data);
503 g_object_unref (individual);
504 tp_clear_object (&contact);
510 individual_view_drag_data_received (GtkWidget *view,
511 GdkDragContext *context,
514 GtkSelectionData *selection,
520 GtkTreeViewDropPosition position;
522 gboolean success = TRUE;
524 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
526 /* Get destination group information. */
527 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
528 x, y, &path, &position);
533 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
535 success = individual_view_individual_drag_received (view,
536 context, model, path, selection);
538 else if (info == DND_DRAG_TYPE_PERSONA_ID)
540 success = individual_view_persona_drag_received (view, context, model,
543 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
545 success = individual_view_file_drag_received (view,
546 context, model, path, selection);
549 gtk_tree_path_free (path);
550 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
554 individual_view_drag_motion_cb (DragMotionData *data)
556 if (data->view != NULL)
558 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
559 g_object_remove_weak_pointer (G_OBJECT (data->view),
560 (gpointer *) &data->view);
563 data->timeout_id = 0;
568 /* Minimum distance between the mouse pointer and a horizontal border when we
569 start auto scrolling. */
570 #define AUTO_SCROLL_MARGIN_SIZE 20
571 /* How far to scroll per one tick. */
572 #define AUTO_SCROLL_PITCH 10
575 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
577 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
581 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
583 if (priv->distance < 0)
584 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
586 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
588 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
589 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
591 gtk_adjustment_set_value (adj, new_value);
597 individual_view_drag_motion (GtkWidget *widget,
598 GdkDragContext *context,
603 EmpathyIndividualViewPriv *priv;
607 static DragMotionData *dm = NULL;
610 gboolean is_different = FALSE;
611 gboolean cleanup = TRUE;
612 gboolean retval = TRUE;
613 GtkAllocation allocation;
615 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
617 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
618 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
621 if (priv->auto_scroll_timeout_id != 0)
623 g_source_remove (priv->auto_scroll_timeout_id);
624 priv->auto_scroll_timeout_id = 0;
627 gtk_widget_get_allocation (widget, &allocation);
629 if (y < AUTO_SCROLL_MARGIN_SIZE ||
630 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
632 if (y < AUTO_SCROLL_MARGIN_SIZE)
633 priv->distance = MIN (-y, -1);
635 priv->distance = MAX (allocation.height - y, 1);
637 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
638 (GSourceFunc) individual_view_auto_scroll_cb, widget);
641 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
642 x, y, &path, NULL, NULL, NULL);
644 cleanup &= (dm == NULL);
648 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
649 is_different = ((dm == NULL) || ((dm != NULL)
650 && gtk_tree_path_compare (dm->path, path) != 0));
657 /* Coordinates don't point to an actual row, so make sure the pointer
658 and highlighting don't indicate that a drag is possible.
660 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
661 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
664 target = gtk_drag_dest_find_target (widget, context, NULL);
665 gtk_tree_model_get_iter (model, &iter, path);
667 /* Determine the DndDragType of the data */
668 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
670 if (target == drag_atoms_dest[i])
672 drag_type = drag_types_dest[i].info;
677 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
678 drag_type == DND_DRAG_TYPE_STRING)
680 /* This is a file drag, and it can only be dropped on contacts,
682 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
683 * even if we have a valid target. */
684 FolksIndividual *individual = NULL;
685 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
687 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
689 gtk_tree_model_get (model, &iter,
690 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
694 if (individual != NULL)
696 EmpathyContact *contact = NULL;
698 contact = empathy_contact_dup_from_folks_individual (individual);
700 caps = empathy_contact_get_capabilities (contact);
702 tp_clear_object (&contact);
705 if (individual != NULL &&
706 folks_presence_details_is_online (
707 FOLKS_PRESENCE_DETAILS (individual)) &&
708 (caps & EMPATHY_CAPABILITIES_FT))
710 gdk_drag_status (context, GDK_ACTION_COPY, time_);
711 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
712 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
716 gdk_drag_status (context, 0, time_);
717 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
721 if (individual != NULL)
722 g_object_unref (individual);
724 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
725 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
726 priv->drag_row == NULL)) ||
727 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
728 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
730 /* If target != GDK_NONE, then we have a contact (individual or persona)
731 drag. If we're pointing to a group, highlight it. Otherwise, if the
732 contact we're pointing to is in a group, highlight that. Otherwise,
733 set the drag position to before the first row for a drag into
734 the "non-group" at the top.
735 If it's an Individual:
736 We only highlight things if the contact is from a different
737 Individual view, or if this Individual view has
738 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
739 which don't have FEATURE_GROUPS_CHANGE, but do have
740 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
742 We only highlight things if we have FEATURE_PERSONA_DROP.
744 GtkTreeIter group_iter;
746 GtkTreePath *group_path;
747 gtk_tree_model_get (model, &iter,
748 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
755 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
756 gtk_tree_model_get (model, &group_iter,
757 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
761 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
762 group_path = gtk_tree_model_get_path (model, &group_iter);
763 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
764 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
765 gtk_tree_path_free (group_path);
769 group_path = gtk_tree_path_new_first ();
770 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
771 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
772 group_path, GTK_TREE_VIEW_DROP_BEFORE);
776 if (!is_different && !cleanup)
781 gtk_tree_path_free (dm->path);
784 g_source_remove (dm->timeout_id);
792 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
794 dm = g_new0 (DragMotionData, 1);
796 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
797 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
798 dm->path = gtk_tree_path_copy (path);
800 dm->timeout_id = g_timeout_add_seconds (1,
801 (GSourceFunc) individual_view_drag_motion_cb, dm);
808 individual_view_drag_begin (GtkWidget *widget,
809 GdkDragContext *context)
811 EmpathyIndividualViewPriv *priv;
812 GtkTreeSelection *selection;
817 priv = GET_PRIV (widget);
819 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
820 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
823 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
826 path = gtk_tree_model_get_path (model, &iter);
827 priv->drag_row = gtk_tree_row_reference_new (model, path);
828 gtk_tree_path_free (path);
832 individual_view_drag_data_get (GtkWidget *widget,
833 GdkDragContext *context,
834 GtkSelectionData *selection,
838 EmpathyIndividualViewPriv *priv;
839 GtkTreePath *src_path;
842 FolksIndividual *individual;
843 const gchar *individual_id;
845 priv = GET_PRIV (widget);
847 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
848 if (priv->drag_row == NULL)
851 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
852 if (src_path == NULL)
855 if (!gtk_tree_model_get_iter (model, &iter, src_path))
857 gtk_tree_path_free (src_path);
861 gtk_tree_path_free (src_path);
864 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
865 if (individual == NULL)
868 individual_id = folks_individual_get_id (individual);
870 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
872 gtk_selection_data_set (selection,
873 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
874 (guchar *) individual_id, strlen (individual_id) + 1);
877 g_object_unref (individual);
881 individual_view_drag_end (GtkWidget *widget,
882 GdkDragContext *context)
884 EmpathyIndividualViewPriv *priv;
886 priv = GET_PRIV (widget);
888 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
893 gtk_tree_row_reference_free (priv->drag_row);
894 priv->drag_row = NULL;
897 if (priv->auto_scroll_timeout_id != 0)
899 g_source_remove (priv->auto_scroll_timeout_id);
900 priv->auto_scroll_timeout_id = 0;
905 individual_view_drag_drop (GtkWidget *widget,
906 GdkDragContext *drag_context,
916 EmpathyIndividualView *view;
922 menu_deactivate_cb (GtkMenuShell *menushell,
925 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
926 g_signal_handlers_disconnect_by_func (menushell,
927 menu_deactivate_cb, user_data);
929 gtk_menu_detach (GTK_MENU (menushell));
933 individual_view_popup_menu_idle_cb (gpointer user_data)
935 MenuPopupData *data = user_data;
938 menu = empathy_individual_view_get_individual_menu (data->view);
940 menu = empathy_individual_view_get_group_menu (data->view);
944 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
946 gtk_widget_show (menu);
947 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
950 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
951 * floating ref. We can either wait that the treeview releases its ref
952 * when it will be destroyed (when leaving Empathy) or explicitely
953 * detach the menu when it's not displayed any more.
954 * We go for the latter as we don't want to keep useless menus in memory
955 * during the whole lifetime of Empathy. */
956 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
960 g_slice_free (MenuPopupData, data);
966 individual_view_button_press_event_cb (EmpathyIndividualView *view,
967 GdkEventButton *event,
970 if (event->button == 3)
974 data = g_slice_new (MenuPopupData);
976 data->button = event->button;
977 data->time = event->time;
978 g_idle_add (individual_view_popup_menu_idle_cb, data);
985 individual_view_key_press_event_cb (EmpathyIndividualView *view,
989 if (event->keyval == GDK_KEY_Menu)
993 data = g_slice_new (MenuPopupData);
996 data->time = event->time;
997 g_idle_add (individual_view_popup_menu_idle_cb, data);
998 } else if (event->keyval == GDK_KEY_F2) {
999 FolksIndividual *individual;
1001 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
1003 individual = empathy_individual_view_dup_selected (view);
1004 if (individual == NULL)
1007 empathy_individual_edit_dialog_show (individual, NULL);
1009 g_object_unref (individual);
1016 individual_view_row_activated (GtkTreeView *view,
1018 GtkTreeViewColumn *column)
1020 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1021 FolksIndividual *individual;
1022 EmpathyContact *contact;
1023 GtkTreeModel *model;
1026 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1029 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1030 gtk_tree_model_get_iter (model, &iter, path);
1031 gtk_tree_model_get (model, &iter,
1032 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1034 if (individual == NULL)
1037 /* Determine which Persona to chat to, by choosing the most available one. */
1038 contact = empathy_contact_dup_best_for_action (individual,
1039 EMPATHY_ACTION_CHAT);
1041 if (contact != NULL)
1043 DEBUG ("Starting a chat");
1045 empathy_chat_with_contact (contact,
1046 gtk_get_current_event_time ());
1049 g_object_unref (individual);
1050 tp_clear_object (&contact);
1054 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1055 const gchar *path_string,
1056 EmpathyIndividualView *view)
1058 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1060 GtkTreeModel *model;
1062 FolksIndividual *individual;
1063 GdkEventButton *event;
1064 GtkMenuShell *shell;
1067 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1070 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1071 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1074 gtk_tree_model_get (model, &iter,
1075 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1076 if (individual == NULL)
1079 event = (GdkEventButton *) gtk_get_current_event ();
1081 menu = empathy_context_menu_new (GTK_WIDGET (view));
1082 shell = GTK_MENU_SHELL (menu);
1085 item = empathy_individual_audio_call_menu_item_new (individual);
1086 gtk_menu_shell_append (shell, item);
1087 gtk_widget_show (item);
1090 item = empathy_individual_video_call_menu_item_new (individual);
1091 gtk_menu_shell_append (shell, item);
1092 gtk_widget_show (item);
1094 gtk_widget_show (menu);
1095 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1096 event->button, event->time);
1098 g_object_unref (individual);
1102 individual_view_cell_set_background (EmpathyIndividualView *view,
1103 GtkCellRenderer *cell,
1107 if (!is_group && is_active)
1109 GtkStyleContext *style;
1112 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1114 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1117 /* Here we take the current theme colour and add it to
1118 * the colour for white and average the two. This
1119 * gives a colour which is inline with the theme but
1122 empathy_make_color_whiter (&color);
1124 g_object_set (cell, "cell-background-rgba", &color, NULL);
1127 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1131 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1132 GtkCellRenderer *cell,
1133 GtkTreeModel *model,
1135 EmpathyIndividualView *view)
1141 gtk_tree_model_get (model, iter,
1142 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1143 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1144 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1147 "visible", !is_group,
1151 tp_clear_object (&pixbuf);
1153 individual_view_cell_set_background (view, cell, is_group, is_active);
1157 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1158 GtkCellRenderer *cell,
1159 GtkTreeModel *model,
1161 EmpathyIndividualView *view)
1163 GdkPixbuf *pixbuf = NULL;
1167 gtk_tree_model_get (model, iter,
1168 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1169 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1174 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1176 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1177 GTK_ICON_SIZE_MENU);
1179 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1181 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1182 GTK_ICON_SIZE_MENU);
1187 "visible", pixbuf != NULL,
1191 tp_clear_object (&pixbuf);
1197 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1198 GtkCellRenderer *cell,
1199 GtkTreeModel *model,
1201 EmpathyIndividualView *view)
1205 gboolean can_audio, can_video;
1207 gtk_tree_model_get (model, iter,
1208 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1209 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1210 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1211 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1214 "visible", !is_group && (can_audio || can_video),
1215 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1218 individual_view_cell_set_background (view, cell, is_group, is_active);
1222 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1223 GtkCellRenderer *cell,
1224 GtkTreeModel *model,
1226 EmpathyIndividualView *view)
1229 gboolean show_avatar;
1233 gtk_tree_model_get (model, iter,
1234 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1235 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1236 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1237 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1240 "visible", !is_group && show_avatar,
1244 tp_clear_object (&pixbuf);
1246 individual_view_cell_set_background (view, cell, is_group, is_active);
1250 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1251 GtkCellRenderer *cell,
1252 GtkTreeModel *model,
1254 EmpathyIndividualView *view)
1259 gtk_tree_model_get (model, iter,
1260 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1261 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1263 individual_view_cell_set_background (view, cell, is_group, is_active);
1267 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1268 GtkCellRenderer *cell,
1269 GtkTreeModel *model,
1271 EmpathyIndividualView *view)
1276 gtk_tree_model_get (model, iter,
1277 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1278 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1280 if (gtk_tree_model_iter_has_child (model, iter))
1283 gboolean row_expanded;
1285 path = gtk_tree_model_get_path (model, iter);
1287 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1288 (gtk_tree_view_column_get_tree_view (column)), path);
1289 gtk_tree_path_free (path);
1294 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1298 g_object_set (cell, "visible", FALSE, NULL);
1300 individual_view_cell_set_background (view, cell, is_group, is_active);
1304 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1309 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1310 GtkTreeModel *model;
1314 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1317 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1319 gtk_tree_model_get (model, iter,
1320 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1322 expanded = GPOINTER_TO_INT (user_data);
1323 empathy_contact_group_set_expanded (name, expanded);
1329 individual_view_start_search_cb (EmpathyIndividualView *view,
1332 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1334 if (priv->search_widget == NULL)
1337 empathy_individual_view_start_search (view);
1343 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1345 EmpathyIndividualView *view)
1347 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1349 GtkTreeViewColumn *focus_column;
1350 GtkTreeModel *model;
1352 gboolean set_cursor = FALSE;
1354 gtk_tree_model_filter_refilter (priv->filter);
1356 /* Set cursor on the first contact. If it is already set on a group,
1357 * set it on its first child contact. Note that first child of a group
1358 * is its separator, that's why we actually set to the 2nd
1361 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1362 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1366 path = gtk_tree_path_new_from_string ("0:1");
1369 else if (gtk_tree_path_get_depth (path) < 2)
1373 gtk_tree_model_get_iter (model, &iter, path);
1374 gtk_tree_model_get (model, &iter,
1375 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1380 gtk_tree_path_down (path);
1381 gtk_tree_path_next (path);
1388 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1390 if (gtk_tree_model_get_iter (model, &iter, path))
1392 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1397 gtk_tree_path_free (path);
1401 individual_view_search_activate_cb (GtkWidget *search,
1402 EmpathyIndividualView *view)
1405 GtkTreeViewColumn *focus_column;
1407 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1410 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1411 gtk_tree_path_free (path);
1413 gtk_widget_hide (search);
1418 individual_view_search_key_navigation_cb (GtkWidget *search,
1420 EmpathyIndividualView *view)
1422 GdkEvent *new_event;
1423 gboolean ret = FALSE;
1425 new_event = gdk_event_copy (event);
1426 gtk_widget_grab_focus (GTK_WIDGET (view));
1427 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1428 gtk_widget_grab_focus (search);
1430 gdk_event_free (new_event);
1436 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1437 EmpathyIndividualView *view)
1439 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1440 GtkTreeModel *model;
1441 GtkTreePath *cursor_path;
1443 gboolean valid = FALSE;
1445 /* block expand or collapse handlers, they would write the
1446 * expand or collapsed setting to file otherwise */
1447 g_signal_handlers_block_by_func (view,
1448 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1449 g_signal_handlers_block_by_func (view,
1450 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1452 /* restore which groups are expanded and which are not */
1453 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1454 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1455 valid; valid = gtk_tree_model_iter_next (model, &iter))
1461 gtk_tree_model_get (model, &iter,
1462 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1463 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1472 path = gtk_tree_model_get_path (model, &iter);
1473 if ((priv->view_features &
1474 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1475 empathy_contact_group_get_expanded (name))
1477 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1481 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1484 gtk_tree_path_free (path);
1488 /* unblock expand or collapse handlers */
1489 g_signal_handlers_unblock_by_func (view,
1490 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1491 g_signal_handlers_unblock_by_func (view,
1492 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1494 /* keep the selected contact visible */
1495 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1497 if (cursor_path != NULL)
1498 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1501 gtk_tree_path_free (cursor_path);
1505 individual_view_search_show_cb (EmpathyLiveSearch *search,
1506 EmpathyIndividualView *view)
1508 /* block expand or collapse handlers during expand all, they would
1509 * write the expand or collapsed setting to file otherwise */
1510 g_signal_handlers_block_by_func (view,
1511 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1513 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1515 g_signal_handlers_unblock_by_func (view,
1516 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1520 expand_idle_foreach_cb (GtkTreeModel *model,
1523 EmpathyIndividualView *self)
1525 EmpathyIndividualViewPriv *priv;
1527 gpointer should_expand;
1530 /* We only want groups */
1531 if (gtk_tree_path_get_depth (path) > 1)
1534 gtk_tree_model_get (model, iter,
1535 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1536 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1545 priv = GET_PRIV (self);
1547 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1550 if (GPOINTER_TO_INT (should_expand))
1551 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1553 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1555 g_hash_table_remove (priv->expand_groups, name);
1564 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1566 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1568 g_signal_handlers_block_by_func (self,
1569 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1570 g_signal_handlers_block_by_func (self,
1571 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1573 /* The store/filter could've been removed while we were in the idle queue */
1574 if (priv->filter != NULL)
1576 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1577 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1580 g_signal_handlers_unblock_by_func (self,
1581 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1582 g_signal_handlers_unblock_by_func (self,
1583 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1585 /* Empty the table of groups to expand/contract, since it may contain groups
1586 * which no longer exist in the tree view. This can happen after going
1587 * offline, for example. */
1588 g_hash_table_remove_all (priv->expand_groups);
1589 priv->expand_groups_idle_handler = 0;
1590 g_object_unref (self);
1596 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1599 EmpathyIndividualView *view)
1601 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1602 gboolean should_expand, is_group = FALSE;
1604 gpointer will_expand;
1606 gtk_tree_model_get (model, iter,
1607 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1608 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1611 if (!is_group || EMP_STR_EMPTY (name))
1617 should_expand = (priv->view_features &
1618 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1619 (priv->search_widget != NULL &&
1620 gtk_widget_get_visible (priv->search_widget)) ||
1621 empathy_contact_group_get_expanded (name);
1623 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1624 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1625 * a hash table, and expand or contract them as appropriate all at once in
1626 * an idle handler which iterates over all the group rows. */
1627 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1629 GPOINTER_TO_INT (will_expand) != should_expand)
1631 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1632 GINT_TO_POINTER (should_expand));
1634 if (priv->expand_groups_idle_handler == 0)
1636 priv->expand_groups_idle_handler =
1637 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1638 g_object_ref (view));
1646 individual_view_is_visible_individual (EmpathyIndividualView *self,
1647 FolksIndividual *individual,
1649 gboolean is_searching,
1651 gboolean is_fake_group,
1654 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1655 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1658 gboolean is_favorite;
1660 /* Always display individuals having pending events */
1661 if (event_count > 0)
1664 /* We're only giving the visibility wrt filtering here, not things like
1666 if (!priv->show_untrusted &&
1667 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1672 if (!priv->show_uninteresting)
1674 gboolean contains_interesting_persona = FALSE;
1676 /* Hide all individuals which consist entirely of uninteresting
1678 personas = folks_individual_get_personas (individual);
1679 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1680 while (!contains_interesting_persona && gee_iterator_next (iter))
1682 FolksPersona *persona = gee_iterator_get (iter);
1684 if (empathy_folks_persona_is_interesting (persona))
1685 contains_interesting_persona = TRUE;
1687 g_clear_object (&persona);
1689 g_clear_object (&iter);
1691 if (!contains_interesting_persona)
1695 is_favorite = folks_favourite_details_get_is_favourite (
1696 FOLKS_FAVOURITE_DETAILS (individual));
1697 if (!is_searching) {
1698 if (is_favorite && is_fake_group &&
1699 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1700 /* Always display favorite contacts in the favorite group */
1703 return (priv->show_offline || is_online);
1706 return empathy_individual_match_string (individual,
1707 empathy_live_search_get_text (live),
1708 empathy_live_search_get_words (live));
1712 get_group (GtkTreeModel *model,
1716 GtkTreeIter parent_iter;
1721 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1724 gtk_tree_model_get (model, &parent_iter,
1725 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1726 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1734 individual_view_filter_visible_func (GtkTreeModel *model,
1738 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1739 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1740 FolksIndividual *individual = NULL;
1741 gboolean is_group, is_separator, valid;
1742 GtkTreeIter child_iter;
1743 gboolean visible, is_online;
1744 gboolean is_searching = TRUE;
1747 if (priv->custom_filter != NULL)
1748 return priv->custom_filter (model, iter, priv->custom_filter_data);
1750 if (priv->search_widget == NULL ||
1751 !gtk_widget_get_visible (priv->search_widget))
1752 is_searching = FALSE;
1754 gtk_tree_model_get (model, iter,
1755 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1756 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1757 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1758 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1759 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1762 if (individual != NULL)
1765 gboolean is_fake_group;
1767 group = get_group (model, iter, &is_fake_group);
1769 visible = individual_view_is_visible_individual (self, individual,
1770 is_online, is_searching, group, is_fake_group, event_count);
1772 g_object_unref (individual);
1781 /* Not a contact, not a separator, must be a group */
1782 g_return_val_if_fail (is_group, FALSE);
1784 /* only show groups which are not empty */
1785 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1786 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1789 gboolean is_fake_group;
1791 gtk_tree_model_get (model, &child_iter,
1792 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1793 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1794 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1797 if (individual == NULL)
1800 group = get_group (model, &child_iter, &is_fake_group);
1802 visible = individual_view_is_visible_individual (self, individual,
1803 is_online, is_searching, group, is_fake_group, event_count);
1805 g_object_unref (individual);
1808 /* show group if it has at least one visible contact in it */
1816 static gchar * empathy_individual_view_dup_selected_group (
1817 EmpathyIndividualView *view,
1818 gboolean *is_fake_group);
1821 text_edited_cb (GtkCellRendererText *cellrenderertext,
1824 EmpathyIndividualView *self)
1826 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1827 gchar *old_name, *new_name;
1829 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1831 new_name = g_strdup (name);
1832 g_strstrip (new_name);
1834 if (tp_str_empty (new_name))
1837 old_name = empathy_individual_view_dup_selected_group (self, NULL);
1838 g_return_if_fail (old_name != NULL);
1840 if (tp_strdiff (old_name, new_name))
1842 EmpathyConnectionAggregator *aggregator;
1844 DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1846 aggregator = empathy_connection_aggregator_dup_singleton ();
1848 empathy_connection_aggregator_rename_group (aggregator, old_name,
1850 g_object_unref (aggregator);
1859 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1860 EmpathyIndividualView *self)
1862 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1864 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1868 individual_view_constructed (GObject *object)
1870 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1871 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1872 GtkCellRenderer *cell;
1873 GtkTreeViewColumn *col;
1878 "headers-visible", FALSE,
1879 "show-expanders", FALSE,
1882 col = gtk_tree_view_column_new ();
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_pixbuf_cell_data_func,
1898 cell = gtk_cell_renderer_pixbuf_new ();
1899 gtk_tree_view_column_pack_start (col, cell, FALSE);
1900 gtk_tree_view_column_set_cell_data_func (col, cell,
1901 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1913 priv->text_renderer = empathy_cell_renderer_text_new ();
1914 gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1915 gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1916 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1918 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1919 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1920 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1921 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1922 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1923 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1924 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1925 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1926 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1927 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1928 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1929 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1930 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1931 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1933 g_signal_connect (priv->text_renderer, "editing-canceled",
1934 G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1935 g_signal_connect (priv->text_renderer, "edited",
1936 G_CALLBACK (text_edited_cb), view);
1938 /* Audio Call Icon */
1939 cell = empathy_cell_renderer_activatable_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_audio_call_cell_data_func,
1945 g_object_set (cell, "visible", FALSE, NULL);
1947 g_signal_connect (cell, "path-activated",
1948 G_CALLBACK (individual_view_call_activated_cb), view);
1951 cell = gtk_cell_renderer_pixbuf_new ();
1952 gtk_tree_view_column_pack_start (col, cell, FALSE);
1953 gtk_tree_view_column_set_cell_data_func (col, cell,
1954 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1966 cell = empathy_cell_renderer_expander_new ();
1967 gtk_tree_view_column_pack_end (col, cell, FALSE);
1968 gtk_tree_view_column_set_cell_data_func (col, cell,
1969 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1972 /* Actually add the column now we have added all cell renderers */
1973 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1976 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1978 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1983 individual_view_set_view_features (EmpathyIndividualView *view,
1984 EmpathyIndividualFeatureFlags features)
1986 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1987 gboolean has_tooltip;
1989 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1991 priv->view_features = features;
1993 /* Setting reorderable is a hack that gets us row previews as drag icons
1994 for free. We override all the drag handlers. It's tricky to get the
1995 position of the drag icon right in drag_begin. GtkTreeView has special
1996 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1999 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
2000 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
2002 /* Update DnD source/dest */
2003 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
2005 gtk_drag_source_set (GTK_WIDGET (view),
2008 G_N_ELEMENTS (drag_types_source),
2009 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2013 gtk_drag_source_unset (GTK_WIDGET (view));
2017 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2019 gtk_drag_dest_set (GTK_WIDGET (view),
2020 GTK_DEST_DEFAULT_ALL,
2022 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2026 /* FIXME: URI could still be droped depending on FT feature */
2027 gtk_drag_dest_unset (GTK_WIDGET (view));
2030 /* Update has-tooltip */
2032 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2033 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2037 individual_view_dispose (GObject *object)
2039 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2040 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2042 tp_clear_object (&priv->store);
2043 tp_clear_object (&priv->filter);
2044 tp_clear_object (&priv->tooltip_widget);
2046 empathy_individual_view_set_live_search (view, NULL);
2048 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2052 individual_view_finalize (GObject *object)
2054 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2056 if (priv->expand_groups_idle_handler != 0)
2057 g_source_remove (priv->expand_groups_idle_handler);
2058 g_hash_table_unref (priv->expand_groups);
2060 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2064 individual_view_get_property (GObject *object,
2069 EmpathyIndividualViewPriv *priv;
2071 priv = GET_PRIV (object);
2076 g_value_set_object (value, priv->store);
2078 case PROP_VIEW_FEATURES:
2079 g_value_set_flags (value, priv->view_features);
2081 case PROP_INDIVIDUAL_FEATURES:
2082 g_value_set_flags (value, priv->individual_features);
2084 case PROP_SHOW_OFFLINE:
2085 g_value_set_boolean (value, priv->show_offline);
2087 case PROP_SHOW_UNTRUSTED:
2088 g_value_set_boolean (value, priv->show_untrusted);
2090 case PROP_SHOW_UNINTERESTING:
2091 g_value_set_boolean (value, priv->show_uninteresting);
2094 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2100 individual_view_set_property (GObject *object,
2102 const GValue *value,
2105 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2106 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2111 empathy_individual_view_set_store (view, g_value_get_object (value));
2113 case PROP_VIEW_FEATURES:
2114 individual_view_set_view_features (view, g_value_get_flags (value));
2116 case PROP_INDIVIDUAL_FEATURES:
2117 priv->individual_features = g_value_get_flags (value);
2119 case PROP_SHOW_OFFLINE:
2120 empathy_individual_view_set_show_offline (view,
2121 g_value_get_boolean (value));
2123 case PROP_SHOW_UNTRUSTED:
2124 empathy_individual_view_set_show_untrusted (view,
2125 g_value_get_boolean (value));
2127 case PROP_SHOW_UNINTERESTING:
2128 empathy_individual_view_set_show_uninteresting (view,
2129 g_value_get_boolean (value));
2131 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2137 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2139 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2140 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2141 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2143 object_class->constructed = individual_view_constructed;
2144 object_class->dispose = individual_view_dispose;
2145 object_class->finalize = individual_view_finalize;
2146 object_class->get_property = individual_view_get_property;
2147 object_class->set_property = individual_view_set_property;
2149 widget_class->drag_data_received = individual_view_drag_data_received;
2150 widget_class->drag_drop = individual_view_drag_drop;
2151 widget_class->drag_begin = individual_view_drag_begin;
2152 widget_class->drag_data_get = individual_view_drag_data_get;
2153 widget_class->drag_end = individual_view_drag_end;
2154 widget_class->drag_motion = individual_view_drag_motion;
2156 /* We use the class method to let user of this widget to connect to
2157 * the signal and stop emission of the signal so the default handler
2158 * won't be called. */
2159 tree_view_class->row_activated = individual_view_row_activated;
2161 klass->drag_individual_received = real_drag_individual_received_cb;
2163 signals[DRAG_INDIVIDUAL_RECEIVED] =
2164 g_signal_new ("drag-individual-received",
2165 G_OBJECT_CLASS_TYPE (klass),
2167 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2169 g_cclosure_marshal_generic,
2170 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2171 G_TYPE_STRING, G_TYPE_STRING);
2173 signals[DRAG_PERSONA_RECEIVED] =
2174 g_signal_new ("drag-persona-received",
2175 G_OBJECT_CLASS_TYPE (klass),
2177 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2179 g_cclosure_marshal_generic,
2180 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2182 g_object_class_install_property (object_class,
2184 g_param_spec_object ("store",
2185 "The store of the view",
2186 "The store of the view",
2187 EMPATHY_TYPE_INDIVIDUAL_STORE,
2188 G_PARAM_READWRITE));
2189 g_object_class_install_property (object_class,
2191 g_param_spec_flags ("view-features",
2192 "Features of the view",
2193 "Flags for all enabled features",
2194 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2195 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2196 g_object_class_install_property (object_class,
2197 PROP_INDIVIDUAL_FEATURES,
2198 g_param_spec_flags ("individual-features",
2199 "Features of the individual menu",
2200 "Flags for all enabled features for the menu",
2201 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2202 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2203 g_object_class_install_property (object_class,
2205 g_param_spec_boolean ("show-offline",
2207 "Whether contact list should display "
2208 "offline contacts", FALSE, G_PARAM_READWRITE));
2209 g_object_class_install_property (object_class,
2210 PROP_SHOW_UNTRUSTED,
2211 g_param_spec_boolean ("show-untrusted",
2212 "Show Untrusted Individuals",
2213 "Whether the view should display untrusted individuals; "
2214 "those who could not be who they say they are.",
2215 TRUE, G_PARAM_READWRITE));
2216 g_object_class_install_property (object_class,
2217 PROP_SHOW_UNINTERESTING,
2218 g_param_spec_boolean ("show-uninteresting",
2219 "Show Uninteresting Individuals",
2220 "Whether the view should not filter out individuals using "
2221 "empathy_folks_persona_is_interesting.",
2222 FALSE, G_PARAM_READWRITE));
2224 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2228 empathy_individual_view_init (EmpathyIndividualView *view)
2230 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2231 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2235 priv->show_untrusted = TRUE;
2236 priv->show_uninteresting = FALSE;
2238 /* Get saved group states. */
2239 empathy_contact_groups_get_all ();
2241 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2242 (GDestroyNotify) g_free, NULL);
2244 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2245 empathy_individual_store_row_separator_func, NULL, NULL);
2247 /* Connect to tree view signals rather than override. */
2248 g_signal_connect (view, "button-press-event",
2249 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2250 g_signal_connect (view, "key-press-event",
2251 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2252 g_signal_connect (view, "row-expanded",
2253 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2254 GINT_TO_POINTER (TRUE));
2255 g_signal_connect (view, "row-collapsed",
2256 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2257 GINT_TO_POINTER (FALSE));
2258 g_signal_connect (view, "query-tooltip",
2259 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2262 EmpathyIndividualView *
2263 empathy_individual_view_new (EmpathyIndividualStore *store,
2264 EmpathyIndividualViewFeatureFlags view_features,
2265 EmpathyIndividualFeatureFlags individual_features)
2267 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2269 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2271 "individual-features", individual_features,
2272 "view-features", view_features, NULL);
2276 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2278 GtkTreeSelection *selection;
2280 GtkTreeModel *model;
2281 FolksIndividual *individual;
2283 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2285 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2286 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2289 gtk_tree_model_get (model, &iter,
2290 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2296 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2297 gboolean *is_fake_group)
2299 GtkTreeSelection *selection;
2301 GtkTreeModel *model;
2306 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2308 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2309 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2312 gtk_tree_model_get (model, &iter,
2313 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2314 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2315 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2323 if (is_fake_group != NULL)
2324 *is_fake_group = fake;
2331 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2332 REMOVE_DIALOG_RESPONSE_DELETE,
2336 individual_view_remove_dialog_show (GtkWindow *parent,
2337 const gchar *message,
2338 const gchar *secondary_text)
2343 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2344 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2346 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2347 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2348 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2349 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2350 "%s", secondary_text);
2352 gtk_widget_show (dialog);
2354 res = gtk_dialog_run (GTK_DIALOG (dialog));
2355 gtk_widget_destroy (dialog);
2361 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2362 EmpathyIndividualView *view)
2366 group = empathy_individual_view_dup_selected_group (view, NULL);
2373 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2375 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2376 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2377 text) == REMOVE_DIALOG_RESPONSE_DELETE)
2379 EmpathyIndividualManager *manager =
2380 empathy_individual_manager_dup_singleton ();
2381 empathy_individual_manager_remove_group (manager, group);
2382 g_object_unref (G_OBJECT (manager));
2392 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2393 EmpathyIndividualView *self)
2395 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2398 GtkTreeSelection *selection;
2399 GtkTreeModel *model;
2401 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2402 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2404 path = gtk_tree_model_get_path (model, &iter);
2406 g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2408 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2409 gtk_widget_grab_focus (GTK_WIDGET (self));
2410 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2411 gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2413 gtk_tree_path_free (path);
2417 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2419 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2424 gboolean is_fake_group;
2426 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2428 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2429 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2432 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2433 if (!group || is_fake_group)
2435 /* We can't alter fake groups */
2440 menu = gtk_menu_new ();
2442 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2444 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
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_rename_activate_cb), view);
2451 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2453 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2454 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2455 GTK_ICON_SIZE_MENU);
2456 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2457 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2458 gtk_widget_show (item);
2459 g_signal_connect (item, "activate",
2460 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2469 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2471 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2472 FolksIndividual *individual;
2473 GtkWidget *menu = NULL;
2475 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2477 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2478 /* No need to create a context menu */
2481 individual = empathy_individual_view_dup_selected (view);
2482 if (individual == NULL)
2485 if (!empathy_folks_individual_contains_contact (individual))
2488 menu = empathy_individual_menu_new (individual, priv->individual_features,
2492 g_object_unref (individual);
2498 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2499 EmpathyLiveSearch *search)
2501 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2503 /* remove old handlers if old search was not null */
2504 if (priv->search_widget != NULL)
2506 g_signal_handlers_disconnect_by_func (view,
2507 individual_view_start_search_cb, NULL);
2509 g_signal_handlers_disconnect_by_func (priv->search_widget,
2510 individual_view_search_text_notify_cb, view);
2511 g_signal_handlers_disconnect_by_func (priv->search_widget,
2512 individual_view_search_activate_cb, view);
2513 g_signal_handlers_disconnect_by_func (priv->search_widget,
2514 individual_view_search_key_navigation_cb, view);
2515 g_signal_handlers_disconnect_by_func (priv->search_widget,
2516 individual_view_search_hide_cb, view);
2517 g_signal_handlers_disconnect_by_func (priv->search_widget,
2518 individual_view_search_show_cb, view);
2519 g_object_unref (priv->search_widget);
2520 priv->search_widget = NULL;
2523 /* connect handlers if new search is not null */
2526 priv->search_widget = g_object_ref (search);
2528 g_signal_connect (view, "start-interactive-search",
2529 G_CALLBACK (individual_view_start_search_cb), NULL);
2531 g_signal_connect (priv->search_widget, "notify::text",
2532 G_CALLBACK (individual_view_search_text_notify_cb), view);
2533 g_signal_connect (priv->search_widget, "activate",
2534 G_CALLBACK (individual_view_search_activate_cb), view);
2535 g_signal_connect (priv->search_widget, "key-navigation",
2536 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2537 g_signal_connect (priv->search_widget, "hide",
2538 G_CALLBACK (individual_view_search_hide_cb), view);
2539 g_signal_connect (priv->search_widget, "show",
2540 G_CALLBACK (individual_view_search_show_cb), view);
2545 empathy_individual_view_is_searching (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->search_widget != NULL &&
2554 gtk_widget_get_visible (priv->search_widget));
2558 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2560 EmpathyIndividualViewPriv *priv;
2562 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2564 priv = GET_PRIV (self);
2566 return priv->show_offline;
2570 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2571 gboolean show_offline)
2573 EmpathyIndividualViewPriv *priv;
2575 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2577 priv = GET_PRIV (self);
2579 priv->show_offline = show_offline;
2581 g_object_notify (G_OBJECT (self), "show-offline");
2582 gtk_tree_model_filter_refilter (priv->filter);
2586 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2588 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2590 return GET_PRIV (self)->show_untrusted;
2594 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2595 gboolean show_untrusted)
2597 EmpathyIndividualViewPriv *priv;
2599 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2601 priv = GET_PRIV (self);
2603 priv->show_untrusted = show_untrusted;
2605 g_object_notify (G_OBJECT (self), "show-untrusted");
2606 gtk_tree_model_filter_refilter (priv->filter);
2609 EmpathyIndividualStore *
2610 empathy_individual_view_get_store (EmpathyIndividualView *self)
2612 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2614 return GET_PRIV (self)->store;
2618 empathy_individual_view_set_store (EmpathyIndividualView *self,
2619 EmpathyIndividualStore *store)
2621 EmpathyIndividualViewPriv *priv;
2623 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2624 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2626 priv = GET_PRIV (self);
2628 /* Destroy the old filter and remove the old store */
2629 if (priv->store != NULL)
2631 g_signal_handlers_disconnect_by_func (priv->filter,
2632 individual_view_row_has_child_toggled_cb, self);
2634 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2637 tp_clear_object (&priv->filter);
2638 tp_clear_object (&priv->store);
2640 /* Set the new store */
2641 priv->store = store;
2645 g_object_ref (store);
2647 /* Create a new filter */
2648 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2649 GTK_TREE_MODEL (priv->store), NULL));
2650 gtk_tree_model_filter_set_visible_func (priv->filter,
2651 individual_view_filter_visible_func, self, NULL);
2653 g_signal_connect (priv->filter, "row-has-child-toggled",
2654 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2655 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2656 GTK_TREE_MODEL (priv->filter));
2661 empathy_individual_view_start_search (EmpathyIndividualView *self)
2663 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2665 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2666 g_return_if_fail (priv->search_widget != NULL);
2668 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2669 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2671 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2675 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2676 GtkTreeModelFilterVisibleFunc filter,
2679 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2681 priv->custom_filter = filter;
2682 priv->custom_filter_data = data;
2686 empathy_individual_view_refilter (EmpathyIndividualView *self)
2688 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2690 gtk_tree_model_filter_refilter (priv->filter);
2694 empathy_individual_view_select_first (EmpathyIndividualView *self)
2696 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2699 gtk_tree_model_filter_refilter (priv->filter);
2701 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2703 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2704 GTK_TREE_VIEW (self));
2706 gtk_tree_selection_select_iter (selection, &iter);
2711 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2712 gboolean show_uninteresting)
2714 EmpathyIndividualViewPriv *priv;
2716 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2718 priv = GET_PRIV (self);
2720 priv->show_uninteresting = show_uninteresting;
2722 g_object_notify (G_OBJECT (self), "show-uninteresting");
2723 gtk_tree_model_filter_refilter (priv->filter);