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-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.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-images.h"
51 #include "empathy-linking-dialog.h"
52 #include "empathy-cell-renderer-expander.h"
53 #include "empathy-cell-renderer-text.h"
54 #include "empathy-cell-renderer-activatable.h"
55 #include "empathy-ui-utils.h"
56 #include "empathy-gtk-enum-types.h"
57 #include "empathy-gtk-marshal.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;
78 GtkTreeModelFilter *filter;
79 GtkWidget *search_widget;
81 guint expand_groups_idle_handler;
82 /* owned string (group name) -> bool (whether to expand/contract) */
83 GHashTable *expand_groups;
84 } EmpathyIndividualViewPriv;
88 EmpathyIndividualView *view;
95 EmpathyIndividualView *view;
96 FolksIndividual *individual;
105 PROP_INDIVIDUAL_FEATURES,
110 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
111 * specific EmpathyContacts (between/in/out of Individuals) */
114 DND_DRAG_TYPE_INDIVIDUAL_ID,
115 DND_DRAG_TYPE_PERSONA_ID,
116 DND_DRAG_TYPE_URI_LIST,
117 DND_DRAG_TYPE_STRING,
120 #define DRAG_TYPE(T,I) \
121 { (gchar *) T, 0, I }
123 static const GtkTargetEntry drag_types_dest[] = {
124 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
125 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
126 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
127 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
128 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
129 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
132 static const GtkTargetEntry drag_types_source[] = {
133 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
138 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
139 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
143 DRAG_INDIVIDUAL_RECEIVED,
144 DRAG_PERSONA_RECEIVED,
148 static guint signals[LAST_SIGNAL];
150 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
154 individual_view_tooltip_destroy_cb (GtkWidget *widget,
155 EmpathyIndividualView *view)
157 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
159 if (priv->tooltip_widget != NULL)
161 DEBUG ("Tooltip destroyed");
162 tp_clear_object (&priv->tooltip_widget);
167 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
170 gboolean keyboard_mode,
174 EmpathyIndividualViewPriv *priv;
175 FolksIndividual *individual;
179 static gint running = 0;
180 gboolean ret = FALSE;
182 priv = GET_PRIV (view);
184 /* Avoid an infinite loop. See GNOME bug #574377 */
190 /* Don't show the tooltip if there's already a popup menu */
191 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
194 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
195 keyboard_mode, &model, &path, &iter))
198 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
199 gtk_tree_path_free (path);
201 gtk_tree_model_get (model, &iter,
202 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
204 if (individual == NULL)
207 if (priv->tooltip_widget == NULL)
209 priv->tooltip_widget = empathy_individual_widget_new (individual,
210 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
211 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
212 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
213 g_object_ref (priv->tooltip_widget);
214 g_signal_connect (priv->tooltip_widget, "destroy",
215 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
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 FolksGroups *groups = FOLKS_GROUPS (source);
240 GError *error = NULL;
242 folks_groups_change_group_finish (groups, 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_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
377 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379 /* Remove contact as favourite */
380 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
382 /* Don't try to remove it */
386 if (new_group != NULL)
388 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
389 groups_change_group_cb, NULL);
392 if (old_group != NULL && action == GDK_ACTION_MOVE)
394 folks_groups_change_group (FOLKS_GROUPS (individual), old_group,
395 FALSE, groups_change_group_cb, NULL);
400 individual_view_persona_drag_received (GtkWidget *self,
401 GdkDragContext *context,
404 GtkSelectionData *selection)
406 EmpathyIndividualViewPriv *priv;
407 EmpathyIndividualManager *manager = NULL;
408 FolksIndividual *individual = NULL;
409 FolksPersona *persona = NULL;
410 const gchar *persona_uid;
411 GList *individuals, *l;
412 gboolean retval = FALSE;
414 priv = GET_PRIV (self);
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));
429 for (p = personas; p != NULL; p = p->next)
431 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
434 persona = g_object_ref (p->data);
435 individual = g_object_ref (l->data);
442 g_list_free (individuals);
444 if (persona == NULL || individual == NULL)
446 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
450 /* Emit a signal notifying of the drag. We change the Individual's groups in
451 * the default signal handler. */
452 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
453 gdk_drag_context_get_selected_action (context), persona, individual,
457 tp_clear_object (&manager);
458 tp_clear_object (&persona);
459 tp_clear_object (&individual);
465 individual_view_file_drag_received (GtkWidget *view,
466 GdkDragContext *context,
469 GtkSelectionData *selection)
472 const gchar *sel_data;
473 FolksIndividual *individual;
474 EmpathyContact *contact;
476 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
478 gtk_tree_model_get_iter (model, &iter, path);
479 gtk_tree_model_get (model, &iter,
480 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
481 if (individual == NULL)
484 contact = empathy_contact_dup_from_folks_individual (individual);
485 empathy_send_file_from_uri_list (contact, sel_data);
487 g_object_unref (individual);
488 tp_clear_object (&contact);
494 individual_view_drag_data_received (GtkWidget *view,
495 GdkDragContext *context,
498 GtkSelectionData *selection,
504 GtkTreeViewDropPosition position;
506 gboolean success = TRUE;
508 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
510 /* Get destination group information. */
511 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
512 x, y, &path, &position);
517 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
519 success = individual_view_individual_drag_received (view,
520 context, model, path, selection);
522 else if (info == DND_DRAG_TYPE_PERSONA_ID)
524 success = individual_view_persona_drag_received (view, context, model,
527 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
529 success = individual_view_file_drag_received (view,
530 context, model, path, selection);
533 gtk_tree_path_free (path);
534 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
538 individual_view_drag_motion_cb (DragMotionData *data)
540 if (data->view != NULL)
542 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
543 g_object_remove_weak_pointer (G_OBJECT (data->view),
544 (gpointer *) &data->view);
547 data->timeout_id = 0;
553 individual_view_drag_motion (GtkWidget *widget,
554 GdkDragContext *context,
559 EmpathyIndividualViewPriv *priv;
563 static DragMotionData *dm = NULL;
566 gboolean is_different = FALSE;
567 gboolean cleanup = TRUE;
568 gboolean retval = TRUE;
570 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
571 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
573 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
574 x, y, &path, NULL, NULL, NULL);
576 cleanup &= (dm == NULL);
580 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
581 is_different = ((dm == NULL) || ((dm != NULL)
582 && gtk_tree_path_compare (dm->path, path) != 0));
589 /* Coordinates don't point to an actual row, so make sure the pointer
590 and highlighting don't indicate that a drag is possible.
592 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
593 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
596 target = gtk_drag_dest_find_target (widget, context, NULL);
597 gtk_tree_model_get_iter (model, &iter, path);
599 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
600 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
602 /* This is a file drag, and it can only be dropped on contacts,
604 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
605 * even if we have a valid target. */
606 FolksIndividual *individual = NULL;
607 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
609 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
611 gtk_tree_model_get (model, &iter,
612 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
616 if (individual != NULL)
618 EmpathyContact *contact = NULL;
620 contact = empathy_contact_dup_from_folks_individual (individual);
621 caps = empathy_contact_get_capabilities (contact);
623 tp_clear_object (&contact);
626 if (individual != NULL &&
627 folks_individual_is_online (individual) &&
628 (caps & EMPATHY_CAPABILITIES_FT))
630 gdk_drag_status (context, GDK_ACTION_COPY, time_);
631 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
632 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
636 gdk_drag_status (context, 0, time_);
637 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
641 if (individual != NULL)
642 g_object_unref (individual);
644 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
645 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
646 priv->drag_row == NULL)) ||
647 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
648 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
650 /* If target != GDK_NONE, then we have a contact (individual or persona)
651 drag. If we're pointing to a group, highlight it. Otherwise, if the
652 contact we're pointing to is in a group, highlight that. Otherwise,
653 set the drag position to before the first row for a drag into
654 the "non-group" at the top.
655 If it's an Individual:
656 We only highlight things if the contact is from a different
657 Individual view, or if this Individual view has
658 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
659 which don't have FEATURE_GROUPS_CHANGE, but do have
660 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
662 We only highlight things if we have FEATURE_PERSONA_DROP.
664 GtkTreeIter group_iter;
666 GtkTreePath *group_path;
667 gtk_tree_model_get (model, &iter,
668 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
675 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
676 gtk_tree_model_get (model, &group_iter,
677 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
681 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
682 group_path = gtk_tree_model_get_path (model, &group_iter);
683 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
684 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
685 gtk_tree_path_free (group_path);
689 group_path = gtk_tree_path_new_first ();
690 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
691 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
692 group_path, GTK_TREE_VIEW_DROP_BEFORE);
696 if (!is_different && !cleanup)
701 gtk_tree_path_free (dm->path);
704 g_source_remove (dm->timeout_id);
712 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
714 dm = g_new0 (DragMotionData, 1);
716 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
717 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
718 dm->path = gtk_tree_path_copy (path);
720 dm->timeout_id = g_timeout_add_seconds (1,
721 (GSourceFunc) individual_view_drag_motion_cb, dm);
728 individual_view_drag_begin (GtkWidget *widget,
729 GdkDragContext *context)
731 EmpathyIndividualViewPriv *priv;
732 GtkTreeSelection *selection;
737 priv = GET_PRIV (widget);
739 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
742 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
743 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
746 path = gtk_tree_model_get_path (model, &iter);
747 priv->drag_row = gtk_tree_row_reference_new (model, path);
748 gtk_tree_path_free (path);
752 individual_view_drag_data_get (GtkWidget *widget,
753 GdkDragContext *context,
754 GtkSelectionData *selection,
758 EmpathyIndividualViewPriv *priv;
759 GtkTreePath *src_path;
762 FolksIndividual *individual;
763 const gchar *individual_id;
765 priv = GET_PRIV (widget);
767 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
768 if (priv->drag_row == NULL)
771 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
772 if (src_path == NULL)
775 if (!gtk_tree_model_get_iter (model, &iter, src_path))
777 gtk_tree_path_free (src_path);
781 gtk_tree_path_free (src_path);
784 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
785 if (individual == NULL)
788 individual_id = folks_individual_get_id (individual);
790 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
792 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
793 (guchar *) individual_id, strlen (individual_id) + 1);
796 g_object_unref (individual);
800 individual_view_drag_end (GtkWidget *widget,
801 GdkDragContext *context)
803 EmpathyIndividualViewPriv *priv;
805 priv = GET_PRIV (widget);
807 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
812 gtk_tree_row_reference_free (priv->drag_row);
813 priv->drag_row = NULL;
818 individual_view_drag_drop (GtkWidget *widget,
819 GdkDragContext *drag_context,
829 EmpathyIndividualView *view;
835 individual_view_popup_menu_idle_cb (gpointer user_data)
837 MenuPopupData *data = user_data;
840 menu = empathy_individual_view_get_individual_menu (data->view);
842 menu = empathy_individual_view_get_group_menu (data->view);
846 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
847 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
849 gtk_widget_show (menu);
850 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
852 g_object_ref_sink (menu);
853 g_object_unref (menu);
856 g_slice_free (MenuPopupData, data);
862 individual_view_button_press_event_cb (EmpathyIndividualView *view,
863 GdkEventButton *event,
866 if (event->button == 3)
870 data = g_slice_new (MenuPopupData);
872 data->button = event->button;
873 data->time = event->time;
874 g_idle_add (individual_view_popup_menu_idle_cb, data);
881 individual_view_key_press_event_cb (EmpathyIndividualView *view,
885 if (event->keyval == GDK_KEY_Menu)
889 data = g_slice_new (MenuPopupData);
892 data->time = event->time;
893 g_idle_add (individual_view_popup_menu_idle_cb, data);
900 individual_view_row_activated (GtkTreeView *view,
902 GtkTreeViewColumn *column)
904 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
905 FolksIndividual *individual;
906 EmpathyContact *contact;
910 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
913 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
914 gtk_tree_model_get_iter (model, &iter, path);
915 gtk_tree_model_get (model, &iter,
916 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
918 if (individual == NULL)
921 /* Determine which Persona to chat to, by choosing the most available one. */
922 contact = empathy_contact_dup_best_for_action (individual,
923 EMPATHY_ACTION_CHAT);
927 DEBUG ("Starting a chat");
929 empathy_dispatcher_chat_with_contact (contact,
930 gtk_get_current_event_time ());
933 g_object_unref (individual);
934 tp_clear_object (&contact);
938 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
939 const gchar *path_string,
940 EmpathyIndividualView *view)
942 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
946 FolksIndividual *individual;
947 GdkEventButton *event;
951 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
954 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
955 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
958 gtk_tree_model_get (model, &iter,
959 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
960 if (individual == NULL)
963 event = (GdkEventButton *) gtk_get_current_event ();
965 menu = gtk_menu_new ();
966 shell = GTK_MENU_SHELL (menu);
969 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
970 gtk_menu_shell_append (shell, item);
971 gtk_widget_show (item);
974 item = empathy_individual_video_call_menu_item_new (individual, NULL);
975 gtk_menu_shell_append (shell, item);
976 gtk_widget_show (item);
978 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
979 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
980 gtk_widget_show (menu);
981 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
982 event->button, event->time);
983 g_object_ref_sink (menu);
984 g_object_unref (menu);
986 g_object_unref (individual);
990 individual_view_cell_set_background (EmpathyIndividualView *view,
991 GtkCellRenderer *cell,
998 style = gtk_widget_get_style (GTK_WIDGET (view));
1000 if (!is_group && is_active)
1002 color = style->bg[GTK_STATE_SELECTED];
1004 /* Here we take the current theme colour and add it to
1005 * the colour for white and average the two. This
1006 * gives a colour which is inline with the theme but
1009 color.red = (color.red + (style->white).red) / 2;
1010 color.green = (color.green + (style->white).green) / 2;
1011 color.blue = (color.blue + (style->white).blue) / 2;
1013 g_object_set (cell, "cell-background-gdk", &color, NULL);
1016 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1020 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1021 GtkCellRenderer *cell,
1022 GtkTreeModel *model,
1024 EmpathyIndividualView *view)
1030 gtk_tree_model_get (model, iter,
1031 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1032 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1033 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1036 "visible", !is_group,
1040 tp_clear_object (&pixbuf);
1042 individual_view_cell_set_background (view, cell, is_group, is_active);
1046 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1047 GtkCellRenderer *cell,
1048 GtkTreeModel *model,
1050 EmpathyIndividualView *view)
1052 GdkPixbuf *pixbuf = NULL;
1056 gtk_tree_model_get (model, iter,
1057 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1058 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1063 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1065 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1066 GTK_ICON_SIZE_MENU);
1068 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1070 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1071 GTK_ICON_SIZE_MENU);
1076 "visible", pixbuf != NULL,
1080 tp_clear_object (&pixbuf);
1086 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1087 GtkCellRenderer *cell,
1088 GtkTreeModel *model,
1090 EmpathyIndividualView *view)
1094 gboolean can_audio, can_video;
1096 gtk_tree_model_get (model, iter,
1097 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1098 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1099 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1100 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1103 "visible", !is_group && (can_audio || can_video),
1104 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1107 individual_view_cell_set_background (view, cell, is_group, is_active);
1111 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1112 GtkCellRenderer *cell,
1113 GtkTreeModel *model,
1115 EmpathyIndividualView *view)
1118 gboolean show_avatar;
1122 gtk_tree_model_get (model, iter,
1123 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1124 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1125 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1126 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1129 "visible", !is_group && show_avatar,
1133 tp_clear_object (&pixbuf);
1135 individual_view_cell_set_background (view, cell, is_group, is_active);
1139 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1140 GtkCellRenderer *cell,
1141 GtkTreeModel *model,
1143 EmpathyIndividualView *view)
1148 gtk_tree_model_get (model, iter,
1149 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1150 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1152 individual_view_cell_set_background (view, cell, is_group, is_active);
1156 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1157 GtkCellRenderer *cell,
1158 GtkTreeModel *model,
1160 EmpathyIndividualView *view)
1165 gtk_tree_model_get (model, iter,
1166 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1167 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1169 if (gtk_tree_model_iter_has_child (model, iter))
1172 gboolean row_expanded;
1174 path = gtk_tree_model_get_path (model, iter);
1176 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1177 (gtk_tree_view_column_get_tree_view (column)), path);
1178 gtk_tree_path_free (path);
1183 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1187 g_object_set (cell, "visible", FALSE, NULL);
1189 individual_view_cell_set_background (view, cell, is_group, is_active);
1193 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1198 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1199 GtkTreeModel *model;
1203 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1206 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1208 gtk_tree_model_get (model, iter,
1209 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1211 expanded = GPOINTER_TO_INT (user_data);
1212 empathy_contact_group_set_expanded (name, expanded);
1218 individual_view_start_search_cb (EmpathyIndividualView *view,
1221 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1223 if (priv->search_widget == NULL)
1226 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1227 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1229 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1235 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1237 EmpathyIndividualView *view)
1239 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1241 GtkTreeViewColumn *focus_column;
1242 GtkTreeModel *model;
1244 gboolean set_cursor = FALSE;
1246 gtk_tree_model_filter_refilter (priv->filter);
1248 /* Set cursor on the first contact. If it is already set on a group,
1249 * set it on its first child contact. Note that first child of a group
1250 * is its separator, that's why we actually set to the 2nd
1253 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1254 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1258 path = gtk_tree_path_new_from_string ("0:1");
1261 else if (gtk_tree_path_get_depth (path) < 2)
1265 gtk_tree_model_get_iter (model, &iter, path);
1266 gtk_tree_model_get (model, &iter,
1267 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1272 gtk_tree_path_down (path);
1273 gtk_tree_path_next (path);
1280 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1282 if (gtk_tree_model_get_iter (model, &iter, path))
1284 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1289 gtk_tree_path_free (path);
1293 individual_view_search_activate_cb (GtkWidget *search,
1294 EmpathyIndividualView *view)
1297 GtkTreeViewColumn *focus_column;
1299 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1302 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1303 gtk_tree_path_free (path);
1305 gtk_widget_hide (search);
1310 individual_view_search_key_navigation_cb (GtkWidget *search,
1312 EmpathyIndividualView *view)
1314 GdkEventKey *eventkey = ((GdkEventKey *) event);
1315 gboolean ret = FALSE;
1317 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
1319 GdkEvent *new_event;
1321 new_event = gdk_event_copy (event);
1322 gtk_widget_grab_focus (GTK_WIDGET (view));
1323 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1324 gtk_widget_grab_focus (search);
1326 gdk_event_free (new_event);
1333 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1334 EmpathyIndividualView *view)
1336 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1337 GtkTreeModel *model;
1338 GtkTreePath *cursor_path;
1340 gboolean valid = FALSE;
1342 /* block expand or collapse handlers, they would write the
1343 * expand or collapsed setting to file otherwise */
1344 g_signal_handlers_block_by_func (view,
1345 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1346 g_signal_handlers_block_by_func (view,
1347 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1349 /* restore which groups are expanded and which are not */
1350 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1351 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1352 valid; valid = gtk_tree_model_iter_next (model, &iter))
1358 gtk_tree_model_get (model, &iter,
1359 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1360 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1369 path = gtk_tree_model_get_path (model, &iter);
1370 if ((priv->view_features &
1371 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1372 empathy_contact_group_get_expanded (name))
1374 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1378 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1381 gtk_tree_path_free (path);
1385 /* unblock expand or collapse handlers */
1386 g_signal_handlers_unblock_by_func (view,
1387 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1388 g_signal_handlers_unblock_by_func (view,
1389 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1391 /* keep the selected contact visible */
1392 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1394 if (cursor_path != NULL)
1395 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1398 gtk_tree_path_free (cursor_path);
1402 individual_view_search_show_cb (EmpathyLiveSearch *search,
1403 EmpathyIndividualView *view)
1405 /* block expand or collapse handlers during expand all, they would
1406 * write the expand or collapsed setting to file otherwise */
1407 g_signal_handlers_block_by_func (view,
1408 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1410 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1412 g_signal_handlers_unblock_by_func (view,
1413 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1417 expand_idle_foreach_cb (GtkTreeModel *model,
1420 EmpathyIndividualView *self)
1422 EmpathyIndividualViewPriv *priv;
1424 gpointer should_expand;
1427 /* We only want groups */
1428 if (gtk_tree_path_get_depth (path) > 1)
1431 gtk_tree_model_get (model, iter,
1432 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1433 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1436 if (is_group == FALSE)
1442 priv = GET_PRIV (self);
1444 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1445 &should_expand) == TRUE)
1447 if (GPOINTER_TO_INT (should_expand) == TRUE)
1448 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1450 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1452 g_hash_table_remove (priv->expand_groups, name);
1461 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1463 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1465 DEBUG ("individual_view_expand_idle_cb");
1467 g_signal_handlers_block_by_func (self,
1468 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1469 g_signal_handlers_block_by_func (self,
1470 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1472 /* The store/filter could've been removed while we were in the idle queue */
1473 if (priv->filter != NULL)
1475 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1476 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1479 g_signal_handlers_unblock_by_func (self,
1480 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1481 g_signal_handlers_unblock_by_func (self,
1482 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1484 /* Empty the table of groups to expand/contract, since it may contain groups
1485 * which no longer exist in the tree view. This can happen after going
1486 * offline, for example. */
1487 g_hash_table_remove_all (priv->expand_groups);
1488 priv->expand_groups_idle_handler = 0;
1489 g_object_unref (self);
1495 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1498 EmpathyIndividualView *view)
1500 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1501 gboolean should_expand, is_group = FALSE;
1503 gpointer will_expand;
1505 gtk_tree_model_get (model, iter,
1506 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1507 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1510 if (!is_group || EMP_STR_EMPTY (name))
1516 should_expand = (priv->view_features &
1517 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1518 (priv->search_widget != NULL &&
1519 gtk_widget_get_visible (priv->search_widget)) ||
1520 empathy_contact_group_get_expanded (name);
1522 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1523 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1524 * a hash table, and expand or contract them as appropriate all at once in
1525 * an idle handler which iterates over all the group rows. */
1526 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1527 &will_expand) == FALSE ||
1528 GPOINTER_TO_INT (will_expand) != should_expand)
1530 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1531 GINT_TO_POINTER (should_expand));
1533 if (priv->expand_groups_idle_handler == 0)
1535 priv->expand_groups_idle_handler =
1536 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1537 g_object_ref (view));
1544 /* FIXME: This is a workaround for bgo#621076 */
1546 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1549 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1550 GtkTreeModel *model;
1551 GtkTreePath *parent_path;
1552 GtkTreeIter parent_iter;
1554 if (gtk_tree_path_get_depth (path) < 2)
1557 /* A group row is visible if and only if at least one if its child is visible.
1558 * So when a row is inserted/deleted/changed in the base model, that could
1559 * modify the visibility of its parent in the filter model.
1562 model = GTK_TREE_MODEL (priv->store);
1563 parent_path = gtk_tree_path_copy (path);
1564 gtk_tree_path_up (parent_path);
1565 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1567 /* This tells the filter to verify the visibility of that row, and
1568 * show/hide it if necessary */
1569 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1570 parent_path, &parent_iter);
1572 gtk_tree_path_free (parent_path);
1576 individual_view_store_row_changed_cb (GtkTreeModel *model,
1579 EmpathyIndividualView *view)
1581 individual_view_verify_group_visibility (view, path);
1585 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1587 EmpathyIndividualView *view)
1589 individual_view_verify_group_visibility (view, path);
1593 individual_view_is_visible_individual (EmpathyIndividualView *self,
1594 FolksIndividual *individual,
1596 gboolean is_searching)
1598 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1599 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1601 GList *personas, *l;
1603 /* We're only giving the visibility wrt filtering here, not things like
1605 if (priv->show_untrusted == FALSE &&
1606 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1611 if (is_searching == FALSE)
1612 return (priv->show_offline || is_online);
1614 /* check alias name */
1615 str = folks_individual_get_alias (individual);
1617 if (empathy_live_search_match (live, str))
1620 /* check contact id, remove the @server.com part */
1621 personas = folks_individual_get_personas (individual);
1622 for (l = personas; l; l = l->next)
1625 gchar *dup_str = NULL;
1628 if (!TPF_IS_PERSONA (l->data))
1631 str = folks_persona_get_display_id (l->data);
1632 p = strstr (str, "@");
1634 str = dup_str = g_strndup (str, p - str);
1636 visible = empathy_live_search_match (live, str);
1642 /* FIXME: Add more rules here, we could check phone numbers in
1643 * contact's vCard for example. */
1649 individual_view_filter_visible_func (GtkTreeModel *model,
1653 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1654 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1655 FolksIndividual *individual = NULL;
1656 gboolean is_group, is_separator, valid;
1657 GtkTreeIter child_iter;
1658 gboolean visible, is_online;
1659 gboolean is_searching = TRUE;
1661 if (priv->search_widget == NULL ||
1662 !gtk_widget_get_visible (priv->search_widget))
1663 is_searching = FALSE;
1665 gtk_tree_model_get (model, iter,
1666 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1667 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1668 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1669 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1672 if (individual != NULL)
1674 visible = individual_view_is_visible_individual (self, individual,
1675 is_online, is_searching);
1677 g_object_unref (individual);
1679 /* FIXME: Work around bgo#626552/bgo#621076 */
1680 if (visible == TRUE)
1682 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1683 individual_view_verify_group_visibility (self, path);
1684 gtk_tree_path_free (path);
1693 /* Not a contact, not a separator, must be a group */
1694 g_return_val_if_fail (is_group, FALSE);
1696 /* only show groups which are not empty */
1697 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1698 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1700 gtk_tree_model_get (model, &child_iter,
1701 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1702 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1705 if (individual == NULL)
1708 visible = individual_view_is_visible_individual (self, individual,
1709 is_online, is_searching);
1710 g_object_unref (individual);
1712 /* show group if it has at least one visible contact in it */
1713 if (visible == TRUE)
1721 individual_view_constructed (GObject *object)
1723 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1724 GtkCellRenderer *cell;
1725 GtkTreeViewColumn *col;
1730 "headers-visible", FALSE,
1731 "show-expanders", FALSE,
1734 col = gtk_tree_view_column_new ();
1737 cell = gtk_cell_renderer_pixbuf_new ();
1738 gtk_tree_view_column_pack_start (col, cell, FALSE);
1739 gtk_tree_view_column_set_cell_data_func (col, cell,
1740 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1750 cell = gtk_cell_renderer_pixbuf_new ();
1751 gtk_tree_view_column_pack_start (col, cell, FALSE);
1752 gtk_tree_view_column_set_cell_data_func (col, cell,
1753 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1765 cell = empathy_cell_renderer_text_new ();
1766 gtk_tree_view_column_pack_start (col, cell, TRUE);
1767 gtk_tree_view_column_set_cell_data_func (col, cell,
1768 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1770 gtk_tree_view_column_add_attribute (col, cell,
1771 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1772 gtk_tree_view_column_add_attribute (col, cell,
1773 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1774 gtk_tree_view_column_add_attribute (col, cell,
1775 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1776 gtk_tree_view_column_add_attribute (col, cell,
1777 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1778 gtk_tree_view_column_add_attribute (col, cell,
1779 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1780 gtk_tree_view_column_add_attribute (col, cell,
1781 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1783 /* Audio Call Icon */
1784 cell = empathy_cell_renderer_activatable_new ();
1785 gtk_tree_view_column_pack_start (col, cell, FALSE);
1786 gtk_tree_view_column_set_cell_data_func (col, cell,
1787 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1790 g_object_set (cell, "visible", FALSE, NULL);
1792 g_signal_connect (cell, "path-activated",
1793 G_CALLBACK (individual_view_call_activated_cb), view);
1796 cell = gtk_cell_renderer_pixbuf_new ();
1797 gtk_tree_view_column_pack_start (col, cell, FALSE);
1798 gtk_tree_view_column_set_cell_data_func (col, cell,
1799 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1811 cell = empathy_cell_renderer_expander_new ();
1812 gtk_tree_view_column_pack_end (col, cell, FALSE);
1813 gtk_tree_view_column_set_cell_data_func (col, cell,
1814 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1817 /* Actually add the column now we have added all cell renderers */
1818 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1821 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1823 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1826 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1828 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1834 individual_view_set_view_features (EmpathyIndividualView *view,
1835 EmpathyIndividualFeatureFlags features)
1837 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1838 gboolean has_tooltip;
1840 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1842 priv->view_features = features;
1844 /* Setting reorderable is a hack that gets us row previews as drag icons
1845 for free. We override all the drag handlers. It's tricky to get the
1846 position of the drag icon right in drag_begin. GtkTreeView has special
1847 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1850 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1851 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1853 /* Update DnD source/dest */
1854 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1856 gtk_drag_source_set (GTK_WIDGET (view),
1859 G_N_ELEMENTS (drag_types_source),
1860 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1864 gtk_drag_source_unset (GTK_WIDGET (view));
1868 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1870 gtk_drag_dest_set (GTK_WIDGET (view),
1871 GTK_DEST_DEFAULT_ALL,
1873 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1877 /* FIXME: URI could still be droped depending on FT feature */
1878 gtk_drag_dest_unset (GTK_WIDGET (view));
1881 /* Update has-tooltip */
1883 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1884 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1888 individual_view_dispose (GObject *object)
1890 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1891 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1893 tp_clear_object (&priv->store);
1894 tp_clear_object (&priv->filter);
1895 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1897 empathy_individual_view_set_live_search (view, NULL);
1899 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1903 individual_view_finalize (GObject *object)
1905 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1907 if (priv->expand_groups_idle_handler != 0)
1908 g_source_remove (priv->expand_groups_idle_handler);
1909 g_hash_table_destroy (priv->expand_groups);
1911 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1915 individual_view_get_property (GObject *object,
1920 EmpathyIndividualViewPriv *priv;
1922 priv = GET_PRIV (object);
1927 g_value_set_object (value, priv->store);
1929 case PROP_VIEW_FEATURES:
1930 g_value_set_flags (value, priv->view_features);
1932 case PROP_INDIVIDUAL_FEATURES:
1933 g_value_set_flags (value, priv->individual_features);
1935 case PROP_SHOW_OFFLINE:
1936 g_value_set_boolean (value, priv->show_offline);
1938 case PROP_SHOW_UNTRUSTED:
1939 g_value_set_boolean (value, priv->show_untrusted);
1942 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1948 individual_view_set_property (GObject *object,
1950 const GValue *value,
1953 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1954 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1959 empathy_individual_view_set_store (view, g_value_get_object (value));
1961 case PROP_VIEW_FEATURES:
1962 individual_view_set_view_features (view, g_value_get_flags (value));
1964 case PROP_INDIVIDUAL_FEATURES:
1965 priv->individual_features = g_value_get_flags (value);
1967 case PROP_SHOW_OFFLINE:
1968 empathy_individual_view_set_show_offline (view,
1969 g_value_get_boolean (value));
1971 case PROP_SHOW_UNTRUSTED:
1972 empathy_individual_view_set_show_untrusted (view,
1973 g_value_get_boolean (value));
1976 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1982 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1984 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1985 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1986 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1988 object_class->constructed = individual_view_constructed;
1989 object_class->dispose = individual_view_dispose;
1990 object_class->finalize = individual_view_finalize;
1991 object_class->get_property = individual_view_get_property;
1992 object_class->set_property = individual_view_set_property;
1994 widget_class->drag_data_received = individual_view_drag_data_received;
1995 widget_class->drag_drop = individual_view_drag_drop;
1996 widget_class->drag_begin = individual_view_drag_begin;
1997 widget_class->drag_data_get = individual_view_drag_data_get;
1998 widget_class->drag_end = individual_view_drag_end;
1999 widget_class->drag_motion = individual_view_drag_motion;
2001 /* We use the class method to let user of this widget to connect to
2002 * the signal and stop emission of the signal so the default handler
2003 * won't be called. */
2004 tree_view_class->row_activated = individual_view_row_activated;
2006 klass->drag_individual_received = real_drag_individual_received_cb;
2008 signals[DRAG_INDIVIDUAL_RECEIVED] =
2009 g_signal_new ("drag-individual-received",
2010 G_OBJECT_CLASS_TYPE (klass),
2012 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2014 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2015 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2016 G_TYPE_STRING, G_TYPE_STRING);
2018 signals[DRAG_PERSONA_RECEIVED] =
2019 g_signal_new ("drag-persona-received",
2020 G_OBJECT_CLASS_TYPE (klass),
2022 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2024 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2025 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2027 g_object_class_install_property (object_class,
2029 g_param_spec_object ("store",
2030 "The store of the view",
2031 "The store of the view",
2032 EMPATHY_TYPE_INDIVIDUAL_STORE,
2033 G_PARAM_READWRITE));
2034 g_object_class_install_property (object_class,
2036 g_param_spec_flags ("view-features",
2037 "Features of the view",
2038 "Flags for all enabled features",
2039 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2040 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2041 g_object_class_install_property (object_class,
2042 PROP_INDIVIDUAL_FEATURES,
2043 g_param_spec_flags ("individual-features",
2044 "Features of the individual menu",
2045 "Flags for all enabled features for the menu",
2046 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2047 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2048 g_object_class_install_property (object_class,
2050 g_param_spec_boolean ("show-offline",
2052 "Whether contact list should display "
2053 "offline contacts", FALSE, G_PARAM_READWRITE));
2054 g_object_class_install_property (object_class,
2055 PROP_SHOW_UNTRUSTED,
2056 g_param_spec_boolean ("show-untrusted",
2057 "Show Untrusted Individuals",
2058 "Whether the view should display untrusted individuals; "
2059 "those who could not be who they say they are.",
2060 TRUE, G_PARAM_READWRITE));
2062 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2066 empathy_individual_view_init (EmpathyIndividualView *view)
2068 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2069 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2073 priv->show_untrusted = TRUE;
2075 /* Get saved group states. */
2076 empathy_contact_groups_get_all ();
2078 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2079 (GDestroyNotify) g_free, NULL);
2081 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2082 empathy_individual_store_row_separator_func, NULL, NULL);
2084 /* Connect to tree view signals rather than override. */
2085 g_signal_connect (view, "button-press-event",
2086 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2087 g_signal_connect (view, "key-press-event",
2088 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2089 g_signal_connect (view, "row-expanded",
2090 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2091 GINT_TO_POINTER (TRUE));
2092 g_signal_connect (view, "row-collapsed",
2093 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2094 GINT_TO_POINTER (FALSE));
2095 g_signal_connect (view, "query-tooltip",
2096 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2099 EmpathyIndividualView *
2100 empathy_individual_view_new (EmpathyIndividualStore *store,
2101 EmpathyIndividualViewFeatureFlags view_features,
2102 EmpathyIndividualFeatureFlags individual_features)
2104 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2106 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2108 "individual-features", individual_features,
2109 "view-features", view_features, NULL);
2113 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2115 EmpathyIndividualViewPriv *priv;
2116 GtkTreeSelection *selection;
2118 GtkTreeModel *model;
2119 FolksIndividual *individual;
2121 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2123 priv = GET_PRIV (view);
2125 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2126 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2129 gtk_tree_model_get (model, &iter,
2130 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2135 EmpathyIndividualManagerFlags
2136 empathy_individual_view_get_flags (EmpathyIndividualView *view)
2138 EmpathyIndividualViewPriv *priv;
2139 GtkTreeSelection *selection;
2141 GtkTreeModel *model;
2142 EmpathyIndividualFeatureFlags flags;
2144 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2146 priv = GET_PRIV (view);
2148 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2149 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2152 gtk_tree_model_get (model, &iter,
2153 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2159 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2160 gboolean *is_fake_group)
2162 EmpathyIndividualViewPriv *priv;
2163 GtkTreeSelection *selection;
2165 GtkTreeModel *model;
2170 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2172 priv = GET_PRIV (view);
2174 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2175 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2178 gtk_tree_model_get (model, &iter,
2179 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2180 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2181 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2189 if (is_fake_group != NULL)
2190 *is_fake_group = fake;
2196 individual_view_remove_dialog_show (GtkWindow *parent,
2197 const gchar *message,
2198 const gchar *secondary_text)
2203 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2204 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2205 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2206 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2207 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2208 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2209 "%s", secondary_text);
2211 gtk_widget_show (dialog);
2213 res = gtk_dialog_run (GTK_DIALOG (dialog));
2214 gtk_widget_destroy (dialog);
2216 return (res == GTK_RESPONSE_YES);
2220 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2221 EmpathyIndividualView *view)
2225 group = empathy_individual_view_get_selected_group (view, NULL);
2232 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2234 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2235 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2238 EmpathyIndividualManager *manager =
2239 empathy_individual_manager_dup_singleton ();
2240 empathy_individual_manager_remove_group (manager, group);
2241 g_object_unref (G_OBJECT (manager));
2251 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2253 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2258 gboolean is_fake_group;
2260 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2262 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2263 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2266 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2267 if (!group || is_fake_group)
2269 /* We can't alter fake groups */
2273 menu = gtk_menu_new ();
2276 if (priv->view_features &
2277 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2278 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2279 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2280 gtk_widget_show (item);
2281 g_signal_connect (item, "activate",
2282 G_CALLBACK (individual_view_group_rename_activate_cb),
2287 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2289 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2290 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2291 GTK_ICON_SIZE_MENU);
2292 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2293 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2294 gtk_widget_show (item);
2295 g_signal_connect (item, "activate",
2296 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2305 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2306 EmpathyIndividualView *view)
2308 FolksIndividual *individual;
2310 individual = empathy_individual_view_dup_selected (view);
2312 if (individual != NULL)
2317 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2320 ("Do you really want to remove the contact '%s'?"),
2321 folks_individual_get_alias (individual));
2322 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2325 EmpathyIndividualManager *manager;
2327 manager = empathy_individual_manager_dup_singleton ();
2328 empathy_individual_manager_remove (manager, individual, "");
2329 g_object_unref (G_OBJECT (manager));
2333 g_object_unref (individual);
2338 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2339 EmpathyLinkingDialog *linking_dialog,
2340 EmpathyIndividualView *self)
2342 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2343 EmpathyIndividualLinker *linker;
2345 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2346 empathy_individual_linker_set_search_text (linker,
2347 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2351 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2353 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2354 FolksIndividual *individual;
2355 GtkWidget *menu = NULL;
2358 EmpathyIndividualManagerFlags flags;
2360 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2362 individual = empathy_individual_view_dup_selected (view);
2363 if (individual == NULL)
2366 flags = empathy_individual_view_get_flags (view);
2368 menu = empathy_individual_menu_new (individual, priv->individual_features);
2370 /* Remove contact */
2371 if (priv->view_features &
2372 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE &&
2373 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2376 /* create the menu if required, or just add a separator */
2378 menu = gtk_menu_new ();
2381 item = gtk_separator_menu_item_new ();
2382 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2383 gtk_widget_show (item);
2387 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2388 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2389 GTK_ICON_SIZE_MENU);
2390 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2391 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2392 gtk_widget_show (item);
2393 g_signal_connect (item, "activate",
2394 G_CALLBACK (individual_view_remove_activate_cb), view);
2397 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2398 * set the live search text on the new linking dialogue to be the same as
2400 g_signal_connect (menu, "link-contacts-activated",
2401 (GCallback) individual_menu_link_contacts_activated_cb, view);
2403 g_object_unref (individual);
2409 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2410 EmpathyLiveSearch *search)
2412 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2414 /* remove old handlers if old search was not null */
2415 if (priv->search_widget != NULL)
2417 g_signal_handlers_disconnect_by_func (view,
2418 individual_view_start_search_cb, NULL);
2420 g_signal_handlers_disconnect_by_func (priv->search_widget,
2421 individual_view_search_text_notify_cb, view);
2422 g_signal_handlers_disconnect_by_func (priv->search_widget,
2423 individual_view_search_activate_cb, view);
2424 g_signal_handlers_disconnect_by_func (priv->search_widget,
2425 individual_view_search_key_navigation_cb, view);
2426 g_signal_handlers_disconnect_by_func (priv->search_widget,
2427 individual_view_search_hide_cb, view);
2428 g_signal_handlers_disconnect_by_func (priv->search_widget,
2429 individual_view_search_show_cb, view);
2430 g_object_unref (priv->search_widget);
2431 priv->search_widget = NULL;
2434 /* connect handlers if new search is not null */
2437 priv->search_widget = g_object_ref (search);
2439 g_signal_connect (view, "start-interactive-search",
2440 G_CALLBACK (individual_view_start_search_cb), NULL);
2442 g_signal_connect (priv->search_widget, "notify::text",
2443 G_CALLBACK (individual_view_search_text_notify_cb), view);
2444 g_signal_connect (priv->search_widget, "activate",
2445 G_CALLBACK (individual_view_search_activate_cb), view);
2446 g_signal_connect (priv->search_widget, "key-navigation",
2447 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2448 g_signal_connect (priv->search_widget, "hide",
2449 G_CALLBACK (individual_view_search_hide_cb), view);
2450 g_signal_connect (priv->search_widget, "show",
2451 G_CALLBACK (individual_view_search_show_cb), view);
2456 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2458 EmpathyIndividualViewPriv *priv;
2460 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2462 priv = GET_PRIV (self);
2464 return (priv->search_widget != NULL &&
2465 gtk_widget_get_visible (priv->search_widget));
2469 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2471 EmpathyIndividualViewPriv *priv;
2473 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2475 priv = GET_PRIV (self);
2477 return priv->show_offline;
2481 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2482 gboolean show_offline)
2484 EmpathyIndividualViewPriv *priv;
2486 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2488 priv = GET_PRIV (self);
2490 priv->show_offline = show_offline;
2492 g_object_notify (G_OBJECT (self), "show-offline");
2493 gtk_tree_model_filter_refilter (priv->filter);
2497 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2499 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2501 return GET_PRIV (self)->show_untrusted;
2505 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2506 gboolean show_untrusted)
2508 EmpathyIndividualViewPriv *priv;
2510 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2512 priv = GET_PRIV (self);
2514 priv->show_untrusted = show_untrusted;
2516 g_object_notify (G_OBJECT (self), "show-untrusted");
2517 gtk_tree_model_filter_refilter (priv->filter);
2520 EmpathyIndividualStore *
2521 empathy_individual_view_get_store (EmpathyIndividualView *self)
2523 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2525 return GET_PRIV (self)->store;
2529 empathy_individual_view_set_store (EmpathyIndividualView *self,
2530 EmpathyIndividualStore *store)
2532 EmpathyIndividualViewPriv *priv;
2534 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2535 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2537 priv = GET_PRIV (self);
2539 /* Destroy the old filter and remove the old store */
2540 if (priv->store != NULL)
2542 g_signal_handlers_disconnect_by_func (priv->store,
2543 individual_view_store_row_changed_cb, self);
2544 g_signal_handlers_disconnect_by_func (priv->store,
2545 individual_view_store_row_deleted_cb, self);
2547 g_signal_handlers_disconnect_by_func (priv->filter,
2548 individual_view_row_has_child_toggled_cb, self);
2550 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2553 tp_clear_object (&priv->filter);
2554 tp_clear_object (&priv->store);
2556 /* Set the new store */
2557 priv->store = store;
2561 g_object_ref (store);
2563 /* Create a new filter */
2564 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2565 GTK_TREE_MODEL (priv->store), NULL));
2566 gtk_tree_model_filter_set_visible_func (priv->filter,
2567 individual_view_filter_visible_func, self, NULL);
2569 g_signal_connect (priv->filter, "row-has-child-toggled",
2570 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2571 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2572 GTK_TREE_MODEL (priv->filter));
2574 tp_g_signal_connect_object (priv->store, "row-changed",
2575 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2576 tp_g_signal_connect_object (priv->store, "row-inserted",
2577 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2578 tp_g_signal_connect_object (priv->store, "row-deleted",
2579 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);