1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
28 #include "empathy-individual-view.h"
30 #include <glib/gi18n-lib.h>
31 #include <tp-account-widgets/tpaw-pixbuf-utils.h>
32 #include <tp-account-widgets/tpaw-utils.h>
34 #include "empathy-cell-renderer-activatable.h"
35 #include "empathy-cell-renderer-expander.h"
36 #include "empathy-cell-renderer-text.h"
37 #include "empathy-connection-aggregator.h"
38 #include "empathy-contact-groups.h"
39 #include "empathy-gtk-enum-types.h"
40 #include "empathy-images.h"
41 #include "empathy-individual-edit-dialog.h"
42 #include "empathy-individual-manager.h"
43 #include "empathy-request-util.h"
44 #include "empathy-ui-utils.h"
45 #include "empathy-utils.h"
47 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
48 #include "empathy-debug.h"
50 /* Active users are those which have recently changed state
51 * (e.g. online, offline or from normal to a busy state).
54 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
57 EmpathyIndividualStore *store;
58 GtkTreeRowReference *drag_row;
59 EmpathyIndividualViewFeatureFlags view_features;
60 EmpathyIndividualFeatureFlags individual_features;
61 GtkWidget *tooltip_widget;
63 gboolean show_offline;
64 gboolean show_untrusted;
65 gboolean show_uninteresting;
67 GtkTreeModelFilter *filter;
68 GtkWidget *search_widget;
70 guint expand_groups_idle_handler;
71 /* owned string (group name) -> bool (whether to expand/contract) */
72 GHashTable *expand_groups;
75 guint auto_scroll_timeout_id;
76 /* Distance between mouse pointer and the nearby border. Negative when
80 GtkTreeModelFilterVisibleFunc custom_filter;
81 gpointer custom_filter_data;
83 GtkCellRenderer *text_renderer;
84 } EmpathyIndividualViewPriv;
88 EmpathyIndividualView *view;
95 EmpathyIndividualView *view;
96 FolksIndividual *individual;
105 PROP_INDIVIDUAL_FEATURES,
108 PROP_SHOW_UNINTERESTING,
111 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
112 * specific EmpathyContacts (between/in/out of Individuals) */
115 DND_DRAG_TYPE_UNKNOWN = -1,
116 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
117 DND_DRAG_TYPE_PERSONA_ID,
118 DND_DRAG_TYPE_URI_LIST,
119 DND_DRAG_TYPE_STRING,
122 #define DRAG_TYPE(T,I) \
123 { (gchar *) T, 0, I }
125 static const GtkTargetEntry drag_types_dest[] = {
126 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
127 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
128 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
129 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
130 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
131 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
134 static const GtkTargetEntry drag_types_source[] = {
135 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
140 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
144 DRAG_INDIVIDUAL_RECEIVED,
145 DRAG_PERSONA_RECEIVED,
149 static guint signals[LAST_SIGNAL];
151 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
155 individual_view_tooltip_destroy_cb (GtkWidget *widget,
156 EmpathyIndividualView *view)
158 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
160 tp_clear_object (&priv->tooltip_widget);
164 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
167 gboolean keyboard_mode,
171 EmpathyIndividualViewPriv *priv;
172 FolksIndividual *individual;
176 static gint running = 0;
177 gboolean ret = FALSE;
179 priv = GET_PRIV (view);
181 /* Avoid an infinite loop. See GNOME bug #574377 */
187 /* Don't show the tooltip if there's already a popup menu */
188 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
191 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
192 keyboard_mode, &model, &path, &iter))
195 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
196 gtk_tree_path_free (path);
198 gtk_tree_model_get (model, &iter,
199 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
201 if (individual == NULL)
204 if (priv->tooltip_widget == NULL)
206 priv->tooltip_widget = empathy_individual_widget_new (individual,
207 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
208 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
209 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
210 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
211 g_object_ref (priv->tooltip_widget);
213 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
214 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
216 gtk_widget_show (priv->tooltip_widget);
220 empathy_individual_widget_set_individual (
221 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
224 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
227 g_object_unref (individual);
235 groups_change_group_cb (GObject *source,
236 GAsyncResult *result,
239 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
240 GError *error = NULL;
242 folks_group_details_change_group_finish (group_details, result, &error);
245 g_warning ("failed to change group: %s", error->message);
246 g_clear_error (&error);
251 group_can_be_modified (const gchar *name,
252 gboolean is_fake_group,
255 /* Real groups can always be modified */
259 /* The favorite fake group can be modified so users can
260 * add/remove favorites using DnD */
261 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
264 /* We can remove contacts from the 'ungrouped' fake group */
265 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
272 individual_view_individual_drag_received (GtkWidget *self,
273 GdkDragContext *context,
276 GtkSelectionData *selection)
278 EmpathyIndividualViewPriv *priv;
279 EmpathyIndividualManager *manager = NULL;
280 FolksIndividual *individual;
281 GtkTreePath *source_path;
282 const gchar *sel_data;
283 gchar *new_group = NULL;
284 gchar *old_group = NULL;
285 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
287 priv = GET_PRIV (self);
289 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
290 new_group = empathy_individual_store_get_parent_group (model, path,
291 NULL, &new_group_is_fake);
293 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
296 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
297 * feature. Otherwise, we just add the dropped contact to whichever group
298 * they were dropped in, and don't remove them from their old group. This
299 * allows for Individual views which shouldn't allow Individuals to have
300 * their groups changed, and also for dragging Individuals between Individual
302 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
303 priv->drag_row != NULL)
305 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
309 empathy_individual_store_get_parent_group (model, source_path,
310 NULL, &old_group_is_fake);
311 gtk_tree_path_free (source_path);
314 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
317 if (!tp_strdiff (old_group, new_group))
320 else if (priv->drag_row != NULL)
322 /* We don't allow changing Individuals' groups, and this Individual was
323 * dragged from another group in *this* Individual view, so we disallow
328 /* XXX: for contacts, we used to ensure the account, create the contact
329 * factory, and then wait on the contacts. But they should already be
330 * created by this point */
332 manager = empathy_individual_manager_dup_singleton ();
333 individual = empathy_individual_manager_lookup_member (manager, sel_data);
335 if (individual == NULL)
337 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
341 /* FIXME: We should probably wait for the cb before calling
344 /* Emit a signal notifying of the drag. We change the Individual's groups in
345 * the default signal handler. */
346 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
347 gdk_drag_context_get_selected_action (context), individual, new_group,
353 tp_clear_object (&manager);
361 real_drag_individual_received_cb (EmpathyIndividualView *self,
362 GdkDragAction action,
363 FolksIndividual *individual,
364 const gchar *new_group,
365 const gchar *old_group)
367 DEBUG ("individual %s dragged from '%s' to '%s'",
368 folks_individual_get_id (individual), old_group, new_group);
370 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
372 /* Mark contact as favourite */
373 folks_favourite_details_set_is_favourite (
374 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
378 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
380 /* Remove contact as favourite */
381 folks_favourite_details_set_is_favourite (
382 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
384 /* Don't try to remove it */
388 if (new_group != NULL)
390 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
391 new_group, TRUE, groups_change_group_cb, NULL);
394 if (old_group != NULL && action == GDK_ACTION_MOVE)
396 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
397 old_group, FALSE, groups_change_group_cb, NULL);
402 individual_view_persona_drag_received (GtkWidget *self,
403 GdkDragContext *context,
406 GtkSelectionData *selection)
408 EmpathyIndividualManager *manager = NULL;
409 FolksIndividual *individual = NULL;
410 FolksPersona *persona = NULL;
411 const gchar *persona_uid;
412 GList *individuals, *l;
413 GeeIterator *iter = NULL;
414 gboolean retval = FALSE;
416 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
418 /* FIXME: This is slow, but the only way to find the Persona we're having
420 manager = empathy_individual_manager_dup_singleton ();
421 individuals = empathy_individual_manager_get_members (manager);
423 for (l = individuals; l != NULL; l = l->next)
427 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
428 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
429 while (gee_iterator_next (iter))
431 FolksPersona *persona_cur = gee_iterator_get (iter);
433 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
435 /* takes ownership of the ref */
436 persona = persona_cur;
437 individual = g_object_ref (l->data);
440 g_clear_object (&persona_cur);
442 g_clear_object (&iter);
446 g_clear_object (&iter);
447 g_list_free (individuals);
449 if (persona == NULL || individual == NULL)
451 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
455 /* Emit a signal notifying of the drag. We change the Individual's groups in
456 * the default signal handler. */
457 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
458 gdk_drag_context_get_selected_action (context), persona, individual,
462 tp_clear_object (&manager);
463 tp_clear_object (&persona);
464 tp_clear_object (&individual);
470 individual_view_file_drag_received (GtkWidget *view,
471 GdkDragContext *context,
474 GtkSelectionData *selection)
477 const gchar *sel_data;
478 FolksIndividual *individual;
479 EmpathyContact *contact;
481 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
483 gtk_tree_model_get_iter (model, &iter, path);
484 gtk_tree_model_get (model, &iter,
485 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
486 if (individual == NULL)
489 contact = empathy_contact_dup_from_folks_individual (individual);
490 empathy_send_file_from_uri_list (contact, sel_data);
492 g_object_unref (individual);
493 tp_clear_object (&contact);
499 individual_view_drag_data_received (GtkWidget *view,
500 GdkDragContext *context,
503 GtkSelectionData *selection,
509 GtkTreeViewDropPosition position;
511 gboolean success = TRUE;
513 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
515 /* Get destination group information. */
516 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
517 x, y, &path, &position);
522 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
524 success = individual_view_individual_drag_received (view,
525 context, model, path, selection);
527 else if (info == DND_DRAG_TYPE_PERSONA_ID)
529 success = individual_view_persona_drag_received (view, context, model,
532 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
534 success = individual_view_file_drag_received (view,
535 context, model, path, selection);
538 gtk_tree_path_free (path);
539 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
543 individual_view_drag_motion_cb (DragMotionData *data)
545 if (data->view != NULL)
547 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
548 g_object_remove_weak_pointer (G_OBJECT (data->view),
549 (gpointer *) &data->view);
552 data->timeout_id = 0;
557 /* Minimum distance between the mouse pointer and a horizontal border when we
558 start auto scrolling. */
559 #define AUTO_SCROLL_MARGIN_SIZE 20
560 /* How far to scroll per one tick. */
561 #define AUTO_SCROLL_PITCH 10
564 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
566 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
570 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
572 if (priv->distance < 0)
573 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
575 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
577 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
578 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
580 gtk_adjustment_set_value (adj, new_value);
586 individual_view_drag_motion (GtkWidget *widget,
587 GdkDragContext *context,
592 EmpathyIndividualViewPriv *priv;
596 static DragMotionData *dm = NULL;
599 gboolean is_different = FALSE;
600 gboolean cleanup = TRUE;
601 gboolean retval = TRUE;
602 GtkAllocation allocation;
604 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
606 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
607 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
610 if (priv->auto_scroll_timeout_id != 0)
612 g_source_remove (priv->auto_scroll_timeout_id);
613 priv->auto_scroll_timeout_id = 0;
616 gtk_widget_get_allocation (widget, &allocation);
618 if (y < AUTO_SCROLL_MARGIN_SIZE ||
619 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
621 if (y < AUTO_SCROLL_MARGIN_SIZE)
622 priv->distance = MIN (-y, -1);
624 priv->distance = MAX (allocation.height - y, 1);
626 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
627 (GSourceFunc) individual_view_auto_scroll_cb, widget);
630 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
631 x, y, &path, NULL, NULL, NULL);
633 cleanup &= (dm == NULL);
637 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
638 is_different = ((dm == NULL) || ((dm != NULL)
639 && gtk_tree_path_compare (dm->path, path) != 0));
646 /* Coordinates don't point to an actual row, so make sure the pointer
647 and highlighting don't indicate that a drag is possible.
649 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
650 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
653 target = gtk_drag_dest_find_target (widget, context, NULL);
654 gtk_tree_model_get_iter (model, &iter, path);
656 /* Determine the DndDragType of the data */
657 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
659 if (target == drag_atoms_dest[i])
661 drag_type = drag_types_dest[i].info;
666 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
667 drag_type == DND_DRAG_TYPE_STRING)
669 /* This is a file drag, and it can only be dropped on contacts,
671 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
672 * even if we have a valid target. */
673 FolksIndividual *individual = NULL;
674 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
676 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
678 gtk_tree_model_get (model, &iter,
679 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
683 if (individual != NULL)
685 EmpathyContact *contact = NULL;
687 contact = empathy_contact_dup_from_folks_individual (individual);
689 caps = empathy_contact_get_capabilities (contact);
691 tp_clear_object (&contact);
694 if (individual != NULL &&
695 folks_presence_details_is_online (
696 FOLKS_PRESENCE_DETAILS (individual)) &&
697 (caps & EMPATHY_CAPABILITIES_FT))
699 gdk_drag_status (context, GDK_ACTION_COPY, time_);
700 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
701 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
705 gdk_drag_status (context, 0, time_);
706 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
710 if (individual != NULL)
711 g_object_unref (individual);
713 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
714 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
715 priv->drag_row == NULL)) ||
716 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
717 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
719 /* If target != GDK_NONE, then we have a contact (individual or persona)
720 drag. If we're pointing to a group, highlight it. Otherwise, if the
721 contact we're pointing to is in a group, highlight that. Otherwise,
722 set the drag position to before the first row for a drag into
723 the "non-group" at the top.
724 If it's an Individual:
725 We only highlight things if the contact is from a different
726 Individual view, or if this Individual view has
727 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
728 which don't have FEATURE_GROUPS_CHANGE, but do have
729 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
731 We only highlight things if we have FEATURE_PERSONA_DROP.
733 GtkTreeIter group_iter;
735 GtkTreePath *group_path;
736 gtk_tree_model_get (model, &iter,
737 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
744 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
745 gtk_tree_model_get (model, &group_iter,
746 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
750 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
751 group_path = gtk_tree_model_get_path (model, &group_iter);
752 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
753 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
754 gtk_tree_path_free (group_path);
758 group_path = gtk_tree_path_new_first ();
759 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
760 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
761 group_path, GTK_TREE_VIEW_DROP_BEFORE);
765 if (!is_different && !cleanup)
770 gtk_tree_path_free (dm->path);
773 g_source_remove (dm->timeout_id);
781 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
783 dm = g_new0 (DragMotionData, 1);
785 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
786 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
787 dm->path = gtk_tree_path_copy (path);
789 dm->timeout_id = g_timeout_add_seconds (1,
790 (GSourceFunc) individual_view_drag_motion_cb, dm);
797 individual_view_drag_begin (GtkWidget *widget,
798 GdkDragContext *context)
800 EmpathyIndividualViewPriv *priv;
801 GtkTreeSelection *selection;
806 priv = GET_PRIV (widget);
808 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
809 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
812 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
815 path = gtk_tree_model_get_path (model, &iter);
816 priv->drag_row = gtk_tree_row_reference_new (model, path);
817 gtk_tree_path_free (path);
821 individual_view_drag_data_get (GtkWidget *widget,
822 GdkDragContext *context,
823 GtkSelectionData *selection,
827 EmpathyIndividualViewPriv *priv;
828 GtkTreePath *src_path;
831 FolksIndividual *individual;
832 const gchar *individual_id;
834 priv = GET_PRIV (widget);
836 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
837 if (priv->drag_row == NULL)
840 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
841 if (src_path == NULL)
844 if (!gtk_tree_model_get_iter (model, &iter, src_path))
846 gtk_tree_path_free (src_path);
850 gtk_tree_path_free (src_path);
853 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
854 if (individual == NULL)
857 individual_id = folks_individual_get_id (individual);
859 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
861 gtk_selection_data_set (selection,
862 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
863 (guchar *) individual_id, strlen (individual_id) + 1);
866 g_object_unref (individual);
870 individual_view_drag_end (GtkWidget *widget,
871 GdkDragContext *context)
873 EmpathyIndividualViewPriv *priv;
875 priv = GET_PRIV (widget);
877 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
882 gtk_tree_row_reference_free (priv->drag_row);
883 priv->drag_row = NULL;
886 if (priv->auto_scroll_timeout_id != 0)
888 g_source_remove (priv->auto_scroll_timeout_id);
889 priv->auto_scroll_timeout_id = 0;
894 individual_view_drag_drop (GtkWidget *widget,
895 GdkDragContext *drag_context,
905 EmpathyIndividualView *view;
911 menu_deactivate_cb (GtkMenuShell *menushell,
914 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
915 g_signal_handlers_disconnect_by_func (menushell,
916 menu_deactivate_cb, user_data);
918 gtk_menu_detach (GTK_MENU (menushell));
922 individual_view_popup_menu_idle_cb (gpointer user_data)
924 MenuPopupData *data = user_data;
927 menu = empathy_individual_view_get_individual_menu (data->view);
929 menu = empathy_individual_view_get_group_menu (data->view);
933 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
935 gtk_widget_show (menu);
936 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
939 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
940 * floating ref. We can either wait that the treeview releases its ref
941 * when it will be destroyed (when leaving Empathy) or explicitely
942 * detach the menu when it's not displayed any more.
943 * We go for the latter as we don't want to keep useless menus in memory
944 * during the whole lifetime of Empathy. */
945 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
949 g_slice_free (MenuPopupData, data);
955 individual_view_button_press_event_cb (EmpathyIndividualView *view,
956 GdkEventButton *event,
959 if (event->button == 3)
963 data = g_slice_new (MenuPopupData);
965 data->button = event->button;
966 data->time = event->time;
967 g_idle_add (individual_view_popup_menu_idle_cb, data);
974 individual_view_key_press_event_cb (EmpathyIndividualView *view,
978 if (event->keyval == GDK_KEY_Menu)
982 data = g_slice_new (MenuPopupData);
985 data->time = event->time;
986 g_idle_add (individual_view_popup_menu_idle_cb, data);
987 } else if (event->keyval == GDK_KEY_F2) {
988 FolksIndividual *individual;
990 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
992 individual = empathy_individual_view_dup_selected (view);
993 if (individual == NULL)
996 empathy_individual_edit_dialog_show (individual, NULL);
998 g_object_unref (individual);
1005 individual_view_row_activated (GtkTreeView *view,
1007 GtkTreeViewColumn *column)
1009 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1010 FolksIndividual *individual;
1011 EmpathyContact *contact;
1012 GtkTreeModel *model;
1015 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1018 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1019 gtk_tree_model_get_iter (model, &iter, path);
1020 gtk_tree_model_get (model, &iter,
1021 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1023 if (individual == NULL)
1026 /* Determine which Persona to chat to, by choosing the most available one. */
1027 contact = empathy_contact_dup_best_for_action (individual,
1028 EMPATHY_ACTION_CHAT);
1030 if (contact != NULL)
1032 DEBUG ("Starting a chat");
1034 empathy_chat_with_contact (contact,
1035 gtk_get_current_event_time ());
1038 g_object_unref (individual);
1039 tp_clear_object (&contact);
1043 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1044 const gchar *path_string,
1045 EmpathyIndividualView *view)
1047 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1049 GtkTreeModel *model;
1051 FolksIndividual *individual;
1052 GdkEventButton *event;
1053 GtkMenuShell *shell;
1056 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1059 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1060 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1063 gtk_tree_model_get (model, &iter,
1064 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1065 if (individual == NULL)
1068 event = (GdkEventButton *) gtk_get_current_event ();
1070 menu = empathy_context_menu_new (GTK_WIDGET (view));
1071 shell = GTK_MENU_SHELL (menu);
1074 item = empathy_individual_audio_call_menu_item_new (individual);
1075 gtk_menu_shell_append (shell, item);
1076 gtk_widget_show (item);
1079 item = empathy_individual_video_call_menu_item_new (individual);
1080 gtk_menu_shell_append (shell, item);
1081 gtk_widget_show (item);
1083 gtk_widget_show (menu);
1084 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1085 event->button, event->time);
1087 g_object_unref (individual);
1091 individual_view_cell_set_background (EmpathyIndividualView *view,
1092 GtkCellRenderer *cell,
1096 if (!is_group && is_active)
1098 GtkStyleContext *style;
1101 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1103 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1106 /* Here we take the current theme colour and add it to
1107 * the colour for white and average the two. This
1108 * gives a colour which is inline with the theme but
1111 empathy_make_color_whiter (&color);
1113 g_object_set (cell, "cell-background-rgba", &color, NULL);
1116 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1120 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1121 GtkCellRenderer *cell,
1122 GtkTreeModel *model,
1124 EmpathyIndividualView *view)
1130 gtk_tree_model_get (model, iter,
1131 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1132 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1133 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1136 "visible", !is_group,
1140 tp_clear_object (&pixbuf);
1142 individual_view_cell_set_background (view, cell, is_group, is_active);
1146 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1147 GtkCellRenderer *cell,
1148 GtkTreeModel *model,
1150 EmpathyIndividualView *view)
1152 GdkPixbuf *pixbuf = NULL;
1156 gtk_tree_model_get (model, iter,
1157 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1158 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1163 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1165 pixbuf = tpaw_pixbuf_from_icon_name ("emblem-favorite",
1166 GTK_ICON_SIZE_MENU);
1168 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1170 pixbuf = tpaw_pixbuf_from_icon_name ("im-local-xmpp",
1171 GTK_ICON_SIZE_MENU);
1176 "visible", pixbuf != NULL,
1180 tp_clear_object (&pixbuf);
1186 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1187 GtkCellRenderer *cell,
1188 GtkTreeModel *model,
1190 EmpathyIndividualView *view)
1194 gboolean can_audio, can_video;
1196 gtk_tree_model_get (model, iter,
1197 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1198 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1199 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1200 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1203 "visible", !is_group && (can_audio || can_video),
1204 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1207 individual_view_cell_set_background (view, cell, is_group, is_active);
1211 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1212 GtkCellRenderer *cell,
1213 GtkTreeModel *model,
1215 EmpathyIndividualView *view)
1218 gboolean show_avatar;
1222 gtk_tree_model_get (model, iter,
1223 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1224 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1225 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1226 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1229 "visible", !is_group && show_avatar,
1233 tp_clear_object (&pixbuf);
1235 individual_view_cell_set_background (view, cell, is_group, is_active);
1239 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1240 GtkCellRenderer *cell,
1241 GtkTreeModel *model,
1243 EmpathyIndividualView *view)
1248 gtk_tree_model_get (model, iter,
1249 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1250 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1252 individual_view_cell_set_background (view, cell, is_group, is_active);
1256 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1257 GtkCellRenderer *cell,
1258 GtkTreeModel *model,
1260 EmpathyIndividualView *view)
1265 gtk_tree_model_get (model, iter,
1266 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1267 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1269 if (gtk_tree_model_iter_has_child (model, iter))
1272 gboolean row_expanded;
1274 path = gtk_tree_model_get_path (model, iter);
1276 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1277 (gtk_tree_view_column_get_tree_view (column)), path);
1278 gtk_tree_path_free (path);
1283 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1287 g_object_set (cell, "visible", FALSE, NULL);
1289 individual_view_cell_set_background (view, cell, is_group, is_active);
1293 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1298 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1299 GtkTreeModel *model;
1303 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1306 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1308 gtk_tree_model_get (model, iter,
1309 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1311 expanded = GPOINTER_TO_INT (user_data);
1312 empathy_contact_group_set_expanded (name, expanded);
1318 individual_view_start_search_cb (EmpathyIndividualView *view,
1321 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1323 if (priv->search_widget == NULL)
1326 empathy_individual_view_start_search (view);
1332 individual_view_search_text_notify_cb (TpawLiveSearch *search,
1334 EmpathyIndividualView *view)
1336 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1338 GtkTreeViewColumn *focus_column;
1339 GtkTreeModel *model;
1341 gboolean set_cursor = FALSE;
1343 gtk_tree_model_filter_refilter (priv->filter);
1345 /* Set cursor on the first contact. If it is already set on a group,
1346 * set it on its first child contact. Note that first child of a group
1347 * is its separator, that's why we actually set to the 2nd
1350 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1351 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1355 path = gtk_tree_path_new_from_string ("0:1");
1358 else if (gtk_tree_path_get_depth (path) < 2)
1362 gtk_tree_model_get_iter (model, &iter, path);
1363 gtk_tree_model_get (model, &iter,
1364 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1369 gtk_tree_path_down (path);
1370 gtk_tree_path_next (path);
1377 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1379 if (gtk_tree_model_get_iter (model, &iter, path))
1381 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1386 gtk_tree_path_free (path);
1390 individual_view_search_activate_cb (GtkWidget *search,
1391 EmpathyIndividualView *view)
1394 GtkTreeViewColumn *focus_column;
1396 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1399 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1400 gtk_tree_path_free (path);
1402 gtk_widget_hide (search);
1407 individual_view_search_key_navigation_cb (GtkWidget *search,
1409 EmpathyIndividualView *view)
1411 GdkEvent *new_event;
1412 gboolean ret = FALSE;
1414 new_event = gdk_event_copy (event);
1415 gtk_widget_grab_focus (GTK_WIDGET (view));
1416 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1417 gtk_widget_grab_focus (search);
1419 gdk_event_free (new_event);
1425 individual_view_search_hide_cb (TpawLiveSearch *search,
1426 EmpathyIndividualView *view)
1428 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1429 GtkTreeModel *model;
1430 GtkTreePath *cursor_path;
1432 gboolean valid = FALSE;
1434 /* block expand or collapse handlers, they would write the
1435 * expand or collapsed setting to file otherwise */
1436 g_signal_handlers_block_by_func (view,
1437 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1438 g_signal_handlers_block_by_func (view,
1439 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1441 /* restore which groups are expanded and which are not */
1442 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1443 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1444 valid; valid = gtk_tree_model_iter_next (model, &iter))
1450 gtk_tree_model_get (model, &iter,
1451 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1452 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1461 path = gtk_tree_model_get_path (model, &iter);
1462 if ((priv->view_features &
1463 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1464 empathy_contact_group_get_expanded (name))
1466 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1470 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1473 gtk_tree_path_free (path);
1477 /* unblock expand or collapse handlers */
1478 g_signal_handlers_unblock_by_func (view,
1479 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1480 g_signal_handlers_unblock_by_func (view,
1481 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1483 /* keep the selected contact visible */
1484 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1486 if (cursor_path != NULL)
1487 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1490 gtk_tree_path_free (cursor_path);
1494 individual_view_search_show_cb (TpawLiveSearch *search,
1495 EmpathyIndividualView *view)
1497 /* block expand or collapse handlers during expand all, they would
1498 * write the expand or collapsed setting to file otherwise */
1499 g_signal_handlers_block_by_func (view,
1500 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1502 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1504 g_signal_handlers_unblock_by_func (view,
1505 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1509 expand_idle_foreach_cb (GtkTreeModel *model,
1512 EmpathyIndividualView *self)
1514 EmpathyIndividualViewPriv *priv;
1516 gpointer should_expand;
1519 /* We only want groups */
1520 if (gtk_tree_path_get_depth (path) > 1)
1523 gtk_tree_model_get (model, iter,
1524 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1525 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1534 priv = GET_PRIV (self);
1536 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1539 if (GPOINTER_TO_INT (should_expand))
1540 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1542 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1544 g_hash_table_remove (priv->expand_groups, name);
1553 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1555 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1557 g_signal_handlers_block_by_func (self,
1558 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1559 g_signal_handlers_block_by_func (self,
1560 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1562 /* The store/filter could've been removed while we were in the idle queue */
1563 if (priv->filter != NULL)
1565 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1566 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1569 g_signal_handlers_unblock_by_func (self,
1570 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1571 g_signal_handlers_unblock_by_func (self,
1572 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1574 /* Empty the table of groups to expand/contract, since it may contain groups
1575 * which no longer exist in the tree view. This can happen after going
1576 * offline, for example. */
1577 g_hash_table_remove_all (priv->expand_groups);
1578 priv->expand_groups_idle_handler = 0;
1579 g_object_unref (self);
1585 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1588 EmpathyIndividualView *view)
1590 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1591 gboolean should_expand, is_group = FALSE;
1593 gpointer will_expand;
1595 gtk_tree_model_get (model, iter,
1596 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1597 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1600 if (!is_group || TPAW_STR_EMPTY (name))
1606 should_expand = (priv->view_features &
1607 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1608 (priv->search_widget != NULL &&
1609 gtk_widget_get_visible (priv->search_widget)) ||
1610 empathy_contact_group_get_expanded (name);
1612 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1613 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1614 * a hash table, and expand or contract them as appropriate all at once in
1615 * an idle handler which iterates over all the group rows. */
1616 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1618 GPOINTER_TO_INT (will_expand) != should_expand)
1620 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1621 GINT_TO_POINTER (should_expand));
1623 if (priv->expand_groups_idle_handler == 0)
1625 priv->expand_groups_idle_handler =
1626 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1627 g_object_ref (view));
1635 individual_view_is_visible_individual (EmpathyIndividualView *self,
1636 FolksIndividual *individual,
1638 gboolean is_searching,
1640 gboolean is_fake_group,
1643 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1644 TpawLiveSearch *live = TPAW_LIVE_SEARCH (priv->search_widget);
1647 gboolean is_favorite;
1649 /* Always display individuals having pending events */
1650 if (event_count > 0)
1653 /* We're only giving the visibility wrt filtering here, not things like
1655 if (!priv->show_untrusted &&
1656 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1661 if (!priv->show_uninteresting)
1663 gboolean contains_interesting_persona = FALSE;
1665 /* Hide all individuals which consist entirely of uninteresting
1667 personas = folks_individual_get_personas (individual);
1668 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1669 while (!contains_interesting_persona && gee_iterator_next (iter))
1671 FolksPersona *persona = gee_iterator_get (iter);
1673 if (empathy_folks_persona_is_interesting (persona))
1674 contains_interesting_persona = TRUE;
1676 g_clear_object (&persona);
1678 g_clear_object (&iter);
1680 if (!contains_interesting_persona)
1684 is_favorite = folks_favourite_details_get_is_favourite (
1685 FOLKS_FAVOURITE_DETAILS (individual));
1686 if (!is_searching) {
1687 if (is_favorite && is_fake_group &&
1688 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1689 /* Always display favorite contacts in the favorite group */
1692 return (priv->show_offline || is_online);
1695 return empathy_individual_match_string (individual,
1696 tpaw_live_search_get_text (live),
1697 tpaw_live_search_get_words (live));
1701 get_group (GtkTreeModel *model,
1705 GtkTreeIter parent_iter;
1710 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1713 gtk_tree_model_get (model, &parent_iter,
1714 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1715 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1723 individual_view_filter_visible_func (GtkTreeModel *model,
1727 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1728 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1729 FolksIndividual *individual = NULL;
1730 gboolean is_group, is_separator, valid;
1731 GtkTreeIter child_iter;
1732 gboolean visible, is_online;
1733 gboolean is_searching = TRUE;
1736 if (priv->custom_filter != NULL)
1737 return priv->custom_filter (model, iter, priv->custom_filter_data);
1739 if (priv->search_widget == NULL ||
1740 !gtk_widget_get_visible (priv->search_widget))
1741 is_searching = FALSE;
1743 gtk_tree_model_get (model, iter,
1744 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1745 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1746 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1747 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1748 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1751 if (individual != NULL)
1754 gboolean is_fake_group;
1756 group = get_group (model, iter, &is_fake_group);
1758 visible = individual_view_is_visible_individual (self, individual,
1759 is_online, is_searching, group, is_fake_group, event_count);
1761 g_object_unref (individual);
1770 /* Not a contact, not a separator, must be a group */
1771 g_return_val_if_fail (is_group, FALSE);
1773 /* only show groups which are not empty */
1774 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1775 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1778 gboolean is_fake_group;
1780 gtk_tree_model_get (model, &child_iter,
1781 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1782 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1783 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1786 if (individual == NULL)
1789 group = get_group (model, &child_iter, &is_fake_group);
1791 visible = individual_view_is_visible_individual (self, individual,
1792 is_online, is_searching, group, is_fake_group, event_count);
1794 g_object_unref (individual);
1797 /* show group if it has at least one visible contact in it */
1805 static gchar * empathy_individual_view_dup_selected_group (
1806 EmpathyIndividualView *view,
1807 gboolean *is_fake_group);
1810 text_edited_cb (GtkCellRendererText *cellrenderertext,
1813 EmpathyIndividualView *self)
1815 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1816 gchar *old_name, *new_name;
1818 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1820 new_name = g_strdup (name);
1821 g_strstrip (new_name);
1823 if (tp_str_empty (new_name))
1826 old_name = empathy_individual_view_dup_selected_group (self, NULL);
1827 g_return_if_fail (old_name != NULL);
1829 if (tp_strdiff (old_name, new_name))
1831 EmpathyConnectionAggregator *aggregator;
1833 DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1835 aggregator = empathy_connection_aggregator_dup_singleton ();
1837 empathy_connection_aggregator_rename_group (aggregator, old_name,
1839 g_object_unref (aggregator);
1848 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1849 EmpathyIndividualView *self)
1851 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1853 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1857 individual_view_constructed (GObject *object)
1859 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1860 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1861 GtkCellRenderer *cell;
1862 GtkTreeViewColumn *col;
1867 "headers-visible", FALSE,
1868 "show-expanders", FALSE,
1871 col = gtk_tree_view_column_new ();
1874 cell = gtk_cell_renderer_pixbuf_new ();
1875 gtk_tree_view_column_pack_start (col, cell, FALSE);
1876 gtk_tree_view_column_set_cell_data_func (col, cell,
1877 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1887 cell = gtk_cell_renderer_pixbuf_new ();
1888 gtk_tree_view_column_pack_start (col, cell, FALSE);
1889 gtk_tree_view_column_set_cell_data_func (col, cell,
1890 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1902 priv->text_renderer = empathy_cell_renderer_text_new ();
1903 gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1904 gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1905 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1907 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1908 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1909 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1910 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1911 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1912 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1913 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1914 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1915 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1916 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1917 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1918 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1919 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1920 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1922 g_signal_connect (priv->text_renderer, "editing-canceled",
1923 G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1924 g_signal_connect (priv->text_renderer, "edited",
1925 G_CALLBACK (text_edited_cb), view);
1927 /* Audio Call Icon */
1928 cell = empathy_cell_renderer_activatable_new ();
1929 gtk_tree_view_column_pack_start (col, cell, FALSE);
1930 gtk_tree_view_column_set_cell_data_func (col, cell,
1931 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1934 g_object_set (cell, "visible", FALSE, NULL);
1936 g_signal_connect (cell, "path-activated",
1937 G_CALLBACK (individual_view_call_activated_cb), view);
1940 cell = gtk_cell_renderer_pixbuf_new ();
1941 gtk_tree_view_column_pack_start (col, cell, FALSE);
1942 gtk_tree_view_column_set_cell_data_func (col, cell,
1943 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1955 cell = empathy_cell_renderer_expander_new ();
1956 gtk_tree_view_column_pack_end (col, cell, FALSE);
1957 gtk_tree_view_column_set_cell_data_func (col, cell,
1958 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1961 /* Actually add the column now we have added all cell renderers */
1962 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1965 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1967 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1972 individual_view_set_view_features (EmpathyIndividualView *view,
1973 EmpathyIndividualFeatureFlags features)
1975 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1976 gboolean has_tooltip;
1978 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1980 priv->view_features = features;
1982 /* Setting reorderable is a hack that gets us row previews as drag icons
1983 for free. We override all the drag handlers. It's tricky to get the
1984 position of the drag icon right in drag_begin. GtkTreeView has special
1985 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1988 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1989 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1991 /* Update DnD source/dest */
1992 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1994 gtk_drag_source_set (GTK_WIDGET (view),
1997 G_N_ELEMENTS (drag_types_source),
1998 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2002 gtk_drag_source_unset (GTK_WIDGET (view));
2006 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2008 gtk_drag_dest_set (GTK_WIDGET (view),
2009 GTK_DEST_DEFAULT_ALL,
2011 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2015 /* FIXME: URI could still be droped depending on FT feature */
2016 gtk_drag_dest_unset (GTK_WIDGET (view));
2019 /* Update has-tooltip */
2021 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2022 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2026 individual_view_dispose (GObject *object)
2028 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2029 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2031 tp_clear_object (&priv->store);
2032 tp_clear_object (&priv->filter);
2033 tp_clear_object (&priv->tooltip_widget);
2035 empathy_individual_view_set_live_search (view, NULL);
2037 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2041 individual_view_finalize (GObject *object)
2043 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2045 if (priv->expand_groups_idle_handler != 0)
2046 g_source_remove (priv->expand_groups_idle_handler);
2047 g_hash_table_unref (priv->expand_groups);
2049 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2053 individual_view_get_property (GObject *object,
2058 EmpathyIndividualViewPriv *priv;
2060 priv = GET_PRIV (object);
2065 g_value_set_object (value, priv->store);
2067 case PROP_VIEW_FEATURES:
2068 g_value_set_flags (value, priv->view_features);
2070 case PROP_INDIVIDUAL_FEATURES:
2071 g_value_set_flags (value, priv->individual_features);
2073 case PROP_SHOW_OFFLINE:
2074 g_value_set_boolean (value, priv->show_offline);
2076 case PROP_SHOW_UNTRUSTED:
2077 g_value_set_boolean (value, priv->show_untrusted);
2079 case PROP_SHOW_UNINTERESTING:
2080 g_value_set_boolean (value, priv->show_uninteresting);
2083 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2089 individual_view_set_property (GObject *object,
2091 const GValue *value,
2094 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2095 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2100 empathy_individual_view_set_store (view, g_value_get_object (value));
2102 case PROP_VIEW_FEATURES:
2103 individual_view_set_view_features (view, g_value_get_flags (value));
2105 case PROP_INDIVIDUAL_FEATURES:
2106 priv->individual_features = g_value_get_flags (value);
2108 case PROP_SHOW_OFFLINE:
2109 empathy_individual_view_set_show_offline (view,
2110 g_value_get_boolean (value));
2112 case PROP_SHOW_UNTRUSTED:
2113 empathy_individual_view_set_show_untrusted (view,
2114 g_value_get_boolean (value));
2116 case PROP_SHOW_UNINTERESTING:
2117 empathy_individual_view_set_show_uninteresting (view,
2118 g_value_get_boolean (value));
2120 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2126 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2128 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2129 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2130 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2132 object_class->constructed = individual_view_constructed;
2133 object_class->dispose = individual_view_dispose;
2134 object_class->finalize = individual_view_finalize;
2135 object_class->get_property = individual_view_get_property;
2136 object_class->set_property = individual_view_set_property;
2138 widget_class->drag_data_received = individual_view_drag_data_received;
2139 widget_class->drag_drop = individual_view_drag_drop;
2140 widget_class->drag_begin = individual_view_drag_begin;
2141 widget_class->drag_data_get = individual_view_drag_data_get;
2142 widget_class->drag_end = individual_view_drag_end;
2143 widget_class->drag_motion = individual_view_drag_motion;
2145 /* We use the class method to let user of this widget to connect to
2146 * the signal and stop emission of the signal so the default handler
2147 * won't be called. */
2148 tree_view_class->row_activated = individual_view_row_activated;
2150 klass->drag_individual_received = real_drag_individual_received_cb;
2152 signals[DRAG_INDIVIDUAL_RECEIVED] =
2153 g_signal_new ("drag-individual-received",
2154 G_OBJECT_CLASS_TYPE (klass),
2156 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2158 g_cclosure_marshal_generic,
2159 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2160 G_TYPE_STRING, G_TYPE_STRING);
2162 signals[DRAG_PERSONA_RECEIVED] =
2163 g_signal_new ("drag-persona-received",
2164 G_OBJECT_CLASS_TYPE (klass),
2166 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2168 g_cclosure_marshal_generic,
2169 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2171 g_object_class_install_property (object_class,
2173 g_param_spec_object ("store",
2174 "The store of the view",
2175 "The store of the view",
2176 EMPATHY_TYPE_INDIVIDUAL_STORE,
2177 G_PARAM_READWRITE));
2178 g_object_class_install_property (object_class,
2180 g_param_spec_flags ("view-features",
2181 "Features of the view",
2182 "Flags for all enabled features",
2183 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2184 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2185 g_object_class_install_property (object_class,
2186 PROP_INDIVIDUAL_FEATURES,
2187 g_param_spec_flags ("individual-features",
2188 "Features of the individual menu",
2189 "Flags for all enabled features for the menu",
2190 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2191 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2192 g_object_class_install_property (object_class,
2194 g_param_spec_boolean ("show-offline",
2196 "Whether contact list should display "
2197 "offline contacts", FALSE, G_PARAM_READWRITE));
2198 g_object_class_install_property (object_class,
2199 PROP_SHOW_UNTRUSTED,
2200 g_param_spec_boolean ("show-untrusted",
2201 "Show Untrusted Individuals",
2202 "Whether the view should display untrusted individuals; "
2203 "those who could not be who they say they are.",
2204 TRUE, G_PARAM_READWRITE));
2205 g_object_class_install_property (object_class,
2206 PROP_SHOW_UNINTERESTING,
2207 g_param_spec_boolean ("show-uninteresting",
2208 "Show Uninteresting Individuals",
2209 "Whether the view should not filter out individuals using "
2210 "empathy_folks_persona_is_interesting.",
2211 FALSE, G_PARAM_READWRITE));
2213 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2217 empathy_individual_view_init (EmpathyIndividualView *view)
2219 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2220 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2224 priv->show_untrusted = TRUE;
2225 priv->show_uninteresting = FALSE;
2227 /* Get saved group states. */
2228 empathy_contact_groups_get_all ();
2230 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2231 (GDestroyNotify) g_free, NULL);
2233 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2234 empathy_individual_store_row_separator_func, NULL, NULL);
2236 /* Connect to tree view signals rather than override. */
2237 g_signal_connect (view, "button-press-event",
2238 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2239 g_signal_connect (view, "key-press-event",
2240 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2241 g_signal_connect (view, "row-expanded",
2242 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2243 GINT_TO_POINTER (TRUE));
2244 g_signal_connect (view, "row-collapsed",
2245 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2246 GINT_TO_POINTER (FALSE));
2247 g_signal_connect (view, "query-tooltip",
2248 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2251 EmpathyIndividualView *
2252 empathy_individual_view_new (EmpathyIndividualStore *store,
2253 EmpathyIndividualViewFeatureFlags view_features,
2254 EmpathyIndividualFeatureFlags individual_features)
2256 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2258 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2260 "individual-features", individual_features,
2261 "view-features", view_features, NULL);
2265 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2267 GtkTreeSelection *selection;
2269 GtkTreeModel *model;
2270 FolksIndividual *individual;
2272 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2274 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2275 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2278 gtk_tree_model_get (model, &iter,
2279 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2285 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2286 gboolean *is_fake_group)
2288 GtkTreeSelection *selection;
2290 GtkTreeModel *model;
2295 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2297 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2298 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2301 gtk_tree_model_get (model, &iter,
2302 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2303 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2304 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2312 if (is_fake_group != NULL)
2313 *is_fake_group = fake;
2320 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2321 REMOVE_DIALOG_RESPONSE_DELETE,
2325 individual_view_remove_dialog_show (GtkWindow *parent,
2326 const gchar *message,
2327 const gchar *secondary_text)
2332 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2333 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2335 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2336 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2337 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2338 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2339 "%s", secondary_text);
2341 gtk_widget_show (dialog);
2343 res = gtk_dialog_run (GTK_DIALOG (dialog));
2344 gtk_widget_destroy (dialog);
2350 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2351 EmpathyIndividualView *view)
2355 group = empathy_individual_view_dup_selected_group (view, NULL);
2362 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2364 parent = tpaw_get_toplevel_window (GTK_WIDGET (view));
2365 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2366 text) == REMOVE_DIALOG_RESPONSE_DELETE)
2368 EmpathyIndividualManager *manager =
2369 empathy_individual_manager_dup_singleton ();
2370 empathy_individual_manager_remove_group (manager, group);
2371 g_object_unref (G_OBJECT (manager));
2381 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2382 EmpathyIndividualView *self)
2384 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2387 GtkTreeSelection *selection;
2388 GtkTreeModel *model;
2390 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2391 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2393 path = gtk_tree_model_get_path (model, &iter);
2395 g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2397 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2398 gtk_widget_grab_focus (GTK_WIDGET (self));
2399 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2400 gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2402 gtk_tree_path_free (path);
2406 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2408 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2413 gboolean is_fake_group;
2415 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2417 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2418 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2421 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2422 if (!group || is_fake_group)
2424 /* We can't alter fake groups */
2429 menu = gtk_menu_new ();
2431 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2433 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2434 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2435 gtk_widget_show (item);
2436 g_signal_connect (item, "activate",
2437 G_CALLBACK (individual_view_group_rename_activate_cb), view);
2440 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2442 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2443 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2444 GTK_ICON_SIZE_MENU);
2445 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2446 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2447 gtk_widget_show (item);
2448 g_signal_connect (item, "activate",
2449 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2458 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2460 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2461 FolksIndividual *individual;
2462 GtkWidget *menu = NULL;
2464 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2466 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2467 /* No need to create a context menu */
2470 individual = empathy_individual_view_dup_selected (view);
2471 if (individual == NULL)
2474 if (!empathy_folks_individual_contains_contact (individual))
2477 menu = empathy_individual_menu_new (individual, NULL,
2478 priv->individual_features, priv->store);
2481 g_object_unref (individual);
2487 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2488 TpawLiveSearch *search)
2490 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2492 /* remove old handlers if old search was not null */
2493 if (priv->search_widget != NULL)
2495 g_signal_handlers_disconnect_by_func (view,
2496 individual_view_start_search_cb, NULL);
2498 g_signal_handlers_disconnect_by_func (priv->search_widget,
2499 individual_view_search_text_notify_cb, view);
2500 g_signal_handlers_disconnect_by_func (priv->search_widget,
2501 individual_view_search_activate_cb, view);
2502 g_signal_handlers_disconnect_by_func (priv->search_widget,
2503 individual_view_search_key_navigation_cb, view);
2504 g_signal_handlers_disconnect_by_func (priv->search_widget,
2505 individual_view_search_hide_cb, view);
2506 g_signal_handlers_disconnect_by_func (priv->search_widget,
2507 individual_view_search_show_cb, view);
2508 g_object_unref (priv->search_widget);
2509 priv->search_widget = NULL;
2512 /* connect handlers if new search is not null */
2515 priv->search_widget = g_object_ref (search);
2517 g_signal_connect (view, "start-interactive-search",
2518 G_CALLBACK (individual_view_start_search_cb), NULL);
2520 g_signal_connect (priv->search_widget, "notify::text",
2521 G_CALLBACK (individual_view_search_text_notify_cb), view);
2522 g_signal_connect (priv->search_widget, "activate",
2523 G_CALLBACK (individual_view_search_activate_cb), view);
2524 g_signal_connect (priv->search_widget, "key-navigation",
2525 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2526 g_signal_connect (priv->search_widget, "hide",
2527 G_CALLBACK (individual_view_search_hide_cb), view);
2528 g_signal_connect (priv->search_widget, "show",
2529 G_CALLBACK (individual_view_search_show_cb), view);
2534 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2536 EmpathyIndividualViewPriv *priv;
2538 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2540 priv = GET_PRIV (self);
2542 return (priv->search_widget != NULL &&
2543 gtk_widget_get_visible (priv->search_widget));
2547 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2549 EmpathyIndividualViewPriv *priv;
2551 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2553 priv = GET_PRIV (self);
2555 return priv->show_offline;
2559 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2560 gboolean show_offline)
2562 EmpathyIndividualViewPriv *priv;
2564 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2566 priv = GET_PRIV (self);
2568 priv->show_offline = show_offline;
2570 g_object_notify (G_OBJECT (self), "show-offline");
2571 gtk_tree_model_filter_refilter (priv->filter);
2575 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2577 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2579 return GET_PRIV (self)->show_untrusted;
2583 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2584 gboolean show_untrusted)
2586 EmpathyIndividualViewPriv *priv;
2588 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2590 priv = GET_PRIV (self);
2592 priv->show_untrusted = show_untrusted;
2594 g_object_notify (G_OBJECT (self), "show-untrusted");
2595 gtk_tree_model_filter_refilter (priv->filter);
2598 EmpathyIndividualStore *
2599 empathy_individual_view_get_store (EmpathyIndividualView *self)
2601 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2603 return GET_PRIV (self)->store;
2607 empathy_individual_view_set_store (EmpathyIndividualView *self,
2608 EmpathyIndividualStore *store)
2610 EmpathyIndividualViewPriv *priv;
2612 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2613 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2615 priv = GET_PRIV (self);
2617 /* Destroy the old filter and remove the old store */
2618 if (priv->store != NULL)
2620 g_signal_handlers_disconnect_by_func (priv->filter,
2621 individual_view_row_has_child_toggled_cb, self);
2623 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2626 tp_clear_object (&priv->filter);
2627 tp_clear_object (&priv->store);
2629 /* Set the new store */
2630 priv->store = store;
2634 g_object_ref (store);
2636 /* Create a new filter */
2637 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2638 GTK_TREE_MODEL (priv->store), NULL));
2639 gtk_tree_model_filter_set_visible_func (priv->filter,
2640 individual_view_filter_visible_func, self, NULL);
2642 g_signal_connect (priv->filter, "row-has-child-toggled",
2643 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2644 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2645 GTK_TREE_MODEL (priv->filter));
2650 empathy_individual_view_start_search (EmpathyIndividualView *self)
2652 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2654 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2655 g_return_if_fail (priv->search_widget != NULL);
2657 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2658 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2660 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2664 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2665 GtkTreeModelFilterVisibleFunc filter,
2668 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2670 priv->custom_filter = filter;
2671 priv->custom_filter_data = data;
2675 empathy_individual_view_refilter (EmpathyIndividualView *self)
2677 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2679 gtk_tree_model_filter_refilter (priv->filter);
2683 empathy_individual_view_select_first (EmpathyIndividualView *self)
2685 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2688 gtk_tree_model_filter_refilter (priv->filter);
2690 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2692 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2693 GTK_TREE_VIEW (self));
2695 gtk_tree_selection_select_iter (selection, &iter);
2700 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2701 gboolean show_uninteresting)
2703 EmpathyIndividualViewPriv *priv;
2705 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2707 priv = GET_PRIV (self);
2709 priv->show_uninteresting = show_uninteresting;
2711 g_object_notify (G_OBJECT (self), "show-uninteresting");
2712 gtk_tree_model_filter_refilter (priv->filter);