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 (NULL,
1076 gtk_menu_shell_append (shell, item);
1077 gtk_widget_show (item);
1080 item = empathy_individual_video_call_menu_item_new_individual (NULL,
1082 gtk_menu_shell_append (shell, item);
1083 gtk_widget_show (item);
1085 gtk_widget_show (menu);
1086 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1087 event->button, event->time);
1089 g_object_unref (individual);
1093 individual_view_cell_set_background (EmpathyIndividualView *view,
1094 GtkCellRenderer *cell,
1098 if (!is_group && is_active)
1100 GtkStyleContext *style;
1103 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1105 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1108 /* Here we take the current theme colour and add it to
1109 * the colour for white and average the two. This
1110 * gives a colour which is inline with the theme but
1113 empathy_make_color_whiter (&color);
1115 g_object_set (cell, "cell-background-rgba", &color, NULL);
1118 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1122 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1123 GtkCellRenderer *cell,
1124 GtkTreeModel *model,
1126 EmpathyIndividualView *view)
1132 gtk_tree_model_get (model, iter,
1133 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1134 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1135 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1138 "visible", !is_group,
1142 tp_clear_object (&pixbuf);
1144 individual_view_cell_set_background (view, cell, is_group, is_active);
1148 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1149 GtkCellRenderer *cell,
1150 GtkTreeModel *model,
1152 EmpathyIndividualView *view)
1154 GdkPixbuf *pixbuf = NULL;
1158 gtk_tree_model_get (model, iter,
1159 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1160 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1165 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1167 pixbuf = tpaw_pixbuf_from_icon_name ("emblem-favorite",
1168 GTK_ICON_SIZE_MENU);
1170 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1172 pixbuf = tpaw_pixbuf_from_icon_name ("im-local-xmpp",
1173 GTK_ICON_SIZE_MENU);
1178 "visible", pixbuf != NULL,
1182 tp_clear_object (&pixbuf);
1188 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1189 GtkCellRenderer *cell,
1190 GtkTreeModel *model,
1192 EmpathyIndividualView *view)
1196 gboolean can_audio, can_video;
1198 gtk_tree_model_get (model, iter,
1199 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1200 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1201 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1202 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1205 "visible", !is_group && (can_audio || can_video),
1206 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1209 individual_view_cell_set_background (view, cell, is_group, is_active);
1213 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1214 GtkCellRenderer *cell,
1215 GtkTreeModel *model,
1217 EmpathyIndividualView *view)
1220 gboolean show_avatar;
1224 gtk_tree_model_get (model, iter,
1225 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1226 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1227 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1228 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1231 "visible", !is_group && show_avatar,
1235 tp_clear_object (&pixbuf);
1237 individual_view_cell_set_background (view, cell, is_group, is_active);
1241 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1242 GtkCellRenderer *cell,
1243 GtkTreeModel *model,
1245 EmpathyIndividualView *view)
1250 gtk_tree_model_get (model, iter,
1251 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1252 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1254 individual_view_cell_set_background (view, cell, is_group, is_active);
1258 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1259 GtkCellRenderer *cell,
1260 GtkTreeModel *model,
1262 EmpathyIndividualView *view)
1267 gtk_tree_model_get (model, iter,
1268 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1269 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1271 if (gtk_tree_model_iter_has_child (model, iter))
1274 gboolean row_expanded;
1276 path = gtk_tree_model_get_path (model, iter);
1278 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1279 (gtk_tree_view_column_get_tree_view (column)), path);
1280 gtk_tree_path_free (path);
1285 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1289 g_object_set (cell, "visible", FALSE, NULL);
1291 individual_view_cell_set_background (view, cell, is_group, is_active);
1295 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1300 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1301 GtkTreeModel *model;
1305 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1308 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1310 gtk_tree_model_get (model, iter,
1311 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1313 expanded = GPOINTER_TO_INT (user_data);
1314 empathy_contact_group_set_expanded (name, expanded);
1320 individual_view_start_search_cb (EmpathyIndividualView *view,
1323 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1325 if (priv->search_widget == NULL)
1328 empathy_individual_view_start_search (view);
1334 individual_view_search_text_notify_cb (TpawLiveSearch *search,
1336 EmpathyIndividualView *view)
1338 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1340 GtkTreeViewColumn *focus_column;
1341 GtkTreeModel *model;
1343 gboolean set_cursor = FALSE;
1345 gtk_tree_model_filter_refilter (priv->filter);
1347 /* Set cursor on the first contact. If it is already set on a group,
1348 * set it on its first child contact. Note that first child of a group
1349 * is its separator, that's why we actually set to the 2nd
1352 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1353 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1357 path = gtk_tree_path_new_from_string ("0:1");
1360 else if (gtk_tree_path_get_depth (path) < 2)
1364 gtk_tree_model_get_iter (model, &iter, path);
1365 gtk_tree_model_get (model, &iter,
1366 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1371 gtk_tree_path_down (path);
1372 gtk_tree_path_next (path);
1379 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1381 if (gtk_tree_model_get_iter (model, &iter, path))
1383 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1388 gtk_tree_path_free (path);
1392 individual_view_search_activate_cb (GtkWidget *search,
1393 EmpathyIndividualView *view)
1396 GtkTreeViewColumn *focus_column;
1398 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1401 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1402 gtk_tree_path_free (path);
1404 gtk_widget_hide (search);
1409 individual_view_search_key_navigation_cb (GtkWidget *search,
1411 EmpathyIndividualView *view)
1413 GdkEvent *new_event;
1414 gboolean ret = FALSE;
1416 new_event = gdk_event_copy (event);
1417 gtk_widget_grab_focus (GTK_WIDGET (view));
1418 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1419 gtk_widget_grab_focus (search);
1421 gdk_event_free (new_event);
1427 individual_view_search_hide_cb (TpawLiveSearch *search,
1428 EmpathyIndividualView *view)
1430 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1431 GtkTreeModel *model;
1432 GtkTreePath *cursor_path;
1434 gboolean valid = FALSE;
1436 /* block expand or collapse handlers, they would write the
1437 * expand or collapsed setting to file otherwise */
1438 g_signal_handlers_block_by_func (view,
1439 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1440 g_signal_handlers_block_by_func (view,
1441 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1443 /* restore which groups are expanded and which are not */
1444 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1445 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1446 valid; valid = gtk_tree_model_iter_next (model, &iter))
1452 gtk_tree_model_get (model, &iter,
1453 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1454 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1463 path = gtk_tree_model_get_path (model, &iter);
1464 if ((priv->view_features &
1465 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1466 empathy_contact_group_get_expanded (name))
1468 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1472 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1475 gtk_tree_path_free (path);
1479 /* unblock expand or collapse handlers */
1480 g_signal_handlers_unblock_by_func (view,
1481 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1482 g_signal_handlers_unblock_by_func (view,
1483 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1485 /* keep the selected contact visible */
1486 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1488 if (cursor_path != NULL)
1489 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1492 gtk_tree_path_free (cursor_path);
1496 individual_view_search_show_cb (TpawLiveSearch *search,
1497 EmpathyIndividualView *view)
1499 /* block expand or collapse handlers during expand all, they would
1500 * write the expand or collapsed setting to file otherwise */
1501 g_signal_handlers_block_by_func (view,
1502 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1504 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1506 g_signal_handlers_unblock_by_func (view,
1507 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1511 expand_idle_foreach_cb (GtkTreeModel *model,
1514 EmpathyIndividualView *self)
1516 EmpathyIndividualViewPriv *priv;
1518 gpointer should_expand;
1521 /* We only want groups */
1522 if (gtk_tree_path_get_depth (path) > 1)
1525 gtk_tree_model_get (model, iter,
1526 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1527 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1536 priv = GET_PRIV (self);
1538 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1541 if (GPOINTER_TO_INT (should_expand))
1542 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1544 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1546 g_hash_table_remove (priv->expand_groups, name);
1555 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1557 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1559 g_signal_handlers_block_by_func (self,
1560 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1561 g_signal_handlers_block_by_func (self,
1562 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1564 /* The store/filter could've been removed while we were in the idle queue */
1565 if (priv->filter != NULL)
1567 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1568 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1571 g_signal_handlers_unblock_by_func (self,
1572 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1573 g_signal_handlers_unblock_by_func (self,
1574 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1576 /* Empty the table of groups to expand/contract, since it may contain groups
1577 * which no longer exist in the tree view. This can happen after going
1578 * offline, for example. */
1579 g_hash_table_remove_all (priv->expand_groups);
1580 priv->expand_groups_idle_handler = 0;
1581 g_object_unref (self);
1587 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1590 EmpathyIndividualView *view)
1592 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1593 gboolean should_expand, is_group = FALSE;
1595 gpointer will_expand;
1597 gtk_tree_model_get (model, iter,
1598 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1599 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1602 if (!is_group || TPAW_STR_EMPTY (name))
1608 should_expand = (priv->view_features &
1609 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1610 (priv->search_widget != NULL &&
1611 gtk_widget_get_visible (priv->search_widget)) ||
1612 empathy_contact_group_get_expanded (name);
1614 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1615 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1616 * a hash table, and expand or contract them as appropriate all at once in
1617 * an idle handler which iterates over all the group rows. */
1618 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1620 GPOINTER_TO_INT (will_expand) != should_expand)
1622 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1623 GINT_TO_POINTER (should_expand));
1625 if (priv->expand_groups_idle_handler == 0)
1627 priv->expand_groups_idle_handler =
1628 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1629 g_object_ref (view));
1637 individual_view_is_visible_individual (EmpathyIndividualView *self,
1638 FolksIndividual *individual,
1640 gboolean is_searching,
1642 gboolean is_fake_group,
1645 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1646 TpawLiveSearch *live = TPAW_LIVE_SEARCH (priv->search_widget);
1649 gboolean is_favorite;
1651 /* Always display individuals having pending events */
1652 if (event_count > 0)
1655 /* We're only giving the visibility wrt filtering here, not things like
1657 if (!priv->show_untrusted &&
1658 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1663 if (!priv->show_uninteresting)
1665 gboolean contains_interesting_persona = FALSE;
1667 /* Hide all individuals which consist entirely of uninteresting
1669 personas = folks_individual_get_personas (individual);
1670 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1671 while (!contains_interesting_persona && gee_iterator_next (iter))
1673 FolksPersona *persona = gee_iterator_get (iter);
1675 if (empathy_folks_persona_is_interesting (persona))
1676 contains_interesting_persona = TRUE;
1678 g_clear_object (&persona);
1680 g_clear_object (&iter);
1682 if (!contains_interesting_persona)
1686 is_favorite = folks_favourite_details_get_is_favourite (
1687 FOLKS_FAVOURITE_DETAILS (individual));
1688 if (!is_searching) {
1689 if (is_favorite && is_fake_group &&
1690 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1691 /* Always display favorite contacts in the favorite group */
1694 return (priv->show_offline || is_online);
1697 return empathy_individual_match_string (individual,
1698 tpaw_live_search_get_text (live),
1699 tpaw_live_search_get_words (live));
1703 get_group (GtkTreeModel *model,
1707 GtkTreeIter parent_iter;
1712 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1715 gtk_tree_model_get (model, &parent_iter,
1716 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1717 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1725 individual_view_filter_visible_func (GtkTreeModel *model,
1729 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1730 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1731 FolksIndividual *individual = NULL;
1732 gboolean is_group, is_separator, valid;
1733 GtkTreeIter child_iter;
1734 gboolean visible, is_online;
1735 gboolean is_searching = TRUE;
1738 if (priv->custom_filter != NULL)
1739 return priv->custom_filter (model, iter, priv->custom_filter_data);
1741 if (priv->search_widget == NULL ||
1742 !gtk_widget_get_visible (priv->search_widget))
1743 is_searching = FALSE;
1745 gtk_tree_model_get (model, iter,
1746 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1747 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1748 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1749 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1750 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1753 if (individual != NULL)
1756 gboolean is_fake_group;
1758 group = get_group (model, iter, &is_fake_group);
1760 visible = individual_view_is_visible_individual (self, individual,
1761 is_online, is_searching, group, is_fake_group, event_count);
1763 g_object_unref (individual);
1772 /* Not a contact, not a separator, must be a group */
1773 g_return_val_if_fail (is_group, FALSE);
1775 /* only show groups which are not empty */
1776 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1777 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1780 gboolean is_fake_group;
1782 gtk_tree_model_get (model, &child_iter,
1783 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1784 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1785 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1788 if (individual == NULL)
1791 group = get_group (model, &child_iter, &is_fake_group);
1793 visible = individual_view_is_visible_individual (self, individual,
1794 is_online, is_searching, group, is_fake_group, event_count);
1796 g_object_unref (individual);
1799 /* show group if it has at least one visible contact in it */
1807 static gchar * empathy_individual_view_dup_selected_group (
1808 EmpathyIndividualView *view,
1809 gboolean *is_fake_group);
1812 text_edited_cb (GtkCellRendererText *cellrenderertext,
1815 EmpathyIndividualView *self)
1817 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1818 gchar *old_name, *new_name;
1820 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1822 new_name = g_strdup (name);
1823 g_strstrip (new_name);
1825 if (tp_str_empty (new_name))
1828 old_name = empathy_individual_view_dup_selected_group (self, NULL);
1829 g_return_if_fail (old_name != NULL);
1831 if (tp_strdiff (old_name, new_name))
1833 EmpathyConnectionAggregator *aggregator;
1835 DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1837 aggregator = empathy_connection_aggregator_dup_singleton ();
1839 empathy_connection_aggregator_rename_group (aggregator, old_name,
1841 g_object_unref (aggregator);
1850 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1851 EmpathyIndividualView *self)
1853 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1855 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1859 individual_view_constructed (GObject *object)
1861 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1862 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1863 GtkCellRenderer *cell;
1864 GtkTreeViewColumn *col;
1869 "headers-visible", FALSE,
1870 "show-expanders", FALSE,
1873 col = gtk_tree_view_column_new ();
1876 cell = gtk_cell_renderer_pixbuf_new ();
1877 gtk_tree_view_column_pack_start (col, cell, FALSE);
1878 gtk_tree_view_column_set_cell_data_func (col, cell,
1879 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1889 cell = gtk_cell_renderer_pixbuf_new ();
1890 gtk_tree_view_column_pack_start (col, cell, FALSE);
1891 gtk_tree_view_column_set_cell_data_func (col, cell,
1892 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1904 priv->text_renderer = empathy_cell_renderer_text_new ();
1905 gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1906 gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1907 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1909 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1910 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1911 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1912 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1913 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1914 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1915 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1916 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1917 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1918 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1919 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1920 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1921 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1922 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1924 g_signal_connect (priv->text_renderer, "editing-canceled",
1925 G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1926 g_signal_connect (priv->text_renderer, "edited",
1927 G_CALLBACK (text_edited_cb), view);
1929 /* Audio Call Icon */
1930 cell = empathy_cell_renderer_activatable_new ();
1931 gtk_tree_view_column_pack_start (col, cell, FALSE);
1932 gtk_tree_view_column_set_cell_data_func (col, cell,
1933 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1936 g_object_set (cell, "visible", FALSE, NULL);
1938 g_signal_connect (cell, "path-activated",
1939 G_CALLBACK (individual_view_call_activated_cb), view);
1942 cell = gtk_cell_renderer_pixbuf_new ();
1943 gtk_tree_view_column_pack_start (col, cell, FALSE);
1944 gtk_tree_view_column_set_cell_data_func (col, cell,
1945 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1957 cell = empathy_cell_renderer_expander_new ();
1958 gtk_tree_view_column_pack_end (col, cell, FALSE);
1959 gtk_tree_view_column_set_cell_data_func (col, cell,
1960 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1963 /* Actually add the column now we have added all cell renderers */
1964 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1967 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1969 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1974 individual_view_set_view_features (EmpathyIndividualView *view,
1975 EmpathyIndividualViewFeatureFlags features)
1977 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1978 gboolean has_tooltip;
1980 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1982 priv->view_features = features;
1984 /* Setting reorderable is a hack that gets us row previews as drag icons
1985 for free. We override all the drag handlers. It's tricky to get the
1986 position of the drag icon right in drag_begin. GtkTreeView has special
1987 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1990 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1991 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1993 /* Update DnD source/dest */
1994 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1996 gtk_drag_source_set (GTK_WIDGET (view),
1999 G_N_ELEMENTS (drag_types_source),
2000 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2004 gtk_drag_source_unset (GTK_WIDGET (view));
2008 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2010 gtk_drag_dest_set (GTK_WIDGET (view),
2011 GTK_DEST_DEFAULT_ALL,
2013 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2017 /* FIXME: URI could still be droped depending on FT feature */
2018 gtk_drag_dest_unset (GTK_WIDGET (view));
2021 /* Update has-tooltip */
2023 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2024 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2028 individual_view_dispose (GObject *object)
2030 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2031 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2033 tp_clear_object (&priv->store);
2034 tp_clear_object (&priv->filter);
2035 tp_clear_object (&priv->tooltip_widget);
2037 empathy_individual_view_set_live_search (view, NULL);
2039 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2043 individual_view_finalize (GObject *object)
2045 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2047 if (priv->expand_groups_idle_handler != 0)
2048 g_source_remove (priv->expand_groups_idle_handler);
2049 g_hash_table_unref (priv->expand_groups);
2051 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2055 individual_view_get_property (GObject *object,
2060 EmpathyIndividualViewPriv *priv;
2062 priv = GET_PRIV (object);
2067 g_value_set_object (value, priv->store);
2069 case PROP_VIEW_FEATURES:
2070 g_value_set_flags (value, priv->view_features);
2072 case PROP_INDIVIDUAL_FEATURES:
2073 g_value_set_flags (value, priv->individual_features);
2075 case PROP_SHOW_OFFLINE:
2076 g_value_set_boolean (value, priv->show_offline);
2078 case PROP_SHOW_UNTRUSTED:
2079 g_value_set_boolean (value, priv->show_untrusted);
2081 case PROP_SHOW_UNINTERESTING:
2082 g_value_set_boolean (value, priv->show_uninteresting);
2085 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2091 individual_view_set_property (GObject *object,
2093 const GValue *value,
2096 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2097 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2102 empathy_individual_view_set_store (view, g_value_get_object (value));
2104 case PROP_VIEW_FEATURES:
2105 individual_view_set_view_features (view, g_value_get_flags (value));
2107 case PROP_INDIVIDUAL_FEATURES:
2108 priv->individual_features = g_value_get_flags (value);
2110 case PROP_SHOW_OFFLINE:
2111 empathy_individual_view_set_show_offline (view,
2112 g_value_get_boolean (value));
2114 case PROP_SHOW_UNTRUSTED:
2115 empathy_individual_view_set_show_untrusted (view,
2116 g_value_get_boolean (value));
2118 case PROP_SHOW_UNINTERESTING:
2119 empathy_individual_view_set_show_uninteresting (view,
2120 g_value_get_boolean (value));
2122 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2128 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2130 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2131 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2132 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2134 object_class->constructed = individual_view_constructed;
2135 object_class->dispose = individual_view_dispose;
2136 object_class->finalize = individual_view_finalize;
2137 object_class->get_property = individual_view_get_property;
2138 object_class->set_property = individual_view_set_property;
2140 widget_class->drag_data_received = individual_view_drag_data_received;
2141 widget_class->drag_drop = individual_view_drag_drop;
2142 widget_class->drag_begin = individual_view_drag_begin;
2143 widget_class->drag_data_get = individual_view_drag_data_get;
2144 widget_class->drag_end = individual_view_drag_end;
2145 widget_class->drag_motion = individual_view_drag_motion;
2147 /* We use the class method to let user of this widget to connect to
2148 * the signal and stop emission of the signal so the default handler
2149 * won't be called. */
2150 tree_view_class->row_activated = individual_view_row_activated;
2152 klass->drag_individual_received = real_drag_individual_received_cb;
2154 signals[DRAG_INDIVIDUAL_RECEIVED] =
2155 g_signal_new ("drag-individual-received",
2156 G_OBJECT_CLASS_TYPE (klass),
2158 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2160 g_cclosure_marshal_generic,
2161 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2162 G_TYPE_STRING, G_TYPE_STRING);
2164 signals[DRAG_PERSONA_RECEIVED] =
2165 g_signal_new ("drag-persona-received",
2166 G_OBJECT_CLASS_TYPE (klass),
2168 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2170 g_cclosure_marshal_generic,
2171 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2173 g_object_class_install_property (object_class,
2175 g_param_spec_object ("store",
2176 "The store of the view",
2177 "The store of the view",
2178 EMPATHY_TYPE_INDIVIDUAL_STORE,
2179 G_PARAM_READWRITE));
2180 g_object_class_install_property (object_class,
2182 g_param_spec_flags ("view-features",
2183 "Features of the view",
2184 "Flags for all enabled features",
2185 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2186 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2187 g_object_class_install_property (object_class,
2188 PROP_INDIVIDUAL_FEATURES,
2189 g_param_spec_flags ("individual-features",
2190 "Features of the individual menu",
2191 "Flags for all enabled features for the menu",
2192 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2193 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2194 g_object_class_install_property (object_class,
2196 g_param_spec_boolean ("show-offline",
2198 "Whether contact list should display "
2199 "offline contacts", FALSE, G_PARAM_READWRITE));
2200 g_object_class_install_property (object_class,
2201 PROP_SHOW_UNTRUSTED,
2202 g_param_spec_boolean ("show-untrusted",
2203 "Show Untrusted Individuals",
2204 "Whether the view should display untrusted individuals; "
2205 "those who could not be who they say they are.",
2206 TRUE, G_PARAM_READWRITE));
2207 g_object_class_install_property (object_class,
2208 PROP_SHOW_UNINTERESTING,
2209 g_param_spec_boolean ("show-uninteresting",
2210 "Show Uninteresting Individuals",
2211 "Whether the view should not filter out individuals using "
2212 "empathy_folks_persona_is_interesting.",
2213 FALSE, G_PARAM_READWRITE));
2215 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2219 empathy_individual_view_init (EmpathyIndividualView *view)
2221 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2222 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2226 priv->show_untrusted = TRUE;
2227 priv->show_uninteresting = FALSE;
2229 /* Get saved group states. */
2230 empathy_contact_groups_get_all ();
2232 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2233 (GDestroyNotify) g_free, NULL);
2235 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2236 empathy_individual_store_row_separator_func, NULL, NULL);
2238 /* Connect to tree view signals rather than override. */
2239 g_signal_connect (view, "button-press-event",
2240 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2241 g_signal_connect (view, "key-press-event",
2242 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2243 g_signal_connect (view, "row-expanded",
2244 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2245 GINT_TO_POINTER (TRUE));
2246 g_signal_connect (view, "row-collapsed",
2247 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2248 GINT_TO_POINTER (FALSE));
2249 g_signal_connect (view, "query-tooltip",
2250 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2253 EmpathyIndividualView *
2254 empathy_individual_view_new (EmpathyIndividualStore *store,
2255 EmpathyIndividualViewFeatureFlags view_features,
2256 EmpathyIndividualFeatureFlags individual_features)
2258 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2260 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2262 "individual-features", individual_features,
2263 "view-features", view_features, NULL);
2267 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2269 GtkTreeSelection *selection;
2271 GtkTreeModel *model;
2272 FolksIndividual *individual;
2274 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2276 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2277 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2280 gtk_tree_model_get (model, &iter,
2281 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2287 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2288 gboolean *is_fake_group)
2290 GtkTreeSelection *selection;
2292 GtkTreeModel *model;
2297 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2299 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2300 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2303 gtk_tree_model_get (model, &iter,
2304 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2305 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2306 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2314 if (is_fake_group != NULL)
2315 *is_fake_group = fake;
2322 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2323 REMOVE_DIALOG_RESPONSE_DELETE,
2327 individual_view_remove_dialog_show (GtkWindow *parent,
2328 const gchar *message,
2329 const gchar *secondary_text)
2334 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2335 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2337 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2338 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2339 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2340 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2341 "%s", secondary_text);
2343 gtk_widget_show (dialog);
2345 res = gtk_dialog_run (GTK_DIALOG (dialog));
2346 gtk_widget_destroy (dialog);
2352 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2353 EmpathyIndividualView *view)
2357 group = empathy_individual_view_dup_selected_group (view, NULL);
2364 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2366 parent = tpaw_get_toplevel_window (GTK_WIDGET (view));
2367 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2368 text) == REMOVE_DIALOG_RESPONSE_DELETE)
2370 EmpathyIndividualManager *manager =
2371 empathy_individual_manager_dup_singleton ();
2372 empathy_individual_manager_remove_group (manager, group);
2373 g_object_unref (G_OBJECT (manager));
2383 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2384 EmpathyIndividualView *self)
2386 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2389 GtkTreeSelection *selection;
2390 GtkTreeModel *model;
2392 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2393 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2395 path = gtk_tree_model_get_path (model, &iter);
2397 g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2399 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2400 gtk_widget_grab_focus (GTK_WIDGET (self));
2401 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2402 gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2404 gtk_tree_path_free (path);
2408 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2410 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2415 gboolean is_fake_group;
2417 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2419 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2420 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2423 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2424 if (!group || is_fake_group)
2426 /* We can't alter fake groups */
2431 menu = gtk_menu_new ();
2433 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2435 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2436 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2437 gtk_widget_show (item);
2438 g_signal_connect (item, "activate",
2439 G_CALLBACK (individual_view_group_rename_activate_cb), view);
2442 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2444 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2445 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2446 GTK_ICON_SIZE_MENU);
2447 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2448 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2449 gtk_widget_show (item);
2450 g_signal_connect (item, "activate",
2451 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2460 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2462 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2463 FolksIndividual *individual;
2464 GtkWidget *menu = NULL;
2466 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2468 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2469 /* No need to create a context menu */
2472 individual = empathy_individual_view_dup_selected (view);
2473 if (individual == NULL)
2476 if (!empathy_folks_individual_contains_contact (individual))
2479 menu = empathy_individual_menu_new (individual, NULL,
2480 priv->individual_features, priv->store);
2483 g_object_unref (individual);
2489 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2490 TpawLiveSearch *search)
2492 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2494 /* remove old handlers if old search was not null */
2495 if (priv->search_widget != NULL)
2497 g_signal_handlers_disconnect_by_func (view,
2498 individual_view_start_search_cb, NULL);
2500 g_signal_handlers_disconnect_by_func (priv->search_widget,
2501 individual_view_search_text_notify_cb, view);
2502 g_signal_handlers_disconnect_by_func (priv->search_widget,
2503 individual_view_search_activate_cb, view);
2504 g_signal_handlers_disconnect_by_func (priv->search_widget,
2505 individual_view_search_key_navigation_cb, view);
2506 g_signal_handlers_disconnect_by_func (priv->search_widget,
2507 individual_view_search_hide_cb, view);
2508 g_signal_handlers_disconnect_by_func (priv->search_widget,
2509 individual_view_search_show_cb, view);
2510 g_object_unref (priv->search_widget);
2511 priv->search_widget = NULL;
2514 /* connect handlers if new search is not null */
2517 priv->search_widget = g_object_ref (search);
2519 g_signal_connect (view, "start-interactive-search",
2520 G_CALLBACK (individual_view_start_search_cb), NULL);
2522 g_signal_connect (priv->search_widget, "notify::text",
2523 G_CALLBACK (individual_view_search_text_notify_cb), view);
2524 g_signal_connect (priv->search_widget, "activate",
2525 G_CALLBACK (individual_view_search_activate_cb), view);
2526 g_signal_connect (priv->search_widget, "key-navigation",
2527 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2528 g_signal_connect (priv->search_widget, "hide",
2529 G_CALLBACK (individual_view_search_hide_cb), view);
2530 g_signal_connect (priv->search_widget, "show",
2531 G_CALLBACK (individual_view_search_show_cb), view);
2536 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2538 EmpathyIndividualViewPriv *priv;
2540 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2542 priv = GET_PRIV (self);
2544 return (priv->search_widget != NULL &&
2545 gtk_widget_get_visible (priv->search_widget));
2549 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2551 EmpathyIndividualViewPriv *priv;
2553 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2555 priv = GET_PRIV (self);
2557 return priv->show_offline;
2561 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2562 gboolean show_offline)
2564 EmpathyIndividualViewPriv *priv;
2566 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2568 priv = GET_PRIV (self);
2570 priv->show_offline = show_offline;
2572 g_object_notify (G_OBJECT (self), "show-offline");
2573 gtk_tree_model_filter_refilter (priv->filter);
2577 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2579 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2581 return GET_PRIV (self)->show_untrusted;
2585 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2586 gboolean show_untrusted)
2588 EmpathyIndividualViewPriv *priv;
2590 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2592 priv = GET_PRIV (self);
2594 priv->show_untrusted = show_untrusted;
2596 g_object_notify (G_OBJECT (self), "show-untrusted");
2597 gtk_tree_model_filter_refilter (priv->filter);
2600 EmpathyIndividualStore *
2601 empathy_individual_view_get_store (EmpathyIndividualView *self)
2603 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2605 return GET_PRIV (self)->store;
2609 empathy_individual_view_set_store (EmpathyIndividualView *self,
2610 EmpathyIndividualStore *store)
2612 EmpathyIndividualViewPriv *priv;
2614 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2615 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2617 priv = GET_PRIV (self);
2619 /* Destroy the old filter and remove the old store */
2620 if (priv->store != NULL)
2622 g_signal_handlers_disconnect_by_func (priv->filter,
2623 individual_view_row_has_child_toggled_cb, self);
2625 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2628 tp_clear_object (&priv->filter);
2629 tp_clear_object (&priv->store);
2631 /* Set the new store */
2632 priv->store = store;
2636 g_object_ref (store);
2638 /* Create a new filter */
2639 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2640 GTK_TREE_MODEL (priv->store), NULL));
2641 gtk_tree_model_filter_set_visible_func (priv->filter,
2642 individual_view_filter_visible_func, self, NULL);
2644 g_signal_connect (priv->filter, "row-has-child-toggled",
2645 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2646 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2647 GTK_TREE_MODEL (priv->filter));
2652 empathy_individual_view_start_search (EmpathyIndividualView *self)
2654 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2656 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2657 g_return_if_fail (priv->search_widget != NULL);
2659 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2660 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2662 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2666 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2667 GtkTreeModelFilterVisibleFunc filter,
2670 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2672 priv->custom_filter = filter;
2673 priv->custom_filter_data = data;
2677 empathy_individual_view_refilter (EmpathyIndividualView *self)
2679 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2681 gtk_tree_model_filter_refilter (priv->filter);
2685 empathy_individual_view_select_first (EmpathyIndividualView *self)
2687 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2690 gtk_tree_model_filter_refilter (priv->filter);
2692 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2694 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2695 GTK_TREE_VIEW (self));
2697 gtk_tree_selection_select_iter (selection, &iter);
2702 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2703 gboolean show_uninteresting)
2705 EmpathyIndividualViewPriv *priv;
2707 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2709 priv = GET_PRIV (self);
2711 priv->show_uninteresting = show_uninteresting;
2713 g_object_notify (G_OBJECT (self), "show-uninteresting");
2714 gtk_tree_model_filter_refilter (priv->filter);