1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-connection-aggregator.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-request-util.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-individual-edit-dialog.h"
51 #include "empathy-individual-dialogs.h"
52 #include "empathy-images.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
62 /* Active users are those which have recently changed state
63 * (e.g. online, offline or from normal to a busy state).
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
69 EmpathyIndividualStore *store;
70 GtkTreeRowReference *drag_row;
71 EmpathyIndividualViewFeatureFlags view_features;
72 EmpathyIndividualFeatureFlags individual_features;
73 GtkWidget *tooltip_widget;
75 gboolean show_offline;
76 gboolean show_untrusted;
77 gboolean show_uninteresting;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
92 GtkTreeModelFilterVisibleFunc custom_filter;
93 gpointer custom_filter_data;
95 GtkCellRenderer *text_renderer;
96 } EmpathyIndividualViewPriv;
100 EmpathyIndividualView *view;
107 EmpathyIndividualView *view;
108 FolksIndividual *individual;
117 PROP_INDIVIDUAL_FEATURES,
120 PROP_SHOW_UNINTERESTING,
123 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
124 * specific EmpathyContacts (between/in/out of Individuals) */
127 DND_DRAG_TYPE_UNKNOWN = -1,
128 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
129 DND_DRAG_TYPE_PERSONA_ID,
130 DND_DRAG_TYPE_URI_LIST,
131 DND_DRAG_TYPE_STRING,
134 #define DRAG_TYPE(T,I) \
135 { (gchar *) T, 0, I }
137 static const GtkTargetEntry drag_types_dest[] = {
138 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
139 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
140 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
141 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
142 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
143 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
146 static const GtkTargetEntry drag_types_source[] = {
147 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
152 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
156 DRAG_INDIVIDUAL_RECEIVED,
157 DRAG_PERSONA_RECEIVED,
161 static guint signals[LAST_SIGNAL];
163 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
167 individual_view_tooltip_destroy_cb (GtkWidget *widget,
168 EmpathyIndividualView *view)
170 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
172 tp_clear_object (&priv->tooltip_widget);
176 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
179 gboolean keyboard_mode,
183 EmpathyIndividualViewPriv *priv;
184 FolksIndividual *individual;
188 static gint running = 0;
189 gboolean ret = FALSE;
191 priv = GET_PRIV (view);
193 /* Avoid an infinite loop. See GNOME bug #574377 */
199 /* Don't show the tooltip if there's already a popup menu */
200 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
203 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
204 keyboard_mode, &model, &path, &iter))
207 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
208 gtk_tree_path_free (path);
210 gtk_tree_model_get (model, &iter,
211 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
213 if (individual == NULL)
216 if (priv->tooltip_widget == NULL)
218 priv->tooltip_widget = empathy_individual_widget_new (individual,
219 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
220 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
221 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
222 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
223 g_object_ref (priv->tooltip_widget);
225 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
226 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
228 gtk_widget_show (priv->tooltip_widget);
232 empathy_individual_widget_set_individual (
233 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
236 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
239 g_object_unref (individual);
247 groups_change_group_cb (GObject *source,
248 GAsyncResult *result,
251 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
252 GError *error = NULL;
254 folks_group_details_change_group_finish (group_details, result, &error);
257 g_warning ("failed to change group: %s", error->message);
258 g_clear_error (&error);
263 group_can_be_modified (const gchar *name,
264 gboolean is_fake_group,
267 /* Real groups can always be modified */
271 /* The favorite fake group can be modified so users can
272 * add/remove favorites using DnD */
273 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
276 /* We can remove contacts from the 'ungrouped' fake group */
277 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
284 individual_view_individual_drag_received (GtkWidget *self,
285 GdkDragContext *context,
288 GtkSelectionData *selection)
290 EmpathyIndividualViewPriv *priv;
291 EmpathyIndividualManager *manager = NULL;
292 FolksIndividual *individual;
293 GtkTreePath *source_path;
294 const gchar *sel_data;
295 gchar *new_group = NULL;
296 gchar *old_group = NULL;
297 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
299 priv = GET_PRIV (self);
301 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
302 new_group = empathy_individual_store_get_parent_group (model, path,
303 NULL, &new_group_is_fake);
305 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
308 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
309 * feature. Otherwise, we just add the dropped contact to whichever group
310 * they were dropped in, and don't remove them from their old group. This
311 * allows for Individual views which shouldn't allow Individuals to have
312 * their groups changed, and also for dragging Individuals between Individual
314 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
315 priv->drag_row != NULL)
317 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
321 empathy_individual_store_get_parent_group (model, source_path,
322 NULL, &old_group_is_fake);
323 gtk_tree_path_free (source_path);
326 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
329 if (!tp_strdiff (old_group, new_group))
332 else if (priv->drag_row != NULL)
334 /* We don't allow changing Individuals' groups, and this Individual was
335 * dragged from another group in *this* Individual view, so we disallow
340 /* XXX: for contacts, we used to ensure the account, create the contact
341 * factory, and then wait on the contacts. But they should already be
342 * created by this point */
344 manager = empathy_individual_manager_dup_singleton ();
345 individual = empathy_individual_manager_lookup_member (manager, sel_data);
347 if (individual == NULL)
349 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
353 /* FIXME: We should probably wait for the cb before calling
356 /* Emit a signal notifying of the drag. We change the Individual's groups in
357 * the default signal handler. */
358 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
359 gdk_drag_context_get_selected_action (context), individual, new_group,
365 tp_clear_object (&manager);
373 real_drag_individual_received_cb (EmpathyIndividualView *self,
374 GdkDragAction action,
375 FolksIndividual *individual,
376 const gchar *new_group,
377 const gchar *old_group)
379 DEBUG ("individual %s dragged from '%s' to '%s'",
380 folks_individual_get_id (individual), old_group, new_group);
382 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
384 /* Mark contact as favourite */
385 folks_favourite_details_set_is_favourite (
386 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
390 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
392 /* Remove contact as favourite */
393 folks_favourite_details_set_is_favourite (
394 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
396 /* Don't try to remove it */
400 if (new_group != NULL)
402 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
403 new_group, TRUE, groups_change_group_cb, NULL);
406 if (old_group != NULL && action == GDK_ACTION_MOVE)
408 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
409 old_group, FALSE, groups_change_group_cb, NULL);
414 individual_view_persona_drag_received (GtkWidget *self,
415 GdkDragContext *context,
418 GtkSelectionData *selection)
420 EmpathyIndividualManager *manager = NULL;
421 FolksIndividual *individual = NULL;
422 FolksPersona *persona = NULL;
423 const gchar *persona_uid;
424 GList *individuals, *l;
425 GeeIterator *iter = NULL;
426 gboolean retval = FALSE;
428 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
430 /* FIXME: This is slow, but the only way to find the Persona we're having
432 manager = empathy_individual_manager_dup_singleton ();
433 individuals = empathy_individual_manager_get_members (manager);
435 for (l = individuals; l != NULL; l = l->next)
439 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
440 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
441 while (gee_iterator_next (iter))
443 FolksPersona *persona_cur = gee_iterator_get (iter);
445 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
447 /* takes ownership of the ref */
448 persona = persona_cur;
449 individual = g_object_ref (l->data);
452 g_clear_object (&persona_cur);
454 g_clear_object (&iter);
458 g_clear_object (&iter);
459 g_list_free (individuals);
461 if (persona == NULL || individual == NULL)
463 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
467 /* Emit a signal notifying of the drag. We change the Individual's groups in
468 * the default signal handler. */
469 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
470 gdk_drag_context_get_selected_action (context), persona, individual,
474 tp_clear_object (&manager);
475 tp_clear_object (&persona);
476 tp_clear_object (&individual);
482 individual_view_file_drag_received (GtkWidget *view,
483 GdkDragContext *context,
486 GtkSelectionData *selection)
489 const gchar *sel_data;
490 FolksIndividual *individual;
491 EmpathyContact *contact;
493 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
495 gtk_tree_model_get_iter (model, &iter, path);
496 gtk_tree_model_get (model, &iter,
497 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
498 if (individual == NULL)
501 contact = empathy_contact_dup_from_folks_individual (individual);
502 empathy_send_file_from_uri_list (contact, sel_data);
504 g_object_unref (individual);
505 tp_clear_object (&contact);
511 individual_view_drag_data_received (GtkWidget *view,
512 GdkDragContext *context,
515 GtkSelectionData *selection,
521 GtkTreeViewDropPosition position;
523 gboolean success = TRUE;
525 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
527 /* Get destination group information. */
528 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
529 x, y, &path, &position);
534 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
536 success = individual_view_individual_drag_received (view,
537 context, model, path, selection);
539 else if (info == DND_DRAG_TYPE_PERSONA_ID)
541 success = individual_view_persona_drag_received (view, context, model,
544 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
546 success = individual_view_file_drag_received (view,
547 context, model, path, selection);
550 gtk_tree_path_free (path);
551 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
555 individual_view_drag_motion_cb (DragMotionData *data)
557 if (data->view != NULL)
559 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
560 g_object_remove_weak_pointer (G_OBJECT (data->view),
561 (gpointer *) &data->view);
564 data->timeout_id = 0;
569 /* Minimum distance between the mouse pointer and a horizontal border when we
570 start auto scrolling. */
571 #define AUTO_SCROLL_MARGIN_SIZE 20
572 /* How far to scroll per one tick. */
573 #define AUTO_SCROLL_PITCH 10
576 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
578 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
582 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
584 if (priv->distance < 0)
585 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
587 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
589 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
590 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
592 gtk_adjustment_set_value (adj, new_value);
598 individual_view_drag_motion (GtkWidget *widget,
599 GdkDragContext *context,
604 EmpathyIndividualViewPriv *priv;
608 static DragMotionData *dm = NULL;
611 gboolean is_different = FALSE;
612 gboolean cleanup = TRUE;
613 gboolean retval = TRUE;
614 GtkAllocation allocation;
616 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
618 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
619 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
622 if (priv->auto_scroll_timeout_id != 0)
624 g_source_remove (priv->auto_scroll_timeout_id);
625 priv->auto_scroll_timeout_id = 0;
628 gtk_widget_get_allocation (widget, &allocation);
630 if (y < AUTO_SCROLL_MARGIN_SIZE ||
631 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
633 if (y < AUTO_SCROLL_MARGIN_SIZE)
634 priv->distance = MIN (-y, -1);
636 priv->distance = MAX (allocation.height - y, 1);
638 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
639 (GSourceFunc) individual_view_auto_scroll_cb, widget);
642 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
643 x, y, &path, NULL, NULL, NULL);
645 cleanup &= (dm == NULL);
649 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
650 is_different = ((dm == NULL) || ((dm != NULL)
651 && gtk_tree_path_compare (dm->path, path) != 0));
658 /* Coordinates don't point to an actual row, so make sure the pointer
659 and highlighting don't indicate that a drag is possible.
661 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
662 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
665 target = gtk_drag_dest_find_target (widget, context, NULL);
666 gtk_tree_model_get_iter (model, &iter, path);
668 /* Determine the DndDragType of the data */
669 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
671 if (target == drag_atoms_dest[i])
673 drag_type = drag_types_dest[i].info;
678 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
679 drag_type == DND_DRAG_TYPE_STRING)
681 /* This is a file drag, and it can only be dropped on contacts,
683 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
684 * even if we have a valid target. */
685 FolksIndividual *individual = NULL;
686 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
688 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
690 gtk_tree_model_get (model, &iter,
691 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
695 if (individual != NULL)
697 EmpathyContact *contact = NULL;
699 contact = empathy_contact_dup_from_folks_individual (individual);
701 caps = empathy_contact_get_capabilities (contact);
703 tp_clear_object (&contact);
706 if (individual != NULL &&
707 folks_presence_details_is_online (
708 FOLKS_PRESENCE_DETAILS (individual)) &&
709 (caps & EMPATHY_CAPABILITIES_FT))
711 gdk_drag_status (context, GDK_ACTION_COPY, time_);
712 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
713 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
717 gdk_drag_status (context, 0, time_);
718 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
722 if (individual != NULL)
723 g_object_unref (individual);
725 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
726 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
727 priv->drag_row == NULL)) ||
728 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
729 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
731 /* If target != GDK_NONE, then we have a contact (individual or persona)
732 drag. If we're pointing to a group, highlight it. Otherwise, if the
733 contact we're pointing to is in a group, highlight that. Otherwise,
734 set the drag position to before the first row for a drag into
735 the "non-group" at the top.
736 If it's an Individual:
737 We only highlight things if the contact is from a different
738 Individual view, or if this Individual view has
739 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
740 which don't have FEATURE_GROUPS_CHANGE, but do have
741 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
743 We only highlight things if we have FEATURE_PERSONA_DROP.
745 GtkTreeIter group_iter;
747 GtkTreePath *group_path;
748 gtk_tree_model_get (model, &iter,
749 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
756 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
757 gtk_tree_model_get (model, &group_iter,
758 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
762 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
763 group_path = gtk_tree_model_get_path (model, &group_iter);
764 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
765 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
766 gtk_tree_path_free (group_path);
770 group_path = gtk_tree_path_new_first ();
771 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
772 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
773 group_path, GTK_TREE_VIEW_DROP_BEFORE);
777 if (!is_different && !cleanup)
782 gtk_tree_path_free (dm->path);
785 g_source_remove (dm->timeout_id);
793 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
795 dm = g_new0 (DragMotionData, 1);
797 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
798 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
799 dm->path = gtk_tree_path_copy (path);
801 dm->timeout_id = g_timeout_add_seconds (1,
802 (GSourceFunc) individual_view_drag_motion_cb, dm);
809 individual_view_drag_begin (GtkWidget *widget,
810 GdkDragContext *context)
812 EmpathyIndividualViewPriv *priv;
813 GtkTreeSelection *selection;
818 priv = GET_PRIV (widget);
820 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
821 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
824 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
827 path = gtk_tree_model_get_path (model, &iter);
828 priv->drag_row = gtk_tree_row_reference_new (model, path);
829 gtk_tree_path_free (path);
833 individual_view_drag_data_get (GtkWidget *widget,
834 GdkDragContext *context,
835 GtkSelectionData *selection,
839 EmpathyIndividualViewPriv *priv;
840 GtkTreePath *src_path;
843 FolksIndividual *individual;
844 const gchar *individual_id;
846 priv = GET_PRIV (widget);
848 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
849 if (priv->drag_row == NULL)
852 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
853 if (src_path == NULL)
856 if (!gtk_tree_model_get_iter (model, &iter, src_path))
858 gtk_tree_path_free (src_path);
862 gtk_tree_path_free (src_path);
865 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
866 if (individual == NULL)
869 individual_id = folks_individual_get_id (individual);
871 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
873 gtk_selection_data_set (selection,
874 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
875 (guchar *) individual_id, strlen (individual_id) + 1);
878 g_object_unref (individual);
882 individual_view_drag_end (GtkWidget *widget,
883 GdkDragContext *context)
885 EmpathyIndividualViewPriv *priv;
887 priv = GET_PRIV (widget);
889 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
894 gtk_tree_row_reference_free (priv->drag_row);
895 priv->drag_row = NULL;
898 if (priv->auto_scroll_timeout_id != 0)
900 g_source_remove (priv->auto_scroll_timeout_id);
901 priv->auto_scroll_timeout_id = 0;
906 individual_view_drag_drop (GtkWidget *widget,
907 GdkDragContext *drag_context,
917 EmpathyIndividualView *view;
923 menu_deactivate_cb (GtkMenuShell *menushell,
926 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
927 g_signal_handlers_disconnect_by_func (menushell,
928 menu_deactivate_cb, user_data);
930 gtk_menu_detach (GTK_MENU (menushell));
934 individual_view_popup_menu_idle_cb (gpointer user_data)
936 MenuPopupData *data = user_data;
939 menu = empathy_individual_view_get_individual_menu (data->view);
941 menu = empathy_individual_view_get_group_menu (data->view);
945 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
947 gtk_widget_show (menu);
948 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
951 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
952 * floating ref. We can either wait that the treeview releases its ref
953 * when it will be destroyed (when leaving Empathy) or explicitely
954 * detach the menu when it's not displayed any more.
955 * We go for the latter as we don't want to keep useless menus in memory
956 * during the whole lifetime of Empathy. */
957 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
961 g_slice_free (MenuPopupData, data);
967 individual_view_button_press_event_cb (EmpathyIndividualView *view,
968 GdkEventButton *event,
971 if (event->button == 3)
975 data = g_slice_new (MenuPopupData);
977 data->button = event->button;
978 data->time = event->time;
979 g_idle_add (individual_view_popup_menu_idle_cb, data);
986 individual_view_key_press_event_cb (EmpathyIndividualView *view,
990 if (event->keyval == GDK_KEY_Menu)
994 data = g_slice_new (MenuPopupData);
997 data->time = event->time;
998 g_idle_add (individual_view_popup_menu_idle_cb, data);
999 } else if (event->keyval == GDK_KEY_F2) {
1000 FolksIndividual *individual;
1002 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
1004 individual = empathy_individual_view_dup_selected (view);
1005 if (individual == NULL)
1008 empathy_individual_edit_dialog_show (individual, NULL);
1010 g_object_unref (individual);
1017 individual_view_row_activated (GtkTreeView *view,
1019 GtkTreeViewColumn *column)
1021 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1022 FolksIndividual *individual;
1023 EmpathyContact *contact;
1024 GtkTreeModel *model;
1027 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1030 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1031 gtk_tree_model_get_iter (model, &iter, path);
1032 gtk_tree_model_get (model, &iter,
1033 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1035 if (individual == NULL)
1038 /* Determine which Persona to chat to, by choosing the most available one. */
1039 contact = empathy_contact_dup_best_for_action (individual,
1040 EMPATHY_ACTION_CHAT);
1042 if (contact != NULL)
1044 DEBUG ("Starting a chat");
1046 empathy_chat_with_contact (contact,
1047 gtk_get_current_event_time ());
1050 g_object_unref (individual);
1051 tp_clear_object (&contact);
1055 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1056 const gchar *path_string,
1057 EmpathyIndividualView *view)
1059 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1061 GtkTreeModel *model;
1063 FolksIndividual *individual;
1064 GdkEventButton *event;
1065 GtkMenuShell *shell;
1068 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1071 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1072 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1075 gtk_tree_model_get (model, &iter,
1076 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1077 if (individual == NULL)
1080 event = (GdkEventButton *) gtk_get_current_event ();
1082 menu = empathy_context_menu_new (GTK_WIDGET (view));
1083 shell = GTK_MENU_SHELL (menu);
1086 item = empathy_individual_audio_call_menu_item_new (individual);
1087 gtk_menu_shell_append (shell, item);
1088 gtk_widget_show (item);
1091 item = empathy_individual_video_call_menu_item_new (individual);
1092 gtk_menu_shell_append (shell, item);
1093 gtk_widget_show (item);
1095 gtk_widget_show (menu);
1096 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1097 event->button, event->time);
1099 g_object_unref (individual);
1103 individual_view_cell_set_background (EmpathyIndividualView *view,
1104 GtkCellRenderer *cell,
1108 if (!is_group && is_active)
1110 GtkStyleContext *style;
1113 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1115 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1118 /* Here we take the current theme colour and add it to
1119 * the colour for white and average the two. This
1120 * gives a colour which is inline with the theme but
1123 empathy_make_color_whiter (&color);
1125 g_object_set (cell, "cell-background-rgba", &color, NULL);
1128 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1132 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1133 GtkCellRenderer *cell,
1134 GtkTreeModel *model,
1136 EmpathyIndividualView *view)
1142 gtk_tree_model_get (model, iter,
1143 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1144 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1145 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1148 "visible", !is_group,
1152 tp_clear_object (&pixbuf);
1154 individual_view_cell_set_background (view, cell, is_group, is_active);
1158 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1159 GtkCellRenderer *cell,
1160 GtkTreeModel *model,
1162 EmpathyIndividualView *view)
1164 GdkPixbuf *pixbuf = NULL;
1168 gtk_tree_model_get (model, iter,
1169 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1170 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1175 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1177 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1178 GTK_ICON_SIZE_MENU);
1180 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1182 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1183 GTK_ICON_SIZE_MENU);
1188 "visible", pixbuf != NULL,
1192 tp_clear_object (&pixbuf);
1198 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1199 GtkCellRenderer *cell,
1200 GtkTreeModel *model,
1202 EmpathyIndividualView *view)
1206 gboolean can_audio, can_video;
1208 gtk_tree_model_get (model, iter,
1209 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1210 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1211 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1212 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1215 "visible", !is_group && (can_audio || can_video),
1216 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1219 individual_view_cell_set_background (view, cell, is_group, is_active);
1223 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1224 GtkCellRenderer *cell,
1225 GtkTreeModel *model,
1227 EmpathyIndividualView *view)
1230 gboolean show_avatar;
1234 gtk_tree_model_get (model, iter,
1235 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1236 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1237 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1238 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1241 "visible", !is_group && show_avatar,
1245 tp_clear_object (&pixbuf);
1247 individual_view_cell_set_background (view, cell, is_group, is_active);
1251 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1252 GtkCellRenderer *cell,
1253 GtkTreeModel *model,
1255 EmpathyIndividualView *view)
1260 gtk_tree_model_get (model, iter,
1261 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1262 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1264 individual_view_cell_set_background (view, cell, is_group, is_active);
1268 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1269 GtkCellRenderer *cell,
1270 GtkTreeModel *model,
1272 EmpathyIndividualView *view)
1277 gtk_tree_model_get (model, iter,
1278 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1279 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1281 if (gtk_tree_model_iter_has_child (model, iter))
1284 gboolean row_expanded;
1286 path = gtk_tree_model_get_path (model, iter);
1288 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1289 (gtk_tree_view_column_get_tree_view (column)), path);
1290 gtk_tree_path_free (path);
1295 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1299 g_object_set (cell, "visible", FALSE, NULL);
1301 individual_view_cell_set_background (view, cell, is_group, is_active);
1305 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1310 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1311 GtkTreeModel *model;
1315 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1318 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1320 gtk_tree_model_get (model, iter,
1321 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1323 expanded = GPOINTER_TO_INT (user_data);
1324 empathy_contact_group_set_expanded (name, expanded);
1330 individual_view_start_search_cb (EmpathyIndividualView *view,
1333 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1335 if (priv->search_widget == NULL)
1338 empathy_individual_view_start_search (view);
1344 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1346 EmpathyIndividualView *view)
1348 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1350 GtkTreeViewColumn *focus_column;
1351 GtkTreeModel *model;
1353 gboolean set_cursor = FALSE;
1355 gtk_tree_model_filter_refilter (priv->filter);
1357 /* Set cursor on the first contact. If it is already set on a group,
1358 * set it on its first child contact. Note that first child of a group
1359 * is its separator, that's why we actually set to the 2nd
1362 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1363 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1367 path = gtk_tree_path_new_from_string ("0:1");
1370 else if (gtk_tree_path_get_depth (path) < 2)
1374 gtk_tree_model_get_iter (model, &iter, path);
1375 gtk_tree_model_get (model, &iter,
1376 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1381 gtk_tree_path_down (path);
1382 gtk_tree_path_next (path);
1389 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1391 if (gtk_tree_model_get_iter (model, &iter, path))
1393 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1398 gtk_tree_path_free (path);
1402 individual_view_search_activate_cb (GtkWidget *search,
1403 EmpathyIndividualView *view)
1406 GtkTreeViewColumn *focus_column;
1408 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1411 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1412 gtk_tree_path_free (path);
1414 gtk_widget_hide (search);
1419 individual_view_search_key_navigation_cb (GtkWidget *search,
1421 EmpathyIndividualView *view)
1423 GdkEvent *new_event;
1424 gboolean ret = FALSE;
1426 new_event = gdk_event_copy (event);
1427 gtk_widget_grab_focus (GTK_WIDGET (view));
1428 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1429 gtk_widget_grab_focus (search);
1431 gdk_event_free (new_event);
1437 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1438 EmpathyIndividualView *view)
1440 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1441 GtkTreeModel *model;
1442 GtkTreePath *cursor_path;
1444 gboolean valid = FALSE;
1446 /* block expand or collapse handlers, they would write the
1447 * expand or collapsed setting to file otherwise */
1448 g_signal_handlers_block_by_func (view,
1449 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1450 g_signal_handlers_block_by_func (view,
1451 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1453 /* restore which groups are expanded and which are not */
1454 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1455 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1456 valid; valid = gtk_tree_model_iter_next (model, &iter))
1462 gtk_tree_model_get (model, &iter,
1463 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1464 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1473 path = gtk_tree_model_get_path (model, &iter);
1474 if ((priv->view_features &
1475 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1476 empathy_contact_group_get_expanded (name))
1478 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1482 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1485 gtk_tree_path_free (path);
1489 /* unblock expand or collapse handlers */
1490 g_signal_handlers_unblock_by_func (view,
1491 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1492 g_signal_handlers_unblock_by_func (view,
1493 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1495 /* keep the selected contact visible */
1496 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1498 if (cursor_path != NULL)
1499 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1502 gtk_tree_path_free (cursor_path);
1506 individual_view_search_show_cb (EmpathyLiveSearch *search,
1507 EmpathyIndividualView *view)
1509 /* block expand or collapse handlers during expand all, they would
1510 * write the expand or collapsed setting to file otherwise */
1511 g_signal_handlers_block_by_func (view,
1512 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1514 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1516 g_signal_handlers_unblock_by_func (view,
1517 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1521 expand_idle_foreach_cb (GtkTreeModel *model,
1524 EmpathyIndividualView *self)
1526 EmpathyIndividualViewPriv *priv;
1528 gpointer should_expand;
1531 /* We only want groups */
1532 if (gtk_tree_path_get_depth (path) > 1)
1535 gtk_tree_model_get (model, iter,
1536 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1537 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1546 priv = GET_PRIV (self);
1548 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1551 if (GPOINTER_TO_INT (should_expand))
1552 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1554 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1556 g_hash_table_remove (priv->expand_groups, name);
1565 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1567 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1569 g_signal_handlers_block_by_func (self,
1570 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1571 g_signal_handlers_block_by_func (self,
1572 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1574 /* The store/filter could've been removed while we were in the idle queue */
1575 if (priv->filter != NULL)
1577 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1578 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1581 g_signal_handlers_unblock_by_func (self,
1582 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1583 g_signal_handlers_unblock_by_func (self,
1584 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1586 /* Empty the table of groups to expand/contract, since it may contain groups
1587 * which no longer exist in the tree view. This can happen after going
1588 * offline, for example. */
1589 g_hash_table_remove_all (priv->expand_groups);
1590 priv->expand_groups_idle_handler = 0;
1591 g_object_unref (self);
1597 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1600 EmpathyIndividualView *view)
1602 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1603 gboolean should_expand, is_group = FALSE;
1605 gpointer will_expand;
1607 gtk_tree_model_get (model, iter,
1608 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1609 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1612 if (!is_group || EMP_STR_EMPTY (name))
1618 should_expand = (priv->view_features &
1619 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1620 (priv->search_widget != NULL &&
1621 gtk_widget_get_visible (priv->search_widget)) ||
1622 empathy_contact_group_get_expanded (name);
1624 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1625 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1626 * a hash table, and expand or contract them as appropriate all at once in
1627 * an idle handler which iterates over all the group rows. */
1628 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1630 GPOINTER_TO_INT (will_expand) != should_expand)
1632 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1633 GINT_TO_POINTER (should_expand));
1635 if (priv->expand_groups_idle_handler == 0)
1637 priv->expand_groups_idle_handler =
1638 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1639 g_object_ref (view));
1647 individual_view_is_visible_individual (EmpathyIndividualView *self,
1648 FolksIndividual *individual,
1650 gboolean is_searching,
1652 gboolean is_fake_group,
1655 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1656 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1659 gboolean is_favorite;
1661 /* Always display individuals having pending events */
1662 if (event_count > 0)
1665 /* We're only giving the visibility wrt filtering here, not things like
1667 if (!priv->show_untrusted &&
1668 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1673 if (!priv->show_uninteresting)
1675 gboolean contains_interesting_persona = FALSE;
1677 /* Hide all individuals which consist entirely of uninteresting
1679 personas = folks_individual_get_personas (individual);
1680 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1681 while (!contains_interesting_persona && gee_iterator_next (iter))
1683 FolksPersona *persona = gee_iterator_get (iter);
1685 if (empathy_folks_persona_is_interesting (persona))
1686 contains_interesting_persona = TRUE;
1688 g_clear_object (&persona);
1690 g_clear_object (&iter);
1692 if (!contains_interesting_persona)
1696 is_favorite = folks_favourite_details_get_is_favourite (
1697 FOLKS_FAVOURITE_DETAILS (individual));
1698 if (!is_searching) {
1699 if (is_favorite && is_fake_group &&
1700 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1701 /* Always display favorite contacts in the favorite group */
1704 return (priv->show_offline || is_online);
1707 return empathy_individual_match_string (individual,
1708 empathy_live_search_get_text (live),
1709 empathy_live_search_get_words (live));
1713 get_group (GtkTreeModel *model,
1717 GtkTreeIter parent_iter;
1722 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1725 gtk_tree_model_get (model, &parent_iter,
1726 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1727 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1735 individual_view_filter_visible_func (GtkTreeModel *model,
1739 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1740 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1741 FolksIndividual *individual = NULL;
1742 gboolean is_group, is_separator, valid;
1743 GtkTreeIter child_iter;
1744 gboolean visible, is_online;
1745 gboolean is_searching = TRUE;
1748 if (priv->custom_filter != NULL)
1749 return priv->custom_filter (model, iter, priv->custom_filter_data);
1751 if (priv->search_widget == NULL ||
1752 !gtk_widget_get_visible (priv->search_widget))
1753 is_searching = FALSE;
1755 gtk_tree_model_get (model, iter,
1756 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1757 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1758 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1759 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1760 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1763 if (individual != NULL)
1766 gboolean is_fake_group;
1768 group = get_group (model, iter, &is_fake_group);
1770 visible = individual_view_is_visible_individual (self, individual,
1771 is_online, is_searching, group, is_fake_group, event_count);
1773 g_object_unref (individual);
1782 /* Not a contact, not a separator, must be a group */
1783 g_return_val_if_fail (is_group, FALSE);
1785 /* only show groups which are not empty */
1786 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1787 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1790 gboolean is_fake_group;
1792 gtk_tree_model_get (model, &child_iter,
1793 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1794 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1795 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1798 if (individual == NULL)
1801 group = get_group (model, &child_iter, &is_fake_group);
1803 visible = individual_view_is_visible_individual (self, individual,
1804 is_online, is_searching, group, is_fake_group, event_count);
1806 g_object_unref (individual);
1809 /* show group if it has at least one visible contact in it */
1817 static gchar * empathy_individual_view_dup_selected_group (
1818 EmpathyIndividualView *view,
1819 gboolean *is_fake_group);
1822 text_edited_cb (GtkCellRendererText *cellrenderertext,
1825 EmpathyIndividualView *self)
1827 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1828 gchar *old_name, *new_name;
1830 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1832 new_name = g_strdup (name);
1833 g_strstrip (new_name);
1835 if (tp_str_empty (new_name))
1838 old_name = empathy_individual_view_dup_selected_group (self, NULL);
1839 g_return_if_fail (old_name != NULL);
1841 if (tp_strdiff (old_name, new_name))
1843 EmpathyConnectionAggregator *aggregator;
1845 DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1847 aggregator = empathy_connection_aggregator_dup_singleton ();
1849 empathy_connection_aggregator_rename_group (aggregator, old_name,
1851 g_object_unref (aggregator);
1860 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1861 EmpathyIndividualView *self)
1863 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1865 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1869 individual_view_constructed (GObject *object)
1871 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1872 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1873 GtkCellRenderer *cell;
1874 GtkTreeViewColumn *col;
1879 "headers-visible", FALSE,
1880 "show-expanders", FALSE,
1883 col = gtk_tree_view_column_new ();
1886 cell = gtk_cell_renderer_pixbuf_new ();
1887 gtk_tree_view_column_pack_start (col, cell, FALSE);
1888 gtk_tree_view_column_set_cell_data_func (col, cell,
1889 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1899 cell = gtk_cell_renderer_pixbuf_new ();
1900 gtk_tree_view_column_pack_start (col, cell, FALSE);
1901 gtk_tree_view_column_set_cell_data_func (col, cell,
1902 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1914 priv->text_renderer = empathy_cell_renderer_text_new ();
1915 gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1916 gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1917 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1919 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1920 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1921 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1922 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1923 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1924 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1925 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1926 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1927 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1928 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1929 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1930 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1931 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1932 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1934 g_signal_connect (priv->text_renderer, "editing-canceled",
1935 G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1936 g_signal_connect (priv->text_renderer, "edited",
1937 G_CALLBACK (text_edited_cb), view);
1939 /* Audio Call Icon */
1940 cell = empathy_cell_renderer_activatable_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_audio_call_cell_data_func,
1946 g_object_set (cell, "visible", FALSE, NULL);
1948 g_signal_connect (cell, "path-activated",
1949 G_CALLBACK (individual_view_call_activated_cb), view);
1952 cell = gtk_cell_renderer_pixbuf_new ();
1953 gtk_tree_view_column_pack_start (col, cell, FALSE);
1954 gtk_tree_view_column_set_cell_data_func (col, cell,
1955 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1967 cell = empathy_cell_renderer_expander_new ();
1968 gtk_tree_view_column_pack_end (col, cell, FALSE);
1969 gtk_tree_view_column_set_cell_data_func (col, cell,
1970 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1973 /* Actually add the column now we have added all cell renderers */
1974 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1977 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1979 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1984 individual_view_set_view_features (EmpathyIndividualView *view,
1985 EmpathyIndividualFeatureFlags features)
1987 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1988 gboolean has_tooltip;
1990 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1992 priv->view_features = features;
1994 /* Setting reorderable is a hack that gets us row previews as drag icons
1995 for free. We override all the drag handlers. It's tricky to get the
1996 position of the drag icon right in drag_begin. GtkTreeView has special
1997 voodoo for it, so we let it do the voodoo that he do (but only if dragging
2000 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
2001 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
2003 /* Update DnD source/dest */
2004 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
2006 gtk_drag_source_set (GTK_WIDGET (view),
2009 G_N_ELEMENTS (drag_types_source),
2010 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2014 gtk_drag_source_unset (GTK_WIDGET (view));
2018 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2020 gtk_drag_dest_set (GTK_WIDGET (view),
2021 GTK_DEST_DEFAULT_ALL,
2023 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2027 /* FIXME: URI could still be droped depending on FT feature */
2028 gtk_drag_dest_unset (GTK_WIDGET (view));
2031 /* Update has-tooltip */
2033 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2034 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2038 individual_view_dispose (GObject *object)
2040 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2041 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2043 tp_clear_object (&priv->store);
2044 tp_clear_object (&priv->filter);
2045 tp_clear_object (&priv->tooltip_widget);
2047 empathy_individual_view_set_live_search (view, NULL);
2049 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2053 individual_view_finalize (GObject *object)
2055 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2057 if (priv->expand_groups_idle_handler != 0)
2058 g_source_remove (priv->expand_groups_idle_handler);
2059 g_hash_table_unref (priv->expand_groups);
2061 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2065 individual_view_get_property (GObject *object,
2070 EmpathyIndividualViewPriv *priv;
2072 priv = GET_PRIV (object);
2077 g_value_set_object (value, priv->store);
2079 case PROP_VIEW_FEATURES:
2080 g_value_set_flags (value, priv->view_features);
2082 case PROP_INDIVIDUAL_FEATURES:
2083 g_value_set_flags (value, priv->individual_features);
2085 case PROP_SHOW_OFFLINE:
2086 g_value_set_boolean (value, priv->show_offline);
2088 case PROP_SHOW_UNTRUSTED:
2089 g_value_set_boolean (value, priv->show_untrusted);
2091 case PROP_SHOW_UNINTERESTING:
2092 g_value_set_boolean (value, priv->show_uninteresting);
2095 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2101 individual_view_set_property (GObject *object,
2103 const GValue *value,
2106 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2107 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2112 empathy_individual_view_set_store (view, g_value_get_object (value));
2114 case PROP_VIEW_FEATURES:
2115 individual_view_set_view_features (view, g_value_get_flags (value));
2117 case PROP_INDIVIDUAL_FEATURES:
2118 priv->individual_features = g_value_get_flags (value);
2120 case PROP_SHOW_OFFLINE:
2121 empathy_individual_view_set_show_offline (view,
2122 g_value_get_boolean (value));
2124 case PROP_SHOW_UNTRUSTED:
2125 empathy_individual_view_set_show_untrusted (view,
2126 g_value_get_boolean (value));
2128 case PROP_SHOW_UNINTERESTING:
2129 empathy_individual_view_set_show_uninteresting (view,
2130 g_value_get_boolean (value));
2132 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2138 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2140 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2141 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2142 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2144 object_class->constructed = individual_view_constructed;
2145 object_class->dispose = individual_view_dispose;
2146 object_class->finalize = individual_view_finalize;
2147 object_class->get_property = individual_view_get_property;
2148 object_class->set_property = individual_view_set_property;
2150 widget_class->drag_data_received = individual_view_drag_data_received;
2151 widget_class->drag_drop = individual_view_drag_drop;
2152 widget_class->drag_begin = individual_view_drag_begin;
2153 widget_class->drag_data_get = individual_view_drag_data_get;
2154 widget_class->drag_end = individual_view_drag_end;
2155 widget_class->drag_motion = individual_view_drag_motion;
2157 /* We use the class method to let user of this widget to connect to
2158 * the signal and stop emission of the signal so the default handler
2159 * won't be called. */
2160 tree_view_class->row_activated = individual_view_row_activated;
2162 klass->drag_individual_received = real_drag_individual_received_cb;
2164 signals[DRAG_INDIVIDUAL_RECEIVED] =
2165 g_signal_new ("drag-individual-received",
2166 G_OBJECT_CLASS_TYPE (klass),
2168 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2170 g_cclosure_marshal_generic,
2171 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2172 G_TYPE_STRING, G_TYPE_STRING);
2174 signals[DRAG_PERSONA_RECEIVED] =
2175 g_signal_new ("drag-persona-received",
2176 G_OBJECT_CLASS_TYPE (klass),
2178 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2180 g_cclosure_marshal_generic,
2181 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2183 g_object_class_install_property (object_class,
2185 g_param_spec_object ("store",
2186 "The store of the view",
2187 "The store of the view",
2188 EMPATHY_TYPE_INDIVIDUAL_STORE,
2189 G_PARAM_READWRITE));
2190 g_object_class_install_property (object_class,
2192 g_param_spec_flags ("view-features",
2193 "Features of the view",
2194 "Flags for all enabled features",
2195 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2196 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2197 g_object_class_install_property (object_class,
2198 PROP_INDIVIDUAL_FEATURES,
2199 g_param_spec_flags ("individual-features",
2200 "Features of the individual menu",
2201 "Flags for all enabled features for the menu",
2202 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2203 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2204 g_object_class_install_property (object_class,
2206 g_param_spec_boolean ("show-offline",
2208 "Whether contact list should display "
2209 "offline contacts", FALSE, G_PARAM_READWRITE));
2210 g_object_class_install_property (object_class,
2211 PROP_SHOW_UNTRUSTED,
2212 g_param_spec_boolean ("show-untrusted",
2213 "Show Untrusted Individuals",
2214 "Whether the view should display untrusted individuals; "
2215 "those who could not be who they say they are.",
2216 TRUE, G_PARAM_READWRITE));
2217 g_object_class_install_property (object_class,
2218 PROP_SHOW_UNINTERESTING,
2219 g_param_spec_boolean ("show-uninteresting",
2220 "Show Uninteresting Individuals",
2221 "Whether the view should not filter out individuals using "
2222 "empathy_folks_persona_is_interesting.",
2223 FALSE, G_PARAM_READWRITE));
2225 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2229 empathy_individual_view_init (EmpathyIndividualView *view)
2231 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2232 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2236 priv->show_untrusted = TRUE;
2237 priv->show_uninteresting = FALSE;
2239 /* Get saved group states. */
2240 empathy_contact_groups_get_all ();
2242 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2243 (GDestroyNotify) g_free, NULL);
2245 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2246 empathy_individual_store_row_separator_func, NULL, NULL);
2248 /* Connect to tree view signals rather than override. */
2249 g_signal_connect (view, "button-press-event",
2250 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2251 g_signal_connect (view, "key-press-event",
2252 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2253 g_signal_connect (view, "row-expanded",
2254 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2255 GINT_TO_POINTER (TRUE));
2256 g_signal_connect (view, "row-collapsed",
2257 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2258 GINT_TO_POINTER (FALSE));
2259 g_signal_connect (view, "query-tooltip",
2260 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2263 EmpathyIndividualView *
2264 empathy_individual_view_new (EmpathyIndividualStore *store,
2265 EmpathyIndividualViewFeatureFlags view_features,
2266 EmpathyIndividualFeatureFlags individual_features)
2268 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2270 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2272 "individual-features", individual_features,
2273 "view-features", view_features, NULL);
2277 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2279 GtkTreeSelection *selection;
2281 GtkTreeModel *model;
2282 FolksIndividual *individual;
2284 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2286 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2287 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2290 gtk_tree_model_get (model, &iter,
2291 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2297 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2298 gboolean *is_fake_group)
2300 GtkTreeSelection *selection;
2302 GtkTreeModel *model;
2307 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2309 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2310 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2313 gtk_tree_model_get (model, &iter,
2314 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2315 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2316 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2324 if (is_fake_group != NULL)
2325 *is_fake_group = fake;
2332 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2333 REMOVE_DIALOG_RESPONSE_DELETE,
2337 individual_view_remove_dialog_show (GtkWindow *parent,
2338 const gchar *message,
2339 const gchar *secondary_text)
2344 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2345 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2347 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2348 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2349 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2350 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2351 "%s", secondary_text);
2353 gtk_widget_show (dialog);
2355 res = gtk_dialog_run (GTK_DIALOG (dialog));
2356 gtk_widget_destroy (dialog);
2362 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2363 EmpathyIndividualView *view)
2367 group = empathy_individual_view_dup_selected_group (view, NULL);
2374 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2376 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2377 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2378 text) == REMOVE_DIALOG_RESPONSE_DELETE)
2380 EmpathyIndividualManager *manager =
2381 empathy_individual_manager_dup_singleton ();
2382 empathy_individual_manager_remove_group (manager, group);
2383 g_object_unref (G_OBJECT (manager));
2393 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2394 EmpathyIndividualView *self)
2396 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2399 GtkTreeSelection *selection;
2400 GtkTreeModel *model;
2402 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2403 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2405 path = gtk_tree_model_get_path (model, &iter);
2407 g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2409 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2410 gtk_widget_grab_focus (GTK_WIDGET (self));
2411 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2412 gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2414 gtk_tree_path_free (path);
2418 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2420 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2425 gboolean is_fake_group;
2427 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2429 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2430 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2433 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2434 if (!group || is_fake_group)
2436 /* We can't alter fake groups */
2441 menu = gtk_menu_new ();
2443 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2445 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
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_rename_activate_cb), view);
2452 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2454 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2455 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2456 GTK_ICON_SIZE_MENU);
2457 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2458 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2459 gtk_widget_show (item);
2460 g_signal_connect (item, "activate",
2461 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2470 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2472 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2473 FolksIndividual *individual;
2474 GtkWidget *menu = NULL;
2476 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2478 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2479 /* No need to create a context menu */
2482 individual = empathy_individual_view_dup_selected (view);
2483 if (individual == NULL)
2486 if (!empathy_folks_individual_contains_contact (individual))
2489 menu = empathy_individual_menu_new (individual, priv->individual_features,
2493 g_object_unref (individual);
2499 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2500 EmpathyLiveSearch *search)
2502 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2504 /* remove old handlers if old search was not null */
2505 if (priv->search_widget != NULL)
2507 g_signal_handlers_disconnect_by_func (view,
2508 individual_view_start_search_cb, NULL);
2510 g_signal_handlers_disconnect_by_func (priv->search_widget,
2511 individual_view_search_text_notify_cb, view);
2512 g_signal_handlers_disconnect_by_func (priv->search_widget,
2513 individual_view_search_activate_cb, view);
2514 g_signal_handlers_disconnect_by_func (priv->search_widget,
2515 individual_view_search_key_navigation_cb, view);
2516 g_signal_handlers_disconnect_by_func (priv->search_widget,
2517 individual_view_search_hide_cb, view);
2518 g_signal_handlers_disconnect_by_func (priv->search_widget,
2519 individual_view_search_show_cb, view);
2520 g_object_unref (priv->search_widget);
2521 priv->search_widget = NULL;
2524 /* connect handlers if new search is not null */
2527 priv->search_widget = g_object_ref (search);
2529 g_signal_connect (view, "start-interactive-search",
2530 G_CALLBACK (individual_view_start_search_cb), NULL);
2532 g_signal_connect (priv->search_widget, "notify::text",
2533 G_CALLBACK (individual_view_search_text_notify_cb), view);
2534 g_signal_connect (priv->search_widget, "activate",
2535 G_CALLBACK (individual_view_search_activate_cb), view);
2536 g_signal_connect (priv->search_widget, "key-navigation",
2537 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2538 g_signal_connect (priv->search_widget, "hide",
2539 G_CALLBACK (individual_view_search_hide_cb), view);
2540 g_signal_connect (priv->search_widget, "show",
2541 G_CALLBACK (individual_view_search_show_cb), view);
2546 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2548 EmpathyIndividualViewPriv *priv;
2550 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2552 priv = GET_PRIV (self);
2554 return (priv->search_widget != NULL &&
2555 gtk_widget_get_visible (priv->search_widget));
2559 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2561 EmpathyIndividualViewPriv *priv;
2563 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2565 priv = GET_PRIV (self);
2567 return priv->show_offline;
2571 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2572 gboolean show_offline)
2574 EmpathyIndividualViewPriv *priv;
2576 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2578 priv = GET_PRIV (self);
2580 priv->show_offline = show_offline;
2582 g_object_notify (G_OBJECT (self), "show-offline");
2583 gtk_tree_model_filter_refilter (priv->filter);
2587 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2589 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2591 return GET_PRIV (self)->show_untrusted;
2595 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2596 gboolean show_untrusted)
2598 EmpathyIndividualViewPriv *priv;
2600 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2602 priv = GET_PRIV (self);
2604 priv->show_untrusted = show_untrusted;
2606 g_object_notify (G_OBJECT (self), "show-untrusted");
2607 gtk_tree_model_filter_refilter (priv->filter);
2610 EmpathyIndividualStore *
2611 empathy_individual_view_get_store (EmpathyIndividualView *self)
2613 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2615 return GET_PRIV (self)->store;
2619 empathy_individual_view_set_store (EmpathyIndividualView *self,
2620 EmpathyIndividualStore *store)
2622 EmpathyIndividualViewPriv *priv;
2624 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2625 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2627 priv = GET_PRIV (self);
2629 /* Destroy the old filter and remove the old store */
2630 if (priv->store != NULL)
2632 g_signal_handlers_disconnect_by_func (priv->filter,
2633 individual_view_row_has_child_toggled_cb, self);
2635 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2638 tp_clear_object (&priv->filter);
2639 tp_clear_object (&priv->store);
2641 /* Set the new store */
2642 priv->store = store;
2646 g_object_ref (store);
2648 /* Create a new filter */
2649 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2650 GTK_TREE_MODEL (priv->store), NULL));
2651 gtk_tree_model_filter_set_visible_func (priv->filter,
2652 individual_view_filter_visible_func, self, NULL);
2654 g_signal_connect (priv->filter, "row-has-child-toggled",
2655 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2656 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2657 GTK_TREE_MODEL (priv->filter));
2662 empathy_individual_view_start_search (EmpathyIndividualView *self)
2664 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2666 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2667 g_return_if_fail (priv->search_widget != NULL);
2669 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2670 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2672 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2676 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2677 GtkTreeModelFilterVisibleFunc filter,
2680 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2682 priv->custom_filter = filter;
2683 priv->custom_filter_data = data;
2687 empathy_individual_view_refilter (EmpathyIndividualView *self)
2689 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2691 gtk_tree_model_filter_refilter (priv->filter);
2695 empathy_individual_view_select_first (EmpathyIndividualView *self)
2697 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2700 gtk_tree_model_filter_refilter (priv->filter);
2702 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2704 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2705 GTK_TREE_VIEW (self));
2707 gtk_tree_selection_select_iter (selection, &iter);
2712 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2713 gboolean show_uninteresting)
2715 EmpathyIndividualViewPriv *priv;
2717 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2719 priv = GET_PRIV (self);
2721 priv->show_uninteresting = show_uninteresting;
2723 g_object_notify (G_OBJECT (self), "show-uninteresting");
2724 gtk_tree_model_filter_refilter (priv->filter);