]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
Merge remote branch 'glassrose/accounts-supporting-chatrooms-only-603027'
[empathy.git] / libempathy-gtk / empathy-individual-view.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  * Copyright (C) 2007-2010 Collabora Ltd.
5  *
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.
10  *
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.
15  *
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
20  *
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>
25  */
26
27 #include "config.h"
28
29 #include <string.h>
30
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
34
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
37
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
40
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>
46
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"
58
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
61
62 /* Active users are those which have recently changed state
63  * (e.g. online, offline or from normal to a busy state).
64  */
65
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
67 typedef struct
68 {
69   EmpathyIndividualStore *store;
70   GtkTreeRowReference *drag_row;
71   EmpathyIndividualViewFeatureFlags view_features;
72   EmpathyIndividualFeatureFlags individual_features;
73   GtkWidget *tooltip_widget;
74
75   gboolean show_offline;
76   gboolean show_untrusted;
77
78   GtkTreeModelFilter *filter;
79   GtkWidget *search_widget;
80
81   guint expand_groups_idle_handler;
82   /* owned string (group name) -> bool (whether to expand/contract) */
83   GHashTable *expand_groups;
84
85   /* Auto scroll */
86   guint auto_scroll_timeout_id;
87   /* Distance between mouse pointer and the nearby border. Negative when
88      scrolling updards.*/
89   gint distance;
90 } EmpathyIndividualViewPriv;
91
92 typedef struct
93 {
94   EmpathyIndividualView *view;
95   GtkTreePath *path;
96   guint timeout_id;
97 } DragMotionData;
98
99 typedef struct
100 {
101   EmpathyIndividualView *view;
102   FolksIndividual *individual;
103   gboolean remove;
104 } ShowActiveData;
105
106 enum
107 {
108   PROP_0,
109   PROP_STORE,
110   PROP_VIEW_FEATURES,
111   PROP_INDIVIDUAL_FEATURES,
112   PROP_SHOW_OFFLINE,
113   PROP_SHOW_UNTRUSTED,
114 };
115
116 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
117  * specific EmpathyContacts (between/in/out of Individuals) */
118 enum DndDragType
119 {
120   DND_DRAG_TYPE_INDIVIDUAL_ID,
121   DND_DRAG_TYPE_PERSONA_ID,
122   DND_DRAG_TYPE_URI_LIST,
123   DND_DRAG_TYPE_STRING,
124 };
125
126 #define DRAG_TYPE(T,I) \
127   { (gchar *) T, 0, I }
128
129 static const GtkTargetEntry drag_types_dest[] = {
130   DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
131   DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
132   DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
133   DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
134   DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
135   DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
136 };
137
138 static const GtkTargetEntry drag_types_source[] = {
139   DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
140 };
141
142 #undef DRAG_TYPE
143
144 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
145 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
146
147 enum
148 {
149   DRAG_INDIVIDUAL_RECEIVED,
150   DRAG_PERSONA_RECEIVED,
151   LAST_SIGNAL
152 };
153
154 static guint signals[LAST_SIGNAL];
155
156 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
157     GTK_TYPE_TREE_VIEW);
158
159 static void
160 individual_view_tooltip_destroy_cb (GtkWidget *widget,
161     EmpathyIndividualView *view)
162 {
163   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
164
165   if (priv->tooltip_widget != NULL)
166     {
167       DEBUG ("Tooltip destroyed");
168       tp_clear_object (&priv->tooltip_widget);
169     }
170 }
171
172 static gboolean
173 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
174     gint x,
175     gint y,
176     gboolean keyboard_mode,
177     GtkTooltip *tooltip,
178     gpointer user_data)
179 {
180   EmpathyIndividualViewPriv *priv;
181   FolksIndividual *individual;
182   GtkTreeModel *model;
183   GtkTreeIter iter;
184   GtkTreePath *path;
185   static gint running = 0;
186   gboolean ret = FALSE;
187
188   priv = GET_PRIV (view);
189
190   /* Avoid an infinite loop. See GNOME bug #574377 */
191   if (running > 0)
192     return FALSE;
193
194   running++;
195
196   /* Don't show the tooltip if there's already a popup menu */
197   if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
198     goto OUT;
199
200   if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
201           keyboard_mode, &model, &path, &iter))
202     goto OUT;
203
204   gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
205   gtk_tree_path_free (path);
206
207   gtk_tree_model_get (model, &iter,
208       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
209       -1);
210   if (individual == NULL)
211     goto OUT;
212
213   if (priv->tooltip_widget == NULL)
214     {
215       priv->tooltip_widget = empathy_individual_widget_new (individual,
216           EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
217           EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
218           EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
219       gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
220       g_object_ref (priv->tooltip_widget);
221       g_signal_connect (priv->tooltip_widget, "destroy",
222           G_CALLBACK (individual_view_tooltip_destroy_cb), view);
223       gtk_widget_show (priv->tooltip_widget);
224     }
225   else
226     {
227       empathy_individual_widget_set_individual (
228         EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
229     }
230
231   gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
232   ret = TRUE;
233
234   g_object_unref (individual);
235 OUT:
236   running--;
237
238   return ret;
239 }
240
241 static void
242 groups_change_group_cb (GObject *source,
243     GAsyncResult *result,
244     gpointer user_data)
245 {
246   FolksGroupable *groupable = FOLKS_GROUPABLE (source);
247   GError *error = NULL;
248
249   folks_groupable_change_group_finish (groupable, result, &error);
250   if (error != NULL)
251     {
252       g_warning ("failed to change group: %s", error->message);
253       g_clear_error (&error);
254     }
255 }
256
257 static gboolean
258 group_can_be_modified (const gchar *name,
259     gboolean is_fake_group,
260     gboolean adding)
261 {
262   /* Real groups can always be modified */
263   if (!is_fake_group)
264     return TRUE;
265
266   /* The favorite fake group can be modified so users can
267    * add/remove favorites using DnD */
268   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
269     return TRUE;
270
271   /* We can remove contacts from the 'ungrouped' fake group */
272   if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
273     return TRUE;
274
275   return FALSE;
276 }
277
278 static gboolean
279 individual_view_individual_drag_received (GtkWidget *self,
280     GdkDragContext *context,
281     GtkTreeModel *model,
282     GtkTreePath *path,
283     GtkSelectionData *selection)
284 {
285   EmpathyIndividualViewPriv *priv;
286   EmpathyIndividualManager *manager = NULL;
287   FolksIndividual *individual;
288   GtkTreePath *source_path;
289   const gchar *sel_data;
290   gchar *new_group = NULL;
291   gchar *old_group = NULL;
292   gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
293
294   priv = GET_PRIV (self);
295
296   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
297   new_group = empathy_individual_store_get_parent_group (model, path,
298       NULL, &new_group_is_fake);
299
300   if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
301     goto finished;
302
303   /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
304    * feature. Otherwise, we just add the dropped contact to whichever group
305    * they were dropped in, and don't remove them from their old group. This
306    * allows for Individual views which shouldn't allow Individuals to have
307    * their groups changed, and also for dragging Individuals between Individual
308    * views. */
309   if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
310       priv->drag_row != NULL)
311     {
312       source_path = gtk_tree_row_reference_get_path (priv->drag_row);
313       if (source_path)
314         {
315           old_group =
316               empathy_individual_store_get_parent_group (model, source_path,
317               NULL, &old_group_is_fake);
318           gtk_tree_path_free (source_path);
319         }
320
321       if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
322         goto finished;
323
324       if (!tp_strdiff (old_group, new_group))
325         goto finished;
326     }
327   else if (priv->drag_row != NULL)
328     {
329       /* We don't allow changing Individuals' groups, and this Individual was
330        * dragged from another group in *this* Individual view, so we disallow
331        * the drop. */
332       goto finished;
333     }
334
335   /* XXX: for contacts, we used to ensure the account, create the contact
336    * factory, and then wait on the contacts. But they should already be
337    * created by this point */
338
339   manager = empathy_individual_manager_dup_singleton ();
340   individual = empathy_individual_manager_lookup_member (manager, sel_data);
341
342   if (individual == NULL)
343     {
344       DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
345       goto finished;
346     }
347
348   /* FIXME: We should probably wait for the cb before calling
349    * gtk_drag_finish */
350
351   /* Emit a signal notifying of the drag. We change the Individual's groups in
352    * the default signal handler. */
353   g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
354       gdk_drag_context_get_selected_action (context), individual, new_group,
355       old_group);
356
357   retval = TRUE;
358
359 finished:
360   tp_clear_object (&manager);
361   g_free (old_group);
362   g_free (new_group);
363
364   return retval;
365 }
366
367 static void
368 real_drag_individual_received_cb (EmpathyIndividualView *self,
369     GdkDragAction action,
370     FolksIndividual *individual,
371     const gchar *new_group,
372     const gchar *old_group)
373 {
374   DEBUG ("individual %s dragged from '%s' to '%s'",
375       folks_individual_get_id (individual), old_group, new_group);
376
377   if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
378     {
379       /* Mark contact as favourite */
380       folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), TRUE);
381       return;
382     }
383
384   if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
385     {
386       /* Remove contact as favourite */
387       folks_favouritable_set_is_favourite (FOLKS_FAVOURITABLE (individual), FALSE);
388
389       /* Don't try to remove it */
390       old_group = NULL;
391     }
392
393   if (new_group != NULL)
394     {
395       folks_groupable_change_group (FOLKS_GROUPABLE (individual), new_group, TRUE,
396           groups_change_group_cb, NULL);
397     }
398
399   if (old_group != NULL && action == GDK_ACTION_MOVE)
400     {
401       folks_groupable_change_group (FOLKS_GROUPABLE (individual), old_group,
402           FALSE, groups_change_group_cb, NULL);
403     }
404 }
405
406 static gboolean
407 individual_view_persona_drag_received (GtkWidget *self,
408     GdkDragContext *context,
409     GtkTreeModel *model,
410     GtkTreePath *path,
411     GtkSelectionData *selection)
412 {
413   EmpathyIndividualViewPriv *priv;
414   EmpathyIndividualManager *manager = NULL;
415   FolksIndividual *individual = NULL;
416   FolksPersona *persona = NULL;
417   const gchar *persona_uid;
418   GList *individuals, *l;
419   gboolean retval = FALSE;
420
421   priv = GET_PRIV (self);
422
423   persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
424
425   /* FIXME: This is slow, but the only way to find the Persona we're having
426    * dropped on us. */
427   manager = empathy_individual_manager_dup_singleton ();
428   individuals = empathy_individual_manager_get_members (manager);
429
430   for (l = individuals; l != NULL; l = l->next)
431     {
432       GList *personas, *p;
433
434       personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435
436       for (p = personas; p != NULL; p = p->next)
437         {
438           if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
439               persona_uid))
440             {
441               persona = g_object_ref (p->data);
442               individual = g_object_ref (l->data);
443               goto got_persona;
444             }
445         }
446     }
447
448 got_persona:
449   g_list_free (individuals);
450
451   if (persona == NULL || individual == NULL)
452     {
453       DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
454     }
455   else
456     {
457       /* Emit a signal notifying of the drag. We change the Individual's groups in
458        * the default signal handler. */
459       g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
460           gdk_drag_context_get_selected_action (context), persona, individual,
461           &retval);
462     }
463
464   tp_clear_object (&manager);
465   tp_clear_object (&persona);
466   tp_clear_object (&individual);
467
468   return retval;
469 }
470
471 static gboolean
472 individual_view_file_drag_received (GtkWidget *view,
473     GdkDragContext *context,
474     GtkTreeModel *model,
475     GtkTreePath *path,
476     GtkSelectionData *selection)
477 {
478   GtkTreeIter iter;
479   const gchar *sel_data;
480   FolksIndividual *individual;
481   EmpathyContact *contact;
482
483   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
484
485   gtk_tree_model_get_iter (model, &iter, path);
486   gtk_tree_model_get (model, &iter,
487       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
488   if (individual == NULL)
489     return FALSE;
490
491   contact = empathy_contact_dup_from_folks_individual (individual);
492   empathy_send_file_from_uri_list (contact, sel_data);
493
494   g_object_unref (individual);
495   tp_clear_object (&contact);
496
497   return TRUE;
498 }
499
500 static void
501 individual_view_drag_data_received (GtkWidget *view,
502     GdkDragContext *context,
503     gint x,
504     gint y,
505     GtkSelectionData *selection,
506     guint info,
507     guint time_)
508 {
509   GtkTreeModel *model;
510   gboolean is_row;
511   GtkTreeViewDropPosition position;
512   GtkTreePath *path;
513   gboolean success = TRUE;
514
515   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
516
517   /* Get destination group information. */
518   is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
519       x, y, &path, &position);
520   if (!is_row)
521     {
522       success = FALSE;
523     }
524   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
525     {
526       success = individual_view_individual_drag_received (view,
527           context, model, path, selection);
528     }
529   else if (info == DND_DRAG_TYPE_PERSONA_ID)
530     {
531       success = individual_view_persona_drag_received (view, context, model,
532           path, selection);
533     }
534   else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
535     {
536       success = individual_view_file_drag_received (view,
537           context, model, path, selection);
538     }
539
540   gtk_tree_path_free (path);
541   gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
542 }
543
544 static gboolean
545 individual_view_drag_motion_cb (DragMotionData *data)
546 {
547   if (data->view != NULL)
548     {
549       gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
550       g_object_remove_weak_pointer (G_OBJECT (data->view),
551           (gpointer *) &data->view);
552     }
553
554   data->timeout_id = 0;
555
556   return FALSE;
557 }
558
559 /* Minimum distance between the mouse pointer and a horizontal border when we
560    start auto scrolling. */
561 #define AUTO_SCROLL_MARGIN_SIZE 20
562 /* How far to scroll per one tick. */
563 #define AUTO_SCROLL_PITCH       10
564
565 static gboolean
566 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
567 {
568         EmpathyIndividualViewPriv *priv = GET_PRIV (self);
569         GtkAdjustment         *adj;
570         gdouble                new_value;
571
572         adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
573
574         if (priv->distance < 0)
575                 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
576         else
577                 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
578
579         new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
580                 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
581
582         gtk_adjustment_set_value (adj, new_value);
583
584         return TRUE;
585 }
586
587 static gboolean
588 individual_view_drag_motion (GtkWidget *widget,
589     GdkDragContext *context,
590     gint x,
591     gint y,
592     guint time_)
593 {
594   EmpathyIndividualViewPriv *priv;
595   GtkTreeModel *model;
596   GdkAtom target;
597   GtkTreeIter iter;
598   static DragMotionData *dm = NULL;
599   GtkTreePath *path;
600   gboolean is_row;
601   gboolean is_different = FALSE;
602   gboolean cleanup = TRUE;
603   gboolean retval = TRUE;
604   GtkAllocation allocation;
605
606   priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
607   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
608
609
610   if (priv->auto_scroll_timeout_id != 0)
611     {
612       g_source_remove (priv->auto_scroll_timeout_id);
613       priv->auto_scroll_timeout_id = 0;
614     }
615
616   gtk_widget_get_allocation (widget, &allocation);
617
618   if (y < AUTO_SCROLL_MARGIN_SIZE ||
619       y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
620     {
621       if (y < AUTO_SCROLL_MARGIN_SIZE)
622         priv->distance = MIN (-y, -1);
623       else
624         priv->distance = MAX (allocation.height - y, 1);
625
626       priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
627           (GSourceFunc) individual_view_auto_scroll_cb, widget);
628     }
629
630   is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
631       x, y, &path, NULL, NULL, NULL);
632
633   cleanup &= (dm == NULL);
634
635   if (is_row)
636     {
637       cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
638       is_different = ((dm == NULL) || ((dm != NULL)
639               && gtk_tree_path_compare (dm->path, path) != 0));
640     }
641   else
642     cleanup &= FALSE;
643
644   if (path == NULL)
645     {
646       /* Coordinates don't point to an actual row, so make sure the pointer
647          and highlighting don't indicate that a drag is possible.
648        */
649       gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
650       gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
651       return FALSE;
652     }
653   target = gtk_drag_dest_find_target (widget, context, NULL);
654   gtk_tree_model_get_iter (model, &iter, path);
655
656   if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
657       target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
658     {
659       /* This is a file drag, and it can only be dropped on contacts,
660        * not groups.
661        * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
662        * even if we have a valid target. */
663       FolksIndividual *individual = NULL;
664       EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
665
666       if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
667         {
668           gtk_tree_model_get (model, &iter,
669               EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
670               -1);
671         }
672
673       if (individual != NULL)
674         {
675           EmpathyContact *contact = NULL;
676
677           contact = empathy_contact_dup_from_folks_individual (individual);
678           caps = empathy_contact_get_capabilities (contact);
679
680           tp_clear_object (&contact);
681         }
682
683       if (individual != NULL &&
684           folks_has_presence_is_online (FOLKS_HAS_PRESENCE (individual)) &&
685           (caps & EMPATHY_CAPABILITIES_FT))
686         {
687           gdk_drag_status (context, GDK_ACTION_COPY, time_);
688           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
689               path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
690         }
691       else
692         {
693           gdk_drag_status (context, 0, time_);
694           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
695           retval = FALSE;
696         }
697
698       if (individual != NULL)
699         g_object_unref (individual);
700     }
701   else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
702       (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
703        priv->drag_row == NULL)) ||
704       (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
705        priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
706     {
707       /* If target != GDK_NONE, then we have a contact (individual or persona)
708          drag.  If we're pointing to a group, highlight it.  Otherwise, if the
709          contact we're pointing to is in a group, highlight that.  Otherwise,
710          set the drag position to before the first row for a drag into
711          the "non-group" at the top.
712          If it's an Individual:
713            We only highlight things if the contact is from a different
714            Individual view, or if this Individual view has
715            FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
716            which don't have FEATURE_GROUPS_CHANGE, but do have
717            FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
718          If it's a Persona:
719            We only highlight things if we have FEATURE_PERSONA_DROP.
720        */
721       GtkTreeIter group_iter;
722       gboolean is_group;
723       GtkTreePath *group_path;
724       gtk_tree_model_get (model, &iter,
725           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
726       if (is_group)
727         {
728           group_iter = iter;
729         }
730       else
731         {
732           if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
733             gtk_tree_model_get (model, &group_iter,
734                 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
735         }
736       if (is_group)
737         {
738           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
739           group_path = gtk_tree_model_get_path (model, &group_iter);
740           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
741               group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
742           gtk_tree_path_free (group_path);
743         }
744       else
745         {
746           group_path = gtk_tree_path_new_first ();
747           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
748           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
749               group_path, GTK_TREE_VIEW_DROP_BEFORE);
750         }
751     }
752
753   if (!is_different && !cleanup)
754     return retval;
755
756   if (dm)
757     {
758       gtk_tree_path_free (dm->path);
759       if (dm->timeout_id)
760         {
761           g_source_remove (dm->timeout_id);
762         }
763
764       g_free (dm);
765
766       dm = NULL;
767     }
768
769   if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
770     {
771       dm = g_new0 (DragMotionData, 1);
772
773       dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
774       g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
775       dm->path = gtk_tree_path_copy (path);
776
777       dm->timeout_id = g_timeout_add_seconds (1,
778           (GSourceFunc) individual_view_drag_motion_cb, dm);
779     }
780
781   return retval;
782 }
783
784 static void
785 individual_view_drag_begin (GtkWidget *widget,
786     GdkDragContext *context)
787 {
788   EmpathyIndividualViewPriv *priv;
789   GtkTreeSelection *selection;
790   GtkTreeModel *model;
791   GtkTreePath *path;
792   GtkTreeIter iter;
793
794   priv = GET_PRIV (widget);
795
796   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
797       context);
798
799   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
800   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
801     return;
802
803   path = gtk_tree_model_get_path (model, &iter);
804   priv->drag_row = gtk_tree_row_reference_new (model, path);
805   gtk_tree_path_free (path);
806 }
807
808 static void
809 individual_view_drag_data_get (GtkWidget *widget,
810     GdkDragContext *context,
811     GtkSelectionData *selection,
812     guint info,
813     guint time_)
814 {
815   EmpathyIndividualViewPriv *priv;
816   GtkTreePath *src_path;
817   GtkTreeIter iter;
818   GtkTreeModel *model;
819   FolksIndividual *individual;
820   const gchar *individual_id;
821
822   priv = GET_PRIV (widget);
823
824   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
825   if (priv->drag_row == NULL)
826     return;
827
828   src_path = gtk_tree_row_reference_get_path (priv->drag_row);
829   if (src_path == NULL)
830     return;
831
832   if (!gtk_tree_model_get_iter (model, &iter, src_path))
833     {
834       gtk_tree_path_free (src_path);
835       return;
836     }
837
838   gtk_tree_path_free (src_path);
839
840   individual =
841       empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
842   if (individual == NULL)
843     return;
844
845   individual_id = folks_individual_get_id (individual);
846
847   if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
848     {
849       gtk_selection_data_set (selection, drag_atoms_source[info], 8,
850           (guchar *) individual_id, strlen (individual_id) + 1);
851     }
852
853   g_object_unref (individual);
854 }
855
856 static void
857 individual_view_drag_end (GtkWidget *widget,
858     GdkDragContext *context)
859 {
860   EmpathyIndividualViewPriv *priv;
861
862   priv = GET_PRIV (widget);
863
864   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
865       context);
866
867   if (priv->drag_row)
868     {
869       gtk_tree_row_reference_free (priv->drag_row);
870       priv->drag_row = NULL;
871     }
872 }
873
874 static gboolean
875 individual_view_drag_drop (GtkWidget *widget,
876     GdkDragContext *drag_context,
877     gint x,
878     gint y,
879     guint time_)
880 {
881   return FALSE;
882 }
883
884 typedef struct
885 {
886   EmpathyIndividualView *view;
887   guint button;
888   guint32 time;
889 } MenuPopupData;
890
891 static gboolean
892 individual_view_popup_menu_idle_cb (gpointer user_data)
893 {
894   MenuPopupData *data = user_data;
895   GtkWidget *menu;
896
897   menu = empathy_individual_view_get_individual_menu (data->view);
898   if (menu == NULL)
899     menu = empathy_individual_view_get_group_menu (data->view);
900
901   if (menu != NULL)
902     {
903       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
904           NULL);
905       gtk_widget_show (menu);
906       gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
907           data->time);
908       g_object_ref_sink (menu);
909       g_object_unref (menu);
910     }
911
912   g_slice_free (MenuPopupData, data);
913
914   return FALSE;
915 }
916
917 static gboolean
918 individual_view_button_press_event_cb (EmpathyIndividualView *view,
919     GdkEventButton *event,
920     gpointer user_data)
921 {
922   if (event->button == 3)
923     {
924       MenuPopupData *data;
925
926       data = g_slice_new (MenuPopupData);
927       data->view = view;
928       data->button = event->button;
929       data->time = event->time;
930       g_idle_add (individual_view_popup_menu_idle_cb, data);
931     }
932
933   return FALSE;
934 }
935
936 static gboolean
937 individual_view_key_press_event_cb (EmpathyIndividualView *view,
938     GdkEventKey *event,
939     gpointer user_data)
940 {
941   if (event->keyval == GDK_KEY_Menu)
942     {
943       MenuPopupData *data;
944
945       data = g_slice_new (MenuPopupData);
946       data->view = view;
947       data->button = 0;
948       data->time = event->time;
949       g_idle_add (individual_view_popup_menu_idle_cb, data);
950     }
951
952   return FALSE;
953 }
954
955 static void
956 individual_view_row_activated (GtkTreeView *view,
957     GtkTreePath *path,
958     GtkTreeViewColumn *column)
959 {
960   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
961   FolksIndividual *individual;
962   EmpathyContact *contact;
963   GtkTreeModel *model;
964   GtkTreeIter iter;
965
966   if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
967     return;
968
969   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
970   gtk_tree_model_get_iter (model, &iter, path);
971   gtk_tree_model_get (model, &iter,
972       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
973
974   if (individual == NULL)
975     return;
976
977   /* Determine which Persona to chat to, by choosing the most available one. */
978   contact = empathy_contact_dup_best_for_action (individual,
979       EMPATHY_ACTION_CHAT);
980
981   if (contact != NULL)
982     {
983       DEBUG ("Starting a chat");
984
985       empathy_dispatcher_chat_with_contact (contact,
986           gtk_get_current_event_time ());
987     }
988
989   g_object_unref (individual);
990   tp_clear_object (&contact);
991 }
992
993 static void
994 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
995     const gchar *path_string,
996     EmpathyIndividualView *view)
997 {
998   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
999   GtkWidget *menu;
1000   GtkTreeModel *model;
1001   GtkTreeIter iter;
1002   FolksIndividual *individual;
1003   GdkEventButton *event;
1004   GtkMenuShell *shell;
1005   GtkWidget *item;
1006
1007   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1008     return;
1009
1010   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1011   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1012     return;
1013
1014   gtk_tree_model_get (model, &iter,
1015       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1016   if (individual == NULL)
1017     return;
1018
1019   event = (GdkEventButton *) gtk_get_current_event ();
1020
1021   menu = gtk_menu_new ();
1022   shell = GTK_MENU_SHELL (menu);
1023
1024   /* audio */
1025   item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1026   gtk_menu_shell_append (shell, item);
1027   gtk_widget_show (item);
1028
1029   /* video */
1030   item = empathy_individual_video_call_menu_item_new (individual, NULL);
1031   gtk_menu_shell_append (shell, item);
1032   gtk_widget_show (item);
1033
1034   gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1035   gtk_widget_show (menu);
1036   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1037       event->button, event->time);
1038   g_object_ref_sink (menu);
1039   g_object_unref (menu);
1040
1041   g_object_unref (individual);
1042 }
1043
1044 static void
1045 individual_view_cell_set_background (EmpathyIndividualView *view,
1046     GtkCellRenderer *cell,
1047     gboolean is_group,
1048     gboolean is_active)
1049 {
1050   if (!is_group && is_active)
1051     {
1052       GtkStyleContext *style;
1053       GdkRGBA color;
1054
1055       style = gtk_widget_get_style_context (GTK_WIDGET (view));
1056
1057       gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1058           &color);
1059
1060       /* Here we take the current theme colour and add it to
1061        * the colour for white and average the two. This
1062        * gives a colour which is inline with the theme but
1063        * slightly whiter.
1064        */
1065       empathy_make_color_whiter (&color);
1066
1067       g_object_set (cell, "cell-background-rgba", &color, NULL);
1068     }
1069   else
1070     g_object_set (cell, "cell-background-rgba", NULL, NULL);
1071 }
1072
1073 static void
1074 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1075     GtkCellRenderer *cell,
1076     GtkTreeModel *model,
1077     GtkTreeIter *iter,
1078     EmpathyIndividualView *view)
1079 {
1080   GdkPixbuf *pixbuf;
1081   gboolean is_group;
1082   gboolean is_active;
1083
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);
1088
1089   g_object_set (cell,
1090       "visible", !is_group,
1091       "pixbuf", pixbuf,
1092       NULL);
1093
1094   tp_clear_object (&pixbuf);
1095
1096   individual_view_cell_set_background (view, cell, is_group, is_active);
1097 }
1098
1099 static void
1100 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1101     GtkCellRenderer *cell,
1102     GtkTreeModel *model,
1103     GtkTreeIter *iter,
1104     EmpathyIndividualView *view)
1105 {
1106   GdkPixbuf *pixbuf = NULL;
1107   gboolean is_group;
1108   gchar *name;
1109
1110   gtk_tree_model_get (model, iter,
1111       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1112       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1113
1114   if (!is_group)
1115     goto out;
1116
1117   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1118     {
1119       pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1120           GTK_ICON_SIZE_MENU);
1121     }
1122   else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1123     {
1124       pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1125           GTK_ICON_SIZE_MENU);
1126     }
1127
1128 out:
1129   g_object_set (cell,
1130       "visible", pixbuf != NULL,
1131       "pixbuf", pixbuf,
1132       NULL);
1133
1134   tp_clear_object (&pixbuf);
1135
1136   g_free (name);
1137 }
1138
1139 static void
1140 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1141     GtkCellRenderer *cell,
1142     GtkTreeModel *model,
1143     GtkTreeIter *iter,
1144     EmpathyIndividualView *view)
1145 {
1146   gboolean is_group;
1147   gboolean is_active;
1148   gboolean can_audio, can_video;
1149
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);
1155
1156   g_object_set (cell,
1157       "visible", !is_group && (can_audio || can_video),
1158       "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1159       NULL);
1160
1161   individual_view_cell_set_background (view, cell, is_group, is_active);
1162 }
1163
1164 static void
1165 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1166     GtkCellRenderer *cell,
1167     GtkTreeModel *model,
1168     GtkTreeIter *iter,
1169     EmpathyIndividualView *view)
1170 {
1171   GdkPixbuf *pixbuf;
1172   gboolean show_avatar;
1173   gboolean is_group;
1174   gboolean is_active;
1175
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);
1181
1182   g_object_set (cell,
1183       "visible", !is_group && show_avatar,
1184       "pixbuf", pixbuf,
1185       NULL);
1186
1187   tp_clear_object (&pixbuf);
1188
1189   individual_view_cell_set_background (view, cell, is_group, is_active);
1190 }
1191
1192 static void
1193 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1194     GtkCellRenderer *cell,
1195     GtkTreeModel *model,
1196     GtkTreeIter *iter,
1197     EmpathyIndividualView *view)
1198 {
1199   gboolean is_group;
1200   gboolean is_active;
1201
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);
1205
1206   individual_view_cell_set_background (view, cell, is_group, is_active);
1207 }
1208
1209 static void
1210 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1211     GtkCellRenderer *cell,
1212     GtkTreeModel *model,
1213     GtkTreeIter *iter,
1214     EmpathyIndividualView *view)
1215 {
1216   gboolean is_group;
1217   gboolean is_active;
1218
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);
1222
1223   if (gtk_tree_model_iter_has_child (model, iter))
1224     {
1225       GtkTreePath *path;
1226       gboolean row_expanded;
1227
1228       path = gtk_tree_model_get_path (model, iter);
1229       row_expanded =
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);
1233
1234       g_object_set (cell,
1235           "visible", TRUE,
1236           "expander-style",
1237           row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1238           NULL);
1239     }
1240   else
1241     g_object_set (cell, "visible", FALSE, NULL);
1242
1243   individual_view_cell_set_background (view, cell, is_group, is_active);
1244 }
1245
1246 static void
1247 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1248     GtkTreeIter *iter,
1249     GtkTreePath *path,
1250     gpointer user_data)
1251 {
1252   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1253   GtkTreeModel *model;
1254   gchar *name;
1255   gboolean expanded;
1256
1257   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1258     return;
1259
1260   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1261
1262   gtk_tree_model_get (model, iter,
1263       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1264
1265   expanded = GPOINTER_TO_INT (user_data);
1266   empathy_contact_group_set_expanded (name, expanded);
1267
1268   g_free (name);
1269 }
1270
1271 static gboolean
1272 individual_view_start_search_cb (EmpathyIndividualView *view,
1273     gpointer data)
1274 {
1275   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1276
1277   if (priv->search_widget == NULL)
1278     return FALSE;
1279
1280   empathy_individual_view_start_search (view);
1281
1282   return TRUE;
1283 }
1284
1285 static void
1286 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1287     GParamSpec *pspec,
1288     EmpathyIndividualView *view)
1289 {
1290   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1291   GtkTreePath *path;
1292   GtkTreeViewColumn *focus_column;
1293   GtkTreeModel *model;
1294   GtkTreeIter iter;
1295   gboolean set_cursor = FALSE;
1296
1297   gtk_tree_model_filter_refilter (priv->filter);
1298
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
1302    */
1303
1304   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1305   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1306
1307   if (path == NULL)
1308     {
1309       path = gtk_tree_path_new_from_string ("0:1");
1310       set_cursor = TRUE;
1311     }
1312   else if (gtk_tree_path_get_depth (path) < 2)
1313     {
1314       gboolean is_group;
1315
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,
1319           -1);
1320
1321       if (is_group)
1322         {
1323           gtk_tree_path_down (path);
1324           gtk_tree_path_next (path);
1325           set_cursor = TRUE;
1326         }
1327     }
1328
1329   if (set_cursor)
1330     {
1331       /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1332        * valid. */
1333       if (gtk_tree_model_get_iter (model, &iter, path))
1334         {
1335           gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1336               FALSE);
1337         }
1338     }
1339
1340   gtk_tree_path_free (path);
1341 }
1342
1343 static void
1344 individual_view_search_activate_cb (GtkWidget *search,
1345   EmpathyIndividualView *view)
1346 {
1347   GtkTreePath *path;
1348   GtkTreeViewColumn *focus_column;
1349
1350   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1351   if (path != NULL)
1352     {
1353       gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1354       gtk_tree_path_free (path);
1355
1356       gtk_widget_hide (search);
1357     }
1358 }
1359
1360 static gboolean
1361 individual_view_search_key_navigation_cb (GtkWidget *search,
1362   GdkEvent *event,
1363   EmpathyIndividualView *view)
1364 {
1365   GdkEventKey *eventkey = ((GdkEventKey *) event);
1366   gboolean ret = FALSE;
1367
1368   if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
1369     {
1370       GdkEvent *new_event;
1371
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);
1376
1377       gdk_event_free (new_event);
1378     }
1379
1380   return ret;
1381 }
1382
1383 static void
1384 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1385     EmpathyIndividualView *view)
1386 {
1387   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1388   GtkTreeModel *model;
1389   GtkTreePath *cursor_path;
1390   GtkTreeIter iter;
1391   gboolean valid = FALSE;
1392
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));
1399
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))
1404     {
1405       gboolean is_group;
1406       gchar *name = NULL;
1407       GtkTreePath *path;
1408
1409       gtk_tree_model_get (model, &iter,
1410           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1411           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1412           -1);
1413
1414       if (!is_group)
1415         {
1416           g_free (name);
1417           continue;
1418         }
1419
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))
1424         {
1425           gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1426         }
1427       else
1428         {
1429           gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1430         }
1431
1432       gtk_tree_path_free (path);
1433       g_free (name);
1434     }
1435
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));
1441
1442   /* keep the selected contact visible */
1443   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1444
1445   if (cursor_path != NULL)
1446     gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1447         FALSE, 0, 0);
1448
1449   gtk_tree_path_free (cursor_path);
1450 }
1451
1452 static void
1453 individual_view_search_show_cb (EmpathyLiveSearch *search,
1454     EmpathyIndividualView *view)
1455 {
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));
1460
1461   gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1462
1463   g_signal_handlers_unblock_by_func (view,
1464       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1465 }
1466
1467 static gboolean
1468 expand_idle_foreach_cb (GtkTreeModel *model,
1469     GtkTreePath *path,
1470     GtkTreeIter *iter,
1471     EmpathyIndividualView *self)
1472 {
1473   EmpathyIndividualViewPriv *priv;
1474   gboolean is_group;
1475   gpointer should_expand;
1476   gchar *name;
1477
1478   /* We only want groups */
1479   if (gtk_tree_path_get_depth (path) > 1)
1480     return FALSE;
1481
1482   gtk_tree_model_get (model, iter,
1483       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1484       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1485       -1);
1486
1487   if (is_group == FALSE)
1488     {
1489       g_free (name);
1490       return FALSE;
1491     }
1492
1493   priv = GET_PRIV (self);
1494
1495   if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1496       &should_expand) == TRUE)
1497     {
1498       if (GPOINTER_TO_INT (should_expand) == TRUE)
1499         gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1500       else
1501         gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1502
1503       g_hash_table_remove (priv->expand_groups, name);
1504     }
1505
1506   g_free (name);
1507
1508   return FALSE;
1509 }
1510
1511 static gboolean
1512 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1513 {
1514   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1515
1516   DEBUG ("individual_view_expand_idle_cb");
1517
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));
1522
1523   /* The store/filter could've been removed while we were in the idle queue */
1524   if (priv->filter != NULL)
1525     {
1526       gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1527           (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1528     }
1529
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));
1534
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);
1541
1542   return FALSE;
1543 }
1544
1545 static void
1546 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1547     GtkTreePath *path,
1548     GtkTreeIter *iter,
1549     EmpathyIndividualView *view)
1550 {
1551   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1552   gboolean should_expand, is_group = FALSE;
1553   gchar *name = NULL;
1554   gpointer will_expand;
1555
1556   gtk_tree_model_get (model, iter,
1557       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1558       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1559       -1);
1560
1561   if (!is_group || EMP_STR_EMPTY (name))
1562     {
1563       g_free (name);
1564       return;
1565     }
1566
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);
1572
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)
1580     {
1581       g_hash_table_insert (priv->expand_groups, g_strdup (name),
1582           GINT_TO_POINTER (should_expand));
1583
1584       if (priv->expand_groups_idle_handler == 0)
1585         {
1586           priv->expand_groups_idle_handler =
1587               g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1588                   g_object_ref (view));
1589         }
1590     }
1591
1592   g_free (name);
1593 }
1594
1595 /* FIXME: This is a workaround for bgo#621076 */
1596 static void
1597 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1598     GtkTreePath *path)
1599 {
1600   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1601   GtkTreeModel *model;
1602   GtkTreePath *parent_path;
1603   GtkTreeIter parent_iter;
1604
1605   if (gtk_tree_path_get_depth (path) < 2)
1606     return;
1607
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.
1611   */
1612
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))
1617     {
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);
1622     }
1623   gtk_tree_path_free (parent_path);
1624 }
1625
1626 static void
1627 individual_view_store_row_changed_cb (GtkTreeModel *model,
1628   GtkTreePath *path,
1629   GtkTreeIter *iter,
1630   EmpathyIndividualView *view)
1631 {
1632   individual_view_verify_group_visibility (view, path);
1633 }
1634
1635 static void
1636 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1637   GtkTreePath *path,
1638   EmpathyIndividualView *view)
1639 {
1640   individual_view_verify_group_visibility (view, path);
1641 }
1642
1643 static gboolean
1644 individual_view_is_visible_individual (EmpathyIndividualView *self,
1645     FolksIndividual *individual,
1646     gboolean is_online,
1647     gboolean is_searching)
1648 {
1649   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1650   EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1651   const gchar *str;
1652   GList *personas, *l;
1653
1654   /* We're only giving the visibility wrt filtering here, not things like
1655    * presence. */
1656   if (priv->show_untrusted == FALSE &&
1657       folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1658     {
1659       return FALSE;
1660     }
1661
1662   if (is_searching == FALSE)
1663     return (priv->show_offline || is_online);
1664
1665   /* check alias name */
1666   str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1667
1668   if (empathy_live_search_match (live, str))
1669     return TRUE;
1670
1671   /* check contact id, remove the @server.com part */
1672   personas = folks_individual_get_personas (individual);
1673   for (l = personas; l; l = l->next)
1674     {
1675       const gchar *p;
1676       gchar *dup_str = NULL;
1677       gboolean visible;
1678
1679       if (!TPF_IS_PERSONA (l->data))
1680         continue;
1681
1682       str = folks_persona_get_display_id (l->data);
1683       p = strstr (str, "@");
1684       if (p != NULL)
1685         str = dup_str = g_strndup (str, p - str);
1686
1687       visible = empathy_live_search_match (live, str);
1688       g_free (dup_str);
1689       if (visible)
1690         return TRUE;
1691     }
1692
1693   /* FIXME: Add more rules here, we could check phone numbers in
1694    * contact's vCard for example. */
1695
1696   return FALSE;
1697 }
1698
1699 static gboolean
1700 individual_view_filter_visible_func (GtkTreeModel *model,
1701     GtkTreeIter *iter,
1702     gpointer user_data)
1703 {
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;
1711
1712   if (priv->search_widget == NULL ||
1713       !gtk_widget_get_visible (priv->search_widget))
1714      is_searching = FALSE;
1715
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,
1721       -1);
1722
1723   if (individual != NULL)
1724     {
1725       visible = individual_view_is_visible_individual (self, individual,
1726           is_online, is_searching);
1727
1728       g_object_unref (individual);
1729
1730       /* FIXME: Work around bgo#626552/bgo#621076 */
1731       if (visible == TRUE)
1732         {
1733           GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1734           individual_view_verify_group_visibility (self, path);
1735           gtk_tree_path_free (path);
1736         }
1737
1738       return visible;
1739     }
1740
1741   if (is_separator)
1742     return TRUE;
1743
1744   /* Not a contact, not a separator, must be a group */
1745   g_return_val_if_fail (is_group, FALSE);
1746
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))
1750     {
1751       gtk_tree_model_get (model, &child_iter,
1752         EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1753         EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1754         -1);
1755
1756       if (individual == NULL)
1757         continue;
1758
1759       visible = individual_view_is_visible_individual (self, individual,
1760           is_online, is_searching);
1761       g_object_unref (individual);
1762
1763       /* show group if it has at least one visible contact in it */
1764       if (visible == TRUE)
1765         return TRUE;
1766     }
1767
1768   return FALSE;
1769 }
1770
1771 static void
1772 individual_view_constructed (GObject *object)
1773 {
1774   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1775   GtkCellRenderer *cell;
1776   GtkTreeViewColumn *col;
1777   guint i;
1778
1779   /* Setup view */
1780   g_object_set (view,
1781       "headers-visible", FALSE,
1782       "show-expanders", FALSE,
1783       NULL);
1784
1785   col = gtk_tree_view_column_new ();
1786
1787   /* State */
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,
1792       view, NULL);
1793
1794   g_object_set (cell,
1795       "xpad", 5,
1796       "ypad", 1,
1797       "visible", FALSE,
1798       NULL);
1799
1800   /* Group icon */
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,
1805       view, NULL);
1806
1807   g_object_set (cell,
1808       "xpad", 0,
1809       "ypad", 0,
1810       "visible", FALSE,
1811       "width", 16,
1812       "height", 16,
1813       NULL);
1814
1815   /* Name */
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);
1820
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);
1833   gtk_tree_view_column_add_attribute (col, cell,
1834       "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1835
1836   /* Audio Call Icon */
1837   cell = empathy_cell_renderer_activatable_new ();
1838   gtk_tree_view_column_pack_start (col, cell, FALSE);
1839   gtk_tree_view_column_set_cell_data_func (col, cell,
1840       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1841       view, NULL);
1842
1843   g_object_set (cell, "visible", FALSE, NULL);
1844
1845   g_signal_connect (cell, "path-activated",
1846       G_CALLBACK (individual_view_call_activated_cb), view);
1847
1848   /* Avatar */
1849   cell = gtk_cell_renderer_pixbuf_new ();
1850   gtk_tree_view_column_pack_start (col, cell, FALSE);
1851   gtk_tree_view_column_set_cell_data_func (col, cell,
1852       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1853       view, NULL);
1854
1855   g_object_set (cell,
1856       "xpad", 0,
1857       "ypad", 0,
1858       "visible", FALSE,
1859       "width", 32,
1860       "height", 32,
1861       NULL);
1862
1863   /* Expander */
1864   cell = empathy_cell_renderer_expander_new ();
1865   gtk_tree_view_column_pack_end (col, cell, FALSE);
1866   gtk_tree_view_column_set_cell_data_func (col, cell,
1867       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1868       view, NULL);
1869
1870   /* Actually add the column now we have added all cell renderers */
1871   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1872
1873   /* Drag & Drop. */
1874   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1875     {
1876       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1877     }
1878
1879   for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1880     {
1881       drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1882           FALSE);
1883     }
1884 }
1885
1886 static void
1887 individual_view_set_view_features (EmpathyIndividualView *view,
1888     EmpathyIndividualFeatureFlags features)
1889 {
1890   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1891   gboolean has_tooltip;
1892
1893   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1894
1895   priv->view_features = features;
1896
1897   /* Setting reorderable is a hack that gets us row previews as drag icons
1898      for free.  We override all the drag handlers.  It's tricky to get the
1899      position of the drag icon right in drag_begin.  GtkTreeView has special
1900      voodoo for it, so we let it do the voodoo that he do (but only if dragging
1901      is enabled).
1902    */
1903   gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1904       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1905
1906   /* Update DnD source/dest */
1907   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1908     {
1909       gtk_drag_source_set (GTK_WIDGET (view),
1910           GDK_BUTTON1_MASK,
1911           drag_types_source,
1912           G_N_ELEMENTS (drag_types_source),
1913           GDK_ACTION_MOVE | GDK_ACTION_COPY);
1914     }
1915   else
1916     {
1917       gtk_drag_source_unset (GTK_WIDGET (view));
1918
1919     }
1920
1921   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1922     {
1923       gtk_drag_dest_set (GTK_WIDGET (view),
1924           GTK_DEST_DEFAULT_ALL,
1925           drag_types_dest,
1926           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1927     }
1928   else
1929     {
1930       /* FIXME: URI could still be droped depending on FT feature */
1931       gtk_drag_dest_unset (GTK_WIDGET (view));
1932     }
1933
1934   /* Update has-tooltip */
1935   has_tooltip =
1936       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1937   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1938 }
1939
1940 static void
1941 individual_view_dispose (GObject *object)
1942 {
1943   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1944   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1945
1946   tp_clear_object (&priv->store);
1947   tp_clear_object (&priv->filter);
1948   tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1949
1950   empathy_individual_view_set_live_search (view, NULL);
1951
1952   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1953 }
1954
1955 static void
1956 individual_view_finalize (GObject *object)
1957 {
1958   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1959
1960   if (priv->expand_groups_idle_handler != 0)
1961     g_source_remove (priv->expand_groups_idle_handler);
1962   g_hash_table_destroy (priv->expand_groups);
1963
1964   G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1965 }
1966
1967 static void
1968 individual_view_get_property (GObject *object,
1969     guint param_id,
1970     GValue *value,
1971     GParamSpec *pspec)
1972 {
1973   EmpathyIndividualViewPriv *priv;
1974
1975   priv = GET_PRIV (object);
1976
1977   switch (param_id)
1978     {
1979     case PROP_STORE:
1980       g_value_set_object (value, priv->store);
1981       break;
1982     case PROP_VIEW_FEATURES:
1983       g_value_set_flags (value, priv->view_features);
1984       break;
1985     case PROP_INDIVIDUAL_FEATURES:
1986       g_value_set_flags (value, priv->individual_features);
1987       break;
1988     case PROP_SHOW_OFFLINE:
1989       g_value_set_boolean (value, priv->show_offline);
1990       break;
1991     case PROP_SHOW_UNTRUSTED:
1992       g_value_set_boolean (value, priv->show_untrusted);
1993       break;
1994     default:
1995       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1996       break;
1997     };
1998 }
1999
2000 static void
2001 individual_view_set_property (GObject *object,
2002     guint param_id,
2003     const GValue *value,
2004     GParamSpec *pspec)
2005 {
2006   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2007   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2008
2009   switch (param_id)
2010     {
2011     case PROP_STORE:
2012       empathy_individual_view_set_store (view, g_value_get_object (value));
2013       break;
2014     case PROP_VIEW_FEATURES:
2015       individual_view_set_view_features (view, g_value_get_flags (value));
2016       break;
2017     case PROP_INDIVIDUAL_FEATURES:
2018       priv->individual_features = g_value_get_flags (value);
2019       break;
2020     case PROP_SHOW_OFFLINE:
2021       empathy_individual_view_set_show_offline (view,
2022           g_value_get_boolean (value));
2023       break;
2024     case PROP_SHOW_UNTRUSTED:
2025       empathy_individual_view_set_show_untrusted (view,
2026           g_value_get_boolean (value));
2027       break;
2028     default:
2029       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2030       break;
2031     };
2032 }
2033
2034 static void
2035 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2036 {
2037   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2038   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2039   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2040
2041   object_class->constructed = individual_view_constructed;
2042   object_class->dispose = individual_view_dispose;
2043   object_class->finalize = individual_view_finalize;
2044   object_class->get_property = individual_view_get_property;
2045   object_class->set_property = individual_view_set_property;
2046
2047   widget_class->drag_data_received = individual_view_drag_data_received;
2048   widget_class->drag_drop = individual_view_drag_drop;
2049   widget_class->drag_begin = individual_view_drag_begin;
2050   widget_class->drag_data_get = individual_view_drag_data_get;
2051   widget_class->drag_end = individual_view_drag_end;
2052   widget_class->drag_motion = individual_view_drag_motion;
2053
2054   /* We use the class method to let user of this widget to connect to
2055    * the signal and stop emission of the signal so the default handler
2056    * won't be called. */
2057   tree_view_class->row_activated = individual_view_row_activated;
2058
2059   klass->drag_individual_received = real_drag_individual_received_cb;
2060
2061   signals[DRAG_INDIVIDUAL_RECEIVED] =
2062       g_signal_new ("drag-individual-received",
2063       G_OBJECT_CLASS_TYPE (klass),
2064       G_SIGNAL_RUN_LAST,
2065       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2066       NULL, NULL,
2067       _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2068       G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2069       G_TYPE_STRING, G_TYPE_STRING);
2070
2071   signals[DRAG_PERSONA_RECEIVED] =
2072       g_signal_new ("drag-persona-received",
2073       G_OBJECT_CLASS_TYPE (klass),
2074       G_SIGNAL_RUN_LAST,
2075       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2076       NULL, NULL,
2077       _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2078       G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2079
2080   g_object_class_install_property (object_class,
2081       PROP_STORE,
2082       g_param_spec_object ("store",
2083           "The store of the view",
2084           "The store of the view",
2085           EMPATHY_TYPE_INDIVIDUAL_STORE,
2086           G_PARAM_READWRITE));
2087   g_object_class_install_property (object_class,
2088       PROP_VIEW_FEATURES,
2089       g_param_spec_flags ("view-features",
2090           "Features of the view",
2091           "Flags for all enabled features",
2092           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2093           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2094   g_object_class_install_property (object_class,
2095       PROP_INDIVIDUAL_FEATURES,
2096       g_param_spec_flags ("individual-features",
2097           "Features of the individual menu",
2098           "Flags for all enabled features for the menu",
2099           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2100           EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2101   g_object_class_install_property (object_class,
2102       PROP_SHOW_OFFLINE,
2103       g_param_spec_boolean ("show-offline",
2104           "Show Offline",
2105           "Whether contact list should display "
2106           "offline contacts", FALSE, G_PARAM_READWRITE));
2107   g_object_class_install_property (object_class,
2108       PROP_SHOW_UNTRUSTED,
2109       g_param_spec_boolean ("show-untrusted",
2110           "Show Untrusted Individuals",
2111           "Whether the view should display untrusted individuals; "
2112           "those who could not be who they say they are.",
2113           TRUE, G_PARAM_READWRITE));
2114
2115   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2116 }
2117
2118 static void
2119 empathy_individual_view_init (EmpathyIndividualView *view)
2120 {
2121   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2122       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2123
2124   view->priv = priv;
2125
2126   priv->show_untrusted = TRUE;
2127
2128   /* Get saved group states. */
2129   empathy_contact_groups_get_all ();
2130
2131   priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2132       (GDestroyNotify) g_free, NULL);
2133
2134   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2135       empathy_individual_store_row_separator_func, NULL, NULL);
2136
2137   /* Connect to tree view signals rather than override. */
2138   g_signal_connect (view, "button-press-event",
2139       G_CALLBACK (individual_view_button_press_event_cb), NULL);
2140   g_signal_connect (view, "key-press-event",
2141       G_CALLBACK (individual_view_key_press_event_cb), NULL);
2142   g_signal_connect (view, "row-expanded",
2143       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2144       GINT_TO_POINTER (TRUE));
2145   g_signal_connect (view, "row-collapsed",
2146       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2147       GINT_TO_POINTER (FALSE));
2148   g_signal_connect (view, "query-tooltip",
2149       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2150 }
2151
2152 EmpathyIndividualView *
2153 empathy_individual_view_new (EmpathyIndividualStore *store,
2154     EmpathyIndividualViewFeatureFlags view_features,
2155     EmpathyIndividualFeatureFlags individual_features)
2156 {
2157   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2158
2159   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2160       "store", store,
2161       "individual-features", individual_features,
2162       "view-features", view_features, NULL);
2163 }
2164
2165 FolksIndividual *
2166 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2167 {
2168   EmpathyIndividualViewPriv *priv;
2169   GtkTreeSelection *selection;
2170   GtkTreeIter iter;
2171   GtkTreeModel *model;
2172   FolksIndividual *individual;
2173
2174   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2175
2176   priv = GET_PRIV (view);
2177
2178   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2179   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2180     return NULL;
2181
2182   gtk_tree_model_get (model, &iter,
2183       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2184
2185   return individual;
2186 }
2187
2188 static gchar *
2189 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2190     gboolean *is_fake_group)
2191 {
2192   EmpathyIndividualViewPriv *priv;
2193   GtkTreeSelection *selection;
2194   GtkTreeIter iter;
2195   GtkTreeModel *model;
2196   gboolean is_group;
2197   gchar *name;
2198   gboolean fake;
2199
2200   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2201
2202   priv = GET_PRIV (view);
2203
2204   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2205   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2206     return NULL;
2207
2208   gtk_tree_model_get (model, &iter,
2209       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2210       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2211       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2212
2213   if (!is_group)
2214     {
2215       g_free (name);
2216       return NULL;
2217     }
2218
2219   if (is_fake_group != NULL)
2220     *is_fake_group = fake;
2221
2222   return name;
2223 }
2224
2225 static gboolean
2226 individual_view_remove_dialog_show (GtkWindow *parent,
2227     const gchar *message,
2228     const gchar *secondary_text)
2229 {
2230   GtkWidget *dialog;
2231   gboolean res;
2232
2233   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2234       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2235   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2236       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2237       GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2238   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2239       "%s", secondary_text);
2240
2241   gtk_widget_show (dialog);
2242
2243   res = gtk_dialog_run (GTK_DIALOG (dialog));
2244   gtk_widget_destroy (dialog);
2245
2246   return (res == GTK_RESPONSE_YES);
2247 }
2248
2249 static void
2250 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2251     EmpathyIndividualView *view)
2252 {
2253   gchar *group;
2254
2255   group = empathy_individual_view_dup_selected_group (view, NULL);
2256   if (group != NULL)
2257     {
2258       gchar *text;
2259       GtkWindow *parent;
2260
2261       text =
2262           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2263           group);
2264       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2265       if (individual_view_remove_dialog_show (parent, _("Removing group"),
2266               text))
2267         {
2268           EmpathyIndividualManager *manager =
2269               empathy_individual_manager_dup_singleton ();
2270           empathy_individual_manager_remove_group (manager, group);
2271           g_object_unref (G_OBJECT (manager));
2272         }
2273
2274       g_free (text);
2275     }
2276
2277   g_free (group);
2278 }
2279
2280 GtkWidget *
2281 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2282 {
2283   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2284   gchar *group;
2285   GtkWidget *menu;
2286   GtkWidget *item;
2287   GtkWidget *image;
2288   gboolean is_fake_group;
2289
2290   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2291
2292   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2293               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2294     return NULL;
2295
2296   group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2297   if (!group || is_fake_group)
2298     {
2299       /* We can't alter fake groups */
2300       g_free (group);
2301       return NULL;
2302     }
2303
2304   menu = gtk_menu_new ();
2305
2306   /* TODO: implement
2307      if (priv->view_features &
2308      EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2309      item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2310      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2311      gtk_widget_show (item);
2312      g_signal_connect (item, "activate",
2313      G_CALLBACK (individual_view_group_rename_activate_cb),
2314      view);
2315      }
2316    */
2317
2318   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2319     {
2320       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2321       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2322           GTK_ICON_SIZE_MENU);
2323       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2324       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2325       gtk_widget_show (item);
2326       g_signal_connect (item, "activate",
2327           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2328     }
2329
2330   g_free (group);
2331
2332   return menu;
2333 }
2334
2335 static void
2336 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2337     EmpathyIndividualView *view)
2338 {
2339   FolksIndividual *individual;
2340
2341   individual = empathy_individual_view_dup_selected (view);
2342
2343   if (individual != NULL)
2344     {
2345       gchar *text;
2346       GtkWindow *parent;
2347       GList *l, *personas;
2348       guint persona_count = 0;
2349
2350       personas = folks_individual_get_personas (individual);
2351
2352       /* If we have more than one TpfPersona, display a different message
2353        * ensuring the user knows that *all* of the meta-contacts' personas will
2354        * be removed. */
2355       for (l = personas; l != NULL; l = l->next)
2356         {
2357           if (!TPF_IS_PERSONA (l->data))
2358             continue;
2359
2360           persona_count++;
2361           if (persona_count >= 2)
2362             break;
2363         }
2364
2365       if (persona_count < 2)
2366         {
2367           /* Not a meta-contact */
2368           text =
2369               g_strdup_printf (
2370                   _("Do you really want to remove the contact '%s'?"),
2371                   folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2372         }
2373       else
2374         {
2375           /* Meta-contact */
2376           text =
2377               g_strdup_printf (
2378                   _("Do you really want to remove the linked contact '%s'? "
2379                     "Note that this will remove all the contacts which make up "
2380                     "this linked contact."),
2381                   folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2382         }
2383
2384       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2385
2386       if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2387               text))
2388         {
2389           EmpathyIndividualManager *manager;
2390
2391           manager = empathy_individual_manager_dup_singleton ();
2392           empathy_individual_manager_remove (manager, individual, "");
2393           g_object_unref (G_OBJECT (manager));
2394         }
2395
2396       g_free (text);
2397       g_object_unref (individual);
2398     }
2399 }
2400
2401 static void
2402 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2403     EmpathyLinkingDialog *linking_dialog,
2404     EmpathyIndividualView *self)
2405 {
2406   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2407   EmpathyIndividualLinker *linker;
2408
2409   linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2410   empathy_individual_linker_set_search_text (linker,
2411       empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2412 }
2413
2414 GtkWidget *
2415 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2416 {
2417   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2418   FolksIndividual *individual;
2419   GtkWidget *menu = NULL;
2420   GtkWidget *item;
2421   GtkWidget *image;
2422   gboolean can_remove = FALSE;
2423   GList *l;
2424
2425   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2426
2427   individual = empathy_individual_view_dup_selected (view);
2428   if (individual == NULL)
2429     return NULL;
2430
2431   /* If any of the Individual's personas can be removed, add an option to
2432    * remove. This will act as a best-effort option. If any Personas cannot be
2433    * removed from the server, then this option will just be inactive upon
2434    * subsequent menu openings */
2435   for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2436     {
2437       FolksPersona *persona = FOLKS_PERSONA (l->data);
2438       FolksPersonaStore *store = folks_persona_get_store (persona);
2439       FolksMaybeBool maybe_can_remove =
2440           folks_persona_store_get_can_remove_personas (store);
2441
2442       if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2443         {
2444           can_remove = TRUE;
2445           break;
2446         }
2447     }
2448
2449   menu = empathy_individual_menu_new (individual, priv->individual_features);
2450
2451   /* Remove contact */
2452   if ((priv->view_features &
2453       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2454       can_remove)
2455     {
2456       /* create the menu if required, or just add a separator */
2457       if (menu == NULL)
2458         menu = gtk_menu_new ();
2459       else
2460         {
2461           item = gtk_separator_menu_item_new ();
2462           gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2463           gtk_widget_show (item);
2464         }
2465
2466       /* Remove */
2467       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2468       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2469           GTK_ICON_SIZE_MENU);
2470       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2471       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2472       gtk_widget_show (item);
2473       g_signal_connect (item, "activate",
2474           G_CALLBACK (individual_view_remove_activate_cb), view);
2475     }
2476
2477   /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2478    * set the live search text on the new linking dialogue to be the same as
2479    * our own. */
2480   g_signal_connect (menu, "link-contacts-activated",
2481       (GCallback) individual_menu_link_contacts_activated_cb, view);
2482
2483   g_object_unref (individual);
2484
2485   return menu;
2486 }
2487
2488 void
2489 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2490     EmpathyLiveSearch *search)
2491 {
2492   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2493
2494   /* remove old handlers if old search was not null */
2495   if (priv->search_widget != NULL)
2496     {
2497       g_signal_handlers_disconnect_by_func (view,
2498           individual_view_start_search_cb, NULL);
2499
2500       g_signal_handlers_disconnect_by_func (priv->search_widget,
2501           individual_view_search_text_notify_cb, view);
2502       g_signal_handlers_disconnect_by_func (priv->search_widget,
2503           individual_view_search_activate_cb, view);
2504       g_signal_handlers_disconnect_by_func (priv->search_widget,
2505           individual_view_search_key_navigation_cb, view);
2506       g_signal_handlers_disconnect_by_func (priv->search_widget,
2507           individual_view_search_hide_cb, view);
2508       g_signal_handlers_disconnect_by_func (priv->search_widget,
2509           individual_view_search_show_cb, view);
2510       g_object_unref (priv->search_widget);
2511       priv->search_widget = NULL;
2512     }
2513
2514   /* connect handlers if new search is not null */
2515   if (search != NULL)
2516     {
2517       priv->search_widget = g_object_ref (search);
2518
2519       g_signal_connect (view, "start-interactive-search",
2520           G_CALLBACK (individual_view_start_search_cb), NULL);
2521
2522       g_signal_connect (priv->search_widget, "notify::text",
2523           G_CALLBACK (individual_view_search_text_notify_cb), view);
2524       g_signal_connect (priv->search_widget, "activate",
2525           G_CALLBACK (individual_view_search_activate_cb), view);
2526       g_signal_connect (priv->search_widget, "key-navigation",
2527           G_CALLBACK (individual_view_search_key_navigation_cb), view);
2528       g_signal_connect (priv->search_widget, "hide",
2529           G_CALLBACK (individual_view_search_hide_cb), view);
2530       g_signal_connect (priv->search_widget, "show",
2531           G_CALLBACK (individual_view_search_show_cb), view);
2532     }
2533 }
2534
2535 gboolean
2536 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2537 {
2538   EmpathyIndividualViewPriv *priv;
2539
2540   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2541
2542   priv = GET_PRIV (self);
2543
2544   return (priv->search_widget != NULL &&
2545           gtk_widget_get_visible (priv->search_widget));
2546 }
2547
2548 gboolean
2549 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2550 {
2551   EmpathyIndividualViewPriv *priv;
2552
2553   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2554
2555   priv = GET_PRIV (self);
2556
2557   return priv->show_offline;
2558 }
2559
2560 void
2561 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2562     gboolean show_offline)
2563 {
2564   EmpathyIndividualViewPriv *priv;
2565
2566   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2567
2568   priv = GET_PRIV (self);
2569
2570   priv->show_offline = show_offline;
2571
2572   g_object_notify (G_OBJECT (self), "show-offline");
2573   gtk_tree_model_filter_refilter (priv->filter);
2574 }
2575
2576 gboolean
2577 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2578 {
2579   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2580
2581   return GET_PRIV (self)->show_untrusted;
2582 }
2583
2584 void
2585 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2586     gboolean show_untrusted)
2587 {
2588   EmpathyIndividualViewPriv *priv;
2589
2590   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2591
2592   priv = GET_PRIV (self);
2593
2594   priv->show_untrusted = show_untrusted;
2595
2596   g_object_notify (G_OBJECT (self), "show-untrusted");
2597   gtk_tree_model_filter_refilter (priv->filter);
2598 }
2599
2600 EmpathyIndividualStore *
2601 empathy_individual_view_get_store (EmpathyIndividualView *self)
2602 {
2603   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2604
2605   return GET_PRIV (self)->store;
2606 }
2607
2608 void
2609 empathy_individual_view_set_store (EmpathyIndividualView *self,
2610     EmpathyIndividualStore *store)
2611 {
2612   EmpathyIndividualViewPriv *priv;
2613
2614   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2615   g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2616
2617   priv = GET_PRIV (self);
2618
2619   /* Destroy the old filter and remove the old store */
2620   if (priv->store != NULL)
2621     {
2622       g_signal_handlers_disconnect_by_func (priv->store,
2623           individual_view_store_row_changed_cb, self);
2624       g_signal_handlers_disconnect_by_func (priv->store,
2625           individual_view_store_row_deleted_cb, self);
2626
2627       g_signal_handlers_disconnect_by_func (priv->filter,
2628           individual_view_row_has_child_toggled_cb, self);
2629
2630       gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2631     }
2632
2633   tp_clear_object (&priv->filter);
2634   tp_clear_object (&priv->store);
2635
2636   /* Set the new store */
2637   priv->store = store;
2638
2639   if (store != NULL)
2640     {
2641       g_object_ref (store);
2642
2643       /* Create a new filter */
2644       priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2645           GTK_TREE_MODEL (priv->store), NULL));
2646       gtk_tree_model_filter_set_visible_func (priv->filter,
2647           individual_view_filter_visible_func, self, NULL);
2648
2649       g_signal_connect (priv->filter, "row-has-child-toggled",
2650           G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2651       gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2652           GTK_TREE_MODEL (priv->filter));
2653
2654       tp_g_signal_connect_object (priv->store, "row-changed",
2655           G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2656       tp_g_signal_connect_object (priv->store, "row-inserted",
2657           G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2658       tp_g_signal_connect_object (priv->store, "row-deleted",
2659           G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2660     }
2661 }
2662
2663 void
2664 empathy_individual_view_start_search (EmpathyIndividualView *self)
2665 {
2666   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2667
2668   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2669   g_return_if_fail (priv->search_widget != NULL);
2670
2671   if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2672     gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2673   else
2674     gtk_widget_show (GTK_WIDGET (priv->search_widget));
2675 }