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-contact-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
91 } EmpathyIndividualViewPriv;
95 EmpathyIndividualView *view;
102 EmpathyIndividualView *view;
103 FolksIndividual *individual;
112 PROP_INDIVIDUAL_FEATURES,
117 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
118 * specific EmpathyContacts (between/in/out of Individuals) */
121 DND_DRAG_TYPE_INDIVIDUAL_ID,
122 DND_DRAG_TYPE_PERSONA_ID,
123 DND_DRAG_TYPE_URI_LIST,
124 DND_DRAG_TYPE_STRING,
127 #define DRAG_TYPE(T,I) \
128 { (gchar *) T, 0, I }
130 static const GtkTargetEntry drag_types_dest[] = {
131 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
132 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
133 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
134 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
135 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
136 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
139 static const GtkTargetEntry drag_types_source[] = {
140 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
145 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
146 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
150 DRAG_INDIVIDUAL_RECEIVED,
151 DRAG_PERSONA_RECEIVED,
155 static guint signals[LAST_SIGNAL];
157 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
161 individual_view_tooltip_destroy_cb (GtkWidget *widget,
162 EmpathyIndividualView *view)
164 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
166 if (priv->tooltip_widget != NULL)
168 DEBUG ("Tooltip destroyed");
169 tp_clear_object (&priv->tooltip_widget);
174 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
177 gboolean keyboard_mode,
181 EmpathyIndividualViewPriv *priv;
182 FolksIndividual *individual;
186 static gint running = 0;
187 gboolean ret = FALSE;
189 priv = GET_PRIV (view);
191 /* Avoid an infinite loop. See GNOME bug #574377 */
197 /* Don't show the tooltip if there's already a popup menu */
198 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
201 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
202 keyboard_mode, &model, &path, &iter))
205 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
206 gtk_tree_path_free (path);
208 gtk_tree_model_get (model, &iter,
209 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
211 if (individual == NULL)
214 if (priv->tooltip_widget == NULL)
216 priv->tooltip_widget = empathy_individual_widget_new (individual,
217 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
219 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
220 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
221 g_object_ref (priv->tooltip_widget);
222 g_signal_connect (priv->tooltip_widget, "destroy",
223 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
224 gtk_widget_show (priv->tooltip_widget);
228 empathy_individual_widget_set_individual (
229 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
232 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
235 g_object_unref (individual);
243 groups_change_group_cb (GObject *source,
244 GAsyncResult *result,
247 FolksGroupable *groupable = FOLKS_GROUPABLE (source);
248 GError *error = NULL;
250 folks_groupable_change_group_finish (groupable, result, &error);
253 g_warning ("failed to change group: %s", error->message);
254 g_clear_error (&error);
259 group_can_be_modified (const gchar *name,
260 gboolean is_fake_group,
263 /* Real groups can always be modified */
267 /* The favorite fake group can be modified so users can
268 * add/remove favorites using DnD */
269 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
272 /* We can remove contacts from the 'ungrouped' fake group */
273 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
280 individual_view_individual_drag_received (GtkWidget *self,
281 GdkDragContext *context,
284 GtkSelectionData *selection)
286 EmpathyIndividualViewPriv *priv;
287 EmpathyIndividualManager *manager = NULL;
288 FolksIndividual *individual;
289 GtkTreePath *source_path;
290 const gchar *sel_data;
291 gchar *new_group = NULL;
292 gchar *old_group = NULL;
293 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
295 priv = GET_PRIV (self);
297 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
298 new_group = empathy_individual_store_get_parent_group (model, path,
299 NULL, &new_group_is_fake);
301 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
304 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
305 * feature. Otherwise, we just add the dropped contact to whichever group
306 * they were dropped in, and don't remove them from their old group. This
307 * allows for Individual views which shouldn't allow Individuals to have
308 * their groups changed, and also for dragging Individuals between Individual
310 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
311 priv->drag_row != NULL)
313 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
317 empathy_individual_store_get_parent_group (model, source_path,
318 NULL, &old_group_is_fake);
319 gtk_tree_path_free (source_path);
322 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
325 if (!tp_strdiff (old_group, new_group))
328 else if (priv->drag_row != NULL)
330 /* We don't allow changing Individuals' groups, and this Individual was
331 * dragged from another group in *this* Individual view, so we disallow
336 /* XXX: for contacts, we used to ensure the account, create the contact
337 * factory, and then wait on the contacts. But they should already be
338 * created by this point */
340 manager = empathy_individual_manager_dup_singleton ();
341 individual = empathy_individual_manager_lookup_member (manager, sel_data);
343 if (individual == NULL)
345 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
349 /* FIXME: We should probably wait for the cb before calling
352 /* Emit a signal notifying of the drag. We change the Individual's groups in
353 * the default signal handler. */
354 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
355 gdk_drag_context_get_selected_action (context), individual, new_group,
361 tp_clear_object (&manager);
369 real_drag_individual_received_cb (EmpathyIndividualView *self,
370 GdkDragAction action,
371 FolksIndividual *individual,
372 const gchar *new_group,
373 const gchar *old_group)
375 DEBUG ("individual %s dragged from '%s' to '%s'",
376 folks_individual_get_id (individual), old_group, new_group);
378 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
380 /* Mark contact as favourite */
381 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
385 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
387 /* Remove contact as favourite */
388 folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
390 /* Don't try to remove it */
394 if (new_group != NULL)
396 folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
397 groups_change_group_cb, NULL);
400 if (old_group != NULL && action == GDK_ACTION_MOVE)
402 folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
403 FALSE, groups_change_group_cb, NULL);
408 individual_view_persona_drag_received (GtkWidget *self,
409 GdkDragContext *context,
412 GtkSelectionData *selection)
414 EmpathyIndividualViewPriv *priv;
415 EmpathyIndividualManager *manager = NULL;
416 FolksIndividual *individual = NULL;
417 FolksPersona *persona = NULL;
418 const gchar *persona_uid;
419 GList *individuals, *l;
420 gboolean retval = FALSE;
422 priv = GET_PRIV (self);
424 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
426 /* FIXME: This is slow, but the only way to find the Persona we're having
428 manager = empathy_individual_manager_dup_singleton ();
429 individuals = empathy_individual_manager_get_members (manager);
431 for (l = individuals; l != NULL; l = l->next)
435 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
437 for (p = personas; p != NULL; p = p->next)
439 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
442 persona = g_object_ref (p->data);
443 individual = g_object_ref (l->data);
450 g_list_free (individuals);
452 if (persona == NULL || individual == NULL)
454 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
458 /* Emit a signal notifying of the drag. We change the Individual's groups in
459 * the default signal handler. */
460 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
461 gdk_drag_context_get_selected_action (context), persona, individual,
465 tp_clear_object (&manager);
466 tp_clear_object (&persona);
467 tp_clear_object (&individual);
473 individual_view_file_drag_received (GtkWidget *view,
474 GdkDragContext *context,
477 GtkSelectionData *selection)
480 const gchar *sel_data;
481 FolksIndividual *individual;
482 EmpathyContact *contact;
484 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
486 gtk_tree_model_get_iter (model, &iter, path);
487 gtk_tree_model_get (model, &iter,
488 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
489 if (individual == NULL)
492 contact = empathy_contact_dup_from_folks_individual (individual);
493 empathy_send_file_from_uri_list (contact, sel_data);
495 g_object_unref (individual);
496 tp_clear_object (&contact);
502 individual_view_drag_data_received (GtkWidget *view,
503 GdkDragContext *context,
506 GtkSelectionData *selection,
512 GtkTreeViewDropPosition position;
514 gboolean success = TRUE;
516 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
518 /* Get destination group information. */
519 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
520 x, y, &path, &position);
525 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
527 success = individual_view_individual_drag_received (view,
528 context, model, path, selection);
530 else if (info == DND_DRAG_TYPE_PERSONA_ID)
532 success = individual_view_persona_drag_received (view, context, model,
535 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
537 success = individual_view_file_drag_received (view,
538 context, model, path, selection);
541 gtk_tree_path_free (path);
542 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
546 individual_view_drag_motion_cb (DragMotionData *data)
548 if (data->view != NULL)
550 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
551 g_object_remove_weak_pointer (G_OBJECT (data->view),
552 (gpointer *) &data->view);
555 data->timeout_id = 0;
560 /* Minimum distance between the mouse pointer and a horizontal border when we
561 start auto scrolling. */
562 #define AUTO_SCROLL_MARGIN_SIZE 20
563 /* How far to scroll per one tick. */
564 #define AUTO_SCROLL_PITCH 10
567 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
569 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
573 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
575 if (priv->distance < 0)
576 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
578 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
580 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
581 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
583 gtk_adjustment_set_value (adj, new_value);
589 individual_view_drag_motion (GtkWidget *widget,
590 GdkDragContext *context,
595 EmpathyIndividualViewPriv *priv;
599 static DragMotionData *dm = NULL;
602 gboolean is_different = FALSE;
603 gboolean cleanup = TRUE;
604 gboolean retval = TRUE;
605 GtkAllocation allocation;
607 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
608 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
611 if (priv->auto_scroll_timeout_id != 0)
613 g_source_remove (priv->auto_scroll_timeout_id);
614 priv->auto_scroll_timeout_id = 0;
617 gtk_widget_get_allocation (widget, &allocation);
619 if (y < AUTO_SCROLL_MARGIN_SIZE ||
620 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
622 if (y < AUTO_SCROLL_MARGIN_SIZE)
623 priv->distance = MIN (-y, -1);
625 priv->distance = MAX (allocation.height - y, 1);
627 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
628 (GSourceFunc) individual_view_auto_scroll_cb, widget);
631 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
632 x, y, &path, NULL, NULL, NULL);
634 cleanup &= (dm == NULL);
638 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
639 is_different = ((dm == NULL) || ((dm != NULL)
640 && gtk_tree_path_compare (dm->path, path) != 0));
647 /* Coordinates don't point to an actual row, so make sure the pointer
648 and highlighting don't indicate that a drag is possible.
650 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
651 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
654 target = gtk_drag_dest_find_target (widget, context, NULL);
655 gtk_tree_model_get_iter (model, &iter, path);
657 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
658 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
660 /* This is a file drag, and it can only be dropped on contacts,
662 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
663 * even if we have a valid target. */
664 FolksIndividual *individual = NULL;
665 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
667 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
669 gtk_tree_model_get (model, &iter,
670 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
674 if (individual != NULL)
676 EmpathyContact *contact = NULL;
678 contact = empathy_contact_dup_from_folks_individual (individual);
679 caps = empathy_contact_get_capabilities (contact);
681 tp_clear_object (&contact);
684 if (individual != NULL &&
685 folks_presence_owner_is_online (FOLKS_PRESENCE_OWNER (individual)) &&
686 (caps & EMPATHY_CAPABILITIES_FT))
688 gdk_drag_status (context, GDK_ACTION_COPY, time_);
689 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
690 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
694 gdk_drag_status (context, 0, time_);
695 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
699 if (individual != NULL)
700 g_object_unref (individual);
702 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
703 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
704 priv->drag_row == NULL)) ||
705 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
706 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
708 /* If target != GDK_NONE, then we have a contact (individual or persona)
709 drag. If we're pointing to a group, highlight it. Otherwise, if the
710 contact we're pointing to is in a group, highlight that. Otherwise,
711 set the drag position to before the first row for a drag into
712 the "non-group" at the top.
713 If it's an Individual:
714 We only highlight things if the contact is from a different
715 Individual view, or if this Individual view has
716 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
717 which don't have FEATURE_GROUPS_CHANGE, but do have
718 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
720 We only highlight things if we have FEATURE_PERSONA_DROP.
722 GtkTreeIter group_iter;
724 GtkTreePath *group_path;
725 gtk_tree_model_get (model, &iter,
726 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
733 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
734 gtk_tree_model_get (model, &group_iter,
735 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
739 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
740 group_path = gtk_tree_model_get_path (model, &group_iter);
741 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
742 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
743 gtk_tree_path_free (group_path);
747 group_path = gtk_tree_path_new_first ();
748 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
749 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
750 group_path, GTK_TREE_VIEW_DROP_BEFORE);
754 if (!is_different && !cleanup)
759 gtk_tree_path_free (dm->path);
762 g_source_remove (dm->timeout_id);
770 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
772 dm = g_new0 (DragMotionData, 1);
774 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
775 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
776 dm->path = gtk_tree_path_copy (path);
778 dm->timeout_id = g_timeout_add_seconds (1,
779 (GSourceFunc) individual_view_drag_motion_cb, dm);
786 individual_view_drag_begin (GtkWidget *widget,
787 GdkDragContext *context)
789 EmpathyIndividualViewPriv *priv;
790 GtkTreeSelection *selection;
795 priv = GET_PRIV (widget);
797 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
800 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
801 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
804 path = gtk_tree_model_get_path (model, &iter);
805 priv->drag_row = gtk_tree_row_reference_new (model, path);
806 gtk_tree_path_free (path);
810 individual_view_drag_data_get (GtkWidget *widget,
811 GdkDragContext *context,
812 GtkSelectionData *selection,
816 EmpathyIndividualViewPriv *priv;
817 GtkTreePath *src_path;
820 FolksIndividual *individual;
821 const gchar *individual_id;
823 priv = GET_PRIV (widget);
825 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
826 if (priv->drag_row == NULL)
829 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
830 if (src_path == NULL)
833 if (!gtk_tree_model_get_iter (model, &iter, src_path))
835 gtk_tree_path_free (src_path);
839 gtk_tree_path_free (src_path);
842 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
843 if (individual == NULL)
846 individual_id = folks_individual_get_id (individual);
848 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
850 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
851 (guchar *) individual_id, strlen (individual_id) + 1);
854 g_object_unref (individual);
858 individual_view_drag_end (GtkWidget *widget,
859 GdkDragContext *context)
861 EmpathyIndividualViewPriv *priv;
863 priv = GET_PRIV (widget);
865 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
870 gtk_tree_row_reference_free (priv->drag_row);
871 priv->drag_row = NULL;
876 individual_view_drag_drop (GtkWidget *widget,
877 GdkDragContext *drag_context,
887 EmpathyIndividualView *view;
893 menu_deactivate_cb (GtkMenuShell *menushell,
896 gtk_menu_detach (GTK_MENU (menushell));
898 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
899 g_signal_handlers_disconnect_by_func (menushell,
900 menu_deactivate_cb, user_data);
904 individual_view_popup_menu_idle_cb (gpointer user_data)
906 MenuPopupData *data = user_data;
909 menu = empathy_individual_view_get_individual_menu (data->view);
911 menu = empathy_individual_view_get_group_menu (data->view);
915 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
917 gtk_widget_show (menu);
918 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
921 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
922 * floating ref. We can either wait that the treeview releases its ref
923 * when it will be destroyed (when leaving Empathy) or explicitely
924 * detach the menu when it's not displayed any more.
925 * We go for the latter as we don't want to keep useless menus in memory
926 * during the whole lifetime of Empathy. */
927 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
931 g_slice_free (MenuPopupData, data);
937 individual_view_button_press_event_cb (EmpathyIndividualView *view,
938 GdkEventButton *event,
941 if (event->button == 3)
945 data = g_slice_new (MenuPopupData);
947 data->button = event->button;
948 data->time = event->time;
949 g_idle_add (individual_view_popup_menu_idle_cb, data);
956 individual_view_key_press_event_cb (EmpathyIndividualView *view,
960 if (event->keyval == GDK_KEY_Menu)
964 data = g_slice_new (MenuPopupData);
967 data->time = event->time;
968 g_idle_add (individual_view_popup_menu_idle_cb, data);
969 } else if (event->keyval == GDK_KEY_F2) {
970 FolksIndividual *individual;
971 EmpathyContact *contact;
973 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
975 individual = empathy_individual_view_dup_selected (view);
976 if (individual == NULL)
979 contact = empathy_contact_dup_from_folks_individual (individual);
980 if (contact == NULL) {
981 g_object_unref (individual);
984 empathy_contact_edit_dialog_show (contact, NULL);
986 g_object_unref (individual);
987 g_object_unref (contact);
994 individual_view_row_activated (GtkTreeView *view,
996 GtkTreeViewColumn *column)
998 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
999 FolksIndividual *individual;
1000 EmpathyContact *contact;
1001 GtkTreeModel *model;
1004 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1007 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1008 gtk_tree_model_get_iter (model, &iter, path);
1009 gtk_tree_model_get (model, &iter,
1010 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1012 if (individual == NULL)
1015 /* Determine which Persona to chat to, by choosing the most available one. */
1016 contact = empathy_contact_dup_best_for_action (individual,
1017 EMPATHY_ACTION_CHAT);
1019 if (contact != NULL)
1021 DEBUG ("Starting a chat");
1023 empathy_dispatcher_chat_with_contact (contact,
1024 gtk_get_current_event_time ());
1027 g_object_unref (individual);
1028 tp_clear_object (&contact);
1032 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1033 const gchar *path_string,
1034 EmpathyIndividualView *view)
1036 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1038 GtkTreeModel *model;
1040 FolksIndividual *individual;
1041 GdkEventButton *event;
1042 GtkMenuShell *shell;
1045 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1048 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1049 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1052 gtk_tree_model_get (model, &iter,
1053 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1054 if (individual == NULL)
1057 event = (GdkEventButton *) gtk_get_current_event ();
1059 menu = gtk_menu_new ();
1060 shell = GTK_MENU_SHELL (menu);
1063 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1064 gtk_menu_shell_append (shell, item);
1065 gtk_widget_show (item);
1068 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1069 gtk_menu_shell_append (shell, item);
1070 gtk_widget_show (item);
1072 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1073 gtk_widget_show (menu);
1074 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1075 event->button, event->time);
1076 g_object_ref_sink (menu);
1077 g_object_unref (menu);
1079 g_object_unref (individual);
1083 individual_view_cell_set_background (EmpathyIndividualView *view,
1084 GtkCellRenderer *cell,
1088 if (!is_group && is_active)
1090 GtkStyleContext *style;
1093 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1095 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1098 /* Here we take the current theme colour and add it to
1099 * the colour for white and average the two. This
1100 * gives a colour which is inline with the theme but
1103 empathy_make_color_whiter (&color);
1105 g_object_set (cell, "cell-background-rgba", &color, NULL);
1108 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1112 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1113 GtkCellRenderer *cell,
1114 GtkTreeModel *model,
1116 EmpathyIndividualView *view)
1122 gtk_tree_model_get (model, iter,
1123 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1124 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1125 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1128 "visible", !is_group,
1132 tp_clear_object (&pixbuf);
1134 individual_view_cell_set_background (view, cell, is_group, is_active);
1138 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1139 GtkCellRenderer *cell,
1140 GtkTreeModel *model,
1142 EmpathyIndividualView *view)
1144 GdkPixbuf *pixbuf = NULL;
1148 gtk_tree_model_get (model, iter,
1149 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1150 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1155 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1157 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1158 GTK_ICON_SIZE_MENU);
1160 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1162 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1163 GTK_ICON_SIZE_MENU);
1168 "visible", pixbuf != NULL,
1172 tp_clear_object (&pixbuf);
1178 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1179 GtkCellRenderer *cell,
1180 GtkTreeModel *model,
1182 EmpathyIndividualView *view)
1186 gboolean can_audio, can_video;
1188 gtk_tree_model_get (model, iter,
1189 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1190 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1191 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1192 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1195 "visible", !is_group && (can_audio || can_video),
1196 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1199 individual_view_cell_set_background (view, cell, is_group, is_active);
1203 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1204 GtkCellRenderer *cell,
1205 GtkTreeModel *model,
1207 EmpathyIndividualView *view)
1210 gboolean show_avatar;
1214 gtk_tree_model_get (model, iter,
1215 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1216 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1217 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1218 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1221 "visible", !is_group && show_avatar,
1225 tp_clear_object (&pixbuf);
1227 individual_view_cell_set_background (view, cell, is_group, is_active);
1231 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1232 GtkCellRenderer *cell,
1233 GtkTreeModel *model,
1235 EmpathyIndividualView *view)
1240 gtk_tree_model_get (model, iter,
1241 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1242 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1244 individual_view_cell_set_background (view, cell, is_group, is_active);
1248 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1249 GtkCellRenderer *cell,
1250 GtkTreeModel *model,
1252 EmpathyIndividualView *view)
1257 gtk_tree_model_get (model, iter,
1258 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1259 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1261 if (gtk_tree_model_iter_has_child (model, iter))
1264 gboolean row_expanded;
1266 path = gtk_tree_model_get_path (model, iter);
1268 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1269 (gtk_tree_view_column_get_tree_view (column)), path);
1270 gtk_tree_path_free (path);
1275 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1279 g_object_set (cell, "visible", FALSE, NULL);
1281 individual_view_cell_set_background (view, cell, is_group, is_active);
1285 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1290 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1291 GtkTreeModel *model;
1295 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1298 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1300 gtk_tree_model_get (model, iter,
1301 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1303 expanded = GPOINTER_TO_INT (user_data);
1304 empathy_contact_group_set_expanded (name, expanded);
1310 individual_view_start_search_cb (EmpathyIndividualView *view,
1313 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1315 if (priv->search_widget == NULL)
1318 empathy_individual_view_start_search (view);
1324 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1326 EmpathyIndividualView *view)
1328 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1330 GtkTreeViewColumn *focus_column;
1331 GtkTreeModel *model;
1333 gboolean set_cursor = FALSE;
1335 gtk_tree_model_filter_refilter (priv->filter);
1337 /* Set cursor on the first contact. If it is already set on a group,
1338 * set it on its first child contact. Note that first child of a group
1339 * is its separator, that's why we actually set to the 2nd
1342 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1343 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1347 path = gtk_tree_path_new_from_string ("0:1");
1350 else if (gtk_tree_path_get_depth (path) < 2)
1354 gtk_tree_model_get_iter (model, &iter, path);
1355 gtk_tree_model_get (model, &iter,
1356 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1361 gtk_tree_path_down (path);
1362 gtk_tree_path_next (path);
1369 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1371 if (gtk_tree_model_get_iter (model, &iter, path))
1373 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1378 gtk_tree_path_free (path);
1382 individual_view_search_activate_cb (GtkWidget *search,
1383 EmpathyIndividualView *view)
1386 GtkTreeViewColumn *focus_column;
1388 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1391 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1392 gtk_tree_path_free (path);
1394 gtk_widget_hide (search);
1399 individual_view_search_key_navigation_cb (GtkWidget *search,
1401 EmpathyIndividualView *view)
1403 GdkEventKey *eventkey = ((GdkEventKey *) event);
1404 gboolean ret = FALSE;
1406 if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down
1407 || eventkey->keyval == GDK_KEY_F2)
1409 GdkEvent *new_event;
1411 new_event = gdk_event_copy (event);
1412 gtk_widget_grab_focus (GTK_WIDGET (view));
1413 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1414 gtk_widget_grab_focus (search);
1416 gdk_event_free (new_event);
1423 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1424 EmpathyIndividualView *view)
1426 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1427 GtkTreeModel *model;
1428 GtkTreePath *cursor_path;
1430 gboolean valid = FALSE;
1432 /* block expand or collapse handlers, they would write the
1433 * expand or collapsed setting to file otherwise */
1434 g_signal_handlers_block_by_func (view,
1435 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1436 g_signal_handlers_block_by_func (view,
1437 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1439 /* restore which groups are expanded and which are not */
1440 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1441 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1442 valid; valid = gtk_tree_model_iter_next (model, &iter))
1448 gtk_tree_model_get (model, &iter,
1449 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1450 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1459 path = gtk_tree_model_get_path (model, &iter);
1460 if ((priv->view_features &
1461 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1462 empathy_contact_group_get_expanded (name))
1464 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1468 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1471 gtk_tree_path_free (path);
1475 /* unblock expand or collapse handlers */
1476 g_signal_handlers_unblock_by_func (view,
1477 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1478 g_signal_handlers_unblock_by_func (view,
1479 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1481 /* keep the selected contact visible */
1482 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1484 if (cursor_path != NULL)
1485 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1488 gtk_tree_path_free (cursor_path);
1492 individual_view_search_show_cb (EmpathyLiveSearch *search,
1493 EmpathyIndividualView *view)
1495 /* block expand or collapse handlers during expand all, they would
1496 * write the expand or collapsed setting to file otherwise */
1497 g_signal_handlers_block_by_func (view,
1498 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1500 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1502 g_signal_handlers_unblock_by_func (view,
1503 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1507 expand_idle_foreach_cb (GtkTreeModel *model,
1510 EmpathyIndividualView *self)
1512 EmpathyIndividualViewPriv *priv;
1514 gpointer should_expand;
1517 /* We only want groups */
1518 if (gtk_tree_path_get_depth (path) > 1)
1521 gtk_tree_model_get (model, iter,
1522 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1523 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1526 if (is_group == FALSE)
1532 priv = GET_PRIV (self);
1534 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1535 &should_expand) == TRUE)
1537 if (GPOINTER_TO_INT (should_expand) == TRUE)
1538 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1540 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1542 g_hash_table_remove (priv->expand_groups, name);
1551 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1553 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1555 DEBUG ("individual_view_expand_idle_cb");
1557 g_signal_handlers_block_by_func (self,
1558 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1559 g_signal_handlers_block_by_func (self,
1560 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1562 /* The store/filter could've been removed while we were in the idle queue */
1563 if (priv->filter != NULL)
1565 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1566 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1569 g_signal_handlers_unblock_by_func (self,
1570 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1571 g_signal_handlers_unblock_by_func (self,
1572 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1574 /* Empty the table of groups to expand/contract, since it may contain groups
1575 * which no longer exist in the tree view. This can happen after going
1576 * offline, for example. */
1577 g_hash_table_remove_all (priv->expand_groups);
1578 priv->expand_groups_idle_handler = 0;
1579 g_object_unref (self);
1585 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1588 EmpathyIndividualView *view)
1590 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1591 gboolean should_expand, is_group = FALSE;
1593 gpointer will_expand;
1595 gtk_tree_model_get (model, iter,
1596 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1597 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1600 if (!is_group || EMP_STR_EMPTY (name))
1606 should_expand = (priv->view_features &
1607 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1608 (priv->search_widget != NULL &&
1609 gtk_widget_get_visible (priv->search_widget)) ||
1610 empathy_contact_group_get_expanded (name);
1612 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1613 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1614 * a hash table, and expand or contract them as appropriate all at once in
1615 * an idle handler which iterates over all the group rows. */
1616 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1617 &will_expand) == FALSE ||
1618 GPOINTER_TO_INT (will_expand) != should_expand)
1620 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1621 GINT_TO_POINTER (should_expand));
1623 if (priv->expand_groups_idle_handler == 0)
1625 priv->expand_groups_idle_handler =
1626 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1627 g_object_ref (view));
1634 /* FIXME: This is a workaround for bgo#621076 */
1636 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1639 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1640 GtkTreeModel *model;
1641 GtkTreePath *parent_path;
1642 GtkTreeIter parent_iter;
1644 if (gtk_tree_path_get_depth (path) < 2)
1647 /* A group row is visible if and only if at least one if its child is visible.
1648 * So when a row is inserted/deleted/changed in the base model, that could
1649 * modify the visibility of its parent in the filter model.
1652 model = GTK_TREE_MODEL (priv->store);
1653 parent_path = gtk_tree_path_copy (path);
1654 gtk_tree_path_up (parent_path);
1655 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1657 /* This tells the filter to verify the visibility of that row, and
1658 * show/hide it if necessary */
1659 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1660 parent_path, &parent_iter);
1662 gtk_tree_path_free (parent_path);
1666 individual_view_store_row_changed_cb (GtkTreeModel *model,
1669 EmpathyIndividualView *view)
1671 individual_view_verify_group_visibility (view, path);
1675 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1677 EmpathyIndividualView *view)
1679 individual_view_verify_group_visibility (view, path);
1683 individual_view_is_visible_individual (EmpathyIndividualView *self,
1684 FolksIndividual *individual,
1686 gboolean is_searching)
1688 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1689 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1691 GList *personas, *l;
1693 /* We're only giving the visibility wrt filtering here, not things like
1695 if (priv->show_untrusted == FALSE &&
1696 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1701 if (is_searching == FALSE)
1702 return (priv->show_offline || is_online);
1704 /* check alias name */
1705 str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1707 if (empathy_live_search_match (live, str))
1710 /* check contact id, remove the @server.com part */
1711 personas = folks_individual_get_personas (individual);
1712 for (l = personas; l; l = l->next)
1715 gchar *dup_str = NULL;
1718 if (!TPF_IS_PERSONA (l->data))
1721 str = folks_persona_get_display_id (l->data);
1722 p = strstr (str, "@");
1724 str = dup_str = g_strndup (str, p - str);
1726 visible = empathy_live_search_match (live, str);
1732 /* FIXME: Add more rules here, we could check phone numbers in
1733 * contact's vCard for example. */
1739 individual_view_filter_visible_func (GtkTreeModel *model,
1743 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1744 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1745 FolksIndividual *individual = NULL;
1746 gboolean is_group, is_separator, valid;
1747 GtkTreeIter child_iter;
1748 gboolean visible, is_online;
1749 gboolean is_searching = TRUE;
1751 if (priv->search_widget == NULL ||
1752 !gtk_widget_get_visible (priv->search_widget))
1753 is_searching = FALSE;
1755 gtk_tree_model_get (model, iter,
1756 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1757 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1758 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1759 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1762 if (individual != NULL)
1764 visible = individual_view_is_visible_individual (self, individual,
1765 is_online, is_searching);
1767 g_object_unref (individual);
1769 /* FIXME: Work around bgo#626552/bgo#621076 */
1770 if (visible == TRUE)
1772 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1773 individual_view_verify_group_visibility (self, path);
1774 gtk_tree_path_free (path);
1783 /* Not a contact, not a separator, must be a group */
1784 g_return_val_if_fail (is_group, FALSE);
1786 /* only show groups which are not empty */
1787 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1788 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1790 gtk_tree_model_get (model, &child_iter,
1791 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1792 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1795 if (individual == NULL)
1798 visible = individual_view_is_visible_individual (self, individual,
1799 is_online, is_searching);
1800 g_object_unref (individual);
1802 /* show group if it has at least one visible contact in it */
1803 if (visible == TRUE)
1811 individual_view_constructed (GObject *object)
1813 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1814 GtkCellRenderer *cell;
1815 GtkTreeViewColumn *col;
1820 "headers-visible", FALSE,
1821 "show-expanders", FALSE,
1824 col = gtk_tree_view_column_new ();
1827 cell = gtk_cell_renderer_pixbuf_new ();
1828 gtk_tree_view_column_pack_start (col, cell, FALSE);
1829 gtk_tree_view_column_set_cell_data_func (col, cell,
1830 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1840 cell = gtk_cell_renderer_pixbuf_new ();
1841 gtk_tree_view_column_pack_start (col, cell, FALSE);
1842 gtk_tree_view_column_set_cell_data_func (col, cell,
1843 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1855 cell = empathy_cell_renderer_text_new ();
1856 gtk_tree_view_column_pack_start (col, cell, TRUE);
1857 gtk_tree_view_column_set_cell_data_func (col, cell,
1858 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1860 gtk_tree_view_column_add_attribute (col, cell,
1861 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1862 gtk_tree_view_column_add_attribute (col, cell,
1863 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1864 gtk_tree_view_column_add_attribute (col, cell,
1865 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1866 gtk_tree_view_column_add_attribute (col, cell,
1867 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1868 gtk_tree_view_column_add_attribute (col, cell,
1869 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1870 gtk_tree_view_column_add_attribute (col, cell,
1871 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1872 gtk_tree_view_column_add_attribute (col, cell,
1873 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1875 /* Audio Call Icon */
1876 cell = empathy_cell_renderer_activatable_new ();
1877 gtk_tree_view_column_pack_start (col, cell, FALSE);
1878 gtk_tree_view_column_set_cell_data_func (col, cell,
1879 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1882 g_object_set (cell, "visible", FALSE, NULL);
1884 g_signal_connect (cell, "path-activated",
1885 G_CALLBACK (individual_view_call_activated_cb), view);
1888 cell = gtk_cell_renderer_pixbuf_new ();
1889 gtk_tree_view_column_pack_start (col, cell, FALSE);
1890 gtk_tree_view_column_set_cell_data_func (col, cell,
1891 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1903 cell = empathy_cell_renderer_expander_new ();
1904 gtk_tree_view_column_pack_end (col, cell, FALSE);
1905 gtk_tree_view_column_set_cell_data_func (col, cell,
1906 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1909 /* Actually add the column now we have added all cell renderers */
1910 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1913 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1915 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1918 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1920 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1926 individual_view_set_view_features (EmpathyIndividualView *view,
1927 EmpathyIndividualFeatureFlags features)
1929 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1930 gboolean has_tooltip;
1932 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1934 priv->view_features = features;
1936 /* Setting reorderable is a hack that gets us row previews as drag icons
1937 for free. We override all the drag handlers. It's tricky to get the
1938 position of the drag icon right in drag_begin. GtkTreeView has special
1939 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1942 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1943 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1945 /* Update DnD source/dest */
1946 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1948 gtk_drag_source_set (GTK_WIDGET (view),
1951 G_N_ELEMENTS (drag_types_source),
1952 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1956 gtk_drag_source_unset (GTK_WIDGET (view));
1960 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1962 gtk_drag_dest_set (GTK_WIDGET (view),
1963 GTK_DEST_DEFAULT_ALL,
1965 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1969 /* FIXME: URI could still be droped depending on FT feature */
1970 gtk_drag_dest_unset (GTK_WIDGET (view));
1973 /* Update has-tooltip */
1975 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1976 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1980 individual_view_dispose (GObject *object)
1982 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1983 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1985 tp_clear_object (&priv->store);
1986 tp_clear_object (&priv->filter);
1987 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1989 empathy_individual_view_set_live_search (view, NULL);
1991 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1995 individual_view_finalize (GObject *object)
1997 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1999 if (priv->expand_groups_idle_handler != 0)
2000 g_source_remove (priv->expand_groups_idle_handler);
2001 g_hash_table_destroy (priv->expand_groups);
2003 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2007 individual_view_get_property (GObject *object,
2012 EmpathyIndividualViewPriv *priv;
2014 priv = GET_PRIV (object);
2019 g_value_set_object (value, priv->store);
2021 case PROP_VIEW_FEATURES:
2022 g_value_set_flags (value, priv->view_features);
2024 case PROP_INDIVIDUAL_FEATURES:
2025 g_value_set_flags (value, priv->individual_features);
2027 case PROP_SHOW_OFFLINE:
2028 g_value_set_boolean (value, priv->show_offline);
2030 case PROP_SHOW_UNTRUSTED:
2031 g_value_set_boolean (value, priv->show_untrusted);
2034 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2040 individual_view_set_property (GObject *object,
2042 const GValue *value,
2045 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2046 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2051 empathy_individual_view_set_store (view, g_value_get_object (value));
2053 case PROP_VIEW_FEATURES:
2054 individual_view_set_view_features (view, g_value_get_flags (value));
2056 case PROP_INDIVIDUAL_FEATURES:
2057 priv->individual_features = g_value_get_flags (value);
2059 case PROP_SHOW_OFFLINE:
2060 empathy_individual_view_set_show_offline (view,
2061 g_value_get_boolean (value));
2063 case PROP_SHOW_UNTRUSTED:
2064 empathy_individual_view_set_show_untrusted (view,
2065 g_value_get_boolean (value));
2068 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2074 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2076 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2077 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2078 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2080 object_class->constructed = individual_view_constructed;
2081 object_class->dispose = individual_view_dispose;
2082 object_class->finalize = individual_view_finalize;
2083 object_class->get_property = individual_view_get_property;
2084 object_class->set_property = individual_view_set_property;
2086 widget_class->drag_data_received = individual_view_drag_data_received;
2087 widget_class->drag_drop = individual_view_drag_drop;
2088 widget_class->drag_begin = individual_view_drag_begin;
2089 widget_class->drag_data_get = individual_view_drag_data_get;
2090 widget_class->drag_end = individual_view_drag_end;
2091 widget_class->drag_motion = individual_view_drag_motion;
2093 /* We use the class method to let user of this widget to connect to
2094 * the signal and stop emission of the signal so the default handler
2095 * won't be called. */
2096 tree_view_class->row_activated = individual_view_row_activated;
2098 klass->drag_individual_received = real_drag_individual_received_cb;
2100 signals[DRAG_INDIVIDUAL_RECEIVED] =
2101 g_signal_new ("drag-individual-received",
2102 G_OBJECT_CLASS_TYPE (klass),
2104 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2106 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2107 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2108 G_TYPE_STRING, G_TYPE_STRING);
2110 signals[DRAG_PERSONA_RECEIVED] =
2111 g_signal_new ("drag-persona-received",
2112 G_OBJECT_CLASS_TYPE (klass),
2114 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2116 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2117 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2119 g_object_class_install_property (object_class,
2121 g_param_spec_object ("store",
2122 "The store of the view",
2123 "The store of the view",
2124 EMPATHY_TYPE_INDIVIDUAL_STORE,
2125 G_PARAM_READWRITE));
2126 g_object_class_install_property (object_class,
2128 g_param_spec_flags ("view-features",
2129 "Features of the view",
2130 "Flags for all enabled features",
2131 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2132 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2133 g_object_class_install_property (object_class,
2134 PROP_INDIVIDUAL_FEATURES,
2135 g_param_spec_flags ("individual-features",
2136 "Features of the individual menu",
2137 "Flags for all enabled features for the menu",
2138 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2139 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2140 g_object_class_install_property (object_class,
2142 g_param_spec_boolean ("show-offline",
2144 "Whether contact list should display "
2145 "offline contacts", FALSE, G_PARAM_READWRITE));
2146 g_object_class_install_property (object_class,
2147 PROP_SHOW_UNTRUSTED,
2148 g_param_spec_boolean ("show-untrusted",
2149 "Show Untrusted Individuals",
2150 "Whether the view should display untrusted individuals; "
2151 "those who could not be who they say they are.",
2152 TRUE, G_PARAM_READWRITE));
2154 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2158 empathy_individual_view_init (EmpathyIndividualView *view)
2160 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2161 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2165 priv->show_untrusted = TRUE;
2167 /* Get saved group states. */
2168 empathy_contact_groups_get_all ();
2170 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2171 (GDestroyNotify) g_free, NULL);
2173 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2174 empathy_individual_store_row_separator_func, NULL, NULL);
2176 /* Connect to tree view signals rather than override. */
2177 g_signal_connect (view, "button-press-event",
2178 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2179 g_signal_connect (view, "key-press-event",
2180 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2181 g_signal_connect (view, "row-expanded",
2182 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2183 GINT_TO_POINTER (TRUE));
2184 g_signal_connect (view, "row-collapsed",
2185 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2186 GINT_TO_POINTER (FALSE));
2187 g_signal_connect (view, "query-tooltip",
2188 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2191 EmpathyIndividualView *
2192 empathy_individual_view_new (EmpathyIndividualStore *store,
2193 EmpathyIndividualViewFeatureFlags view_features,
2194 EmpathyIndividualFeatureFlags individual_features)
2196 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2198 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2200 "individual-features", individual_features,
2201 "view-features", view_features, NULL);
2205 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2207 EmpathyIndividualViewPriv *priv;
2208 GtkTreeSelection *selection;
2210 GtkTreeModel *model;
2211 FolksIndividual *individual;
2213 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2215 priv = GET_PRIV (view);
2217 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2218 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2221 gtk_tree_model_get (model, &iter,
2222 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2228 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2229 gboolean *is_fake_group)
2231 EmpathyIndividualViewPriv *priv;
2232 GtkTreeSelection *selection;
2234 GtkTreeModel *model;
2239 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2241 priv = GET_PRIV (view);
2243 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2244 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2247 gtk_tree_model_get (model, &iter,
2248 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2249 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2250 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2258 if (is_fake_group != NULL)
2259 *is_fake_group = fake;
2265 individual_view_remove_dialog_show (GtkWindow *parent,
2266 const gchar *message,
2267 const gchar *secondary_text)
2272 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2273 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2274 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2275 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2276 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2277 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2278 "%s", secondary_text);
2280 gtk_widget_show (dialog);
2282 res = gtk_dialog_run (GTK_DIALOG (dialog));
2283 gtk_widget_destroy (dialog);
2285 return (res == GTK_RESPONSE_YES);
2289 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2290 EmpathyIndividualView *view)
2294 group = empathy_individual_view_dup_selected_group (view, NULL);
2301 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2303 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2304 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2307 EmpathyIndividualManager *manager =
2308 empathy_individual_manager_dup_singleton ();
2309 empathy_individual_manager_remove_group (manager, group);
2310 g_object_unref (G_OBJECT (manager));
2320 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2322 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2327 gboolean is_fake_group;
2329 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2331 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2332 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2335 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2336 if (!group || is_fake_group)
2338 /* We can't alter fake groups */
2343 menu = gtk_menu_new ();
2346 if (priv->view_features &
2347 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2348 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2349 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2350 gtk_widget_show (item);
2351 g_signal_connect (item, "activate",
2352 G_CALLBACK (individual_view_group_rename_activate_cb),
2357 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2359 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2360 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2361 GTK_ICON_SIZE_MENU);
2362 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2363 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2364 gtk_widget_show (item);
2365 g_signal_connect (item, "activate",
2366 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2375 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2376 EmpathyIndividualView *view)
2378 FolksIndividual *individual;
2380 individual = empathy_individual_view_dup_selected (view);
2382 if (individual != NULL)
2386 GList *l, *personas;
2387 guint persona_count = 0;
2389 personas = folks_individual_get_personas (individual);
2391 /* If we have more than one TpfPersona, display a different message
2392 * ensuring the user knows that *all* of the meta-contacts' personas will
2394 for (l = personas; l != NULL; l = l->next)
2396 if (!TPF_IS_PERSONA (l->data))
2400 if (persona_count >= 2)
2404 if (persona_count < 2)
2406 /* Not a meta-contact */
2409 _("Do you really want to remove the contact '%s'?"),
2410 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2417 _("Do you really want to remove the linked contact '%s'? "
2418 "Note that this will remove all the contacts which make up "
2419 "this linked contact."),
2420 folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2423 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2425 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2428 EmpathyIndividualManager *manager;
2430 manager = empathy_individual_manager_dup_singleton ();
2431 empathy_individual_manager_remove (manager, individual, "");
2432 g_object_unref (G_OBJECT (manager));
2436 g_object_unref (individual);
2441 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2442 EmpathyLinkingDialog *linking_dialog,
2443 EmpathyIndividualView *self)
2445 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2446 EmpathyIndividualLinker *linker;
2448 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2449 empathy_individual_linker_set_search_text (linker,
2450 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2454 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2456 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2457 FolksIndividual *individual;
2458 GtkWidget *menu = NULL;
2461 gboolean can_remove = FALSE;
2464 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2466 individual = empathy_individual_view_dup_selected (view);
2467 if (individual == NULL)
2470 /* If any of the Individual's personas can be removed, add an option to
2471 * remove. This will act as a best-effort option. If any Personas cannot be
2472 * removed from the server, then this option will just be inactive upon
2473 * subsequent menu openings */
2474 for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2476 FolksPersona *persona = FOLKS_PERSONA (l->data);
2477 FolksPersonaStore *store = folks_persona_get_store (persona);
2478 FolksMaybeBool maybe_can_remove =
2479 folks_persona_store_get_can_remove_personas (store);
2481 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2488 menu = empathy_individual_menu_new (individual, priv->individual_features);
2490 /* Remove contact */
2491 if ((priv->view_features &
2492 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2495 /* create the menu if required, or just add a separator */
2497 menu = gtk_menu_new ();
2500 item = gtk_separator_menu_item_new ();
2501 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2502 gtk_widget_show (item);
2506 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2507 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2508 GTK_ICON_SIZE_MENU);
2509 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2510 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2511 gtk_widget_show (item);
2512 g_signal_connect (item, "activate",
2513 G_CALLBACK (individual_view_remove_activate_cb), view);
2516 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2517 * set the live search text on the new linking dialogue to be the same as
2519 g_signal_connect (menu, "link-contacts-activated",
2520 (GCallback) individual_menu_link_contacts_activated_cb, view);
2522 g_object_unref (individual);
2528 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2529 EmpathyLiveSearch *search)
2531 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2533 /* remove old handlers if old search was not null */
2534 if (priv->search_widget != NULL)
2536 g_signal_handlers_disconnect_by_func (view,
2537 individual_view_start_search_cb, NULL);
2539 g_signal_handlers_disconnect_by_func (priv->search_widget,
2540 individual_view_search_text_notify_cb, view);
2541 g_signal_handlers_disconnect_by_func (priv->search_widget,
2542 individual_view_search_activate_cb, view);
2543 g_signal_handlers_disconnect_by_func (priv->search_widget,
2544 individual_view_search_key_navigation_cb, view);
2545 g_signal_handlers_disconnect_by_func (priv->search_widget,
2546 individual_view_search_hide_cb, view);
2547 g_signal_handlers_disconnect_by_func (priv->search_widget,
2548 individual_view_search_show_cb, view);
2549 g_object_unref (priv->search_widget);
2550 priv->search_widget = NULL;
2553 /* connect handlers if new search is not null */
2556 priv->search_widget = g_object_ref (search);
2558 g_signal_connect (view, "start-interactive-search",
2559 G_CALLBACK (individual_view_start_search_cb), NULL);
2561 g_signal_connect (priv->search_widget, "notify::text",
2562 G_CALLBACK (individual_view_search_text_notify_cb), view);
2563 g_signal_connect (priv->search_widget, "activate",
2564 G_CALLBACK (individual_view_search_activate_cb), view);
2565 g_signal_connect (priv->search_widget, "key-navigation",
2566 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2567 g_signal_connect (priv->search_widget, "hide",
2568 G_CALLBACK (individual_view_search_hide_cb), view);
2569 g_signal_connect (priv->search_widget, "show",
2570 G_CALLBACK (individual_view_search_show_cb), view);
2575 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2577 EmpathyIndividualViewPriv *priv;
2579 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2581 priv = GET_PRIV (self);
2583 return (priv->search_widget != NULL &&
2584 gtk_widget_get_visible (priv->search_widget));
2588 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2590 EmpathyIndividualViewPriv *priv;
2592 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2594 priv = GET_PRIV (self);
2596 return priv->show_offline;
2600 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2601 gboolean show_offline)
2603 EmpathyIndividualViewPriv *priv;
2605 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2607 priv = GET_PRIV (self);
2609 priv->show_offline = show_offline;
2611 g_object_notify (G_OBJECT (self), "show-offline");
2612 gtk_tree_model_filter_refilter (priv->filter);
2616 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2618 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2620 return GET_PRIV (self)->show_untrusted;
2624 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2625 gboolean show_untrusted)
2627 EmpathyIndividualViewPriv *priv;
2629 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2631 priv = GET_PRIV (self);
2633 priv->show_untrusted = show_untrusted;
2635 g_object_notify (G_OBJECT (self), "show-untrusted");
2636 gtk_tree_model_filter_refilter (priv->filter);
2639 EmpathyIndividualStore *
2640 empathy_individual_view_get_store (EmpathyIndividualView *self)
2642 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2644 return GET_PRIV (self)->store;
2648 empathy_individual_view_set_store (EmpathyIndividualView *self,
2649 EmpathyIndividualStore *store)
2651 EmpathyIndividualViewPriv *priv;
2653 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2654 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2656 priv = GET_PRIV (self);
2658 /* Destroy the old filter and remove the old store */
2659 if (priv->store != NULL)
2661 g_signal_handlers_disconnect_by_func (priv->store,
2662 individual_view_store_row_changed_cb, self);
2663 g_signal_handlers_disconnect_by_func (priv->store,
2664 individual_view_store_row_deleted_cb, self);
2666 g_signal_handlers_disconnect_by_func (priv->filter,
2667 individual_view_row_has_child_toggled_cb, self);
2669 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2672 tp_clear_object (&priv->filter);
2673 tp_clear_object (&priv->store);
2675 /* Set the new store */
2676 priv->store = store;
2680 g_object_ref (store);
2682 /* Create a new filter */
2683 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2684 GTK_TREE_MODEL (priv->store), NULL));
2685 gtk_tree_model_filter_set_visible_func (priv->filter,
2686 individual_view_filter_visible_func, self, NULL);
2688 g_signal_connect (priv->filter, "row-has-child-toggled",
2689 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2690 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2691 GTK_TREE_MODEL (priv->filter));
2693 tp_g_signal_connect_object (priv->store, "row-changed",
2694 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2695 tp_g_signal_connect_object (priv->store, "row-inserted",
2696 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2697 tp_g_signal_connect_object (priv->store, "row-deleted",
2698 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2703 empathy_individual_view_start_search (EmpathyIndividualView *self)
2705 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2707 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2708 g_return_if_fail (priv->search_widget != NULL);
2710 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2711 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2713 gtk_widget_show (GTK_WIDGET (priv->search_widget));