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-cell-renderer-expander.h"
52 #include "empathy-cell-renderer-text.h"
53 #include "empathy-cell-renderer-activatable.h"
54 #include "empathy-ui-utils.h"
55 #include "empathy-gtk-enum-types.h"
56 #include "empathy-gtk-marshal.h"
58 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
59 #include <libempathy/empathy-debug.h>
61 /* Active users are those which have recently changed state
62 * (e.g. online, offline or from normal to a busy state).
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
68 EmpathyIndividualStore *store;
69 GtkTreeRowReference *drag_row;
70 EmpathyIndividualViewFeatureFlags view_features;
71 EmpathyIndividualFeatureFlags individual_features;
72 GtkWidget *tooltip_widget;
74 gboolean show_offline;
75 gboolean show_untrusted;
77 GtkTreeModelFilter *filter;
78 GtkWidget *search_widget;
80 guint expand_groups_idle_handler;
81 /* owned string (group name) -> bool (whether to expand/contract) */
82 GHashTable *expand_groups;
83 } EmpathyIndividualViewPriv;
87 EmpathyIndividualView *view;
94 EmpathyIndividualView *view;
95 FolksIndividual *individual;
104 PROP_INDIVIDUAL_FEATURES,
109 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
110 * specific EmpathyContacts (between/in/out of Individuals) */
113 DND_DRAG_TYPE_INDIVIDUAL_ID,
114 DND_DRAG_TYPE_PERSONA_ID,
115 DND_DRAG_TYPE_URI_LIST,
116 DND_DRAG_TYPE_STRING,
119 #define DRAG_TYPE(T,I) \
120 { (gchar *) T, 0, I }
122 static const GtkTargetEntry drag_types_dest[] = {
123 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
124 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
125 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
126 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
127 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
128 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
131 static const GtkTargetEntry drag_types_source[] = {
132 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
137 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
138 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
142 DRAG_INDIVIDUAL_RECEIVED,
143 DRAG_PERSONA_RECEIVED,
147 static guint signals[LAST_SIGNAL];
149 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
153 individual_view_tooltip_destroy_cb (GtkWidget *widget,
154 EmpathyIndividualView *view)
156 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
158 if (priv->tooltip_widget != NULL)
160 DEBUG ("Tooltip destroyed");
161 tp_clear_object (&priv->tooltip_widget);
166 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
169 gboolean keyboard_mode,
173 EmpathyIndividualViewPriv *priv;
174 FolksIndividual *individual;
178 static gint running = 0;
179 gboolean ret = FALSE;
181 priv = GET_PRIV (view);
183 /* Avoid an infinite loop. See GNOME bug #574377 */
189 /* Don't show the tooltip if there's already a popup menu */
190 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
193 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
194 keyboard_mode, &model, &path, &iter))
197 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
198 gtk_tree_path_free (path);
200 gtk_tree_model_get (model, &iter,
201 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
203 if (individual == NULL)
206 if (priv->tooltip_widget == NULL)
208 priv->tooltip_widget = empathy_individual_widget_new (individual,
209 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
210 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
211 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
212 g_object_ref (priv->tooltip_widget);
213 g_signal_connect (priv->tooltip_widget, "destroy",
214 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
215 gtk_widget_show (priv->tooltip_widget);
219 empathy_individual_widget_set_individual (
220 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
223 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
226 g_object_unref (individual);
234 groups_change_group_cb (GObject *source,
235 GAsyncResult *result,
238 FolksGroups *groups = FOLKS_GROUPS (source);
239 GError *error = NULL;
241 folks_groups_change_group_finish (groups, result, &error);
244 g_warning ("failed to change group: %s", error->message);
245 g_clear_error (&error);
250 group_can_be_modified (const gchar *name,
251 gboolean is_fake_group,
254 /* Real groups can always be modified */
258 /* The favorite fake group can be modified so users can
259 * add/remove favorites using DnD */
260 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
263 /* We can remove contacts from the 'ungrouped' fake group */
264 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
271 individual_view_individual_drag_received (GtkWidget *self,
272 GdkDragContext *context,
275 GtkSelectionData *selection)
277 EmpathyIndividualViewPriv *priv;
278 EmpathyIndividualManager *manager = NULL;
279 FolksIndividual *individual;
280 GtkTreePath *source_path;
281 const gchar *sel_data;
282 gchar *new_group = NULL;
283 gchar *old_group = NULL;
284 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
286 priv = GET_PRIV (self);
288 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
289 new_group = empathy_individual_store_get_parent_group (model, path,
290 NULL, &new_group_is_fake);
292 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
295 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
296 * feature. Otherwise, we just add the dropped contact to whichever group
297 * they were dropped in, and don't remove them from their old group. This
298 * allows for Individual views which shouldn't allow Individuals to have
299 * their groups changed, and also for dragging Individuals between Individual
301 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
302 priv->drag_row != NULL)
304 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
308 empathy_individual_store_get_parent_group (model, source_path,
309 NULL, &old_group_is_fake);
310 gtk_tree_path_free (source_path);
313 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
316 if (!tp_strdiff (old_group, new_group))
319 else if (priv->drag_row != NULL)
321 /* We don't allow changing Individuals' groups, and this Individual was
322 * dragged from another group in *this* Individual view, so we disallow
327 /* XXX: for contacts, we used to ensure the account, create the contact
328 * factory, and then wait on the contacts. But they should already be
329 * created by this point */
331 manager = empathy_individual_manager_dup_singleton ();
332 individual = empathy_individual_manager_lookup_member (manager, sel_data);
334 if (individual == NULL)
336 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
340 /* FIXME: We should probably wait for the cb before calling
343 /* Emit a signal notifying of the drag. We change the Individual's groups in
344 * the default signal handler. */
345 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
346 gdk_drag_context_get_selected_action (context), individual, new_group,
352 tp_clear_object (&manager);
360 real_drag_individual_received_cb (EmpathyIndividualView *self,
361 GdkDragAction action,
362 FolksIndividual *individual,
363 const gchar *new_group,
364 const gchar *old_group)
366 DEBUG ("individual %s dragged from '%s' to '%s'",
367 folks_individual_get_id (individual), old_group, new_group);
369 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
371 /* Mark contact as favourite */
372 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
376 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
378 /* Remove contact as favourite */
379 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
381 /* Don't try to remove it */
385 if (new_group != NULL)
387 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
388 groups_change_group_cb, NULL);
391 if (old_group != NULL && action == GDK_ACTION_MOVE)
393 folks_groups_change_group (FOLKS_GROUPS (individual), old_group,
394 FALSE, groups_change_group_cb, NULL);
399 individual_view_persona_drag_received (GtkWidget *self,
400 GdkDragContext *context,
403 GtkSelectionData *selection)
405 EmpathyIndividualViewPriv *priv;
406 EmpathyIndividualManager *manager = NULL;
407 FolksIndividual *individual = NULL;
408 FolksPersona *persona = NULL;
409 const gchar *persona_uid;
410 GList *individuals, *l;
411 gboolean retval = FALSE;
413 priv = GET_PRIV (self);
415 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
417 /* FIXME: This is slow, but the only way to find the Persona we're having
419 manager = empathy_individual_manager_dup_singleton ();
420 individuals = empathy_individual_manager_get_members (manager);
422 for (l = individuals; l != NULL; l = l->next)
426 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
428 for (p = personas; p != NULL; p = p->next)
430 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
433 persona = g_object_ref (p->data);
434 individual = g_object_ref (l->data);
441 g_list_free (individuals);
443 if (persona == NULL || individual == NULL)
445 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
449 /* Emit a signal notifying of the drag. We change the Individual's groups in
450 * the default signal handler. */
451 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
452 gdk_drag_context_get_selected_action (context), persona, individual,
456 tp_clear_object (&manager);
457 tp_clear_object (&persona);
458 tp_clear_object (&individual);
464 individual_view_file_drag_received (GtkWidget *view,
465 GdkDragContext *context,
468 GtkSelectionData *selection)
471 const gchar *sel_data;
472 FolksIndividual *individual;
473 EmpathyContact *contact;
475 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
477 gtk_tree_model_get_iter (model, &iter, path);
478 gtk_tree_model_get (model, &iter,
479 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
480 if (individual == NULL)
483 contact = empathy_contact_dup_from_folks_individual (individual);
484 empathy_send_file_from_uri_list (contact, sel_data);
486 g_object_unref (individual);
487 tp_clear_object (&contact);
493 individual_view_drag_data_received (GtkWidget *view,
494 GdkDragContext *context,
497 GtkSelectionData *selection,
503 GtkTreeViewDropPosition position;
505 gboolean success = TRUE;
507 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
509 /* Get destination group information. */
510 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
511 x, y, &path, &position);
516 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
518 success = individual_view_individual_drag_received (view,
519 context, model, path, selection);
521 else if (info == DND_DRAG_TYPE_PERSONA_ID)
523 success = individual_view_persona_drag_received (view, context, model,
526 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
528 success = individual_view_file_drag_received (view,
529 context, model, path, selection);
532 gtk_tree_path_free (path);
533 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
537 individual_view_drag_motion_cb (DragMotionData *data)
539 if (data->view != NULL)
541 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
542 g_object_remove_weak_pointer (G_OBJECT (data->view),
543 (gpointer *) &data->view);
546 data->timeout_id = 0;
552 individual_view_drag_motion (GtkWidget *widget,
553 GdkDragContext *context,
558 EmpathyIndividualViewPriv *priv;
562 static DragMotionData *dm = NULL;
565 gboolean is_different = FALSE;
566 gboolean cleanup = TRUE;
567 gboolean retval = TRUE;
569 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
570 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
572 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
573 x, y, &path, NULL, NULL, NULL);
575 cleanup &= (dm == NULL);
579 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
580 is_different = ((dm == NULL) || ((dm != NULL)
581 && gtk_tree_path_compare (dm->path, path) != 0));
588 /* Coordinates don't point to an actual row, so make sure the pointer
589 and highlighting don't indicate that a drag is possible.
591 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
592 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
595 target = gtk_drag_dest_find_target (widget, context, NULL);
596 gtk_tree_model_get_iter (model, &iter, path);
598 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
599 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
601 /* This is a file drag, and it can only be dropped on contacts,
603 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
604 * even if we have a valid target. */
605 FolksIndividual *individual = NULL;
606 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
608 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
610 gtk_tree_model_get (model, &iter,
611 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
615 if (individual != NULL)
617 EmpathyContact *contact = NULL;
619 contact = empathy_contact_dup_from_folks_individual (individual);
620 caps = empathy_contact_get_capabilities (contact);
622 tp_clear_object (&contact);
625 if (individual != NULL &&
626 folks_individual_is_online (individual) &&
627 (caps & EMPATHY_CAPABILITIES_FT))
629 gdk_drag_status (context, GDK_ACTION_COPY, time_);
630 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
631 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
635 gdk_drag_status (context, 0, time_);
636 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
640 if (individual != NULL)
641 g_object_unref (individual);
643 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
644 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
645 priv->drag_row == NULL)) ||
646 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
647 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
649 /* If target != GDK_NONE, then we have a contact (individual or persona)
650 drag. If we're pointing to a group, highlight it. Otherwise, if the
651 contact we're pointing to is in a group, highlight that. Otherwise,
652 set the drag position to before the first row for a drag into
653 the "non-group" at the top.
654 If it's an Individual:
655 We only highlight things if the contact is from a different
656 Individual view, or if this Individual view has
657 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
658 which don't have FEATURE_GROUPS_CHANGE, but do have
659 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
661 We only highlight things if we have FEATURE_PERSONA_DROP.
663 GtkTreeIter group_iter;
665 GtkTreePath *group_path;
666 gtk_tree_model_get (model, &iter,
667 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
674 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
675 gtk_tree_model_get (model, &group_iter,
676 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
680 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
681 group_path = gtk_tree_model_get_path (model, &group_iter);
682 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
683 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
684 gtk_tree_path_free (group_path);
688 group_path = gtk_tree_path_new_first ();
689 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
690 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
691 group_path, GTK_TREE_VIEW_DROP_BEFORE);
695 if (!is_different && !cleanup)
700 gtk_tree_path_free (dm->path);
703 g_source_remove (dm->timeout_id);
711 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
713 dm = g_new0 (DragMotionData, 1);
715 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
716 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
717 dm->path = gtk_tree_path_copy (path);
719 dm->timeout_id = g_timeout_add_seconds (1,
720 (GSourceFunc) individual_view_drag_motion_cb, dm);
727 individual_view_drag_begin (GtkWidget *widget,
728 GdkDragContext *context)
730 EmpathyIndividualViewPriv *priv;
731 GtkTreeSelection *selection;
736 priv = GET_PRIV (widget);
738 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
741 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
742 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
745 path = gtk_tree_model_get_path (model, &iter);
746 priv->drag_row = gtk_tree_row_reference_new (model, path);
747 gtk_tree_path_free (path);
751 individual_view_drag_data_get (GtkWidget *widget,
752 GdkDragContext *context,
753 GtkSelectionData *selection,
757 EmpathyIndividualViewPriv *priv;
758 GtkTreePath *src_path;
761 FolksIndividual *individual;
762 const gchar *individual_id;
764 priv = GET_PRIV (widget);
766 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
767 if (priv->drag_row == NULL)
770 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
771 if (src_path == NULL)
774 if (!gtk_tree_model_get_iter (model, &iter, src_path))
776 gtk_tree_path_free (src_path);
780 gtk_tree_path_free (src_path);
783 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
784 if (individual == NULL)
787 individual_id = folks_individual_get_id (individual);
789 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
791 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
792 (guchar *) individual_id, strlen (individual_id) + 1);
795 g_object_unref (individual);
799 individual_view_drag_end (GtkWidget *widget,
800 GdkDragContext *context)
802 EmpathyIndividualViewPriv *priv;
804 priv = GET_PRIV (widget);
806 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
811 gtk_tree_row_reference_free (priv->drag_row);
812 priv->drag_row = NULL;
817 individual_view_drag_drop (GtkWidget *widget,
818 GdkDragContext *drag_context,
828 EmpathyIndividualView *view;
834 individual_view_popup_menu_idle_cb (gpointer user_data)
836 MenuPopupData *data = user_data;
839 menu = empathy_individual_view_get_individual_menu (data->view);
841 menu = empathy_individual_view_get_group_menu (data->view);
845 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
846 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
848 gtk_widget_show (menu);
849 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
851 g_object_ref_sink (menu);
852 g_object_unref (menu);
855 g_slice_free (MenuPopupData, data);
861 individual_view_button_press_event_cb (EmpathyIndividualView *view,
862 GdkEventButton *event,
865 if (event->button == 3)
869 data = g_slice_new (MenuPopupData);
871 data->button = event->button;
872 data->time = event->time;
873 g_idle_add (individual_view_popup_menu_idle_cb, data);
880 individual_view_key_press_event_cb (EmpathyIndividualView *view,
884 if (event->keyval == GDK_Menu)
888 data = g_slice_new (MenuPopupData);
891 data->time = event->time;
892 g_idle_add (individual_view_popup_menu_idle_cb, data);
899 individual_view_row_activated (GtkTreeView *view,
901 GtkTreeViewColumn *column)
903 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
904 FolksIndividual *individual;
905 EmpathyContact *contact = NULL;
906 FolksPresenceType best_presence = FOLKS_PRESENCE_TYPE_UNSET;
911 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
914 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
915 gtk_tree_model_get_iter (model, &iter, path);
916 gtk_tree_model_get (model, &iter,
917 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
919 if (individual == NULL)
922 /* Determine which Persona to chat to, by choosing the most available one. */
923 personas = folks_individual_get_personas (individual);
924 for (l = personas; l != NULL; l = l->next)
926 FolksPresenceType presence;
928 if (!TPF_IS_PERSONA (l->data))
931 /* Only choose the contact if it has a higher presence than our current
932 * best choice of contact. */
933 presence = folks_presence_get_presence_type (FOLKS_PRESENCE (l->data));
934 if (folks_presence_typecmp (presence, best_presence) > 0)
936 TpContact *tp_contact;
938 tp_clear_object (&contact);
939 tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
940 contact = empathy_contact_dup_from_tp_contact (tp_contact);
941 empathy_contact_set_persona (contact, FOLKS_PERSONA (l->data));
943 best_presence = presence;
949 DEBUG ("Starting a chat");
951 empathy_dispatcher_chat_with_contact (contact,
952 gtk_get_current_event_time ());
955 g_object_unref (individual);
956 tp_clear_object (&contact);
960 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
961 const gchar *path_string,
962 EmpathyIndividualView *view)
964 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
968 FolksIndividual *individual;
969 GdkEventButton *event;
973 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
976 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
977 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
980 gtk_tree_model_get (model, &iter,
981 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
982 if (individual == NULL)
985 event = (GdkEventButton *) gtk_get_current_event ();
987 menu = gtk_menu_new ();
988 shell = GTK_MENU_SHELL (menu);
991 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
992 gtk_menu_shell_append (shell, item);
993 gtk_widget_show (item);
996 item = empathy_individual_video_call_menu_item_new (individual, NULL);
997 gtk_menu_shell_append (shell, item);
998 gtk_widget_show (item);
1000 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
1001 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1002 gtk_widget_show (menu);
1003 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1004 event->button, event->time);
1005 g_object_ref_sink (menu);
1006 g_object_unref (menu);
1008 g_object_unref (individual);
1012 individual_view_cell_set_background (EmpathyIndividualView *view,
1013 GtkCellRenderer *cell,
1020 style = gtk_widget_get_style (GTK_WIDGET (view));
1022 if (!is_group && is_active)
1024 color = style->bg[GTK_STATE_SELECTED];
1026 /* Here we take the current theme colour and add it to
1027 * the colour for white and average the two. This
1028 * gives a colour which is inline with the theme but
1031 color.red = (color.red + (style->white).red) / 2;
1032 color.green = (color.green + (style->white).green) / 2;
1033 color.blue = (color.blue + (style->white).blue) / 2;
1035 g_object_set (cell, "cell-background-gdk", &color, NULL);
1038 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1042 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1043 GtkCellRenderer *cell,
1044 GtkTreeModel *model,
1046 EmpathyIndividualView *view)
1052 gtk_tree_model_get (model, iter,
1053 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1054 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1055 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1058 "visible", !is_group,
1062 tp_clear_object (&pixbuf);
1064 individual_view_cell_set_background (view, cell, is_group, is_active);
1068 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1069 GtkCellRenderer *cell,
1070 GtkTreeModel *model,
1072 EmpathyIndividualView *view)
1074 GdkPixbuf *pixbuf = NULL;
1078 gtk_tree_model_get (model, iter,
1079 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1080 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1085 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1087 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1088 GTK_ICON_SIZE_MENU);
1090 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1092 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1093 GTK_ICON_SIZE_MENU);
1098 "visible", pixbuf != NULL,
1102 tp_clear_object (&pixbuf);
1108 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1109 GtkCellRenderer *cell,
1110 GtkTreeModel *model,
1112 EmpathyIndividualView *view)
1116 gboolean can_audio, can_video;
1118 gtk_tree_model_get (model, iter,
1119 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1120 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1121 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1122 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1125 "visible", !is_group && (can_audio || can_video),
1126 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1129 individual_view_cell_set_background (view, cell, is_group, is_active);
1133 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1134 GtkCellRenderer *cell,
1135 GtkTreeModel *model,
1137 EmpathyIndividualView *view)
1140 gboolean show_avatar;
1144 gtk_tree_model_get (model, iter,
1145 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1146 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1147 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1148 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1151 "visible", !is_group && show_avatar,
1155 tp_clear_object (&pixbuf);
1157 individual_view_cell_set_background (view, cell, is_group, is_active);
1161 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1162 GtkCellRenderer *cell,
1163 GtkTreeModel *model,
1165 EmpathyIndividualView *view)
1170 gtk_tree_model_get (model, iter,
1171 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1172 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1174 individual_view_cell_set_background (view, cell, is_group, is_active);
1178 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1179 GtkCellRenderer *cell,
1180 GtkTreeModel *model,
1182 EmpathyIndividualView *view)
1187 gtk_tree_model_get (model, iter,
1188 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1189 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1191 if (gtk_tree_model_iter_has_child (model, iter))
1194 gboolean row_expanded;
1196 path = gtk_tree_model_get_path (model, iter);
1198 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1199 (gtk_tree_view_column_get_tree_view (column)), path);
1200 gtk_tree_path_free (path);
1205 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1209 g_object_set (cell, "visible", FALSE, NULL);
1211 individual_view_cell_set_background (view, cell, is_group, is_active);
1215 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1220 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1221 GtkTreeModel *model;
1225 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1228 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1230 gtk_tree_model_get (model, iter,
1231 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1233 expanded = GPOINTER_TO_INT (user_data);
1234 empathy_contact_group_set_expanded (name, expanded);
1240 individual_view_start_search_cb (EmpathyIndividualView *view,
1243 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1245 if (priv->search_widget == NULL)
1248 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1249 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1251 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1257 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1259 EmpathyIndividualView *view)
1261 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1263 GtkTreeViewColumn *focus_column;
1264 GtkTreeModel *model;
1266 gboolean set_cursor = FALSE;
1268 gtk_tree_model_filter_refilter (priv->filter);
1270 /* Set cursor on the first contact. If it is already set on a group,
1271 * set it on its first child contact. Note that first child of a group
1272 * is its separator, that's why we actually set to the 2nd
1275 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1276 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1280 path = gtk_tree_path_new_from_string ("0:1");
1283 else if (gtk_tree_path_get_depth (path) < 2)
1287 gtk_tree_model_get_iter (model, &iter, path);
1288 gtk_tree_model_get (model, &iter,
1289 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1294 gtk_tree_path_down (path);
1295 gtk_tree_path_next (path);
1302 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1304 if (gtk_tree_model_get_iter (model, &iter, path))
1306 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1311 gtk_tree_path_free (path);
1315 individual_view_search_activate_cb (GtkWidget *search,
1316 EmpathyIndividualView *view)
1319 GtkTreeViewColumn *focus_column;
1321 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1324 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1325 gtk_tree_path_free (path);
1327 gtk_widget_hide (search);
1332 individual_view_search_key_navigation_cb (GtkWidget *search,
1334 EmpathyIndividualView *view)
1336 GdkEventKey *eventkey = ((GdkEventKey *) event);
1337 gboolean ret = FALSE;
1339 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1341 GdkEvent *new_event;
1343 new_event = gdk_event_copy (event);
1344 gtk_widget_grab_focus (GTK_WIDGET (view));
1345 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1346 gtk_widget_grab_focus (search);
1348 gdk_event_free (new_event);
1355 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1356 EmpathyIndividualView *view)
1358 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1359 GtkTreeModel *model;
1360 GtkTreePath *cursor_path;
1362 gboolean valid = FALSE;
1364 /* block expand or collapse handlers, they would write the
1365 * expand or collapsed setting to file otherwise */
1366 g_signal_handlers_block_by_func (view,
1367 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1368 g_signal_handlers_block_by_func (view,
1369 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1371 /* restore which groups are expanded and which are not */
1372 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1373 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1374 valid; valid = gtk_tree_model_iter_next (model, &iter))
1380 gtk_tree_model_get (model, &iter,
1381 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1382 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1391 path = gtk_tree_model_get_path (model, &iter);
1392 if ((priv->view_features &
1393 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1394 empathy_contact_group_get_expanded (name))
1396 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1400 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1403 gtk_tree_path_free (path);
1407 /* unblock expand or collapse handlers */
1408 g_signal_handlers_unblock_by_func (view,
1409 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1410 g_signal_handlers_unblock_by_func (view,
1411 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1413 /* keep the selected contact visible */
1414 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1416 if (cursor_path != NULL)
1417 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1420 gtk_tree_path_free (cursor_path);
1424 individual_view_search_show_cb (EmpathyLiveSearch *search,
1425 EmpathyIndividualView *view)
1427 /* block expand or collapse handlers during expand all, they would
1428 * write the expand or collapsed setting to file otherwise */
1429 g_signal_handlers_block_by_func (view,
1430 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1432 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1434 g_signal_handlers_unblock_by_func (view,
1435 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1439 expand_idle_foreach_cb (GtkTreeModel *model,
1442 EmpathyIndividualView *self)
1444 EmpathyIndividualViewPriv *priv;
1446 gpointer should_expand;
1449 /* We only want groups */
1450 if (gtk_tree_path_get_depth (path) > 1)
1453 gtk_tree_model_get (model, iter,
1454 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1455 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1458 if (is_group == FALSE)
1464 priv = GET_PRIV (self);
1466 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1467 &should_expand) == TRUE)
1469 if (GPOINTER_TO_INT (should_expand) == TRUE)
1470 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1472 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1474 g_hash_table_remove (priv->expand_groups, name);
1483 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1485 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1487 DEBUG ("individual_view_expand_idle_cb");
1489 g_signal_handlers_block_by_func (self,
1490 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1491 g_signal_handlers_block_by_func (self,
1492 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1494 /* The store/filter could've been removed while we were in the idle queue */
1495 if (priv->filter != NULL)
1497 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1498 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1501 g_signal_handlers_unblock_by_func (self,
1502 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1503 g_signal_handlers_unblock_by_func (self,
1504 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1506 /* Empty the table of groups to expand/contract, since it may contain groups
1507 * which no longer exist in the tree view. This can happen after going
1508 * offline, for example. */
1509 g_hash_table_remove_all (priv->expand_groups);
1510 priv->expand_groups_idle_handler = 0;
1511 g_object_unref (self);
1517 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1520 EmpathyIndividualView *view)
1522 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1523 gboolean should_expand, is_group = FALSE;
1525 gpointer will_expand;
1527 gtk_tree_model_get (model, iter,
1528 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1529 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1532 if (!is_group || EMP_STR_EMPTY (name))
1538 should_expand = (priv->view_features &
1539 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1540 (priv->search_widget != NULL &&
1541 gtk_widget_get_visible (priv->search_widget)) ||
1542 empathy_contact_group_get_expanded (name);
1544 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1545 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1546 * a hash table, and expand or contract them as appropriate all at once in
1547 * an idle handler which iterates over all the group rows. */
1548 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1549 &will_expand) == FALSE ||
1550 GPOINTER_TO_INT (will_expand) != should_expand)
1552 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1553 GINT_TO_POINTER (should_expand));
1555 if (priv->expand_groups_idle_handler == 0)
1557 priv->expand_groups_idle_handler =
1558 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1559 g_object_ref (view));
1566 /* FIXME: This is a workaround for bgo#621076 */
1568 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1571 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1572 GtkTreeModel *model;
1573 GtkTreePath *parent_path;
1574 GtkTreeIter parent_iter;
1576 if (gtk_tree_path_get_depth (path) < 2)
1579 /* A group row is visible if and only if at least one if its child is visible.
1580 * So when a row is inserted/deleted/changed in the base model, that could
1581 * modify the visibility of its parent in the filter model.
1584 model = GTK_TREE_MODEL (priv->store);
1585 parent_path = gtk_tree_path_copy (path);
1586 gtk_tree_path_up (parent_path);
1587 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1589 /* This tells the filter to verify the visibility of that row, and
1590 * show/hide it if necessary */
1591 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1592 parent_path, &parent_iter);
1594 gtk_tree_path_free (parent_path);
1598 individual_view_store_row_changed_cb (GtkTreeModel *model,
1601 EmpathyIndividualView *view)
1603 individual_view_verify_group_visibility (view, path);
1607 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1609 EmpathyIndividualView *view)
1611 individual_view_verify_group_visibility (view, path);
1615 individual_view_is_visible_individual (EmpathyIndividualView *self,
1616 FolksIndividual *individual,
1618 gboolean is_searching)
1620 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1621 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1623 GList *personas, *l;
1625 /* We're only giving the visibility wrt filtering here, not things like
1627 if (priv->show_untrusted == FALSE &&
1628 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1633 if (is_searching == FALSE)
1634 return (priv->show_offline || is_online);
1636 /* check alias name */
1637 str = folks_individual_get_alias (individual);
1639 if (empathy_live_search_match (live, str))
1642 /* check contact id, remove the @server.com part */
1643 personas = folks_individual_get_personas (individual);
1644 for (l = personas; l; l = l->next)
1647 gchar *dup_str = NULL;
1650 if (!TPF_IS_PERSONA (l->data))
1653 str = folks_persona_get_display_id (l->data);
1654 p = strstr (str, "@");
1656 str = dup_str = g_strndup (str, p - str);
1658 visible = empathy_live_search_match (live, str);
1664 /* FIXME: Add more rules here, we could check phone numbers in
1665 * contact's vCard for example. */
1671 individual_view_filter_visible_func (GtkTreeModel *model,
1675 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1676 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1677 FolksIndividual *individual = NULL;
1678 gboolean is_group, is_separator, valid;
1679 GtkTreeIter child_iter;
1680 gboolean visible, is_online;
1681 gboolean is_searching = TRUE;
1683 if (priv->search_widget == NULL ||
1684 !gtk_widget_get_visible (priv->search_widget))
1685 is_searching = FALSE;
1687 gtk_tree_model_get (model, iter,
1688 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1689 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1690 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1691 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1694 if (individual != NULL)
1696 visible = individual_view_is_visible_individual (self, individual,
1697 is_online, is_searching);
1699 g_object_unref (individual);
1701 /* FIXME: Work around bgo#626552/bgo#621076 */
1702 if (visible == TRUE)
1704 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1705 individual_view_verify_group_visibility (self, path);
1706 gtk_tree_path_free (path);
1715 /* Not a contact, not a separator, must be a group */
1716 g_return_val_if_fail (is_group, FALSE);
1718 /* only show groups which are not empty */
1719 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1720 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1722 gtk_tree_model_get (model, &child_iter,
1723 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1724 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1727 if (individual == NULL)
1730 visible = individual_view_is_visible_individual (self, individual,
1731 is_online, is_searching);
1732 g_object_unref (individual);
1734 /* show group if it has at least one visible contact in it */
1735 if (visible == TRUE)
1743 individual_view_constructed (GObject *object)
1745 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1746 GtkCellRenderer *cell;
1747 GtkTreeViewColumn *col;
1752 "headers-visible", FALSE,
1753 "show-expanders", FALSE,
1756 col = gtk_tree_view_column_new ();
1759 cell = gtk_cell_renderer_pixbuf_new ();
1760 gtk_tree_view_column_pack_start (col, cell, FALSE);
1761 gtk_tree_view_column_set_cell_data_func (col, cell,
1762 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1772 cell = gtk_cell_renderer_pixbuf_new ();
1773 gtk_tree_view_column_pack_start (col, cell, FALSE);
1774 gtk_tree_view_column_set_cell_data_func (col, cell,
1775 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1787 cell = empathy_cell_renderer_text_new ();
1788 gtk_tree_view_column_pack_start (col, cell, TRUE);
1789 gtk_tree_view_column_set_cell_data_func (col, cell,
1790 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1792 gtk_tree_view_column_add_attribute (col, cell,
1793 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1794 gtk_tree_view_column_add_attribute (col, cell,
1795 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1796 gtk_tree_view_column_add_attribute (col, cell,
1797 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1798 gtk_tree_view_column_add_attribute (col, cell,
1799 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1800 gtk_tree_view_column_add_attribute (col, cell,
1801 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1802 gtk_tree_view_column_add_attribute (col, cell,
1803 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1805 /* Audio Call Icon */
1806 cell = empathy_cell_renderer_activatable_new ();
1807 gtk_tree_view_column_pack_start (col, cell, FALSE);
1808 gtk_tree_view_column_set_cell_data_func (col, cell,
1809 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1812 g_object_set (cell, "visible", FALSE, NULL);
1814 g_signal_connect (cell, "path-activated",
1815 G_CALLBACK (individual_view_call_activated_cb), view);
1818 cell = gtk_cell_renderer_pixbuf_new ();
1819 gtk_tree_view_column_pack_start (col, cell, FALSE);
1820 gtk_tree_view_column_set_cell_data_func (col, cell,
1821 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1833 cell = empathy_cell_renderer_expander_new ();
1834 gtk_tree_view_column_pack_end (col, cell, FALSE);
1835 gtk_tree_view_column_set_cell_data_func (col, cell,
1836 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1839 /* Actually add the column now we have added all cell renderers */
1840 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1843 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1845 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1848 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1850 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1856 individual_view_set_view_features (EmpathyIndividualView *view,
1857 EmpathyIndividualFeatureFlags features)
1859 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1860 gboolean has_tooltip;
1862 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1864 priv->view_features = features;
1866 /* Setting reorderable is a hack that gets us row previews as drag icons
1867 for free. We override all the drag handlers. It's tricky to get the
1868 position of the drag icon right in drag_begin. GtkTreeView has special
1869 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1872 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1873 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1875 /* Update DnD source/dest */
1876 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1878 gtk_drag_source_set (GTK_WIDGET (view),
1881 G_N_ELEMENTS (drag_types_source),
1882 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1886 gtk_drag_source_unset (GTK_WIDGET (view));
1890 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1892 gtk_drag_dest_set (GTK_WIDGET (view),
1893 GTK_DEST_DEFAULT_ALL,
1895 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1899 /* FIXME: URI could still be droped depending on FT feature */
1900 gtk_drag_dest_unset (GTK_WIDGET (view));
1903 /* Update has-tooltip */
1905 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1906 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1910 individual_view_dispose (GObject *object)
1912 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1913 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1915 tp_clear_object (&priv->store);
1916 tp_clear_object (&priv->filter);
1917 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1919 empathy_individual_view_set_live_search (view, NULL);
1921 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1925 individual_view_finalize (GObject *object)
1927 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1929 if (priv->expand_groups_idle_handler != 0)
1930 g_source_remove (priv->expand_groups_idle_handler);
1931 g_hash_table_destroy (priv->expand_groups);
1933 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1937 individual_view_get_property (GObject *object,
1942 EmpathyIndividualViewPriv *priv;
1944 priv = GET_PRIV (object);
1949 g_value_set_object (value, priv->store);
1951 case PROP_VIEW_FEATURES:
1952 g_value_set_flags (value, priv->view_features);
1954 case PROP_INDIVIDUAL_FEATURES:
1955 g_value_set_flags (value, priv->individual_features);
1957 case PROP_SHOW_OFFLINE:
1958 g_value_set_boolean (value, priv->show_offline);
1960 case PROP_SHOW_UNTRUSTED:
1961 g_value_set_boolean (value, priv->show_untrusted);
1964 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1970 individual_view_set_property (GObject *object,
1972 const GValue *value,
1975 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1976 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1981 empathy_individual_view_set_store (view, g_value_get_object (value));
1983 case PROP_VIEW_FEATURES:
1984 individual_view_set_view_features (view, g_value_get_flags (value));
1986 case PROP_INDIVIDUAL_FEATURES:
1987 priv->individual_features = g_value_get_flags (value);
1989 case PROP_SHOW_OFFLINE:
1990 empathy_individual_view_set_show_offline (view,
1991 g_value_get_boolean (value));
1993 case PROP_SHOW_UNTRUSTED:
1994 empathy_individual_view_set_show_untrusted (view,
1995 g_value_get_boolean (value));
1998 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2004 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2006 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2007 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2008 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2010 object_class->constructed = individual_view_constructed;
2011 object_class->dispose = individual_view_dispose;
2012 object_class->finalize = individual_view_finalize;
2013 object_class->get_property = individual_view_get_property;
2014 object_class->set_property = individual_view_set_property;
2016 widget_class->drag_data_received = individual_view_drag_data_received;
2017 widget_class->drag_drop = individual_view_drag_drop;
2018 widget_class->drag_begin = individual_view_drag_begin;
2019 widget_class->drag_data_get = individual_view_drag_data_get;
2020 widget_class->drag_end = individual_view_drag_end;
2021 widget_class->drag_motion = individual_view_drag_motion;
2023 /* We use the class method to let user of this widget to connect to
2024 * the signal and stop emission of the signal so the default handler
2025 * won't be called. */
2026 tree_view_class->row_activated = individual_view_row_activated;
2028 klass->drag_individual_received = real_drag_individual_received_cb;
2030 signals[DRAG_INDIVIDUAL_RECEIVED] =
2031 g_signal_new ("drag-individual-received",
2032 G_OBJECT_CLASS_TYPE (klass),
2034 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2036 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2037 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2038 G_TYPE_STRING, G_TYPE_STRING);
2040 signals[DRAG_PERSONA_RECEIVED] =
2041 g_signal_new ("drag-persona-received",
2042 G_OBJECT_CLASS_TYPE (klass),
2044 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2046 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2047 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2049 g_object_class_install_property (object_class,
2051 g_param_spec_object ("store",
2052 "The store of the view",
2053 "The store of the view",
2054 EMPATHY_TYPE_INDIVIDUAL_STORE,
2055 G_PARAM_READWRITE));
2056 g_object_class_install_property (object_class,
2058 g_param_spec_flags ("view-features",
2059 "Features of the view",
2060 "Flags for all enabled features",
2061 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2062 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2063 g_object_class_install_property (object_class,
2064 PROP_INDIVIDUAL_FEATURES,
2065 g_param_spec_flags ("individual-features",
2066 "Features of the individual menu",
2067 "Flags for all enabled features for the menu",
2068 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2069 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2070 g_object_class_install_property (object_class,
2072 g_param_spec_boolean ("show-offline",
2074 "Whether contact list should display "
2075 "offline contacts", FALSE, G_PARAM_READWRITE));
2076 g_object_class_install_property (object_class,
2077 PROP_SHOW_UNTRUSTED,
2078 g_param_spec_boolean ("show-untrusted",
2079 "Show Untrusted Individuals",
2080 "Whether the view should display untrusted individuals; "
2081 "those who could not be who they say they are.",
2082 TRUE, G_PARAM_READWRITE));
2084 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2088 empathy_individual_view_init (EmpathyIndividualView *view)
2090 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2091 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2095 priv->show_untrusted = TRUE;
2097 /* Get saved group states. */
2098 empathy_contact_groups_get_all ();
2100 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2101 (GDestroyNotify) g_free, NULL);
2103 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2104 empathy_individual_store_row_separator_func, NULL, NULL);
2106 /* Connect to tree view signals rather than override. */
2107 g_signal_connect (view, "button-press-event",
2108 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2109 g_signal_connect (view, "key-press-event",
2110 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2111 g_signal_connect (view, "row-expanded",
2112 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2113 GINT_TO_POINTER (TRUE));
2114 g_signal_connect (view, "row-collapsed",
2115 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2116 GINT_TO_POINTER (FALSE));
2117 g_signal_connect (view, "query-tooltip",
2118 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2121 EmpathyIndividualView *
2122 empathy_individual_view_new (EmpathyIndividualStore *store,
2123 EmpathyIndividualViewFeatureFlags view_features,
2124 EmpathyIndividualFeatureFlags individual_features)
2126 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2128 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2130 "individual-features", individual_features,
2131 "view-features", view_features, NULL);
2135 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2137 EmpathyIndividualViewPriv *priv;
2138 GtkTreeSelection *selection;
2140 GtkTreeModel *model;
2141 FolksIndividual *individual;
2143 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2145 priv = GET_PRIV (view);
2147 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2148 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2151 gtk_tree_model_get (model, &iter,
2152 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2157 EmpathyIndividualManagerFlags
2158 empathy_individual_view_get_flags (EmpathyIndividualView *view)
2160 EmpathyIndividualViewPriv *priv;
2161 GtkTreeSelection *selection;
2163 GtkTreeModel *model;
2164 EmpathyIndividualFeatureFlags flags;
2166 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2168 priv = GET_PRIV (view);
2170 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2171 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2174 gtk_tree_model_get (model, &iter,
2175 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2181 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2182 gboolean *is_fake_group)
2184 EmpathyIndividualViewPriv *priv;
2185 GtkTreeSelection *selection;
2187 GtkTreeModel *model;
2192 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2194 priv = GET_PRIV (view);
2196 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2197 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2200 gtk_tree_model_get (model, &iter,
2201 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2202 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2203 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2211 if (is_fake_group != NULL)
2212 *is_fake_group = fake;
2218 individual_view_remove_dialog_show (GtkWindow *parent,
2219 const gchar *message,
2220 const gchar *secondary_text)
2225 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2226 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2227 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2228 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2229 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2230 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2231 "%s", secondary_text);
2233 gtk_widget_show (dialog);
2235 res = gtk_dialog_run (GTK_DIALOG (dialog));
2236 gtk_widget_destroy (dialog);
2238 return (res == GTK_RESPONSE_YES);
2242 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2243 EmpathyIndividualView *view)
2247 group = empathy_individual_view_get_selected_group (view, NULL);
2254 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2256 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2257 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2260 EmpathyIndividualManager *manager =
2261 empathy_individual_manager_dup_singleton ();
2262 empathy_individual_manager_remove_group (manager, group);
2263 g_object_unref (G_OBJECT (manager));
2273 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2275 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2280 gboolean is_fake_group;
2282 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2284 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2285 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2288 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2289 if (!group || is_fake_group)
2291 /* We can't alter fake groups */
2295 menu = gtk_menu_new ();
2298 if (priv->view_features &
2299 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2300 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2301 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2302 gtk_widget_show (item);
2303 g_signal_connect (item, "activate",
2304 G_CALLBACK (individual_view_group_rename_activate_cb),
2309 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2311 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2312 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2313 GTK_ICON_SIZE_MENU);
2314 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2315 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2316 gtk_widget_show (item);
2317 g_signal_connect (item, "activate",
2318 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2327 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2328 EmpathyIndividualView *view)
2330 FolksIndividual *individual;
2332 individual = empathy_individual_view_dup_selected (view);
2334 if (individual != NULL)
2339 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2342 ("Do you really want to remove the contact '%s'?"),
2343 folks_individual_get_alias (individual));
2344 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2347 EmpathyIndividualManager *manager;
2349 manager = empathy_individual_manager_dup_singleton ();
2350 empathy_individual_manager_remove (manager, individual, "");
2351 g_object_unref (G_OBJECT (manager));
2355 g_object_unref (individual);
2360 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2362 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2363 FolksIndividual *individual;
2364 GtkWidget *menu = NULL;
2367 EmpathyIndividualManagerFlags flags;
2369 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2371 individual = empathy_individual_view_dup_selected (view);
2372 if (individual == NULL)
2375 flags = empathy_individual_view_get_flags (view);
2377 menu = empathy_individual_menu_new (individual, priv->individual_features);
2379 /* Remove contact */
2380 if (priv->view_features &
2381 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE &&
2382 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2385 /* create the menu if required, or just add a separator */
2387 menu = gtk_menu_new ();
2390 item = gtk_separator_menu_item_new ();
2391 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2392 gtk_widget_show (item);
2396 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2397 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2398 GTK_ICON_SIZE_MENU);
2399 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2400 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2401 gtk_widget_show (item);
2402 g_signal_connect (item, "activate",
2403 G_CALLBACK (individual_view_remove_activate_cb), view);
2406 g_object_unref (individual);
2412 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2413 EmpathyLiveSearch *search)
2415 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2417 /* remove old handlers if old search was not null */
2418 if (priv->search_widget != NULL)
2420 g_signal_handlers_disconnect_by_func (view,
2421 individual_view_start_search_cb, NULL);
2423 g_signal_handlers_disconnect_by_func (priv->search_widget,
2424 individual_view_search_text_notify_cb, view);
2425 g_signal_handlers_disconnect_by_func (priv->search_widget,
2426 individual_view_search_activate_cb, view);
2427 g_signal_handlers_disconnect_by_func (priv->search_widget,
2428 individual_view_search_key_navigation_cb, view);
2429 g_signal_handlers_disconnect_by_func (priv->search_widget,
2430 individual_view_search_hide_cb, view);
2431 g_signal_handlers_disconnect_by_func (priv->search_widget,
2432 individual_view_search_show_cb, view);
2433 g_object_unref (priv->search_widget);
2434 priv->search_widget = NULL;
2437 /* connect handlers if new search is not null */
2440 priv->search_widget = g_object_ref (search);
2442 g_signal_connect (view, "start-interactive-search",
2443 G_CALLBACK (individual_view_start_search_cb), NULL);
2445 g_signal_connect (priv->search_widget, "notify::text",
2446 G_CALLBACK (individual_view_search_text_notify_cb), view);
2447 g_signal_connect (priv->search_widget, "activate",
2448 G_CALLBACK (individual_view_search_activate_cb), view);
2449 g_signal_connect (priv->search_widget, "key-navigation",
2450 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2451 g_signal_connect (priv->search_widget, "hide",
2452 G_CALLBACK (individual_view_search_hide_cb), view);
2453 g_signal_connect (priv->search_widget, "show",
2454 G_CALLBACK (individual_view_search_show_cb), view);
2459 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2461 EmpathyIndividualViewPriv *priv;
2463 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2465 priv = GET_PRIV (self);
2467 return (priv->search_widget != NULL &&
2468 gtk_widget_get_visible (priv->search_widget));
2472 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2474 EmpathyIndividualViewPriv *priv;
2476 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2478 priv = GET_PRIV (self);
2480 return priv->show_offline;
2484 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2485 gboolean show_offline)
2487 EmpathyIndividualViewPriv *priv;
2489 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2491 priv = GET_PRIV (self);
2493 priv->show_offline = show_offline;
2495 g_object_notify (G_OBJECT (self), "show-offline");
2496 gtk_tree_model_filter_refilter (priv->filter);
2500 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2502 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2504 return GET_PRIV (self)->show_untrusted;
2508 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2509 gboolean show_untrusted)
2511 EmpathyIndividualViewPriv *priv;
2513 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2515 priv = GET_PRIV (self);
2517 priv->show_untrusted = show_untrusted;
2519 g_object_notify (G_OBJECT (self), "show-untrusted");
2520 gtk_tree_model_filter_refilter (priv->filter);
2523 EmpathyIndividualStore *
2524 empathy_individual_view_get_store (EmpathyIndividualView *self)
2526 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2528 return GET_PRIV (self)->store;
2532 empathy_individual_view_set_store (EmpathyIndividualView *self,
2533 EmpathyIndividualStore *store)
2535 EmpathyIndividualViewPriv *priv;
2537 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2538 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2540 priv = GET_PRIV (self);
2542 /* Destroy the old filter and remove the old store */
2543 if (priv->store != NULL)
2545 g_signal_handlers_disconnect_by_func (priv->store,
2546 individual_view_store_row_changed_cb, self);
2547 g_signal_handlers_disconnect_by_func (priv->store,
2548 individual_view_store_row_deleted_cb, self);
2550 g_signal_handlers_disconnect_by_func (priv->filter,
2551 individual_view_row_has_child_toggled_cb, self);
2553 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2556 tp_clear_object (&priv->filter);
2557 tp_clear_object (&priv->store);
2559 /* Set the new store */
2560 priv->store = store;
2564 g_object_ref (store);
2566 /* Create a new filter */
2567 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2568 GTK_TREE_MODEL (priv->store), NULL));
2569 gtk_tree_model_filter_set_visible_func (priv->filter,
2570 individual_view_filter_visible_func, self, NULL);
2572 g_signal_connect (priv->filter, "row-has-child-toggled",
2573 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2574 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2575 GTK_TREE_MODEL (priv->filter));
2577 tp_g_signal_connect_object (priv->store, "row-changed",
2578 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2579 tp_g_signal_connect_object (priv->store, "row-inserted",
2580 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2581 tp_g_signal_connect_object (priv->store, "row-deleted",
2582 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);