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;
76 GtkTreeModelFilter *filter;
77 GtkWidget *search_widget;
79 guint expand_groups_idle_handler;
80 /* owned string (group name) -> bool (whether to expand/contract) */
81 GHashTable *expand_groups;
82 } EmpathyIndividualViewPriv;
86 EmpathyIndividualView *view;
93 EmpathyIndividualView *view;
94 FolksIndividual *individual;
103 PROP_INDIVIDUAL_FEATURES,
107 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
108 * specific EmpathyContacts (between/in/out of Individuals) */
111 DND_DRAG_TYPE_INDIVIDUAL_ID,
112 DND_DRAG_TYPE_PERSONA_ID,
113 DND_DRAG_TYPE_URI_LIST,
114 DND_DRAG_TYPE_STRING,
117 #define DRAG_TYPE(T,I) \
118 { (gchar *) T, 0, I }
120 static const GtkTargetEntry drag_types_dest[] = {
121 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
122 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
123 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
124 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
125 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
126 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
129 static const GtkTargetEntry drag_types_source[] = {
130 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
135 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
136 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
140 DRAG_INDIVIDUAL_RECEIVED,
141 DRAG_PERSONA_RECEIVED,
145 static guint signals[LAST_SIGNAL];
147 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
151 individual_view_tooltip_destroy_cb (GtkWidget *widget,
152 EmpathyIndividualView *view)
154 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
156 if (priv->tooltip_widget != NULL)
158 DEBUG ("Tooltip destroyed");
159 tp_clear_object (&priv->tooltip_widget);
164 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
167 gboolean keyboard_mode,
171 EmpathyIndividualViewPriv *priv;
172 FolksIndividual *individual;
176 static gint running = 0;
177 gboolean ret = FALSE;
179 priv = GET_PRIV (view);
181 /* Avoid an infinite loop. See GNOME bug #574377 */
187 /* Don't show the tooltip if there's already a popup menu */
188 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
191 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
192 keyboard_mode, &model, &path, &iter))
195 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
196 gtk_tree_path_free (path);
198 gtk_tree_model_get (model, &iter,
199 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
201 if (individual == NULL)
204 if (priv->tooltip_widget == NULL)
206 priv->tooltip_widget = empathy_individual_widget_new (individual,
207 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
208 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
209 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
210 g_object_ref (priv->tooltip_widget);
211 g_signal_connect (priv->tooltip_widget, "destroy",
212 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
213 gtk_widget_show (priv->tooltip_widget);
217 empathy_individual_widget_set_individual (
218 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
221 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
224 g_object_unref (individual);
232 groups_change_group_cb (GObject *source,
233 GAsyncResult *result,
236 FolksGroups *groups = FOLKS_GROUPS (source);
237 GError *error = NULL;
239 folks_groups_change_group_finish (groups, result, &error);
242 g_warning ("failed to change group: %s", error->message);
243 g_clear_error (&error);
248 group_can_be_modified (const gchar *name,
249 gboolean is_fake_group,
252 /* Real groups can always be modified */
256 /* The favorite fake group can be modified so users can
257 * add/remove favorites using DnD */
258 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
261 /* We can remove contacts from the 'ungrouped' fake group */
262 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
269 individual_view_individual_drag_received (GtkWidget *self,
270 GdkDragContext *context,
273 GtkSelectionData *selection)
275 EmpathyIndividualViewPriv *priv;
276 EmpathyIndividualManager *manager = NULL;
277 FolksIndividual *individual;
278 GtkTreePath *source_path;
279 const gchar *sel_data;
280 gchar *new_group = NULL;
281 gchar *old_group = NULL;
282 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
284 priv = GET_PRIV (self);
286 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
287 new_group = empathy_individual_store_get_parent_group (model, path,
288 NULL, &new_group_is_fake);
290 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
293 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
294 * feature. Otherwise, we just add the dropped contact to whichever group
295 * they were dropped in, and don't remove them from their old group. This
296 * allows for Individual views which shouldn't allow Individuals to have
297 * their groups changed, and also for dragging Individuals between Individual
299 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
300 priv->drag_row != NULL)
302 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
306 empathy_individual_store_get_parent_group (model, source_path,
307 NULL, &old_group_is_fake);
308 gtk_tree_path_free (source_path);
311 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
314 if (!tp_strdiff (old_group, new_group))
317 else if (priv->drag_row != NULL)
319 /* We don't allow changing Individuals' groups, and this Individual was
320 * dragged from another group in *this* Individual view, so we disallow
325 /* XXX: for contacts, we used to ensure the account, create the contact
326 * factory, and then wait on the contacts. But they should already be
327 * created by this point */
329 manager = empathy_individual_manager_dup_singleton ();
330 individual = empathy_individual_manager_lookup_member (manager, sel_data);
332 if (individual == NULL)
334 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
338 /* FIXME: We should probably wait for the cb before calling
341 /* Emit a signal notifying of the drag. We change the Individual's groups in
342 * the default signal handler. */
343 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
344 gdk_drag_context_get_selected_action (context), individual, new_group,
350 tp_clear_object (&manager);
358 real_drag_individual_received_cb (EmpathyIndividualView *self,
359 GdkDragAction action,
360 FolksIndividual *individual,
361 const gchar *new_group,
362 const gchar *old_group)
364 DEBUG ("individual %s dragged from '%s' to '%s'",
365 folks_individual_get_id (individual), old_group, new_group);
367 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
369 /* Mark contact as favourite */
370 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
374 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
376 /* Remove contact as favourite */
377 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
379 /* Don't try to remove it */
383 if (new_group != NULL)
385 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
386 groups_change_group_cb, NULL);
389 if (old_group != NULL && action == GDK_ACTION_MOVE)
391 folks_groups_change_group (FOLKS_GROUPS (individual), old_group,
392 FALSE, groups_change_group_cb, NULL);
397 individual_view_persona_drag_received (GtkWidget *self,
398 GdkDragContext *context,
401 GtkSelectionData *selection)
403 EmpathyIndividualViewPriv *priv;
404 EmpathyIndividualManager *manager = NULL;
405 FolksIndividual *individual = NULL;
406 FolksPersona *persona = NULL;
407 const gchar *persona_uid;
408 GList *individuals, *l;
409 gboolean retval = FALSE;
411 priv = GET_PRIV (self);
413 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
415 /* FIXME: This is slow, but the only way to find the Persona we're having
417 manager = empathy_individual_manager_dup_singleton ();
418 individuals = empathy_individual_manager_get_members (manager);
420 for (l = individuals; l != NULL; l = l->next)
424 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
426 for (p = personas; p != NULL; p = p->next)
428 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
431 persona = g_object_ref (p->data);
432 individual = g_object_ref (l->data);
439 g_list_free (individuals);
441 if (persona == NULL || individual == NULL)
443 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
447 /* Emit a signal notifying of the drag. We change the Individual's groups in
448 * the default signal handler. */
449 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
450 gdk_drag_context_get_selected_action (context), persona, individual,
454 tp_clear_object (&manager);
455 tp_clear_object (&persona);
456 tp_clear_object (&individual);
462 individual_view_file_drag_received (GtkWidget *view,
463 GdkDragContext *context,
466 GtkSelectionData *selection)
469 const gchar *sel_data;
470 FolksIndividual *individual;
471 EmpathyContact *contact;
473 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
475 gtk_tree_model_get_iter (model, &iter, path);
476 gtk_tree_model_get (model, &iter,
477 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
478 if (individual == NULL)
481 contact = empathy_contact_dup_from_folks_individual (individual);
482 empathy_send_file_from_uri_list (contact, sel_data);
484 g_object_unref (individual);
485 tp_clear_object (&contact);
491 individual_view_drag_data_received (GtkWidget *view,
492 GdkDragContext *context,
495 GtkSelectionData *selection,
501 GtkTreeViewDropPosition position;
503 gboolean success = TRUE;
505 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
507 /* Get destination group information. */
508 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
509 x, y, &path, &position);
514 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
516 success = individual_view_individual_drag_received (view,
517 context, model, path, selection);
519 else if (info == DND_DRAG_TYPE_PERSONA_ID)
521 success = individual_view_persona_drag_received (view, context, model,
524 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
526 success = individual_view_file_drag_received (view,
527 context, model, path, selection);
530 gtk_tree_path_free (path);
531 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
535 individual_view_drag_motion_cb (DragMotionData *data)
537 if (data->view != NULL)
539 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
540 g_object_remove_weak_pointer (G_OBJECT (data->view),
541 (gpointer *) &data->view);
544 data->timeout_id = 0;
550 individual_view_drag_motion (GtkWidget *widget,
551 GdkDragContext *context,
556 EmpathyIndividualViewPriv *priv;
560 static DragMotionData *dm = NULL;
563 gboolean is_different = FALSE;
564 gboolean cleanup = TRUE;
565 gboolean retval = TRUE;
567 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
568 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
570 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
571 x, y, &path, NULL, NULL, NULL);
573 cleanup &= (dm == NULL);
577 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
578 is_different = ((dm == NULL) || ((dm != NULL)
579 && gtk_tree_path_compare (dm->path, path) != 0));
586 /* Coordinates don't point to an actual row, so make sure the pointer
587 and highlighting don't indicate that a drag is possible.
589 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
590 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
593 target = gtk_drag_dest_find_target (widget, context, NULL);
594 gtk_tree_model_get_iter (model, &iter, path);
596 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
597 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
599 /* This is a file drag, and it can only be dropped on contacts,
601 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
602 * even if we have a valid target. */
603 FolksIndividual *individual = NULL;
604 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
606 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
608 gtk_tree_model_get (model, &iter,
609 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
613 if (individual != NULL)
615 EmpathyContact *contact = NULL;
617 contact = empathy_contact_dup_from_folks_individual (individual);
618 caps = empathy_contact_get_capabilities (contact);
620 tp_clear_object (&contact);
623 if (individual != NULL &&
624 folks_individual_is_online (individual) &&
625 (caps & EMPATHY_CAPABILITIES_FT))
627 gdk_drag_status (context, GDK_ACTION_COPY, time_);
628 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
629 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
633 gdk_drag_status (context, 0, time_);
634 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
638 if (individual != NULL)
639 g_object_unref (individual);
641 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
642 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
643 priv->drag_row == NULL)) ||
644 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
645 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
647 /* If target != GDK_NONE, then we have a contact (individual or persona)
648 drag. If we're pointing to a group, highlight it. Otherwise, if the
649 contact we're pointing to is in a group, highlight that. Otherwise,
650 set the drag position to before the first row for a drag into
651 the "non-group" at the top.
652 If it's an Individual:
653 We only highlight things if the contact is from a different
654 Individual view, or if this Individual view has
655 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
656 which don't have FEATURE_GROUPS_CHANGE, but do have
657 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
659 We only highlight things if we have FEATURE_PERSONA_DROP.
661 GtkTreeIter group_iter;
663 GtkTreePath *group_path;
664 gtk_tree_model_get (model, &iter,
665 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
672 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
673 gtk_tree_model_get (model, &group_iter,
674 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
678 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
679 group_path = gtk_tree_model_get_path (model, &group_iter);
680 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
681 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
682 gtk_tree_path_free (group_path);
686 group_path = gtk_tree_path_new_first ();
687 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
688 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
689 group_path, GTK_TREE_VIEW_DROP_BEFORE);
693 if (!is_different && !cleanup)
698 gtk_tree_path_free (dm->path);
701 g_source_remove (dm->timeout_id);
709 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
711 dm = g_new0 (DragMotionData, 1);
713 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
714 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
715 dm->path = gtk_tree_path_copy (path);
717 dm->timeout_id = g_timeout_add_seconds (1,
718 (GSourceFunc) individual_view_drag_motion_cb, dm);
725 individual_view_drag_begin (GtkWidget *widget,
726 GdkDragContext *context)
728 EmpathyIndividualViewPriv *priv;
729 GtkTreeSelection *selection;
734 priv = GET_PRIV (widget);
736 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
739 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
740 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
743 path = gtk_tree_model_get_path (model, &iter);
744 priv->drag_row = gtk_tree_row_reference_new (model, path);
745 gtk_tree_path_free (path);
749 individual_view_drag_data_get (GtkWidget *widget,
750 GdkDragContext *context,
751 GtkSelectionData *selection,
755 EmpathyIndividualViewPriv *priv;
756 GtkTreePath *src_path;
759 FolksIndividual *individual;
760 const gchar *individual_id;
762 priv = GET_PRIV (widget);
764 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
765 if (priv->drag_row == NULL)
768 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
769 if (src_path == NULL)
772 if (!gtk_tree_model_get_iter (model, &iter, src_path))
774 gtk_tree_path_free (src_path);
778 gtk_tree_path_free (src_path);
781 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
782 if (individual == NULL)
785 individual_id = folks_individual_get_id (individual);
787 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
789 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
790 (guchar *) individual_id, strlen (individual_id) + 1);
793 g_object_unref (individual);
797 individual_view_drag_end (GtkWidget *widget,
798 GdkDragContext *context)
800 EmpathyIndividualViewPriv *priv;
802 priv = GET_PRIV (widget);
804 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
809 gtk_tree_row_reference_free (priv->drag_row);
810 priv->drag_row = NULL;
815 individual_view_drag_drop (GtkWidget *widget,
816 GdkDragContext *drag_context,
826 EmpathyIndividualView *view;
832 individual_view_popup_menu_idle_cb (gpointer user_data)
834 MenuPopupData *data = user_data;
837 menu = empathy_individual_view_get_individual_menu (data->view);
839 menu = empathy_individual_view_get_group_menu (data->view);
843 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
844 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
846 gtk_widget_show (menu);
847 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
849 g_object_ref_sink (menu);
850 g_object_unref (menu);
853 g_slice_free (MenuPopupData, data);
859 individual_view_button_press_event_cb (EmpathyIndividualView *view,
860 GdkEventButton *event,
863 if (event->button == 3)
867 data = g_slice_new (MenuPopupData);
869 data->button = event->button;
870 data->time = event->time;
871 g_idle_add (individual_view_popup_menu_idle_cb, data);
878 individual_view_key_press_event_cb (EmpathyIndividualView *view,
882 if (event->keyval == GDK_Menu)
886 data = g_slice_new (MenuPopupData);
889 data->time = event->time;
890 g_idle_add (individual_view_popup_menu_idle_cb, data);
897 individual_view_row_activated (GtkTreeView *view,
899 GtkTreeViewColumn *column)
901 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
902 FolksIndividual *individual;
903 EmpathyContact *contact = NULL;
907 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
910 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
911 gtk_tree_model_get_iter (model, &iter, path);
912 gtk_tree_model_get (model, &iter,
913 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
915 if (individual == NULL)
918 contact = empathy_contact_dup_from_folks_individual (individual);
921 DEBUG ("Starting a chat");
923 empathy_dispatcher_chat_with_contact (contact,
924 gtk_get_current_event_time ());
927 g_object_unref (individual);
928 tp_clear_object (&contact);
932 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
933 const gchar *path_string,
934 EmpathyIndividualView *view)
939 FolksIndividual *individual;
940 GdkEventButton *event;
944 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
945 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
948 gtk_tree_model_get (model, &iter,
949 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
950 if (individual == NULL)
953 event = (GdkEventButton *) gtk_get_current_event ();
955 menu = gtk_menu_new ();
956 shell = GTK_MENU_SHELL (menu);
959 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
960 gtk_menu_shell_append (shell, item);
961 gtk_widget_show (item);
964 item = empathy_individual_video_call_menu_item_new (individual, NULL);
965 gtk_menu_shell_append (shell, item);
966 gtk_widget_show (item);
968 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
969 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
970 gtk_widget_show (menu);
971 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
972 event->button, event->time);
973 g_object_ref_sink (menu);
974 g_object_unref (menu);
976 g_object_unref (individual);
980 individual_view_cell_set_background (EmpathyIndividualView *view,
981 GtkCellRenderer *cell,
988 style = gtk_widget_get_style (GTK_WIDGET (view));
990 if (!is_group && is_active)
992 color = style->bg[GTK_STATE_SELECTED];
994 /* Here we take the current theme colour and add it to
995 * the colour for white and average the two. This
996 * gives a colour which is inline with the theme but
999 color.red = (color.red + (style->white).red) / 2;
1000 color.green = (color.green + (style->white).green) / 2;
1001 color.blue = (color.blue + (style->white).blue) / 2;
1003 g_object_set (cell, "cell-background-gdk", &color, NULL);
1006 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1010 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1011 GtkCellRenderer *cell,
1012 GtkTreeModel *model,
1014 EmpathyIndividualView *view)
1020 gtk_tree_model_get (model, iter,
1021 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1022 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1023 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1026 "visible", !is_group,
1030 tp_clear_object (&pixbuf);
1032 individual_view_cell_set_background (view, cell, is_group, is_active);
1036 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1037 GtkCellRenderer *cell,
1038 GtkTreeModel *model,
1040 EmpathyIndividualView *view)
1042 GdkPixbuf *pixbuf = NULL;
1046 gtk_tree_model_get (model, iter,
1047 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1048 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1053 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1055 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1056 GTK_ICON_SIZE_MENU);
1058 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1060 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1061 GTK_ICON_SIZE_MENU);
1066 "visible", pixbuf != NULL,
1070 tp_clear_object (&pixbuf);
1076 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1077 GtkCellRenderer *cell,
1078 GtkTreeModel *model,
1080 EmpathyIndividualView *view)
1084 gboolean can_audio, can_video;
1086 gtk_tree_model_get (model, iter,
1087 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1088 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1089 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1090 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1093 "visible", !is_group && (can_audio || can_video),
1094 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1097 individual_view_cell_set_background (view, cell, is_group, is_active);
1101 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1102 GtkCellRenderer *cell,
1103 GtkTreeModel *model,
1105 EmpathyIndividualView *view)
1108 gboolean show_avatar;
1112 gtk_tree_model_get (model, iter,
1113 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1114 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1115 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1116 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1119 "visible", !is_group && show_avatar,
1123 tp_clear_object (&pixbuf);
1125 individual_view_cell_set_background (view, cell, is_group, is_active);
1129 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1130 GtkCellRenderer *cell,
1131 GtkTreeModel *model,
1133 EmpathyIndividualView *view)
1138 gtk_tree_model_get (model, iter,
1139 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1140 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1142 individual_view_cell_set_background (view, cell, is_group, is_active);
1146 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1147 GtkCellRenderer *cell,
1148 GtkTreeModel *model,
1150 EmpathyIndividualView *view)
1155 gtk_tree_model_get (model, iter,
1156 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1157 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1159 if (gtk_tree_model_iter_has_child (model, iter))
1162 gboolean row_expanded;
1164 path = gtk_tree_model_get_path (model, iter);
1166 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1167 (gtk_tree_view_column_get_tree_view (column)), path);
1168 gtk_tree_path_free (path);
1173 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1177 g_object_set (cell, "visible", FALSE, NULL);
1179 individual_view_cell_set_background (view, cell, is_group, is_active);
1183 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1188 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1189 GtkTreeModel *model;
1193 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1196 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1198 gtk_tree_model_get (model, iter,
1199 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1201 expanded = GPOINTER_TO_INT (user_data);
1202 empathy_contact_group_set_expanded (name, expanded);
1208 individual_view_start_search_cb (EmpathyIndividualView *view,
1211 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1213 if (priv->search_widget == NULL)
1216 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1217 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1219 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1225 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1227 EmpathyIndividualView *view)
1229 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1231 GtkTreeViewColumn *focus_column;
1232 GtkTreeModel *model;
1234 gboolean set_cursor = FALSE;
1236 gtk_tree_model_filter_refilter (priv->filter);
1238 /* Set cursor on the first contact. If it is already set on a group,
1239 * set it on its first child contact. Note that first child of a group
1240 * is its separator, that's why we actually set to the 2nd
1243 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1244 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1248 path = gtk_tree_path_new_from_string ("0:1");
1251 else if (gtk_tree_path_get_depth (path) < 2)
1255 gtk_tree_model_get_iter (model, &iter, path);
1256 gtk_tree_model_get (model, &iter,
1257 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1262 gtk_tree_path_down (path);
1263 gtk_tree_path_next (path);
1270 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1272 if (gtk_tree_model_get_iter (model, &iter, path))
1274 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1279 gtk_tree_path_free (path);
1283 individual_view_search_activate_cb (GtkWidget *search,
1284 EmpathyIndividualView *view)
1287 GtkTreeViewColumn *focus_column;
1289 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1292 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1293 gtk_tree_path_free (path);
1295 gtk_widget_hide (search);
1300 individual_view_search_key_navigation_cb (GtkWidget *search,
1302 EmpathyIndividualView *view)
1304 GdkEventKey *eventkey = ((GdkEventKey *) event);
1305 gboolean ret = FALSE;
1307 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1309 GdkEvent *new_event;
1311 new_event = gdk_event_copy (event);
1312 gtk_widget_grab_focus (GTK_WIDGET (view));
1313 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1314 gtk_widget_grab_focus (search);
1316 gdk_event_free (new_event);
1323 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1324 EmpathyIndividualView *view)
1326 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1327 GtkTreeModel *model;
1328 GtkTreePath *cursor_path;
1330 gboolean valid = FALSE;
1332 /* block expand or collapse handlers, they would write the
1333 * expand or collapsed setting to file otherwise */
1334 g_signal_handlers_block_by_func (view,
1335 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1336 g_signal_handlers_block_by_func (view,
1337 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1339 /* restore which groups are expanded and which are not */
1340 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1341 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1342 valid; valid = gtk_tree_model_iter_next (model, &iter))
1348 gtk_tree_model_get (model, &iter,
1349 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1350 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1359 path = gtk_tree_model_get_path (model, &iter);
1360 if ((priv->view_features &
1361 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1362 empathy_contact_group_get_expanded (name))
1364 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1368 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1371 gtk_tree_path_free (path);
1375 /* unblock expand or collapse handlers */
1376 g_signal_handlers_unblock_by_func (view,
1377 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1378 g_signal_handlers_unblock_by_func (view,
1379 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1381 /* keep the selected contact visible */
1382 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1384 if (cursor_path != NULL)
1385 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1388 gtk_tree_path_free (cursor_path);
1392 individual_view_search_show_cb (EmpathyLiveSearch *search,
1393 EmpathyIndividualView *view)
1395 /* block expand or collapse handlers during expand all, they would
1396 * write the expand or collapsed setting to file otherwise */
1397 g_signal_handlers_block_by_func (view,
1398 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1400 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1402 g_signal_handlers_unblock_by_func (view,
1403 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1407 expand_idle_foreach_cb (GtkTreeModel *model,
1410 EmpathyIndividualView *self)
1412 EmpathyIndividualViewPriv *priv;
1414 gpointer should_expand;
1417 /* We only want groups */
1418 if (gtk_tree_path_get_depth (path) > 1)
1421 gtk_tree_model_get (model, iter,
1422 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1423 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1426 if (is_group == FALSE)
1432 priv = GET_PRIV (self);
1434 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1435 &should_expand) == TRUE)
1437 if (GPOINTER_TO_INT (should_expand) == TRUE)
1438 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1440 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1442 g_hash_table_remove (priv->expand_groups, name);
1451 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1453 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1455 DEBUG ("individual_view_expand_idle_cb");
1457 g_signal_handlers_block_by_func (self,
1458 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1459 g_signal_handlers_block_by_func (self,
1460 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1462 /* The store/filter could've been removed while we were in the idle queue */
1463 if (priv->filter != NULL)
1465 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1466 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1469 g_signal_handlers_unblock_by_func (self,
1470 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1471 g_signal_handlers_unblock_by_func (self,
1472 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1474 g_object_unref (self);
1475 priv->expand_groups_idle_handler = 0;
1481 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1484 EmpathyIndividualView *view)
1486 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1487 gboolean should_expand, is_group = FALSE;
1489 gpointer will_expand;
1491 gtk_tree_model_get (model, iter,
1492 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1493 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1496 if (!is_group || EMP_STR_EMPTY (name))
1502 should_expand = (priv->view_features &
1503 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1504 (priv->search_widget != NULL &&
1505 gtk_widget_get_visible (priv->search_widget)) ||
1506 empathy_contact_group_get_expanded (name);
1508 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1509 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1510 * a hash table, and expand or contract them as appropriate all at once in
1511 * an idle handler which iterates over all the group rows. */
1512 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1513 &will_expand) == FALSE &&
1514 GPOINTER_TO_INT (will_expand) != should_expand)
1516 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1517 GINT_TO_POINTER (should_expand));
1519 if (priv->expand_groups_idle_handler == 0)
1521 priv->expand_groups_idle_handler =
1522 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1523 g_object_ref (view));
1530 /* FIXME: This is a workaround for bgo#621076 */
1532 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1535 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1536 GtkTreeModel *model;
1537 GtkTreePath *parent_path;
1538 GtkTreeIter parent_iter;
1540 if (gtk_tree_path_get_depth (path) < 2)
1543 /* A group row is visible if and only if at least one if its child is visible.
1544 * So when a row is inserted/deleted/changed in the base model, that could
1545 * modify the visibility of its parent in the filter model.
1548 model = GTK_TREE_MODEL (priv->store);
1549 parent_path = gtk_tree_path_copy (path);
1550 gtk_tree_path_up (parent_path);
1551 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1553 /* This tells the filter to verify the visibility of that row, and
1554 * show/hide it if necessary */
1555 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1556 parent_path, &parent_iter);
1558 gtk_tree_path_free (parent_path);
1562 individual_view_store_row_changed_cb (GtkTreeModel *model,
1565 EmpathyIndividualView *view)
1567 individual_view_verify_group_visibility (view, path);
1571 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1573 EmpathyIndividualView *view)
1575 individual_view_verify_group_visibility (view, path);
1579 individual_view_is_visible_individual (EmpathyIndividualView *self,
1580 FolksIndividual *individual)
1582 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1583 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1585 GList *personas, *l;
1587 /* We're only giving the visibility wrt filtering here, not things like
1589 if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE)
1592 /* check alias name */
1593 str = folks_individual_get_alias (individual);
1595 if (empathy_live_search_match (live, str))
1598 /* check contact id, remove the @server.com part */
1599 personas = folks_individual_get_personas (individual);
1600 for (l = personas; l; l = l->next)
1603 gchar *dup_str = NULL;
1606 if (!TPF_IS_PERSONA (l->data))
1609 str = folks_persona_get_display_id (l->data);
1610 p = strstr (str, "@");
1612 str = dup_str = g_strndup (str, p - str);
1614 visible = empathy_live_search_match (live, str);
1620 /* FIXME: Add more rules here, we could check phone numbers in
1621 * contact's vCard for example. */
1627 individual_view_filter_visible_func (GtkTreeModel *model,
1631 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1632 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1633 FolksIndividual *individual = NULL;
1634 gboolean is_group, is_separator, valid;
1635 GtkTreeIter child_iter;
1636 gboolean visible, is_online;
1637 gboolean is_searching = TRUE;
1639 if (priv->search_widget == NULL ||
1640 !gtk_widget_get_visible (priv->search_widget))
1641 is_searching = FALSE;
1643 gtk_tree_model_get (model, iter,
1644 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1645 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1646 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1647 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1650 if (individual != NULL)
1652 if (is_searching == TRUE)
1653 visible = individual_view_is_visible_individual (self, individual);
1655 visible = (priv->show_offline || is_online);
1657 g_object_unref (individual);
1659 /* FIXME: Work around bgo#626552/bgo#621076 */
1660 if (visible == TRUE)
1662 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1663 individual_view_verify_group_visibility (self, path);
1664 gtk_tree_path_free (path);
1673 /* Not a contact, not a separator, must be a group */
1674 g_return_val_if_fail (is_group, FALSE);
1676 /* only show groups which are not empty */
1677 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1678 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1680 gtk_tree_model_get (model, &child_iter,
1681 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1682 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1685 if (individual == NULL)
1688 visible = individual_view_is_visible_individual (self, individual);
1689 g_object_unref (individual);
1691 /* show group if it has at least one visible contact in it */
1692 if ((is_searching && visible) ||
1693 (!is_searching && (priv->show_offline || is_online)))
1701 individual_view_constructed (GObject *object)
1703 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1704 GtkCellRenderer *cell;
1705 GtkTreeViewColumn *col;
1710 "headers-visible", FALSE,
1711 "show-expanders", FALSE,
1714 col = gtk_tree_view_column_new ();
1717 cell = gtk_cell_renderer_pixbuf_new ();
1718 gtk_tree_view_column_pack_start (col, cell, FALSE);
1719 gtk_tree_view_column_set_cell_data_func (col, cell,
1720 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1730 cell = gtk_cell_renderer_pixbuf_new ();
1731 gtk_tree_view_column_pack_start (col, cell, FALSE);
1732 gtk_tree_view_column_set_cell_data_func (col, cell,
1733 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1745 cell = empathy_cell_renderer_text_new ();
1746 gtk_tree_view_column_pack_start (col, cell, TRUE);
1747 gtk_tree_view_column_set_cell_data_func (col, cell,
1748 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1750 gtk_tree_view_column_add_attribute (col, cell,
1751 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1752 gtk_tree_view_column_add_attribute (col, cell,
1753 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1754 gtk_tree_view_column_add_attribute (col, cell,
1755 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1756 gtk_tree_view_column_add_attribute (col, cell,
1757 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1758 gtk_tree_view_column_add_attribute (col, cell,
1759 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1760 gtk_tree_view_column_add_attribute (col, cell,
1761 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1763 /* Audio Call Icon */
1764 cell = empathy_cell_renderer_activatable_new ();
1765 gtk_tree_view_column_pack_start (col, cell, FALSE);
1766 gtk_tree_view_column_set_cell_data_func (col, cell,
1767 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1770 g_object_set (cell, "visible", FALSE, NULL);
1772 g_signal_connect (cell, "path-activated",
1773 G_CALLBACK (individual_view_call_activated_cb), view);
1776 cell = gtk_cell_renderer_pixbuf_new ();
1777 gtk_tree_view_column_pack_start (col, cell, FALSE);
1778 gtk_tree_view_column_set_cell_data_func (col, cell,
1779 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1791 cell = empathy_cell_renderer_expander_new ();
1792 gtk_tree_view_column_pack_end (col, cell, FALSE);
1793 gtk_tree_view_column_set_cell_data_func (col, cell,
1794 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1797 /* Actually add the column now we have added all cell renderers */
1798 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1801 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1803 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1806 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1808 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1814 individual_view_set_view_features (EmpathyIndividualView *view,
1815 EmpathyIndividualFeatureFlags features)
1817 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1818 gboolean has_tooltip;
1820 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1822 priv->view_features = features;
1824 /* Setting reorderable is a hack that gets us row previews as drag icons
1825 for free. We override all the drag handlers. It's tricky to get the
1826 position of the drag icon right in drag_begin. GtkTreeView has special
1827 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1830 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1831 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1833 /* Update DnD source/dest */
1834 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1836 gtk_drag_source_set (GTK_WIDGET (view),
1839 G_N_ELEMENTS (drag_types_source),
1840 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1844 gtk_drag_source_unset (GTK_WIDGET (view));
1848 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1850 gtk_drag_dest_set (GTK_WIDGET (view),
1851 GTK_DEST_DEFAULT_ALL,
1853 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1857 /* FIXME: URI could still be droped depending on FT feature */
1858 gtk_drag_dest_unset (GTK_WIDGET (view));
1861 /* Update has-tooltip */
1863 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1864 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1868 individual_view_dispose (GObject *object)
1870 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1871 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1873 tp_clear_object (&priv->store);
1874 tp_clear_object (&priv->filter);
1875 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1877 empathy_individual_view_set_live_search (view, NULL);
1879 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1883 individual_view_finalize (GObject *object)
1885 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1887 g_hash_table_destroy (priv->expand_groups);
1889 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1893 individual_view_get_property (GObject *object,
1898 EmpathyIndividualViewPriv *priv;
1900 priv = GET_PRIV (object);
1905 g_value_set_object (value, priv->store);
1907 case PROP_VIEW_FEATURES:
1908 g_value_set_flags (value, priv->view_features);
1910 case PROP_INDIVIDUAL_FEATURES:
1911 g_value_set_flags (value, priv->individual_features);
1913 case PROP_SHOW_OFFLINE:
1914 g_value_set_boolean (value, priv->show_offline);
1917 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1923 individual_view_set_property (GObject *object,
1925 const GValue *value,
1928 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1929 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1934 empathy_individual_view_set_store (view, g_value_get_object (value));
1936 case PROP_VIEW_FEATURES:
1937 individual_view_set_view_features (view, g_value_get_flags (value));
1939 case PROP_INDIVIDUAL_FEATURES:
1940 priv->individual_features = g_value_get_flags (value);
1942 case PROP_SHOW_OFFLINE:
1943 empathy_individual_view_set_show_offline (view,
1944 g_value_get_boolean (value));
1947 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1953 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1955 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1956 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1957 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1959 object_class->constructed = individual_view_constructed;
1960 object_class->dispose = individual_view_dispose;
1961 object_class->finalize = individual_view_finalize;
1962 object_class->get_property = individual_view_get_property;
1963 object_class->set_property = individual_view_set_property;
1965 widget_class->drag_data_received = individual_view_drag_data_received;
1966 widget_class->drag_drop = individual_view_drag_drop;
1967 widget_class->drag_begin = individual_view_drag_begin;
1968 widget_class->drag_data_get = individual_view_drag_data_get;
1969 widget_class->drag_end = individual_view_drag_end;
1970 widget_class->drag_motion = individual_view_drag_motion;
1972 /* We use the class method to let user of this widget to connect to
1973 * the signal and stop emission of the signal so the default handler
1974 * won't be called. */
1975 tree_view_class->row_activated = individual_view_row_activated;
1977 klass->drag_individual_received = real_drag_individual_received_cb;
1979 signals[DRAG_INDIVIDUAL_RECEIVED] =
1980 g_signal_new ("drag-individual-received",
1981 G_OBJECT_CLASS_TYPE (klass),
1983 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
1985 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
1986 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
1987 G_TYPE_STRING, G_TYPE_STRING);
1989 signals[DRAG_PERSONA_RECEIVED] =
1990 g_signal_new ("drag-persona-received",
1991 G_OBJECT_CLASS_TYPE (klass),
1993 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
1995 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
1996 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
1998 g_object_class_install_property (object_class,
2000 g_param_spec_object ("store",
2001 "The store of the view",
2002 "The store of the view",
2003 EMPATHY_TYPE_INDIVIDUAL_STORE,
2004 G_PARAM_READWRITE));
2005 g_object_class_install_property (object_class,
2007 g_param_spec_flags ("view-features",
2008 "Features of the view",
2009 "Flags for all enabled features",
2010 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2011 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2012 g_object_class_install_property (object_class,
2013 PROP_INDIVIDUAL_FEATURES,
2014 g_param_spec_flags ("individual-features",
2015 "Features of the individual menu",
2016 "Flags for all enabled features for the menu",
2017 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2018 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2019 g_object_class_install_property (object_class,
2021 g_param_spec_boolean ("show-offline",
2023 "Whether contact list should display "
2024 "offline contacts", FALSE, G_PARAM_READWRITE));
2026 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2030 empathy_individual_view_init (EmpathyIndividualView *view)
2032 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2033 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2036 /* Get saved group states. */
2037 empathy_contact_groups_get_all ();
2039 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2040 (GDestroyNotify) g_free, NULL);
2042 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2043 empathy_individual_store_row_separator_func, NULL, NULL);
2045 /* Connect to tree view signals rather than override. */
2046 g_signal_connect (view, "button-press-event",
2047 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2048 g_signal_connect (view, "key-press-event",
2049 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2050 g_signal_connect (view, "row-expanded",
2051 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2052 GINT_TO_POINTER (TRUE));
2053 g_signal_connect (view, "row-collapsed",
2054 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2055 GINT_TO_POINTER (FALSE));
2056 g_signal_connect (view, "query-tooltip",
2057 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2060 EmpathyIndividualView *
2061 empathy_individual_view_new (EmpathyIndividualStore *store,
2062 EmpathyIndividualViewFeatureFlags view_features,
2063 EmpathyIndividualFeatureFlags individual_features)
2065 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2067 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2069 "individual-features", individual_features,
2070 "view-features", view_features, NULL);
2074 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2076 EmpathyIndividualViewPriv *priv;
2077 GtkTreeSelection *selection;
2079 GtkTreeModel *model;
2080 FolksIndividual *individual;
2082 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2084 priv = GET_PRIV (view);
2086 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2087 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2090 gtk_tree_model_get (model, &iter,
2091 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2096 EmpathyIndividualManagerFlags
2097 empathy_individual_view_get_flags (EmpathyIndividualView *view)
2099 EmpathyIndividualViewPriv *priv;
2100 GtkTreeSelection *selection;
2102 GtkTreeModel *model;
2103 EmpathyIndividualFeatureFlags flags;
2105 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2107 priv = GET_PRIV (view);
2109 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2110 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2113 gtk_tree_model_get (model, &iter,
2114 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2120 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2121 gboolean *is_fake_group)
2123 EmpathyIndividualViewPriv *priv;
2124 GtkTreeSelection *selection;
2126 GtkTreeModel *model;
2131 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2133 priv = GET_PRIV (view);
2135 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2136 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2139 gtk_tree_model_get (model, &iter,
2140 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2141 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2142 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2150 if (is_fake_group != NULL)
2151 *is_fake_group = fake;
2157 individual_view_remove_dialog_show (GtkWindow *parent,
2158 const gchar *message,
2159 const gchar *secondary_text)
2164 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2165 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2166 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2167 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2168 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2169 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2170 "%s", secondary_text);
2172 gtk_widget_show (dialog);
2174 res = gtk_dialog_run (GTK_DIALOG (dialog));
2175 gtk_widget_destroy (dialog);
2177 return (res == GTK_RESPONSE_YES);
2181 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2182 EmpathyIndividualView *view)
2186 group = empathy_individual_view_get_selected_group (view, NULL);
2193 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2195 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2196 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2199 EmpathyIndividualManager *manager =
2200 empathy_individual_manager_dup_singleton ();
2201 empathy_individual_manager_remove_group (manager, group);
2202 g_object_unref (G_OBJECT (manager));
2212 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2214 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2219 gboolean is_fake_group;
2221 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2223 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2224 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2227 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2228 if (!group || is_fake_group)
2230 /* We can't alter fake groups */
2234 menu = gtk_menu_new ();
2237 if (priv->view_features &
2238 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2239 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2240 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2241 gtk_widget_show (item);
2242 g_signal_connect (item, "activate",
2243 G_CALLBACK (individual_view_group_rename_activate_cb),
2248 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2250 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2251 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2252 GTK_ICON_SIZE_MENU);
2253 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2254 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2255 gtk_widget_show (item);
2256 g_signal_connect (item, "activate",
2257 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2266 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2267 EmpathyIndividualView *view)
2269 FolksIndividual *individual;
2271 individual = empathy_individual_view_dup_selected (view);
2273 if (individual != NULL)
2278 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2281 ("Do you really want to remove the contact '%s'?"),
2282 folks_individual_get_alias (individual));
2283 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2286 EmpathyIndividualManager *manager;
2288 manager = empathy_individual_manager_dup_singleton ();
2289 empathy_individual_manager_remove (manager, individual, "");
2290 g_object_unref (G_OBJECT (manager));
2294 g_object_unref (individual);
2299 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2301 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2302 FolksIndividual *individual;
2303 GtkWidget *menu = NULL;
2306 EmpathyIndividualManagerFlags flags;
2308 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2310 individual = empathy_individual_view_dup_selected (view);
2311 if (individual == NULL)
2314 flags = empathy_individual_view_get_flags (view);
2316 menu = empathy_individual_menu_new (individual, priv->individual_features);
2318 /* Remove contact */
2319 if (priv->view_features &
2320 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE &&
2321 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2324 /* create the menu if required, or just add a separator */
2326 menu = gtk_menu_new ();
2329 item = gtk_separator_menu_item_new ();
2330 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2331 gtk_widget_show (item);
2335 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2336 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2337 GTK_ICON_SIZE_MENU);
2338 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2339 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2340 gtk_widget_show (item);
2341 g_signal_connect (item, "activate",
2342 G_CALLBACK (individual_view_remove_activate_cb), view);
2345 g_object_unref (individual);
2351 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2352 EmpathyLiveSearch *search)
2354 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2356 /* remove old handlers if old search was not null */
2357 if (priv->search_widget != NULL)
2359 g_signal_handlers_disconnect_by_func (view,
2360 individual_view_start_search_cb, NULL);
2362 g_signal_handlers_disconnect_by_func (priv->search_widget,
2363 individual_view_search_text_notify_cb, view);
2364 g_signal_handlers_disconnect_by_func (priv->search_widget,
2365 individual_view_search_activate_cb, view);
2366 g_signal_handlers_disconnect_by_func (priv->search_widget,
2367 individual_view_search_key_navigation_cb, view);
2368 g_signal_handlers_disconnect_by_func (priv->search_widget,
2369 individual_view_search_hide_cb, view);
2370 g_signal_handlers_disconnect_by_func (priv->search_widget,
2371 individual_view_search_show_cb, view);
2372 g_object_unref (priv->search_widget);
2373 priv->search_widget = NULL;
2376 /* connect handlers if new search is not null */
2379 priv->search_widget = g_object_ref (search);
2381 g_signal_connect (view, "start-interactive-search",
2382 G_CALLBACK (individual_view_start_search_cb), NULL);
2384 g_signal_connect (priv->search_widget, "notify::text",
2385 G_CALLBACK (individual_view_search_text_notify_cb), view);
2386 g_signal_connect (priv->search_widget, "activate",
2387 G_CALLBACK (individual_view_search_activate_cb), view);
2388 g_signal_connect (priv->search_widget, "key-navigation",
2389 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2390 g_signal_connect (priv->search_widget, "hide",
2391 G_CALLBACK (individual_view_search_hide_cb), view);
2392 g_signal_connect (priv->search_widget, "show",
2393 G_CALLBACK (individual_view_search_show_cb), view);
2398 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2400 EmpathyIndividualViewPriv *priv;
2402 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2404 priv = GET_PRIV (self);
2406 return (priv->search_widget != NULL &&
2407 gtk_widget_get_visible (priv->search_widget));
2411 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2413 EmpathyIndividualViewPriv *priv;
2415 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2417 priv = GET_PRIV (self);
2419 return priv->show_offline;
2423 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2424 gboolean show_offline)
2426 EmpathyIndividualViewPriv *priv;
2428 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2430 priv = GET_PRIV (self);
2432 priv->show_offline = show_offline;
2434 g_object_notify (G_OBJECT (self), "show-offline");
2435 gtk_tree_model_filter_refilter (priv->filter);
2438 EmpathyIndividualStore *
2439 empathy_individual_view_get_store (EmpathyIndividualView *self)
2441 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2443 return GET_PRIV (self)->store;
2447 empathy_individual_view_set_store (EmpathyIndividualView *self,
2448 EmpathyIndividualStore *store)
2450 EmpathyIndividualViewPriv *priv;
2452 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2453 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2455 priv = GET_PRIV (self);
2457 /* Destroy the old filter and remove the old store */
2458 if (priv->store != NULL)
2460 g_signal_handlers_disconnect_by_func (priv->store,
2461 individual_view_store_row_changed_cb, self);
2462 g_signal_handlers_disconnect_by_func (priv->store,
2463 individual_view_store_row_deleted_cb, self);
2465 g_signal_handlers_disconnect_by_func (priv->filter,
2466 individual_view_row_has_child_toggled_cb, self);
2468 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2471 tp_clear_object (&priv->filter);
2472 tp_clear_object (&priv->store);
2474 /* Set the new store */
2475 priv->store = store;
2479 g_object_ref (store);
2481 /* Create a new filter */
2482 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2483 GTK_TREE_MODEL (priv->store), NULL));
2484 gtk_tree_model_filter_set_visible_func (priv->filter,
2485 individual_view_filter_visible_func, self, NULL);
2487 g_signal_connect (priv->filter, "row-has-child-toggled",
2488 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2489 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2490 GTK_TREE_MODEL (priv->filter));
2492 tp_g_signal_connect_object (priv->store, "row-changed",
2493 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2494 tp_g_signal_connect_object (priv->store, "row-inserted",
2495 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2496 tp_g_signal_connect_object (priv->store, "row-deleted",
2497 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);