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;
97 /* Distance between mouse pointer and the nearby border. Negative when
104 EmpathyIndividualView *view;
105 FolksIndividual *individual;
114 PROP_INDIVIDUAL_FEATURES,
119 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
120 * specific EmpathyContacts (between/in/out of Individuals) */
123 DND_DRAG_TYPE_INDIVIDUAL_ID,
124 DND_DRAG_TYPE_PERSONA_ID,
125 DND_DRAG_TYPE_URI_LIST,
126 DND_DRAG_TYPE_STRING,
129 #define DRAG_TYPE(T,I) \
130 { (gchar *) T, 0, I }
132 static const GtkTargetEntry drag_types_dest[] = {
133 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
134 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
135 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
136 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
137 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
138 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
141 static const GtkTargetEntry drag_types_source[] = {
142 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
147 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
148 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
152 DRAG_INDIVIDUAL_RECEIVED,
153 DRAG_PERSONA_RECEIVED,
157 static guint signals[LAST_SIGNAL];
159 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
163 individual_view_tooltip_destroy_cb (GtkWidget *widget,
164 EmpathyIndividualView *view)
166 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
168 if (priv->tooltip_widget != NULL)
170 DEBUG ("Tooltip destroyed");
171 tp_clear_object (&priv->tooltip_widget);
176 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
179 gboolean keyboard_mode,
183 EmpathyIndividualViewPriv *priv;
184 FolksIndividual *individual;
188 static gint running = 0;
189 gboolean ret = FALSE;
191 priv = GET_PRIV (view);
193 /* Avoid an infinite loop. See GNOME bug #574377 */
199 /* Don't show the tooltip if there's already a popup menu */
200 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
203 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
204 keyboard_mode, &model, &path, &iter))
207 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
208 gtk_tree_path_free (path);
210 gtk_tree_model_get (model, &iter,
211 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
213 if (individual == NULL)
216 if (priv->tooltip_widget == NULL)
218 priv->tooltip_widget = empathy_individual_widget_new (individual,
219 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
220 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
221 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
222 g_object_ref (priv->tooltip_widget);
223 g_signal_connect (priv->tooltip_widget, "destroy",
224 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
225 gtk_widget_show (priv->tooltip_widget);
229 empathy_individual_widget_set_individual (
230 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
233 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
236 g_object_unref (individual);
244 groups_change_group_cb (GObject *source,
245 GAsyncResult *result,
248 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
249 GError *error = NULL;
251 folks_groupable_change_group_finish (groupable, result, &error);
254 g_warning ("failed to change group: %s", error->message);
255 g_clear_error (&error);
260 group_can_be_modified (const gchar *name,
261 gboolean is_fake_group,
264 /* Real groups can always be modified */
268 /* The favorite fake group can be modified so users can
269 * add/remove favorites using DnD */
270 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
273 /* We can remove contacts from the 'ungrouped' fake group */
274 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
281 individual_view_individual_drag_received (GtkWidget *self,
282 GdkDragContext *context,
285 GtkSelectionData *selection)
287 EmpathyIndividualViewPriv *priv;
288 EmpathyIndividualManager *manager = NULL;
289 FolksIndividual *individual;
290 GtkTreePath *source_path;
291 const gchar *sel_data;
292 gchar *new_group = NULL;
293 gchar *old_group = NULL;
294 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
296 priv = GET_PRIV (self);
298 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
299 new_group = empathy_individual_store_get_parent_group (model, path,
300 NULL, &new_group_is_fake);
302 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
305 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
306 * feature. Otherwise, we just add the dropped contact to whichever group
307 * they were dropped in, and don't remove them from their old group. This
308 * allows for Individual views which shouldn't allow Individuals to have
309 * their groups changed, and also for dragging Individuals between Individual
311 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
312 priv->drag_row != NULL)
314 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
318 empathy_individual_store_get_parent_group (model, source_path,
319 NULL, &old_group_is_fake);
320 gtk_tree_path_free (source_path);
323 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
326 if (!tp_strdiff (old_group, new_group))
329 else if (priv->drag_row != NULL)
331 /* We don't allow changing Individuals' groups, and this Individual was
332 * dragged from another group in *this* Individual view, so we disallow
337 /* XXX: for contacts, we used to ensure the account, create the contact
338 * factory, and then wait on the contacts. But they should already be
339 * created by this point */
341 manager = empathy_individual_manager_dup_singleton ();
342 individual = empathy_individual_manager_lookup_member (manager, sel_data);
344 if (individual == NULL)
346 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
350 /* FIXME: We should probably wait for the cb before calling
353 /* Emit a signal notifying of the drag. We change the Individual's groups in
354 * the default signal handler. */
355 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
356 gdk_drag_context_get_selected_action (context), individual, new_group,
362 tp_clear_object (&manager);
370 real_drag_individual_received_cb (EmpathyIndividualView *self,
371 GdkDragAction action,
372 FolksIndividual *individual,
373 const gchar *new_group,
374 const gchar *old_group)
376 DEBUG ("individual %s dragged from '%s' to '%s'",
377 folks_individual_get_id (individual), old_group, new_group);
379 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
381 /* Mark contact as favourite */
382 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
386 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
388 /* Remove contact as favourite */
389 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
391 /* Don't try to remove it */
395 if (new_group != NULL)
397 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
398 groups_change_group_cb, NULL);
401 if (old_group != NULL && action == GDK_ACTION_MOVE)
403 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
404 FALSE, groups_change_group_cb, NULL);
409 individual_view_persona_drag_received (GtkWidget *self,
410 GdkDragContext *context,
413 GtkSelectionData *selection)
415 EmpathyIndividualViewPriv *priv;
416 EmpathyIndividualManager *manager = NULL;
417 FolksIndividual *individual = NULL;
418 FolksPersona *persona = NULL;
419 const gchar *persona_uid;
420 GList *individuals, *l;
421 gboolean retval = FALSE;
423 priv = GET_PRIV (self);
425 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
427 /* FIXME: This is slow, but the only way to find the Persona we're having
429 manager = empathy_individual_manager_dup_singleton ();
430 individuals = empathy_individual_manager_get_members (manager);
432 for (l = individuals; l != NULL; l = l->next)
436 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
438 for (p = personas; p != NULL; p = p->next)
440 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
443 persona = g_object_ref (p->data);
444 individual = g_object_ref (l->data);
451 g_list_free (individuals);
453 if (persona == NULL || individual == NULL)
455 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
459 /* Emit a signal notifying of the drag. We change the Individual's groups in
460 * the default signal handler. */
461 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
462 gdk_drag_context_get_selected_action (context), persona, individual,
466 tp_clear_object (&manager);
467 tp_clear_object (&persona);
468 tp_clear_object (&individual);
474 individual_view_file_drag_received (GtkWidget *view,
475 GdkDragContext *context,
478 GtkSelectionData *selection)
481 const gchar *sel_data;
482 FolksIndividual *individual;
483 EmpathyContact *contact;
485 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
487 gtk_tree_model_get_iter (model, &iter, path);
488 gtk_tree_model_get (model, &iter,
489 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
490 if (individual == NULL)
493 contact = empathy_contact_dup_from_folks_individual (individual);
494 empathy_send_file_from_uri_list (contact, sel_data);
496 g_object_unref (individual);
497 tp_clear_object (&contact);
503 individual_view_drag_data_received (GtkWidget *view,
504 GdkDragContext *context,
507 GtkSelectionData *selection,
513 GtkTreeViewDropPosition position;
515 gboolean success = TRUE;
517 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
519 /* Get destination group information. */
520 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
521 x, y, &path, &position);
526 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
528 success = individual_view_individual_drag_received (view,
529 context, model, path, selection);
531 else if (info == DND_DRAG_TYPE_PERSONA_ID)
533 success = individual_view_persona_drag_received (view, context, model,
536 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
538 success = individual_view_file_drag_received (view,
539 context, model, path, selection);
542 gtk_tree_path_free (path);
543 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
547 individual_view_drag_motion_cb (DragMotionData *data)
549 if (data->view != NULL)
551 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
552 g_object_remove_weak_pointer (G_OBJECT (data->view),
553 (gpointer *) &data->view);
556 data->timeout_id = 0;
561 /* Minimum distance between the mouse pointer and a horizontal border when we
562 start auto scrolling. */
563 #define AUTO_SCROLL_MARGIN_SIZE 20
564 /* How far to scroll per one tick. */
565 #define AUTO_SCROLL_PITCH 10
568 individual_view_auto_scroll_cb (AutoScrollData *data)
573 adj = gtk_scrollable_get_vadjustment (GTK_TREE_VIEW (data->view));
575 if (data->distance < 0)
576 new_value = adj->value - AUTO_SCROLL_PITCH;
578 new_value = adj->value + AUTO_SCROLL_PITCH;
580 new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
582 gtk_adjustment_set_value (adj, new_value);
588 individual_view_drag_motion (GtkWidget *widget,
589 GdkDragContext *context,
594 EmpathyIndividualViewPriv *priv;
598 static DragMotionData *dm = NULL;
599 static AutoScrollData as;
602 gboolean is_different = FALSE;
603 gboolean cleanup = TRUE;
604 gboolean retval = TRUE;
606 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
607 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
610 as.view = EMPATHY_INDIVIDUAL_VIEW (widget);
613 g_source_remove (as.timeout_id);
615 if (y < AUTO_SCROLL_MARGIN_SIZE ||
616 y > (widget->allocation.height - AUTO_SCROLL_MARGIN_SIZE))
618 if (y < AUTO_SCROLL_MARGIN_SIZE)
619 as.distance = MIN (-y, -1);
621 as.distance = MAX (widget->allocation.height - y, 1);
623 as.timeout_id = g_timeout_add (10 * ABS (as.distance),
624 (GSourceFunc) individual_view_auto_scroll_cb, &as);
627 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
628 x, y, &path, NULL, NULL, NULL);
630 cleanup &= (dm == NULL);
634 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
635 is_different = ((dm == NULL) || ((dm != NULL)
636 && gtk_tree_path_compare (dm->path, path) != 0));
643 /* Coordinates don't point to an actual row, so make sure the pointer
644 and highlighting don't indicate that a drag is possible.
646 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
647 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
650 target = gtk_drag_dest_find_target (widget, context, NULL);
651 gtk_tree_model_get_iter (model, &iter, path);
653 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
654 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
656 /* This is a file drag, and it can only be dropped on contacts,
658 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
659 * even if we have a valid target. */
660 FolksIndividual *individual = NULL;
661 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
663 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
665 gtk_tree_model_get (model, &iter,
666 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
670 if (individual != NULL)
672 EmpathyContact *contact = NULL;
674 contact = empathy_contact_dup_from_folks_individual (individual);
675 caps = empathy_contact_get_capabilities (contact);
677 tp_clear_object (&contact);
680 if (individual != NULL &&
681 folks_individual_is_online (individual) &&
682 (caps & EMPATHY_CAPABILITIES_FT))
684 gdk_drag_status (context, GDK_ACTION_COPY, time_);
685 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
686 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
690 gdk_drag_status (context, 0, time_);
691 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
695 if (individual != NULL)
696 g_object_unref (individual);
698 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
699 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
700 priv->drag_row == NULL)) ||
701 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
702 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
704 /* If target != GDK_NONE, then we have a contact (individual or persona)
705 drag. If we're pointing to a group, highlight it. Otherwise, if the
706 contact we're pointing to is in a group, highlight that. Otherwise,
707 set the drag position to before the first row for a drag into
708 the "non-group" at the top.
709 If it's an Individual:
710 We only highlight things if the contact is from a different
711 Individual view, or if this Individual view has
712 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
713 which don't have FEATURE_GROUPS_CHANGE, but do have
714 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
716 We only highlight things if we have FEATURE_PERSONA_DROP.
718 GtkTreeIter group_iter;
720 GtkTreePath *group_path;
721 gtk_tree_model_get (model, &iter,
722 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
729 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
730 gtk_tree_model_get (model, &group_iter,
731 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
735 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
736 group_path = gtk_tree_model_get_path (model, &group_iter);
737 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
738 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
739 gtk_tree_path_free (group_path);
743 group_path = gtk_tree_path_new_first ();
744 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
745 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
746 group_path, GTK_TREE_VIEW_DROP_BEFORE);
750 if (!is_different && !cleanup)
755 gtk_tree_path_free (dm->path);
758 g_source_remove (dm->timeout_id);
766 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
768 dm = g_new0 (DragMotionData, 1);
770 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
771 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
772 dm->path = gtk_tree_path_copy (path);
774 dm->timeout_id = g_timeout_add_seconds (1,
775 (GSourceFunc) individual_view_drag_motion_cb, dm);
782 individual_view_drag_begin (GtkWidget *widget,
783 GdkDragContext *context)
785 EmpathyIndividualViewPriv *priv;
786 GtkTreeSelection *selection;
791 priv = GET_PRIV (widget);
793 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
796 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
797 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
800 path = gtk_tree_model_get_path (model, &iter);
801 priv->drag_row = gtk_tree_row_reference_new (model, path);
802 gtk_tree_path_free (path);
806 individual_view_drag_data_get (GtkWidget *widget,
807 GdkDragContext *context,
808 GtkSelectionData *selection,
812 EmpathyIndividualViewPriv *priv;
813 GtkTreePath *src_path;
816 FolksIndividual *individual;
817 const gchar *individual_id;
819 priv = GET_PRIV (widget);
821 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
822 if (priv->drag_row == NULL)
825 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
826 if (src_path == NULL)
829 if (!gtk_tree_model_get_iter (model, &iter, src_path))
831 gtk_tree_path_free (src_path);
835 gtk_tree_path_free (src_path);
838 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
839 if (individual == NULL)
842 individual_id = folks_individual_get_id (individual);
844 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
846 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
847 (guchar *) individual_id, strlen (individual_id) + 1);
850 g_object_unref (individual);
854 individual_view_drag_end (GtkWidget *widget,
855 GdkDragContext *context)
857 EmpathyIndividualViewPriv *priv;
859 priv = GET_PRIV (widget);
861 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
866 gtk_tree_row_reference_free (priv->drag_row);
867 priv->drag_row = NULL;
872 individual_view_drag_drop (GtkWidget *widget,
873 GdkDragContext *drag_context,
883 EmpathyIndividualView *view;
889 individual_view_popup_menu_idle_cb (gpointer user_data)
891 MenuPopupData *data = user_data;
894 menu = empathy_individual_view_get_individual_menu (data->view);
896 menu = empathy_individual_view_get_group_menu (data->view);
900 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
901 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
903 gtk_widget_show (menu);
904 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
906 g_object_ref_sink (menu);
907 g_object_unref (menu);
910 g_slice_free (MenuPopupData, data);
916 individual_view_button_press_event_cb (EmpathyIndividualView *view,
917 GdkEventButton *event,
920 if (event->button == 3)
924 data = g_slice_new (MenuPopupData);
926 data->button = event->button;
927 data->time = event->time;
928 g_idle_add (individual_view_popup_menu_idle_cb, data);
935 individual_view_key_press_event_cb (EmpathyIndividualView *view,
939 if (event->keyval == GDK_KEY_Menu)
943 data = g_slice_new (MenuPopupData);
946 data->time = event->time;
947 g_idle_add (individual_view_popup_menu_idle_cb, data);
954 individual_view_row_activated (GtkTreeView *view,
956 GtkTreeViewColumn *column)
958 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
959 FolksIndividual *individual;
960 EmpathyContact *contact;
964 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
967 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
968 gtk_tree_model_get_iter (model, &iter, path);
969 gtk_tree_model_get (model, &iter,
970 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
972 if (individual == NULL)
975 /* Determine which Persona to chat to, by choosing the most available one. */
976 contact = empathy_contact_dup_best_for_action (individual,
977 EMPATHY_ACTION_CHAT);
981 DEBUG ("Starting a chat");
983 empathy_dispatcher_chat_with_contact (contact,
984 gtk_get_current_event_time ());
987 g_object_unref (individual);
988 tp_clear_object (&contact);
992 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
993 const gchar *path_string,
994 EmpathyIndividualView *view)
996 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1000 FolksIndividual *individual;
1001 GdkEventButton *event;
1002 GtkMenuShell *shell;
1005 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1008 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1009 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1012 gtk_tree_model_get (model, &iter,
1013 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1014 if (individual == NULL)
1017 event = (GdkEventButton *) gtk_get_current_event ();
1019 menu = gtk_menu_new ();
1020 shell = GTK_MENU_SHELL (menu);
1023 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1024 gtk_menu_shell_append (shell, item);
1025 gtk_widget_show (item);
1028 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1029 gtk_menu_shell_append (shell, item);
1030 gtk_widget_show (item);
1032 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
1033 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1034 gtk_widget_show (menu);
1035 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1036 event->button, event->time);
1037 g_object_ref_sink (menu);
1038 g_object_unref (menu);
1040 g_object_unref (individual);
1044 individual_view_cell_set_background (EmpathyIndividualView *view,
1045 GtkCellRenderer *cell,
1052 style = gtk_widget_get_style (GTK_WIDGET (view));
1054 if (!is_group && is_active)
1056 color = style->bg[GTK_STATE_SELECTED];
1058 /* Here we take the current theme colour and add it to
1059 * the colour for white and average the two. This
1060 * gives a colour which is inline with the theme but
1063 color.red = (color.red + (style->white).red) / 2;
1064 color.green = (color.green + (style->white).green) / 2;
1065 color.blue = (color.blue + (style->white).blue) / 2;
1067 g_object_set (cell, "cell-background-gdk", &color, NULL);
1070 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1074 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1075 GtkCellRenderer *cell,
1076 GtkTreeModel *model,
1078 EmpathyIndividualView *view)
1084 gtk_tree_model_get (model, iter,
1085 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1086 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1087 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1090 "visible", !is_group,
1094 tp_clear_object (&pixbuf);
1096 individual_view_cell_set_background (view, cell, is_group, is_active);
1100 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1101 GtkCellRenderer *cell,
1102 GtkTreeModel *model,
1104 EmpathyIndividualView *view)
1106 GdkPixbuf *pixbuf = NULL;
1110 gtk_tree_model_get (model, iter,
1111 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1112 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1117 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1119 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1120 GTK_ICON_SIZE_MENU);
1122 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1124 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1125 GTK_ICON_SIZE_MENU);
1130 "visible", pixbuf != NULL,
1134 tp_clear_object (&pixbuf);
1140 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1141 GtkCellRenderer *cell,
1142 GtkTreeModel *model,
1144 EmpathyIndividualView *view)
1148 gboolean can_audio, can_video;
1150 gtk_tree_model_get (model, iter,
1151 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1152 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1153 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1154 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1157 "visible", !is_group && (can_audio || can_video),
1158 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1161 individual_view_cell_set_background (view, cell, is_group, is_active);
1165 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1166 GtkCellRenderer *cell,
1167 GtkTreeModel *model,
1169 EmpathyIndividualView *view)
1172 gboolean show_avatar;
1176 gtk_tree_model_get (model, iter,
1177 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1178 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1179 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1180 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1183 "visible", !is_group && show_avatar,
1187 tp_clear_object (&pixbuf);
1189 individual_view_cell_set_background (view, cell, is_group, is_active);
1193 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1194 GtkCellRenderer *cell,
1195 GtkTreeModel *model,
1197 EmpathyIndividualView *view)
1202 gtk_tree_model_get (model, iter,
1203 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1204 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1206 individual_view_cell_set_background (view, cell, is_group, is_active);
1210 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1211 GtkCellRenderer *cell,
1212 GtkTreeModel *model,
1214 EmpathyIndividualView *view)
1219 gtk_tree_model_get (model, iter,
1220 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1221 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1223 if (gtk_tree_model_iter_has_child (model, iter))
1226 gboolean row_expanded;
1228 path = gtk_tree_model_get_path (model, iter);
1230 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1231 (gtk_tree_view_column_get_tree_view (column)), path);
1232 gtk_tree_path_free (path);
1237 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1241 g_object_set (cell, "visible", FALSE, NULL);
1243 individual_view_cell_set_background (view, cell, is_group, is_active);
1247 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1252 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1253 GtkTreeModel *model;
1257 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1260 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1262 gtk_tree_model_get (model, iter,
1263 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1265 expanded = GPOINTER_TO_INT (user_data);
1266 empathy_contact_group_set_expanded (name, expanded);
1272 individual_view_start_search_cb (EmpathyIndividualView *view,
1275 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1277 if (priv->search_widget == NULL)
1280 empathy_individual_view_start_search (view);
1286 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1288 EmpathyIndividualView *view)
1290 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1292 GtkTreeViewColumn *focus_column;
1293 GtkTreeModel *model;
1295 gboolean set_cursor = FALSE;
1297 gtk_tree_model_filter_refilter (priv->filter);
1299 /* Set cursor on the first contact. If it is already set on a group,
1300 * set it on its first child contact. Note that first child of a group
1301 * is its separator, that's why we actually set to the 2nd
1304 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1305 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1309 path = gtk_tree_path_new_from_string ("0:1");
1312 else if (gtk_tree_path_get_depth (path) < 2)
1316 gtk_tree_model_get_iter (model, &iter, path);
1317 gtk_tree_model_get (model, &iter,
1318 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1323 gtk_tree_path_down (path);
1324 gtk_tree_path_next (path);
1331 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1333 if (gtk_tree_model_get_iter (model, &iter, path))
1335 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1340 gtk_tree_path_free (path);
1344 individual_view_search_activate_cb (GtkWidget *search,
1345 EmpathyIndividualView *view)
1348 GtkTreeViewColumn *focus_column;
1350 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1353 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1354 gtk_tree_path_free (path);
1356 gtk_widget_hide (search);
1361 individual_view_search_key_navigation_cb (GtkWidget *search,
1363 EmpathyIndividualView *view)
1365 GdkEventKey *eventkey = ((GdkEventKey *) event);
1366 gboolean ret = FALSE;
1368 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
1370 GdkEvent *new_event;
1372 new_event = gdk_event_copy (event);
1373 gtk_widget_grab_focus (GTK_WIDGET (view));
1374 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1375 gtk_widget_grab_focus (search);
1377 gdk_event_free (new_event);
1384 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1385 EmpathyIndividualView *view)
1387 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1388 GtkTreeModel *model;
1389 GtkTreePath *cursor_path;
1391 gboolean valid = FALSE;
1393 /* block expand or collapse handlers, they would write the
1394 * expand or collapsed setting to file otherwise */
1395 g_signal_handlers_block_by_func (view,
1396 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1397 g_signal_handlers_block_by_func (view,
1398 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1400 /* restore which groups are expanded and which are not */
1401 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1402 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1403 valid; valid = gtk_tree_model_iter_next (model, &iter))
1409 gtk_tree_model_get (model, &iter,
1410 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1411 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1420 path = gtk_tree_model_get_path (model, &iter);
1421 if ((priv->view_features &
1422 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1423 empathy_contact_group_get_expanded (name))
1425 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1429 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1432 gtk_tree_path_free (path);
1436 /* unblock expand or collapse handlers */
1437 g_signal_handlers_unblock_by_func (view,
1438 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1439 g_signal_handlers_unblock_by_func (view,
1440 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1442 /* keep the selected contact visible */
1443 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1445 if (cursor_path != NULL)
1446 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1449 gtk_tree_path_free (cursor_path);
1453 individual_view_search_show_cb (EmpathyLiveSearch *search,
1454 EmpathyIndividualView *view)
1456 /* block expand or collapse handlers during expand all, they would
1457 * write the expand or collapsed setting to file otherwise */
1458 g_signal_handlers_block_by_func (view,
1459 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1461 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1463 g_signal_handlers_unblock_by_func (view,
1464 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1468 expand_idle_foreach_cb (GtkTreeModel *model,
1471 EmpathyIndividualView *self)
1473 EmpathyIndividualViewPriv *priv;
1475 gpointer should_expand;
1478 /* We only want groups */
1479 if (gtk_tree_path_get_depth (path) > 1)
1482 gtk_tree_model_get (model, iter,
1483 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1484 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1487 if (is_group == FALSE)
1493 priv = GET_PRIV (self);
1495 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1496 &should_expand) == TRUE)
1498 if (GPOINTER_TO_INT (should_expand) == TRUE)
1499 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1501 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1503 g_hash_table_remove (priv->expand_groups, name);
1512 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1514 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1516 DEBUG ("individual_view_expand_idle_cb");
1518 g_signal_handlers_block_by_func (self,
1519 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1520 g_signal_handlers_block_by_func (self,
1521 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1523 /* The store/filter could've been removed while we were in the idle queue */
1524 if (priv->filter != NULL)
1526 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1527 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1530 g_signal_handlers_unblock_by_func (self,
1531 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1532 g_signal_handlers_unblock_by_func (self,
1533 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1535 /* Empty the table of groups to expand/contract, since it may contain groups
1536 * which no longer exist in the tree view. This can happen after going
1537 * offline, for example. */
1538 g_hash_table_remove_all (priv->expand_groups);
1539 priv->expand_groups_idle_handler = 0;
1540 g_object_unref (self);
1546 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1549 EmpathyIndividualView *view)
1551 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1552 gboolean should_expand, is_group = FALSE;
1554 gpointer will_expand;
1556 gtk_tree_model_get (model, iter,
1557 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1558 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1561 if (!is_group || EMP_STR_EMPTY (name))
1567 should_expand = (priv->view_features &
1568 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1569 (priv->search_widget != NULL &&
1570 gtk_widget_get_visible (priv->search_widget)) ||
1571 empathy_contact_group_get_expanded (name);
1573 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1574 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1575 * a hash table, and expand or contract them as appropriate all at once in
1576 * an idle handler which iterates over all the group rows. */
1577 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1578 &will_expand) == FALSE ||
1579 GPOINTER_TO_INT (will_expand) != should_expand)
1581 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1582 GINT_TO_POINTER (should_expand));
1584 if (priv->expand_groups_idle_handler == 0)
1586 priv->expand_groups_idle_handler =
1587 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1588 g_object_ref (view));
1595 /* FIXME: This is a workaround for bgo#621076 */
1597 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1600 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1601 GtkTreeModel *model;
1602 GtkTreePath *parent_path;
1603 GtkTreeIter parent_iter;
1605 if (gtk_tree_path_get_depth (path) < 2)
1608 /* A group row is visible if and only if at least one if its child is visible.
1609 * So when a row is inserted/deleted/changed in the base model, that could
1610 * modify the visibility of its parent in the filter model.
1613 model = GTK_TREE_MODEL (priv->store);
1614 parent_path = gtk_tree_path_copy (path);
1615 gtk_tree_path_up (parent_path);
1616 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1618 /* This tells the filter to verify the visibility of that row, and
1619 * show/hide it if necessary */
1620 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1621 parent_path, &parent_iter);
1623 gtk_tree_path_free (parent_path);
1627 individual_view_store_row_changed_cb (GtkTreeModel *model,
1630 EmpathyIndividualView *view)
1632 individual_view_verify_group_visibility (view, path);
1636 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1638 EmpathyIndividualView *view)
1640 individual_view_verify_group_visibility (view, path);
1644 individual_view_is_visible_individual (EmpathyIndividualView *self,
1645 FolksIndividual *individual,
1647 gboolean is_searching)
1649 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1650 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1652 GList *personas, *l;
1654 /* We're only giving the visibility wrt filtering here, not things like
1656 if (priv->show_untrusted == FALSE &&
1657 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1662 if (is_searching == FALSE)
1663 return (priv->show_offline || is_online);
1665 /* check alias name */
1666 str = folks_individual_get_alias (individual);
1668 if (empathy_live_search_match (live, str))
1671 /* check contact id, remove the @server.com part */
1672 personas = folks_individual_get_personas (individual);
1673 for (l = personas; l; l = l->next)
1676 gchar *dup_str = NULL;
1679 if (!TPF_IS_PERSONA (l->data))
1682 str = folks_persona_get_display_id (l->data);
1683 p = strstr (str, "@");
1685 str = dup_str = g_strndup (str, p - str);
1687 visible = empathy_live_search_match (live, str);
1693 /* FIXME: Add more rules here, we could check phone numbers in
1694 * contact's vCard for example. */
1700 individual_view_filter_visible_func (GtkTreeModel *model,
1704 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1705 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1706 FolksIndividual *individual = NULL;
1707 gboolean is_group, is_separator, valid;
1708 GtkTreeIter child_iter;
1709 gboolean visible, is_online;
1710 gboolean is_searching = TRUE;
1712 if (priv->search_widget == NULL ||
1713 !gtk_widget_get_visible (priv->search_widget))
1714 is_searching = FALSE;
1716 gtk_tree_model_get (model, iter,
1717 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1718 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1719 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1720 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1723 if (individual != NULL)
1725 visible = individual_view_is_visible_individual (self, individual,
1726 is_online, is_searching);
1728 g_object_unref (individual);
1730 /* FIXME: Work around bgo#626552/bgo#621076 */
1731 if (visible == TRUE)
1733 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1734 individual_view_verify_group_visibility (self, path);
1735 gtk_tree_path_free (path);
1744 /* Not a contact, not a separator, must be a group */
1745 g_return_val_if_fail (is_group, FALSE);
1747 /* only show groups which are not empty */
1748 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1749 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1751 gtk_tree_model_get (model, &child_iter,
1752 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1753 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1756 if (individual == NULL)
1759 visible = individual_view_is_visible_individual (self, individual,
1760 is_online, is_searching);
1761 g_object_unref (individual);
1763 /* show group if it has at least one visible contact in it */
1764 if (visible == TRUE)
1772 individual_view_constructed (GObject *object)
1774 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1775 GtkCellRenderer *cell;
1776 GtkTreeViewColumn *col;
1781 "headers-visible", FALSE,
1782 "show-expanders", FALSE,
1785 col = gtk_tree_view_column_new ();
1788 cell = gtk_cell_renderer_pixbuf_new ();
1789 gtk_tree_view_column_pack_start (col, cell, FALSE);
1790 gtk_tree_view_column_set_cell_data_func (col, cell,
1791 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1801 cell = gtk_cell_renderer_pixbuf_new ();
1802 gtk_tree_view_column_pack_start (col, cell, FALSE);
1803 gtk_tree_view_column_set_cell_data_func (col, cell,
1804 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1816 cell = empathy_cell_renderer_text_new ();
1817 gtk_tree_view_column_pack_start (col, cell, TRUE);
1818 gtk_tree_view_column_set_cell_data_func (col, cell,
1819 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1821 gtk_tree_view_column_add_attribute (col, cell,
1822 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1823 gtk_tree_view_column_add_attribute (col, cell,
1824 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1825 gtk_tree_view_column_add_attribute (col, cell,
1826 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1827 gtk_tree_view_column_add_attribute (col, cell,
1828 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1829 gtk_tree_view_column_add_attribute (col, cell,
1830 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1831 gtk_tree_view_column_add_attribute (col, cell,
1832 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1834 /* Audio Call Icon */
1835 cell = empathy_cell_renderer_activatable_new ();
1836 gtk_tree_view_column_pack_start (col, cell, FALSE);
1837 gtk_tree_view_column_set_cell_data_func (col, cell,
1838 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1841 g_object_set (cell, "visible", FALSE, NULL);
1843 g_signal_connect (cell, "path-activated",
1844 G_CALLBACK (individual_view_call_activated_cb), view);
1847 cell = gtk_cell_renderer_pixbuf_new ();
1848 gtk_tree_view_column_pack_start (col, cell, FALSE);
1849 gtk_tree_view_column_set_cell_data_func (col, cell,
1850 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1862 cell = empathy_cell_renderer_expander_new ();
1863 gtk_tree_view_column_pack_end (col, cell, FALSE);
1864 gtk_tree_view_column_set_cell_data_func (col, cell,
1865 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1868 /* Actually add the column now we have added all cell renderers */
1869 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1872 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1874 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1877 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1879 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1885 individual_view_set_view_features (EmpathyIndividualView *view,
1886 EmpathyIndividualFeatureFlags features)
1888 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1889 gboolean has_tooltip;
1891 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1893 priv->view_features = features;
1895 /* Setting reorderable is a hack that gets us row previews as drag icons
1896 for free. We override all the drag handlers. It's tricky to get the
1897 position of the drag icon right in drag_begin. GtkTreeView has special
1898 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1901 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1902 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1904 /* Update DnD source/dest */
1905 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1907 gtk_drag_source_set (GTK_WIDGET (view),
1910 G_N_ELEMENTS (drag_types_source),
1911 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1915 gtk_drag_source_unset (GTK_WIDGET (view));
1919 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1921 gtk_drag_dest_set (GTK_WIDGET (view),
1922 GTK_DEST_DEFAULT_ALL,
1924 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1928 /* FIXME: URI could still be droped depending on FT feature */
1929 gtk_drag_dest_unset (GTK_WIDGET (view));
1932 /* Update has-tooltip */
1934 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1935 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1939 individual_view_dispose (GObject *object)
1941 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1942 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1944 tp_clear_object (&priv->store);
1945 tp_clear_object (&priv->filter);
1946 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1948 empathy_individual_view_set_live_search (view, NULL);
1950 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1954 individual_view_finalize (GObject *object)
1956 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1958 if (priv->expand_groups_idle_handler != 0)
1959 g_source_remove (priv->expand_groups_idle_handler);
1960 g_hash_table_destroy (priv->expand_groups);
1962 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1966 individual_view_get_property (GObject *object,
1971 EmpathyIndividualViewPriv *priv;
1973 priv = GET_PRIV (object);
1978 g_value_set_object (value, priv->store);
1980 case PROP_VIEW_FEATURES:
1981 g_value_set_flags (value, priv->view_features);
1983 case PROP_INDIVIDUAL_FEATURES:
1984 g_value_set_flags (value, priv->individual_features);
1986 case PROP_SHOW_OFFLINE:
1987 g_value_set_boolean (value, priv->show_offline);
1989 case PROP_SHOW_UNTRUSTED:
1990 g_value_set_boolean (value, priv->show_untrusted);
1993 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1999 individual_view_set_property (GObject *object,
2001 const GValue *value,
2004 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2005 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2010 empathy_individual_view_set_store (view, g_value_get_object (value));
2012 case PROP_VIEW_FEATURES:
2013 individual_view_set_view_features (view, g_value_get_flags (value));
2015 case PROP_INDIVIDUAL_FEATURES:
2016 priv->individual_features = g_value_get_flags (value);
2018 case PROP_SHOW_OFFLINE:
2019 empathy_individual_view_set_show_offline (view,
2020 g_value_get_boolean (value));
2022 case PROP_SHOW_UNTRUSTED:
2023 empathy_individual_view_set_show_untrusted (view,
2024 g_value_get_boolean (value));
2027 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2033 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2035 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2036 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2037 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2039 object_class->constructed = individual_view_constructed;
2040 object_class->dispose = individual_view_dispose;
2041 object_class->finalize = individual_view_finalize;
2042 object_class->get_property = individual_view_get_property;
2043 object_class->set_property = individual_view_set_property;
2045 widget_class->drag_data_received = individual_view_drag_data_received;
2046 widget_class->drag_drop = individual_view_drag_drop;
2047 widget_class->drag_begin = individual_view_drag_begin;
2048 widget_class->drag_data_get = individual_view_drag_data_get;
2049 widget_class->drag_end = individual_view_drag_end;
2050 widget_class->drag_motion = individual_view_drag_motion;
2052 /* We use the class method to let user of this widget to connect to
2053 * the signal and stop emission of the signal so the default handler
2054 * won't be called. */
2055 tree_view_class->row_activated = individual_view_row_activated;
2057 klass->drag_individual_received = real_drag_individual_received_cb;
2059 signals[DRAG_INDIVIDUAL_RECEIVED] =
2060 g_signal_new ("drag-individual-received",
2061 G_OBJECT_CLASS_TYPE (klass),
2063 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2065 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2066 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2067 G_TYPE_STRING, G_TYPE_STRING);
2069 signals[DRAG_PERSONA_RECEIVED] =
2070 g_signal_new ("drag-persona-received",
2071 G_OBJECT_CLASS_TYPE (klass),
2073 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2075 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2076 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2078 g_object_class_install_property (object_class,
2080 g_param_spec_object ("store",
2081 "The store of the view",
2082 "The store of the view",
2083 EMPATHY_TYPE_INDIVIDUAL_STORE,
2084 G_PARAM_READWRITE));
2085 g_object_class_install_property (object_class,
2087 g_param_spec_flags ("view-features",
2088 "Features of the view",
2089 "Flags for all enabled features",
2090 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2091 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2092 g_object_class_install_property (object_class,
2093 PROP_INDIVIDUAL_FEATURES,
2094 g_param_spec_flags ("individual-features",
2095 "Features of the individual menu",
2096 "Flags for all enabled features for the menu",
2097 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2098 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2099 g_object_class_install_property (object_class,
2101 g_param_spec_boolean ("show-offline",
2103 "Whether contact list should display "
2104 "offline contacts", FALSE, G_PARAM_READWRITE));
2105 g_object_class_install_property (object_class,
2106 PROP_SHOW_UNTRUSTED,
2107 g_param_spec_boolean ("show-untrusted",
2108 "Show Untrusted Individuals",
2109 "Whether the view should display untrusted individuals; "
2110 "those who could not be who they say they are.",
2111 TRUE, G_PARAM_READWRITE));
2113 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2117 empathy_individual_view_init (EmpathyIndividualView *view)
2119 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2120 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2124 priv->show_untrusted = TRUE;
2126 /* Get saved group states. */
2127 empathy_contact_groups_get_all ();
2129 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2130 (GDestroyNotify) g_free, NULL);
2132 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2133 empathy_individual_store_row_separator_func, NULL, NULL);
2135 /* Connect to tree view signals rather than override. */
2136 g_signal_connect (view, "button-press-event",
2137 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2138 g_signal_connect (view, "key-press-event",
2139 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2140 g_signal_connect (view, "row-expanded",
2141 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2142 GINT_TO_POINTER (TRUE));
2143 g_signal_connect (view, "row-collapsed",
2144 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2145 GINT_TO_POINTER (FALSE));
2146 g_signal_connect (view, "query-tooltip",
2147 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2150 EmpathyIndividualView *
2151 empathy_individual_view_new (EmpathyIndividualStore *store,
2152 EmpathyIndividualViewFeatureFlags view_features,
2153 EmpathyIndividualFeatureFlags individual_features)
2155 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2157 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2159 "individual-features", individual_features,
2160 "view-features", view_features, NULL);
2164 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2166 EmpathyIndividualViewPriv *priv;
2167 GtkTreeSelection *selection;
2169 GtkTreeModel *model;
2170 FolksIndividual *individual;
2172 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2174 priv = GET_PRIV (view);
2176 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2177 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2180 gtk_tree_model_get (model, &iter,
2181 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2187 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2188 gboolean *is_fake_group)
2190 EmpathyIndividualViewPriv *priv;
2191 GtkTreeSelection *selection;
2193 GtkTreeModel *model;
2198 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2200 priv = GET_PRIV (view);
2202 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2203 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2206 gtk_tree_model_get (model, &iter,
2207 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2208 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2209 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2217 if (is_fake_group != NULL)
2218 *is_fake_group = fake;
2224 individual_view_remove_dialog_show (GtkWindow *parent,
2225 const gchar *message,
2226 const gchar *secondary_text)
2231 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2232 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2233 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2234 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2235 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2236 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2237 "%s", secondary_text);
2239 gtk_widget_show (dialog);
2241 res = gtk_dialog_run (GTK_DIALOG (dialog));
2242 gtk_widget_destroy (dialog);
2244 return (res == GTK_RESPONSE_YES);
2248 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2249 EmpathyIndividualView *view)
2253 group = empathy_individual_view_get_selected_group (view, NULL);
2260 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2262 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2263 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2266 EmpathyIndividualManager *manager =
2267 empathy_individual_manager_dup_singleton ();
2268 empathy_individual_manager_remove_group (manager, group);
2269 g_object_unref (G_OBJECT (manager));
2279 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2281 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2286 gboolean is_fake_group;
2288 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2290 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2291 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2294 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2295 if (!group || is_fake_group)
2297 /* We can't alter fake groups */
2301 menu = gtk_menu_new ();
2304 if (priv->view_features &
2305 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2306 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2307 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2308 gtk_widget_show (item);
2309 g_signal_connect (item, "activate",
2310 G_CALLBACK (individual_view_group_rename_activate_cb),
2315 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2317 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2318 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2319 GTK_ICON_SIZE_MENU);
2320 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2321 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2322 gtk_widget_show (item);
2323 g_signal_connect (item, "activate",
2324 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2333 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2334 EmpathyIndividualView *view)
2336 FolksIndividual *individual;
2338 individual = empathy_individual_view_dup_selected (view);
2340 if (individual != NULL)
2345 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2348 ("Do you really want to remove the contact '%s'?"),
2349 folks_individual_get_alias (individual));
2350 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2353 EmpathyIndividualManager *manager;
2355 manager = empathy_individual_manager_dup_singleton ();
2356 empathy_individual_manager_remove (manager, individual, "");
2357 g_object_unref (G_OBJECT (manager));
2361 g_object_unref (individual);
2366 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2367 EmpathyLinkingDialog *linking_dialog,
2368 EmpathyIndividualView *self)
2370 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2371 EmpathyIndividualLinker *linker;
2373 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2374 empathy_individual_linker_set_search_text (linker,
2375 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2379 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2381 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2382 FolksIndividual *individual;
2383 GtkWidget *menu = NULL;
2386 gboolean can_remove = FALSE;
2389 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2391 individual = empathy_individual_view_dup_selected (view);
2392 if (individual == NULL)
2395 /* If any of the Individual's personas can be removed, add an option to
2396 * remove. This will act as a best-effort option. If any Personas cannot be
2397 * removed from the server, then this option will just be inactive upon
2398 * subsequent menu openings */
2399 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2401 FolksPersona *persona = FOLKS_PERSONA (l->data);
2402 FolksPersonaStore *store = folks_persona_get_store (persona);
2403 FolksMaybeBool maybe_can_remove =
2404 folks_persona_store_get_can_remove_personas (store);
2406 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2413 menu = empathy_individual_menu_new (individual, priv->individual_features);
2415 /* Remove contact */
2416 if ((priv->view_features &
2417 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2420 /* create the menu if required, or just add a separator */
2422 menu = gtk_menu_new ();
2425 item = gtk_separator_menu_item_new ();
2426 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2427 gtk_widget_show (item);
2431 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2432 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2433 GTK_ICON_SIZE_MENU);
2434 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2435 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2436 gtk_widget_show (item);
2437 g_signal_connect (item, "activate",
2438 G_CALLBACK (individual_view_remove_activate_cb), view);
2441 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2442 * set the live search text on the new linking dialogue to be the same as
2444 g_signal_connect (menu, "link-contacts-activated",
2445 (GCallback) individual_menu_link_contacts_activated_cb, view);
2447 g_object_unref (individual);
2453 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2454 EmpathyLiveSearch *search)
2456 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2458 /* remove old handlers if old search was not null */
2459 if (priv->search_widget != NULL)
2461 g_signal_handlers_disconnect_by_func (view,
2462 individual_view_start_search_cb, NULL);
2464 g_signal_handlers_disconnect_by_func (priv->search_widget,
2465 individual_view_search_text_notify_cb, view);
2466 g_signal_handlers_disconnect_by_func (priv->search_widget,
2467 individual_view_search_activate_cb, view);
2468 g_signal_handlers_disconnect_by_func (priv->search_widget,
2469 individual_view_search_key_navigation_cb, view);
2470 g_signal_handlers_disconnect_by_func (priv->search_widget,
2471 individual_view_search_hide_cb, view);
2472 g_signal_handlers_disconnect_by_func (priv->search_widget,
2473 individual_view_search_show_cb, view);
2474 g_object_unref (priv->search_widget);
2475 priv->search_widget = NULL;
2478 /* connect handlers if new search is not null */
2481 priv->search_widget = g_object_ref (search);
2483 g_signal_connect (view, "start-interactive-search",
2484 G_CALLBACK (individual_view_start_search_cb), NULL);
2486 g_signal_connect (priv->search_widget, "notify::text",
2487 G_CALLBACK (individual_view_search_text_notify_cb), view);
2488 g_signal_connect (priv->search_widget, "activate",
2489 G_CALLBACK (individual_view_search_activate_cb), view);
2490 g_signal_connect (priv->search_widget, "key-navigation",
2491 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2492 g_signal_connect (priv->search_widget, "hide",
2493 G_CALLBACK (individual_view_search_hide_cb), view);
2494 g_signal_connect (priv->search_widget, "show",
2495 G_CALLBACK (individual_view_search_show_cb), view);
2500 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2502 EmpathyIndividualViewPriv *priv;
2504 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2506 priv = GET_PRIV (self);
2508 return (priv->search_widget != NULL &&
2509 gtk_widget_get_visible (priv->search_widget));
2513 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2515 EmpathyIndividualViewPriv *priv;
2517 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2519 priv = GET_PRIV (self);
2521 return priv->show_offline;
2525 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2526 gboolean show_offline)
2528 EmpathyIndividualViewPriv *priv;
2530 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2532 priv = GET_PRIV (self);
2534 priv->show_offline = show_offline;
2536 g_object_notify (G_OBJECT (self), "show-offline");
2537 gtk_tree_model_filter_refilter (priv->filter);
2541 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2543 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2545 return GET_PRIV (self)->show_untrusted;
2549 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2550 gboolean show_untrusted)
2552 EmpathyIndividualViewPriv *priv;
2554 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2556 priv = GET_PRIV (self);
2558 priv->show_untrusted = show_untrusted;
2560 g_object_notify (G_OBJECT (self), "show-untrusted");
2561 gtk_tree_model_filter_refilter (priv->filter);
2564 EmpathyIndividualStore *
2565 empathy_individual_view_get_store (EmpathyIndividualView *self)
2567 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2569 return GET_PRIV (self)->store;
2573 empathy_individual_view_set_store (EmpathyIndividualView *self,
2574 EmpathyIndividualStore *store)
2576 EmpathyIndividualViewPriv *priv;
2578 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2579 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2581 priv = GET_PRIV (self);
2583 /* Destroy the old filter and remove the old store */
2584 if (priv->store != NULL)
2586 g_signal_handlers_disconnect_by_func (priv->store,
2587 individual_view_store_row_changed_cb, self);
2588 g_signal_handlers_disconnect_by_func (priv->store,
2589 individual_view_store_row_deleted_cb, self);
2591 g_signal_handlers_disconnect_by_func (priv->filter,
2592 individual_view_row_has_child_toggled_cb, self);
2594 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2597 tp_clear_object (&priv->filter);
2598 tp_clear_object (&priv->store);
2600 /* Set the new store */
2601 priv->store = store;
2605 g_object_ref (store);
2607 /* Create a new filter */
2608 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2609 GTK_TREE_MODEL (priv->store), NULL));
2610 gtk_tree_model_filter_set_visible_func (priv->filter,
2611 individual_view_filter_visible_func, self, NULL);
2613 g_signal_connect (priv->filter, "row-has-child-toggled",
2614 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2615 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2616 GTK_TREE_MODEL (priv->filter));
2618 tp_g_signal_connect_object (priv->store, "row-changed",
2619 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2620 tp_g_signal_connect_object (priv->store, "row-inserted",
2621 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2622 tp_g_signal_connect_object (priv->store, "row-deleted",
2623 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2628 empathy_individual_view_start_search (EmpathyIndividualView *self)
2630 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2632 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2633 g_return_if_fail (priv->search_widget != NULL);
2635 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2636 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2638 gtk_widget_show (GTK_WIDGET (priv->search_widget));