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 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
240 GError *error = NULL;
242 folks_groupable_change_group_finish (groupable, 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_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
389 groups_change_group_cb, NULL);
392 if (old_group != NULL && action == GDK_ACTION_MOVE)
394 folks_groupable_change_group (FOLKS_GROUPABLE (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 empathy_individual_view_start_search (view);
1232 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1234 EmpathyIndividualView *view)
1236 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1238 GtkTreeViewColumn *focus_column;
1239 GtkTreeModel *model;
1241 gboolean set_cursor = FALSE;
1243 gtk_tree_model_filter_refilter (priv->filter);
1245 /* Set cursor on the first contact. If it is already set on a group,
1246 * set it on its first child contact. Note that first child of a group
1247 * is its separator, that's why we actually set to the 2nd
1250 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1251 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1255 path = gtk_tree_path_new_from_string ("0:1");
1258 else if (gtk_tree_path_get_depth (path) < 2)
1262 gtk_tree_model_get_iter (model, &iter, path);
1263 gtk_tree_model_get (model, &iter,
1264 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1269 gtk_tree_path_down (path);
1270 gtk_tree_path_next (path);
1277 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1279 if (gtk_tree_model_get_iter (model, &iter, path))
1281 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1286 gtk_tree_path_free (path);
1290 individual_view_search_activate_cb (GtkWidget *search,
1291 EmpathyIndividualView *view)
1294 GtkTreeViewColumn *focus_column;
1296 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1299 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1300 gtk_tree_path_free (path);
1302 gtk_widget_hide (search);
1307 individual_view_search_key_navigation_cb (GtkWidget *search,
1309 EmpathyIndividualView *view)
1311 GdkEventKey *eventkey = ((GdkEventKey *) event);
1312 gboolean ret = FALSE;
1314 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
1316 GdkEvent *new_event;
1318 new_event = gdk_event_copy (event);
1319 gtk_widget_grab_focus (GTK_WIDGET (view));
1320 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1321 gtk_widget_grab_focus (search);
1323 gdk_event_free (new_event);
1330 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1331 EmpathyIndividualView *view)
1333 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1334 GtkTreeModel *model;
1335 GtkTreePath *cursor_path;
1337 gboolean valid = FALSE;
1339 /* block expand or collapse handlers, they would write the
1340 * expand or collapsed setting to file otherwise */
1341 g_signal_handlers_block_by_func (view,
1342 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1343 g_signal_handlers_block_by_func (view,
1344 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1346 /* restore which groups are expanded and which are not */
1347 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1348 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1349 valid; valid = gtk_tree_model_iter_next (model, &iter))
1355 gtk_tree_model_get (model, &iter,
1356 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1357 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1366 path = gtk_tree_model_get_path (model, &iter);
1367 if ((priv->view_features &
1368 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1369 empathy_contact_group_get_expanded (name))
1371 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1375 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1378 gtk_tree_path_free (path);
1382 /* unblock expand or collapse handlers */
1383 g_signal_handlers_unblock_by_func (view,
1384 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1385 g_signal_handlers_unblock_by_func (view,
1386 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1388 /* keep the selected contact visible */
1389 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1391 if (cursor_path != NULL)
1392 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1395 gtk_tree_path_free (cursor_path);
1399 individual_view_search_show_cb (EmpathyLiveSearch *search,
1400 EmpathyIndividualView *view)
1402 /* block expand or collapse handlers during expand all, they would
1403 * write the expand or collapsed setting to file otherwise */
1404 g_signal_handlers_block_by_func (view,
1405 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1407 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1409 g_signal_handlers_unblock_by_func (view,
1410 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1414 expand_idle_foreach_cb (GtkTreeModel *model,
1417 EmpathyIndividualView *self)
1419 EmpathyIndividualViewPriv *priv;
1421 gpointer should_expand;
1424 /* We only want groups */
1425 if (gtk_tree_path_get_depth (path) > 1)
1428 gtk_tree_model_get (model, iter,
1429 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1430 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1433 if (is_group == FALSE)
1439 priv = GET_PRIV (self);
1441 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1442 &should_expand) == TRUE)
1444 if (GPOINTER_TO_INT (should_expand) == TRUE)
1445 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1447 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1449 g_hash_table_remove (priv->expand_groups, name);
1458 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1460 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1462 DEBUG ("individual_view_expand_idle_cb");
1464 g_signal_handlers_block_by_func (self,
1465 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1466 g_signal_handlers_block_by_func (self,
1467 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1469 /* The store/filter could've been removed while we were in the idle queue */
1470 if (priv->filter != NULL)
1472 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1473 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1476 g_signal_handlers_unblock_by_func (self,
1477 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1478 g_signal_handlers_unblock_by_func (self,
1479 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1481 /* Empty the table of groups to expand/contract, since it may contain groups
1482 * which no longer exist in the tree view. This can happen after going
1483 * offline, for example. */
1484 g_hash_table_remove_all (priv->expand_groups);
1485 priv->expand_groups_idle_handler = 0;
1486 g_object_unref (self);
1492 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1495 EmpathyIndividualView *view)
1497 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1498 gboolean should_expand, is_group = FALSE;
1500 gpointer will_expand;
1502 gtk_tree_model_get (model, iter,
1503 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1504 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1507 if (!is_group || EMP_STR_EMPTY (name))
1513 should_expand = (priv->view_features &
1514 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1515 (priv->search_widget != NULL &&
1516 gtk_widget_get_visible (priv->search_widget)) ||
1517 empathy_contact_group_get_expanded (name);
1519 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1520 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1521 * a hash table, and expand or contract them as appropriate all at once in
1522 * an idle handler which iterates over all the group rows. */
1523 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1524 &will_expand) == FALSE ||
1525 GPOINTER_TO_INT (will_expand) != should_expand)
1527 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1528 GINT_TO_POINTER (should_expand));
1530 if (priv->expand_groups_idle_handler == 0)
1532 priv->expand_groups_idle_handler =
1533 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1534 g_object_ref (view));
1541 /* FIXME: This is a workaround for bgo#621076 */
1543 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1546 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1547 GtkTreeModel *model;
1548 GtkTreePath *parent_path;
1549 GtkTreeIter parent_iter;
1551 if (gtk_tree_path_get_depth (path) < 2)
1554 /* A group row is visible if and only if at least one if its child is visible.
1555 * So when a row is inserted/deleted/changed in the base model, that could
1556 * modify the visibility of its parent in the filter model.
1559 model = GTK_TREE_MODEL (priv->store);
1560 parent_path = gtk_tree_path_copy (path);
1561 gtk_tree_path_up (parent_path);
1562 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1564 /* This tells the filter to verify the visibility of that row, and
1565 * show/hide it if necessary */
1566 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1567 parent_path, &parent_iter);
1569 gtk_tree_path_free (parent_path);
1573 individual_view_store_row_changed_cb (GtkTreeModel *model,
1576 EmpathyIndividualView *view)
1578 individual_view_verify_group_visibility (view, path);
1582 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1584 EmpathyIndividualView *view)
1586 individual_view_verify_group_visibility (view, path);
1590 individual_view_is_visible_individual (EmpathyIndividualView *self,
1591 FolksIndividual *individual,
1593 gboolean is_searching)
1595 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1596 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1598 GList *personas, *l;
1600 /* We're only giving the visibility wrt filtering here, not things like
1602 if (priv->show_untrusted == FALSE &&
1603 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1608 if (is_searching == FALSE)
1609 return (priv->show_offline || is_online);
1611 /* check alias name */
1612 str = folks_individual_get_alias (individual);
1614 if (empathy_live_search_match (live, str))
1617 /* check contact id, remove the @server.com part */
1618 personas = folks_individual_get_personas (individual);
1619 for (l = personas; l; l = l->next)
1622 gchar *dup_str = NULL;
1625 if (!TPF_IS_PERSONA (l->data))
1628 str = folks_persona_get_display_id (l->data);
1629 p = strstr (str, "@");
1631 str = dup_str = g_strndup (str, p - str);
1633 visible = empathy_live_search_match (live, str);
1639 /* FIXME: Add more rules here, we could check phone numbers in
1640 * contact's vCard for example. */
1646 individual_view_filter_visible_func (GtkTreeModel *model,
1650 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1651 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1652 FolksIndividual *individual = NULL;
1653 gboolean is_group, is_separator, valid;
1654 GtkTreeIter child_iter;
1655 gboolean visible, is_online;
1656 gboolean is_searching = TRUE;
1658 if (priv->search_widget == NULL ||
1659 !gtk_widget_get_visible (priv->search_widget))
1660 is_searching = FALSE;
1662 gtk_tree_model_get (model, iter,
1663 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1664 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1665 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1666 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1669 if (individual != NULL)
1671 visible = individual_view_is_visible_individual (self, individual,
1672 is_online, is_searching);
1674 g_object_unref (individual);
1676 /* FIXME: Work around bgo#626552/bgo#621076 */
1677 if (visible == TRUE)
1679 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1680 individual_view_verify_group_visibility (self, path);
1681 gtk_tree_path_free (path);
1690 /* Not a contact, not a separator, must be a group */
1691 g_return_val_if_fail (is_group, FALSE);
1693 /* only show groups which are not empty */
1694 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1695 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1697 gtk_tree_model_get (model, &child_iter,
1698 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1699 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1702 if (individual == NULL)
1705 visible = individual_view_is_visible_individual (self, individual,
1706 is_online, is_searching);
1707 g_object_unref (individual);
1709 /* show group if it has at least one visible contact in it */
1710 if (visible == TRUE)
1718 individual_view_constructed (GObject *object)
1720 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1721 GtkCellRenderer *cell;
1722 GtkTreeViewColumn *col;
1727 "headers-visible", FALSE,
1728 "show-expanders", FALSE,
1731 col = gtk_tree_view_column_new ();
1734 cell = gtk_cell_renderer_pixbuf_new ();
1735 gtk_tree_view_column_pack_start (col, cell, FALSE);
1736 gtk_tree_view_column_set_cell_data_func (col, cell,
1737 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1747 cell = gtk_cell_renderer_pixbuf_new ();
1748 gtk_tree_view_column_pack_start (col, cell, FALSE);
1749 gtk_tree_view_column_set_cell_data_func (col, cell,
1750 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1762 cell = empathy_cell_renderer_text_new ();
1763 gtk_tree_view_column_pack_start (col, cell, TRUE);
1764 gtk_tree_view_column_set_cell_data_func (col, cell,
1765 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1767 gtk_tree_view_column_add_attribute (col, cell,
1768 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1769 gtk_tree_view_column_add_attribute (col, cell,
1770 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1771 gtk_tree_view_column_add_attribute (col, cell,
1772 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1773 gtk_tree_view_column_add_attribute (col, cell,
1774 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1775 gtk_tree_view_column_add_attribute (col, cell,
1776 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1777 gtk_tree_view_column_add_attribute (col, cell,
1778 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1780 /* Audio Call Icon */
1781 cell = empathy_cell_renderer_activatable_new ();
1782 gtk_tree_view_column_pack_start (col, cell, FALSE);
1783 gtk_tree_view_column_set_cell_data_func (col, cell,
1784 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1787 g_object_set (cell, "visible", FALSE, NULL);
1789 g_signal_connect (cell, "path-activated",
1790 G_CALLBACK (individual_view_call_activated_cb), view);
1793 cell = gtk_cell_renderer_pixbuf_new ();
1794 gtk_tree_view_column_pack_start (col, cell, FALSE);
1795 gtk_tree_view_column_set_cell_data_func (col, cell,
1796 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1808 cell = empathy_cell_renderer_expander_new ();
1809 gtk_tree_view_column_pack_end (col, cell, FALSE);
1810 gtk_tree_view_column_set_cell_data_func (col, cell,
1811 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1814 /* Actually add the column now we have added all cell renderers */
1815 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1818 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1820 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1823 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1825 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1831 individual_view_set_view_features (EmpathyIndividualView *view,
1832 EmpathyIndividualFeatureFlags features)
1834 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1835 gboolean has_tooltip;
1837 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1839 priv->view_features = features;
1841 /* Setting reorderable is a hack that gets us row previews as drag icons
1842 for free. We override all the drag handlers. It's tricky to get the
1843 position of the drag icon right in drag_begin. GtkTreeView has special
1844 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1847 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1848 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1850 /* Update DnD source/dest */
1851 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1853 gtk_drag_source_set (GTK_WIDGET (view),
1856 G_N_ELEMENTS (drag_types_source),
1857 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1861 gtk_drag_source_unset (GTK_WIDGET (view));
1865 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1867 gtk_drag_dest_set (GTK_WIDGET (view),
1868 GTK_DEST_DEFAULT_ALL,
1870 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1874 /* FIXME: URI could still be droped depending on FT feature */
1875 gtk_drag_dest_unset (GTK_WIDGET (view));
1878 /* Update has-tooltip */
1880 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1881 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1885 individual_view_dispose (GObject *object)
1887 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1888 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1890 tp_clear_object (&priv->store);
1891 tp_clear_object (&priv->filter);
1892 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1894 empathy_individual_view_set_live_search (view, NULL);
1896 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1900 individual_view_finalize (GObject *object)
1902 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1904 if (priv->expand_groups_idle_handler != 0)
1905 g_source_remove (priv->expand_groups_idle_handler);
1906 g_hash_table_destroy (priv->expand_groups);
1908 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1912 individual_view_get_property (GObject *object,
1917 EmpathyIndividualViewPriv *priv;
1919 priv = GET_PRIV (object);
1924 g_value_set_object (value, priv->store);
1926 case PROP_VIEW_FEATURES:
1927 g_value_set_flags (value, priv->view_features);
1929 case PROP_INDIVIDUAL_FEATURES:
1930 g_value_set_flags (value, priv->individual_features);
1932 case PROP_SHOW_OFFLINE:
1933 g_value_set_boolean (value, priv->show_offline);
1935 case PROP_SHOW_UNTRUSTED:
1936 g_value_set_boolean (value, priv->show_untrusted);
1939 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1945 individual_view_set_property (GObject *object,
1947 const GValue *value,
1950 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1951 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1956 empathy_individual_view_set_store (view, g_value_get_object (value));
1958 case PROP_VIEW_FEATURES:
1959 individual_view_set_view_features (view, g_value_get_flags (value));
1961 case PROP_INDIVIDUAL_FEATURES:
1962 priv->individual_features = g_value_get_flags (value);
1964 case PROP_SHOW_OFFLINE:
1965 empathy_individual_view_set_show_offline (view,
1966 g_value_get_boolean (value));
1968 case PROP_SHOW_UNTRUSTED:
1969 empathy_individual_view_set_show_untrusted (view,
1970 g_value_get_boolean (value));
1973 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1979 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1981 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1982 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1983 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1985 object_class->constructed = individual_view_constructed;
1986 object_class->dispose = individual_view_dispose;
1987 object_class->finalize = individual_view_finalize;
1988 object_class->get_property = individual_view_get_property;
1989 object_class->set_property = individual_view_set_property;
1991 widget_class->drag_data_received = individual_view_drag_data_received;
1992 widget_class->drag_drop = individual_view_drag_drop;
1993 widget_class->drag_begin = individual_view_drag_begin;
1994 widget_class->drag_data_get = individual_view_drag_data_get;
1995 widget_class->drag_end = individual_view_drag_end;
1996 widget_class->drag_motion = individual_view_drag_motion;
1998 /* We use the class method to let user of this widget to connect to
1999 * the signal and stop emission of the signal so the default handler
2000 * won't be called. */
2001 tree_view_class->row_activated = individual_view_row_activated;
2003 klass->drag_individual_received = real_drag_individual_received_cb;
2005 signals[DRAG_INDIVIDUAL_RECEIVED] =
2006 g_signal_new ("drag-individual-received",
2007 G_OBJECT_CLASS_TYPE (klass),
2009 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2011 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2012 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2013 G_TYPE_STRING, G_TYPE_STRING);
2015 signals[DRAG_PERSONA_RECEIVED] =
2016 g_signal_new ("drag-persona-received",
2017 G_OBJECT_CLASS_TYPE (klass),
2019 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2021 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2022 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2024 g_object_class_install_property (object_class,
2026 g_param_spec_object ("store",
2027 "The store of the view",
2028 "The store of the view",
2029 EMPATHY_TYPE_INDIVIDUAL_STORE,
2030 G_PARAM_READWRITE));
2031 g_object_class_install_property (object_class,
2033 g_param_spec_flags ("view-features",
2034 "Features of the view",
2035 "Flags for all enabled features",
2036 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2037 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2038 g_object_class_install_property (object_class,
2039 PROP_INDIVIDUAL_FEATURES,
2040 g_param_spec_flags ("individual-features",
2041 "Features of the individual menu",
2042 "Flags for all enabled features for the menu",
2043 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2044 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2045 g_object_class_install_property (object_class,
2047 g_param_spec_boolean ("show-offline",
2049 "Whether contact list should display "
2050 "offline contacts", FALSE, G_PARAM_READWRITE));
2051 g_object_class_install_property (object_class,
2052 PROP_SHOW_UNTRUSTED,
2053 g_param_spec_boolean ("show-untrusted",
2054 "Show Untrusted Individuals",
2055 "Whether the view should display untrusted individuals; "
2056 "those who could not be who they say they are.",
2057 TRUE, G_PARAM_READWRITE));
2059 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2063 empathy_individual_view_init (EmpathyIndividualView *view)
2065 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2066 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2070 priv->show_untrusted = TRUE;
2072 /* Get saved group states. */
2073 empathy_contact_groups_get_all ();
2075 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2076 (GDestroyNotify) g_free, NULL);
2078 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2079 empathy_individual_store_row_separator_func, NULL, NULL);
2081 /* Connect to tree view signals rather than override. */
2082 g_signal_connect (view, "button-press-event",
2083 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2084 g_signal_connect (view, "key-press-event",
2085 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2086 g_signal_connect (view, "row-expanded",
2087 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2088 GINT_TO_POINTER (TRUE));
2089 g_signal_connect (view, "row-collapsed",
2090 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2091 GINT_TO_POINTER (FALSE));
2092 g_signal_connect (view, "query-tooltip",
2093 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2096 EmpathyIndividualView *
2097 empathy_individual_view_new (EmpathyIndividualStore *store,
2098 EmpathyIndividualViewFeatureFlags view_features,
2099 EmpathyIndividualFeatureFlags individual_features)
2101 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2103 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2105 "individual-features", individual_features,
2106 "view-features", view_features, NULL);
2110 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2112 EmpathyIndividualViewPriv *priv;
2113 GtkTreeSelection *selection;
2115 GtkTreeModel *model;
2116 FolksIndividual *individual;
2118 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2120 priv = GET_PRIV (view);
2122 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2123 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2126 gtk_tree_model_get (model, &iter,
2127 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2132 EmpathyIndividualManagerFlags
2133 empathy_individual_view_get_flags (EmpathyIndividualView *view)
2135 EmpathyIndividualViewPriv *priv;
2136 GtkTreeSelection *selection;
2138 GtkTreeModel *model;
2139 EmpathyIndividualFeatureFlags flags;
2141 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2143 priv = GET_PRIV (view);
2145 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2146 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2149 gtk_tree_model_get (model, &iter,
2150 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2156 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2157 gboolean *is_fake_group)
2159 EmpathyIndividualViewPriv *priv;
2160 GtkTreeSelection *selection;
2162 GtkTreeModel *model;
2167 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2169 priv = GET_PRIV (view);
2171 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2172 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2175 gtk_tree_model_get (model, &iter,
2176 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2177 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2178 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2186 if (is_fake_group != NULL)
2187 *is_fake_group = fake;
2193 individual_view_remove_dialog_show (GtkWindow *parent,
2194 const gchar *message,
2195 const gchar *secondary_text)
2200 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2201 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2202 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2203 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2204 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2205 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2206 "%s", secondary_text);
2208 gtk_widget_show (dialog);
2210 res = gtk_dialog_run (GTK_DIALOG (dialog));
2211 gtk_widget_destroy (dialog);
2213 return (res == GTK_RESPONSE_YES);
2217 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2218 EmpathyIndividualView *view)
2222 group = empathy_individual_view_get_selected_group (view, NULL);
2229 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2231 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2232 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2235 EmpathyIndividualManager *manager =
2236 empathy_individual_manager_dup_singleton ();
2237 empathy_individual_manager_remove_group (manager, group);
2238 g_object_unref (G_OBJECT (manager));
2248 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2250 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2255 gboolean is_fake_group;
2257 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2259 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2260 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2263 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2264 if (!group || is_fake_group)
2266 /* We can't alter fake groups */
2270 menu = gtk_menu_new ();
2273 if (priv->view_features &
2274 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2275 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2276 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2277 gtk_widget_show (item);
2278 g_signal_connect (item, "activate",
2279 G_CALLBACK (individual_view_group_rename_activate_cb),
2284 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2286 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2287 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2288 GTK_ICON_SIZE_MENU);
2289 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2290 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2291 gtk_widget_show (item);
2292 g_signal_connect (item, "activate",
2293 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2302 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2303 EmpathyIndividualView *view)
2305 FolksIndividual *individual;
2307 individual = empathy_individual_view_dup_selected (view);
2309 if (individual != NULL)
2314 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2317 ("Do you really want to remove the contact '%s'?"),
2318 folks_individual_get_alias (individual));
2319 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2322 EmpathyIndividualManager *manager;
2324 manager = empathy_individual_manager_dup_singleton ();
2325 empathy_individual_manager_remove (manager, individual, "");
2326 g_object_unref (G_OBJECT (manager));
2330 g_object_unref (individual);
2335 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2336 EmpathyLinkingDialog *linking_dialog,
2337 EmpathyIndividualView *self)
2339 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2340 EmpathyIndividualLinker *linker;
2342 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2343 empathy_individual_linker_set_search_text (linker,
2344 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2348 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2350 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2351 FolksIndividual *individual;
2352 GtkWidget *menu = NULL;
2355 EmpathyIndividualManagerFlags flags;
2357 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2359 individual = empathy_individual_view_dup_selected (view);
2360 if (individual == NULL)
2363 flags = empathy_individual_view_get_flags (view);
2365 menu = empathy_individual_menu_new (individual, priv->individual_features);
2367 /* Remove contact */
2368 if (priv->view_features &
2369 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE &&
2370 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2373 /* create the menu if required, or just add a separator */
2375 menu = gtk_menu_new ();
2378 item = gtk_separator_menu_item_new ();
2379 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2380 gtk_widget_show (item);
2384 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2385 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2386 GTK_ICON_SIZE_MENU);
2387 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2388 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2389 gtk_widget_show (item);
2390 g_signal_connect (item, "activate",
2391 G_CALLBACK (individual_view_remove_activate_cb), view);
2394 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2395 * set the live search text on the new linking dialogue to be the same as
2397 g_signal_connect (menu, "link-contacts-activated",
2398 (GCallback) individual_menu_link_contacts_activated_cb, view);
2400 g_object_unref (individual);
2406 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2407 EmpathyLiveSearch *search)
2409 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2411 /* remove old handlers if old search was not null */
2412 if (priv->search_widget != NULL)
2414 g_signal_handlers_disconnect_by_func (view,
2415 individual_view_start_search_cb, NULL);
2417 g_signal_handlers_disconnect_by_func (priv->search_widget,
2418 individual_view_search_text_notify_cb, view);
2419 g_signal_handlers_disconnect_by_func (priv->search_widget,
2420 individual_view_search_activate_cb, view);
2421 g_signal_handlers_disconnect_by_func (priv->search_widget,
2422 individual_view_search_key_navigation_cb, view);
2423 g_signal_handlers_disconnect_by_func (priv->search_widget,
2424 individual_view_search_hide_cb, view);
2425 g_signal_handlers_disconnect_by_func (priv->search_widget,
2426 individual_view_search_show_cb, view);
2427 g_object_unref (priv->search_widget);
2428 priv->search_widget = NULL;
2431 /* connect handlers if new search is not null */
2434 priv->search_widget = g_object_ref (search);
2436 g_signal_connect (view, "start-interactive-search",
2437 G_CALLBACK (individual_view_start_search_cb), NULL);
2439 g_signal_connect (priv->search_widget, "notify::text",
2440 G_CALLBACK (individual_view_search_text_notify_cb), view);
2441 g_signal_connect (priv->search_widget, "activate",
2442 G_CALLBACK (individual_view_search_activate_cb), view);
2443 g_signal_connect (priv->search_widget, "key-navigation",
2444 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2445 g_signal_connect (priv->search_widget, "hide",
2446 G_CALLBACK (individual_view_search_hide_cb), view);
2447 g_signal_connect (priv->search_widget, "show",
2448 G_CALLBACK (individual_view_search_show_cb), view);
2453 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2455 EmpathyIndividualViewPriv *priv;
2457 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2459 priv = GET_PRIV (self);
2461 return (priv->search_widget != NULL &&
2462 gtk_widget_get_visible (priv->search_widget));
2466 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2468 EmpathyIndividualViewPriv *priv;
2470 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2472 priv = GET_PRIV (self);
2474 return priv->show_offline;
2478 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2479 gboolean show_offline)
2481 EmpathyIndividualViewPriv *priv;
2483 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2485 priv = GET_PRIV (self);
2487 priv->show_offline = show_offline;
2489 g_object_notify (G_OBJECT (self), "show-offline");
2490 gtk_tree_model_filter_refilter (priv->filter);
2494 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2496 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2498 return GET_PRIV (self)->show_untrusted;
2502 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2503 gboolean show_untrusted)
2505 EmpathyIndividualViewPriv *priv;
2507 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2509 priv = GET_PRIV (self);
2511 priv->show_untrusted = show_untrusted;
2513 g_object_notify (G_OBJECT (self), "show-untrusted");
2514 gtk_tree_model_filter_refilter (priv->filter);
2517 EmpathyIndividualStore *
2518 empathy_individual_view_get_store (EmpathyIndividualView *self)
2520 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2522 return GET_PRIV (self)->store;
2526 empathy_individual_view_set_store (EmpathyIndividualView *self,
2527 EmpathyIndividualStore *store)
2529 EmpathyIndividualViewPriv *priv;
2531 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2532 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2534 priv = GET_PRIV (self);
2536 /* Destroy the old filter and remove the old store */
2537 if (priv->store != NULL)
2539 g_signal_handlers_disconnect_by_func (priv->store,
2540 individual_view_store_row_changed_cb, self);
2541 g_signal_handlers_disconnect_by_func (priv->store,
2542 individual_view_store_row_deleted_cb, self);
2544 g_signal_handlers_disconnect_by_func (priv->filter,
2545 individual_view_row_has_child_toggled_cb, self);
2547 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2550 tp_clear_object (&priv->filter);
2551 tp_clear_object (&priv->store);
2553 /* Set the new store */
2554 priv->store = store;
2558 g_object_ref (store);
2560 /* Create a new filter */
2561 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2562 GTK_TREE_MODEL (priv->store), NULL));
2563 gtk_tree_model_filter_set_visible_func (priv->filter,
2564 individual_view_filter_visible_func, self, NULL);
2566 g_signal_connect (priv->filter, "row-has-child-toggled",
2567 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2568 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2569 GTK_TREE_MODEL (priv->filter));
2571 tp_g_signal_connect_object (priv->store, "row-changed",
2572 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2573 tp_g_signal_connect_object (priv->store, "row-inserted",
2574 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2575 tp_g_signal_connect_object (priv->store, "row-deleted",
2576 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2581 empathy_individual_view_start_search (EmpathyIndividualView *self)
2583 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2585 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2586 g_return_if_fail (priv->search_widget != NULL);
2588 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2589 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2591 gtk_widget_show (GTK_WIDGET (priv->search_widget));