]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
Merge branch 'sasl-gui'
[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   GdkColor color;
1051   GtkStyle *style;
1052
1053   style = gtk_widget_get_style (GTK_WIDGET (view));
1054
1055   if (!is_group && is_active)
1056     {
1057       color = style->bg[GTK_STATE_SELECTED];
1058
1059       /* Here we take the current theme colour and add it to
1060        * the colour for white and average the two. This
1061        * gives a colour which is inline with the theme but
1062        * slightly whiter.
1063        */
1064       color.red = (color.red + (style->white).red) / 2;
1065       color.green = (color.green + (style->white).green) / 2;
1066       color.blue = (color.blue + (style->white).blue) / 2;
1067
1068       g_object_set (cell, "cell-background-gdk", &color, NULL);
1069     }
1070   else
1071     g_object_set (cell, "cell-background-gdk", NULL, NULL);
1072 }
1073
1074 static void
1075 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1076     GtkCellRenderer *cell,
1077     GtkTreeModel *model,
1078     GtkTreeIter *iter,
1079     EmpathyIndividualView *view)
1080 {
1081   GdkPixbuf *pixbuf;
1082   gboolean is_group;
1083   gboolean is_active;
1084
1085   gtk_tree_model_get (model, iter,
1086       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1087       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1088       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1089
1090   g_object_set (cell,
1091       "visible", !is_group,
1092       "pixbuf", pixbuf,
1093       NULL);
1094
1095   tp_clear_object (&pixbuf);
1096
1097   individual_view_cell_set_background (view, cell, is_group, is_active);
1098 }
1099
1100 static void
1101 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1102     GtkCellRenderer *cell,
1103     GtkTreeModel *model,
1104     GtkTreeIter *iter,
1105     EmpathyIndividualView *view)
1106 {
1107   GdkPixbuf *pixbuf = NULL;
1108   gboolean is_group;
1109   gchar *name;
1110
1111   gtk_tree_model_get (model, iter,
1112       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1113       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1114
1115   if (!is_group)
1116     goto out;
1117
1118   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1119     {
1120       pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1121           GTK_ICON_SIZE_MENU);
1122     }
1123   else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1124     {
1125       pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1126           GTK_ICON_SIZE_MENU);
1127     }
1128
1129 out:
1130   g_object_set (cell,
1131       "visible", pixbuf != NULL,
1132       "pixbuf", pixbuf,
1133       NULL);
1134
1135   tp_clear_object (&pixbuf);
1136
1137   g_free (name);
1138 }
1139
1140 static void
1141 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1142     GtkCellRenderer *cell,
1143     GtkTreeModel *model,
1144     GtkTreeIter *iter,
1145     EmpathyIndividualView *view)
1146 {
1147   gboolean is_group;
1148   gboolean is_active;
1149   gboolean can_audio, can_video;
1150
1151   gtk_tree_model_get (model, iter,
1152       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1153       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1154       EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1155       EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1156
1157   g_object_set (cell,
1158       "visible", !is_group && (can_audio || can_video),
1159       "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1160       NULL);
1161
1162   individual_view_cell_set_background (view, cell, is_group, is_active);
1163 }
1164
1165 static void
1166 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1167     GtkCellRenderer *cell,
1168     GtkTreeModel *model,
1169     GtkTreeIter *iter,
1170     EmpathyIndividualView *view)
1171 {
1172   GdkPixbuf *pixbuf;
1173   gboolean show_avatar;
1174   gboolean is_group;
1175   gboolean is_active;
1176
1177   gtk_tree_model_get (model, iter,
1178       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1179       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1180       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1181       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1182
1183   g_object_set (cell,
1184       "visible", !is_group && show_avatar,
1185       "pixbuf", pixbuf,
1186       NULL);
1187
1188   tp_clear_object (&pixbuf);
1189
1190   individual_view_cell_set_background (view, cell, is_group, is_active);
1191 }
1192
1193 static void
1194 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1195     GtkCellRenderer *cell,
1196     GtkTreeModel *model,
1197     GtkTreeIter *iter,
1198     EmpathyIndividualView *view)
1199 {
1200   gboolean is_group;
1201   gboolean is_active;
1202
1203   gtk_tree_model_get (model, iter,
1204       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1205       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1206
1207   individual_view_cell_set_background (view, cell, is_group, is_active);
1208 }
1209
1210 static void
1211 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1212     GtkCellRenderer *cell,
1213     GtkTreeModel *model,
1214     GtkTreeIter *iter,
1215     EmpathyIndividualView *view)
1216 {
1217   gboolean is_group;
1218   gboolean is_active;
1219
1220   gtk_tree_model_get (model, iter,
1221       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1222       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1223
1224   if (gtk_tree_model_iter_has_child (model, iter))
1225     {
1226       GtkTreePath *path;
1227       gboolean row_expanded;
1228
1229       path = gtk_tree_model_get_path (model, iter);
1230       row_expanded =
1231           gtk_tree_view_row_expanded (GTK_TREE_VIEW
1232           (gtk_tree_view_column_get_tree_view (column)), path);
1233       gtk_tree_path_free (path);
1234
1235       g_object_set (cell,
1236           "visible", TRUE,
1237           "expander-style",
1238           row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1239           NULL);
1240     }
1241   else
1242     g_object_set (cell, "visible", FALSE, NULL);
1243
1244   individual_view_cell_set_background (view, cell, is_group, is_active);
1245 }
1246
1247 static void
1248 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1249     GtkTreeIter *iter,
1250     GtkTreePath *path,
1251     gpointer user_data)
1252 {
1253   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1254   GtkTreeModel *model;
1255   gchar *name;
1256   gboolean expanded;
1257
1258   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1259     return;
1260
1261   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1262
1263   gtk_tree_model_get (model, iter,
1264       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1265
1266   expanded = GPOINTER_TO_INT (user_data);
1267   empathy_contact_group_set_expanded (name, expanded);
1268
1269   g_free (name);
1270 }
1271
1272 static gboolean
1273 individual_view_start_search_cb (EmpathyIndividualView *view,
1274     gpointer data)
1275 {
1276   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1277
1278   if (priv->search_widget == NULL)
1279     return FALSE;
1280
1281   empathy_individual_view_start_search (view);
1282
1283   return TRUE;
1284 }
1285
1286 static void
1287 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1288     GParamSpec *pspec,
1289     EmpathyIndividualView *view)
1290 {
1291   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1292   GtkTreePath *path;
1293   GtkTreeViewColumn *focus_column;
1294   GtkTreeModel *model;
1295   GtkTreeIter iter;
1296   gboolean set_cursor = FALSE;
1297
1298   gtk_tree_model_filter_refilter (priv->filter);
1299
1300   /* Set cursor on the first contact. If it is already set on a group,
1301    * set it on its first child contact. Note that first child of a group
1302    * is its separator, that's why we actually set to the 2nd
1303    */
1304
1305   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1306   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1307
1308   if (path == NULL)
1309     {
1310       path = gtk_tree_path_new_from_string ("0:1");
1311       set_cursor = TRUE;
1312     }
1313   else if (gtk_tree_path_get_depth (path) < 2)
1314     {
1315       gboolean is_group;
1316
1317       gtk_tree_model_get_iter (model, &iter, path);
1318       gtk_tree_model_get (model, &iter,
1319           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1320           -1);
1321
1322       if (is_group)
1323         {
1324           gtk_tree_path_down (path);
1325           gtk_tree_path_next (path);
1326           set_cursor = TRUE;
1327         }
1328     }
1329
1330   if (set_cursor)
1331     {
1332       /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1333        * valid. */
1334       if (gtk_tree_model_get_iter (model, &iter, path))
1335         {
1336           gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1337               FALSE);
1338         }
1339     }
1340
1341   gtk_tree_path_free (path);
1342 }
1343
1344 static void
1345 individual_view_search_activate_cb (GtkWidget *search,
1346   EmpathyIndividualView *view)
1347 {
1348   GtkTreePath *path;
1349   GtkTreeViewColumn *focus_column;
1350
1351   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1352   if (path != NULL)
1353     {
1354       gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1355       gtk_tree_path_free (path);
1356
1357       gtk_widget_hide (search);
1358     }
1359 }
1360
1361 static gboolean
1362 individual_view_search_key_navigation_cb (GtkWidget *search,
1363   GdkEvent *event,
1364   EmpathyIndividualView *view)
1365 {
1366   GdkEventKey *eventkey = ((GdkEventKey *) event);
1367   gboolean ret = FALSE;
1368
1369   if (eventkey->keyval == GDK_KEY_Up || eventkey->keyval == GDK_KEY_Down)
1370     {
1371       GdkEvent *new_event;
1372
1373       new_event = gdk_event_copy (event);
1374       gtk_widget_grab_focus (GTK_WIDGET (view));
1375       ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1376       gtk_widget_grab_focus (search);
1377
1378       gdk_event_free (new_event);
1379     }
1380
1381   return ret;
1382 }
1383
1384 static void
1385 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1386     EmpathyIndividualView *view)
1387 {
1388   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1389   GtkTreeModel *model;
1390   GtkTreePath *cursor_path;
1391   GtkTreeIter iter;
1392   gboolean valid = FALSE;
1393
1394   /* block expand or collapse handlers, they would write the
1395    * expand or collapsed setting to file otherwise */
1396   g_signal_handlers_block_by_func (view,
1397       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1398   g_signal_handlers_block_by_func (view,
1399     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1400
1401   /* restore which groups are expanded and which are not */
1402   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1403   for (valid = gtk_tree_model_get_iter_first (model, &iter);
1404        valid; valid = gtk_tree_model_iter_next (model, &iter))
1405     {
1406       gboolean is_group;
1407       gchar *name = NULL;
1408       GtkTreePath *path;
1409
1410       gtk_tree_model_get (model, &iter,
1411           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1412           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1413           -1);
1414
1415       if (!is_group)
1416         {
1417           g_free (name);
1418           continue;
1419         }
1420
1421       path = gtk_tree_model_get_path (model, &iter);
1422       if ((priv->view_features &
1423             EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1424           empathy_contact_group_get_expanded (name))
1425         {
1426           gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1427         }
1428       else
1429         {
1430           gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1431         }
1432
1433       gtk_tree_path_free (path);
1434       g_free (name);
1435     }
1436
1437   /* unblock expand or collapse handlers */
1438   g_signal_handlers_unblock_by_func (view,
1439       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1440   g_signal_handlers_unblock_by_func (view,
1441       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1442
1443   /* keep the selected contact visible */
1444   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1445
1446   if (cursor_path != NULL)
1447     gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1448         FALSE, 0, 0);
1449
1450   gtk_tree_path_free (cursor_path);
1451 }
1452
1453 static void
1454 individual_view_search_show_cb (EmpathyLiveSearch *search,
1455     EmpathyIndividualView *view)
1456 {
1457   /* block expand or collapse handlers during expand all, they would
1458    * write the expand or collapsed setting to file otherwise */
1459   g_signal_handlers_block_by_func (view,
1460       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1461
1462   gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1463
1464   g_signal_handlers_unblock_by_func (view,
1465       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1466 }
1467
1468 static gboolean
1469 expand_idle_foreach_cb (GtkTreeModel *model,
1470     GtkTreePath *path,
1471     GtkTreeIter *iter,
1472     EmpathyIndividualView *self)
1473 {
1474   EmpathyIndividualViewPriv *priv;
1475   gboolean is_group;
1476   gpointer should_expand;
1477   gchar *name;
1478
1479   /* We only want groups */
1480   if (gtk_tree_path_get_depth (path) > 1)
1481     return FALSE;
1482
1483   gtk_tree_model_get (model, iter,
1484       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1485       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1486       -1);
1487
1488   if (is_group == FALSE)
1489     {
1490       g_free (name);
1491       return FALSE;
1492     }
1493
1494   priv = GET_PRIV (self);
1495
1496   if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1497       &should_expand) == TRUE)
1498     {
1499       if (GPOINTER_TO_INT (should_expand) == TRUE)
1500         gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1501       else
1502         gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1503
1504       g_hash_table_remove (priv->expand_groups, name);
1505     }
1506
1507   g_free (name);
1508
1509   return FALSE;
1510 }
1511
1512 static gboolean
1513 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1514 {
1515   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1516
1517   DEBUG ("individual_view_expand_idle_cb");
1518
1519   g_signal_handlers_block_by_func (self,
1520     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1521   g_signal_handlers_block_by_func (self,
1522     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1523
1524   /* The store/filter could've been removed while we were in the idle queue */
1525   if (priv->filter != NULL)
1526     {
1527       gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1528           (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1529     }
1530
1531   g_signal_handlers_unblock_by_func (self,
1532       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1533   g_signal_handlers_unblock_by_func (self,
1534       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1535
1536   /* Empty the table of groups to expand/contract, since it may contain groups
1537    * which no longer exist in the tree view. This can happen after going
1538    * offline, for example. */
1539   g_hash_table_remove_all (priv->expand_groups);
1540   priv->expand_groups_idle_handler = 0;
1541   g_object_unref (self);
1542
1543   return FALSE;
1544 }
1545
1546 static void
1547 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1548     GtkTreePath *path,
1549     GtkTreeIter *iter,
1550     EmpathyIndividualView *view)
1551 {
1552   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1553   gboolean should_expand, is_group = FALSE;
1554   gchar *name = NULL;
1555   gpointer will_expand;
1556
1557   gtk_tree_model_get (model, iter,
1558       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1559       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1560       -1);
1561
1562   if (!is_group || EMP_STR_EMPTY (name))
1563     {
1564       g_free (name);
1565       return;
1566     }
1567
1568   should_expand = (priv->view_features &
1569           EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1570       (priv->search_widget != NULL &&
1571           gtk_widget_get_visible (priv->search_widget)) ||
1572       empathy_contact_group_get_expanded (name);
1573
1574   /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1575    * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1576    * a hash table, and expand or contract them as appropriate all at once in
1577    * an idle handler which iterates over all the group rows. */
1578   if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1579       &will_expand) == FALSE ||
1580       GPOINTER_TO_INT (will_expand) != should_expand)
1581     {
1582       g_hash_table_insert (priv->expand_groups, g_strdup (name),
1583           GINT_TO_POINTER (should_expand));
1584
1585       if (priv->expand_groups_idle_handler == 0)
1586         {
1587           priv->expand_groups_idle_handler =
1588               g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1589                   g_object_ref (view));
1590         }
1591     }
1592
1593   g_free (name);
1594 }
1595
1596 /* FIXME: This is a workaround for bgo#621076 */
1597 static void
1598 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1599     GtkTreePath *path)
1600 {
1601   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1602   GtkTreeModel *model;
1603   GtkTreePath *parent_path;
1604   GtkTreeIter parent_iter;
1605
1606   if (gtk_tree_path_get_depth (path) < 2)
1607     return;
1608
1609   /* A group row is visible if and only if at least one if its child is visible.
1610    * So when a row is inserted/deleted/changed in the base model, that could
1611    * modify the visibility of its parent in the filter model.
1612   */
1613
1614   model = GTK_TREE_MODEL (priv->store);
1615   parent_path = gtk_tree_path_copy (path);
1616   gtk_tree_path_up (parent_path);
1617   if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1618     {
1619       /* This tells the filter to verify the visibility of that row, and
1620        * show/hide it if necessary */
1621       gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1622               parent_path, &parent_iter);
1623     }
1624   gtk_tree_path_free (parent_path);
1625 }
1626
1627 static void
1628 individual_view_store_row_changed_cb (GtkTreeModel *model,
1629   GtkTreePath *path,
1630   GtkTreeIter *iter,
1631   EmpathyIndividualView *view)
1632 {
1633   individual_view_verify_group_visibility (view, path);
1634 }
1635
1636 static void
1637 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1638   GtkTreePath *path,
1639   EmpathyIndividualView *view)
1640 {
1641   individual_view_verify_group_visibility (view, path);
1642 }
1643
1644 static gboolean
1645 individual_view_is_visible_individual (EmpathyIndividualView *self,
1646     FolksIndividual *individual,
1647     gboolean is_online,
1648     gboolean is_searching)
1649 {
1650   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1651   EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1652   const gchar *str;
1653   GList *personas, *l;
1654
1655   /* We're only giving the visibility wrt filtering here, not things like
1656    * presence. */
1657   if (priv->show_untrusted == FALSE &&
1658       folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1659     {
1660       return FALSE;
1661     }
1662
1663   if (is_searching == FALSE)
1664     return (priv->show_offline || is_online);
1665
1666   /* check alias name */
1667   str = folks_aliasable_get_alias (FOLKS_ALIASABLE (individual));
1668
1669   if (empathy_live_search_match (live, str))
1670     return TRUE;
1671
1672   /* check contact id, remove the @server.com part */
1673   personas = folks_individual_get_personas (individual);
1674   for (l = personas; l; l = l->next)
1675     {
1676       const gchar *p;
1677       gchar *dup_str = NULL;
1678       gboolean visible;
1679
1680       if (!TPF_IS_PERSONA (l->data))
1681         continue;
1682
1683       str = folks_persona_get_display_id (l->data);
1684       p = strstr (str, "@");
1685       if (p != NULL)
1686         str = dup_str = g_strndup (str, p - str);
1687
1688       visible = empathy_live_search_match (live, str);
1689       g_free (dup_str);
1690       if (visible)
1691         return TRUE;
1692     }
1693
1694   /* FIXME: Add more rules here, we could check phone numbers in
1695    * contact's vCard for example. */
1696
1697   return FALSE;
1698 }
1699
1700 static gboolean
1701 individual_view_filter_visible_func (GtkTreeModel *model,
1702     GtkTreeIter *iter,
1703     gpointer user_data)
1704 {
1705   EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1706   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1707   FolksIndividual *individual = NULL;
1708   gboolean is_group, is_separator, valid;
1709   GtkTreeIter child_iter;
1710   gboolean visible, is_online;
1711   gboolean is_searching = TRUE;
1712
1713   if (priv->search_widget == NULL ||
1714       !gtk_widget_get_visible (priv->search_widget))
1715      is_searching = FALSE;
1716
1717   gtk_tree_model_get (model, iter,
1718       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1719       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1720       EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1721       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1722       -1);
1723
1724   if (individual != NULL)
1725     {
1726       visible = individual_view_is_visible_individual (self, individual,
1727           is_online, is_searching);
1728
1729       g_object_unref (individual);
1730
1731       /* FIXME: Work around bgo#626552/bgo#621076 */
1732       if (visible == TRUE)
1733         {
1734           GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1735           individual_view_verify_group_visibility (self, path);
1736           gtk_tree_path_free (path);
1737         }
1738
1739       return visible;
1740     }
1741
1742   if (is_separator)
1743     return TRUE;
1744
1745   /* Not a contact, not a separator, must be a group */
1746   g_return_val_if_fail (is_group, FALSE);
1747
1748   /* only show groups which are not empty */
1749   for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1750        valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1751     {
1752       gtk_tree_model_get (model, &child_iter,
1753         EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1754         EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1755         -1);
1756
1757       if (individual == NULL)
1758         continue;
1759
1760       visible = individual_view_is_visible_individual (self, individual,
1761           is_online, is_searching);
1762       g_object_unref (individual);
1763
1764       /* show group if it has at least one visible contact in it */
1765       if (visible == TRUE)
1766         return TRUE;
1767     }
1768
1769   return FALSE;
1770 }
1771
1772 static void
1773 individual_view_constructed (GObject *object)
1774 {
1775   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1776   GtkCellRenderer *cell;
1777   GtkTreeViewColumn *col;
1778   guint i;
1779
1780   /* Setup view */
1781   g_object_set (view,
1782       "headers-visible", FALSE,
1783       "show-expanders", FALSE,
1784       NULL);
1785
1786   col = gtk_tree_view_column_new ();
1787
1788   /* State */
1789   cell = gtk_cell_renderer_pixbuf_new ();
1790   gtk_tree_view_column_pack_start (col, cell, FALSE);
1791   gtk_tree_view_column_set_cell_data_func (col, cell,
1792       (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1793       view, NULL);
1794
1795   g_object_set (cell,
1796       "xpad", 5,
1797       "ypad", 1,
1798       "visible", FALSE,
1799       NULL);
1800
1801   /* Group icon */
1802   cell = gtk_cell_renderer_pixbuf_new ();
1803   gtk_tree_view_column_pack_start (col, cell, FALSE);
1804   gtk_tree_view_column_set_cell_data_func (col, cell,
1805       (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1806       view, NULL);
1807
1808   g_object_set (cell,
1809       "xpad", 0,
1810       "ypad", 0,
1811       "visible", FALSE,
1812       "width", 16,
1813       "height", 16,
1814       NULL);
1815
1816   /* Name */
1817   cell = empathy_cell_renderer_text_new ();
1818   gtk_tree_view_column_pack_start (col, cell, TRUE);
1819   gtk_tree_view_column_set_cell_data_func (col, cell,
1820       (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1821
1822   gtk_tree_view_column_add_attribute (col, cell,
1823       "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1824   gtk_tree_view_column_add_attribute (col, cell,
1825       "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1826   gtk_tree_view_column_add_attribute (col, cell,
1827       "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1828   gtk_tree_view_column_add_attribute (col, cell,
1829       "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1830   gtk_tree_view_column_add_attribute (col, cell,
1831       "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1832   gtk_tree_view_column_add_attribute (col, cell,
1833       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1834   gtk_tree_view_column_add_attribute (col, cell,
1835       "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1836
1837   /* Audio Call Icon */
1838   cell = empathy_cell_renderer_activatable_new ();
1839   gtk_tree_view_column_pack_start (col, cell, FALSE);
1840   gtk_tree_view_column_set_cell_data_func (col, cell,
1841       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1842       view, NULL);
1843
1844   g_object_set (cell, "visible", FALSE, NULL);
1845
1846   g_signal_connect (cell, "path-activated",
1847       G_CALLBACK (individual_view_call_activated_cb), view);
1848
1849   /* Avatar */
1850   cell = gtk_cell_renderer_pixbuf_new ();
1851   gtk_tree_view_column_pack_start (col, cell, FALSE);
1852   gtk_tree_view_column_set_cell_data_func (col, cell,
1853       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1854       view, NULL);
1855
1856   g_object_set (cell,
1857       "xpad", 0,
1858       "ypad", 0,
1859       "visible", FALSE,
1860       "width", 32,
1861       "height", 32,
1862       NULL);
1863
1864   /* Expander */
1865   cell = empathy_cell_renderer_expander_new ();
1866   gtk_tree_view_column_pack_end (col, cell, FALSE);
1867   gtk_tree_view_column_set_cell_data_func (col, cell,
1868       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1869       view, NULL);
1870
1871   /* Actually add the column now we have added all cell renderers */
1872   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1873
1874   /* Drag & Drop. */
1875   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1876     {
1877       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1878     }
1879
1880   for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1881     {
1882       drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1883           FALSE);
1884     }
1885 }
1886
1887 static void
1888 individual_view_set_view_features (EmpathyIndividualView *view,
1889     EmpathyIndividualFeatureFlags features)
1890 {
1891   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1892   gboolean has_tooltip;
1893
1894   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1895
1896   priv->view_features = features;
1897
1898   /* Setting reorderable is a hack that gets us row previews as drag icons
1899      for free.  We override all the drag handlers.  It's tricky to get the
1900      position of the drag icon right in drag_begin.  GtkTreeView has special
1901      voodoo for it, so we let it do the voodoo that he do (but only if dragging
1902      is enabled).
1903    */
1904   gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1905       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1906
1907   /* Update DnD source/dest */
1908   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1909     {
1910       gtk_drag_source_set (GTK_WIDGET (view),
1911           GDK_BUTTON1_MASK,
1912           drag_types_source,
1913           G_N_ELEMENTS (drag_types_source),
1914           GDK_ACTION_MOVE | GDK_ACTION_COPY);
1915     }
1916   else
1917     {
1918       gtk_drag_source_unset (GTK_WIDGET (view));
1919
1920     }
1921
1922   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1923     {
1924       gtk_drag_dest_set (GTK_WIDGET (view),
1925           GTK_DEST_DEFAULT_ALL,
1926           drag_types_dest,
1927           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1928     }
1929   else
1930     {
1931       /* FIXME: URI could still be droped depending on FT feature */
1932       gtk_drag_dest_unset (GTK_WIDGET (view));
1933     }
1934
1935   /* Update has-tooltip */
1936   has_tooltip =
1937       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1938   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1939 }
1940
1941 static void
1942 individual_view_dispose (GObject *object)
1943 {
1944   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1945   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1946
1947   tp_clear_object (&priv->store);
1948   tp_clear_object (&priv->filter);
1949   tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1950
1951   empathy_individual_view_set_live_search (view, NULL);
1952
1953   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1954 }
1955
1956 static void
1957 individual_view_finalize (GObject *object)
1958 {
1959   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1960
1961   if (priv->expand_groups_idle_handler != 0)
1962     g_source_remove (priv->expand_groups_idle_handler);
1963   g_hash_table_destroy (priv->expand_groups);
1964
1965   G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1966 }
1967
1968 static void
1969 individual_view_get_property (GObject *object,
1970     guint param_id,
1971     GValue *value,
1972     GParamSpec *pspec)
1973 {
1974   EmpathyIndividualViewPriv *priv;
1975
1976   priv = GET_PRIV (object);
1977
1978   switch (param_id)
1979     {
1980     case PROP_STORE:
1981       g_value_set_object (value, priv->store);
1982       break;
1983     case PROP_VIEW_FEATURES:
1984       g_value_set_flags (value, priv->view_features);
1985       break;
1986     case PROP_INDIVIDUAL_FEATURES:
1987       g_value_set_flags (value, priv->individual_features);
1988       break;
1989     case PROP_SHOW_OFFLINE:
1990       g_value_set_boolean (value, priv->show_offline);
1991       break;
1992     case PROP_SHOW_UNTRUSTED:
1993       g_value_set_boolean (value, priv->show_untrusted);
1994       break;
1995     default:
1996       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1997       break;
1998     };
1999 }
2000
2001 static void
2002 individual_view_set_property (GObject *object,
2003     guint param_id,
2004     const GValue *value,
2005     GParamSpec *pspec)
2006 {
2007   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2008   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2009
2010   switch (param_id)
2011     {
2012     case PROP_STORE:
2013       empathy_individual_view_set_store (view, g_value_get_object (value));
2014       break;
2015     case PROP_VIEW_FEATURES:
2016       individual_view_set_view_features (view, g_value_get_flags (value));
2017       break;
2018     case PROP_INDIVIDUAL_FEATURES:
2019       priv->individual_features = g_value_get_flags (value);
2020       break;
2021     case PROP_SHOW_OFFLINE:
2022       empathy_individual_view_set_show_offline (view,
2023           g_value_get_boolean (value));
2024       break;
2025     case PROP_SHOW_UNTRUSTED:
2026       empathy_individual_view_set_show_untrusted (view,
2027           g_value_get_boolean (value));
2028       break;
2029     default:
2030       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2031       break;
2032     };
2033 }
2034
2035 static void
2036 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2037 {
2038   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2039   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2040   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2041
2042   object_class->constructed = individual_view_constructed;
2043   object_class->dispose = individual_view_dispose;
2044   object_class->finalize = individual_view_finalize;
2045   object_class->get_property = individual_view_get_property;
2046   object_class->set_property = individual_view_set_property;
2047
2048   widget_class->drag_data_received = individual_view_drag_data_received;
2049   widget_class->drag_drop = individual_view_drag_drop;
2050   widget_class->drag_begin = individual_view_drag_begin;
2051   widget_class->drag_data_get = individual_view_drag_data_get;
2052   widget_class->drag_end = individual_view_drag_end;
2053   widget_class->drag_motion = individual_view_drag_motion;
2054
2055   /* We use the class method to let user of this widget to connect to
2056    * the signal and stop emission of the signal so the default handler
2057    * won't be called. */
2058   tree_view_class->row_activated = individual_view_row_activated;
2059
2060   klass->drag_individual_received = real_drag_individual_received_cb;
2061
2062   signals[DRAG_INDIVIDUAL_RECEIVED] =
2063       g_signal_new ("drag-individual-received",
2064       G_OBJECT_CLASS_TYPE (klass),
2065       G_SIGNAL_RUN_LAST,
2066       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2067       NULL, NULL,
2068       _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2069       G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2070       G_TYPE_STRING, G_TYPE_STRING);
2071
2072   signals[DRAG_PERSONA_RECEIVED] =
2073       g_signal_new ("drag-persona-received",
2074       G_OBJECT_CLASS_TYPE (klass),
2075       G_SIGNAL_RUN_LAST,
2076       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2077       NULL, NULL,
2078       _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2079       G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2080
2081   g_object_class_install_property (object_class,
2082       PROP_STORE,
2083       g_param_spec_object ("store",
2084           "The store of the view",
2085           "The store of the view",
2086           EMPATHY_TYPE_INDIVIDUAL_STORE,
2087           G_PARAM_READWRITE));
2088   g_object_class_install_property (object_class,
2089       PROP_VIEW_FEATURES,
2090       g_param_spec_flags ("view-features",
2091           "Features of the view",
2092           "Flags for all enabled features",
2093           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2094           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2095   g_object_class_install_property (object_class,
2096       PROP_INDIVIDUAL_FEATURES,
2097       g_param_spec_flags ("individual-features",
2098           "Features of the individual menu",
2099           "Flags for all enabled features for the menu",
2100           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2101           EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2102   g_object_class_install_property (object_class,
2103       PROP_SHOW_OFFLINE,
2104       g_param_spec_boolean ("show-offline",
2105           "Show Offline",
2106           "Whether contact list should display "
2107           "offline contacts", FALSE, G_PARAM_READWRITE));
2108   g_object_class_install_property (object_class,
2109       PROP_SHOW_UNTRUSTED,
2110       g_param_spec_boolean ("show-untrusted",
2111           "Show Untrusted Individuals",
2112           "Whether the view should display untrusted individuals; "
2113           "those who could not be who they say they are.",
2114           TRUE, G_PARAM_READWRITE));
2115
2116   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2117 }
2118
2119 static void
2120 empathy_individual_view_init (EmpathyIndividualView *view)
2121 {
2122   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2123       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2124
2125   view->priv = priv;
2126
2127   priv->show_untrusted = TRUE;
2128
2129   /* Get saved group states. */
2130   empathy_contact_groups_get_all ();
2131
2132   priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2133       (GDestroyNotify) g_free, NULL);
2134
2135   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2136       empathy_individual_store_row_separator_func, NULL, NULL);
2137
2138   /* Connect to tree view signals rather than override. */
2139   g_signal_connect (view, "button-press-event",
2140       G_CALLBACK (individual_view_button_press_event_cb), NULL);
2141   g_signal_connect (view, "key-press-event",
2142       G_CALLBACK (individual_view_key_press_event_cb), NULL);
2143   g_signal_connect (view, "row-expanded",
2144       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2145       GINT_TO_POINTER (TRUE));
2146   g_signal_connect (view, "row-collapsed",
2147       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2148       GINT_TO_POINTER (FALSE));
2149   g_signal_connect (view, "query-tooltip",
2150       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2151 }
2152
2153 EmpathyIndividualView *
2154 empathy_individual_view_new (EmpathyIndividualStore *store,
2155     EmpathyIndividualViewFeatureFlags view_features,
2156     EmpathyIndividualFeatureFlags individual_features)
2157 {
2158   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2159
2160   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2161       "store", store,
2162       "individual-features", individual_features,
2163       "view-features", view_features, NULL);
2164 }
2165
2166 FolksIndividual *
2167 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2168 {
2169   EmpathyIndividualViewPriv *priv;
2170   GtkTreeSelection *selection;
2171   GtkTreeIter iter;
2172   GtkTreeModel *model;
2173   FolksIndividual *individual;
2174
2175   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2176
2177   priv = GET_PRIV (view);
2178
2179   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2180   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2181     return NULL;
2182
2183   gtk_tree_model_get (model, &iter,
2184       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2185
2186   return individual;
2187 }
2188
2189 static gchar *
2190 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2191     gboolean *is_fake_group)
2192 {
2193   EmpathyIndividualViewPriv *priv;
2194   GtkTreeSelection *selection;
2195   GtkTreeIter iter;
2196   GtkTreeModel *model;
2197   gboolean is_group;
2198   gchar *name;
2199   gboolean fake;
2200
2201   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2202
2203   priv = GET_PRIV (view);
2204
2205   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2206   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2207     return NULL;
2208
2209   gtk_tree_model_get (model, &iter,
2210       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2211       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2212       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2213
2214   if (!is_group)
2215     {
2216       g_free (name);
2217       return NULL;
2218     }
2219
2220   if (is_fake_group != NULL)
2221     *is_fake_group = fake;
2222
2223   return name;
2224 }
2225
2226 static gboolean
2227 individual_view_remove_dialog_show (GtkWindow *parent,
2228     const gchar *message,
2229     const gchar *secondary_text)
2230 {
2231   GtkWidget *dialog;
2232   gboolean res;
2233
2234   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2235       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2236   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2237       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2238       GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2239   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2240       "%s", secondary_text);
2241
2242   gtk_widget_show (dialog);
2243
2244   res = gtk_dialog_run (GTK_DIALOG (dialog));
2245   gtk_widget_destroy (dialog);
2246
2247   return (res == GTK_RESPONSE_YES);
2248 }
2249
2250 static void
2251 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2252     EmpathyIndividualView *view)
2253 {
2254   gchar *group;
2255
2256   group = empathy_individual_view_dup_selected_group (view, NULL);
2257   if (group != NULL)
2258     {
2259       gchar *text;
2260       GtkWindow *parent;
2261
2262       text =
2263           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2264           group);
2265       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2266       if (individual_view_remove_dialog_show (parent, _("Removing group"),
2267               text))
2268         {
2269           EmpathyIndividualManager *manager =
2270               empathy_individual_manager_dup_singleton ();
2271           empathy_individual_manager_remove_group (manager, group);
2272           g_object_unref (G_OBJECT (manager));
2273         }
2274
2275       g_free (text);
2276     }
2277
2278   g_free (group);
2279 }
2280
2281 GtkWidget *
2282 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2283 {
2284   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2285   gchar *group;
2286   GtkWidget *menu;
2287   GtkWidget *item;
2288   GtkWidget *image;
2289   gboolean is_fake_group;
2290
2291   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2292
2293   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2294               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2295     return NULL;
2296
2297   group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2298   if (!group || is_fake_group)
2299     {
2300       /* We can't alter fake groups */
2301       g_free (group);
2302       return NULL;
2303     }
2304
2305   menu = gtk_menu_new ();
2306
2307   /* TODO: implement
2308      if (priv->view_features &
2309      EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2310      item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2311      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2312      gtk_widget_show (item);
2313      g_signal_connect (item, "activate",
2314      G_CALLBACK (individual_view_group_rename_activate_cb),
2315      view);
2316      }
2317    */
2318
2319   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2320     {
2321       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2322       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2323           GTK_ICON_SIZE_MENU);
2324       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2325       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2326       gtk_widget_show (item);
2327       g_signal_connect (item, "activate",
2328           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2329     }
2330
2331   g_free (group);
2332
2333   return menu;
2334 }
2335
2336 static void
2337 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2338     EmpathyIndividualView *view)
2339 {
2340   FolksIndividual *individual;
2341
2342   individual = empathy_individual_view_dup_selected (view);
2343
2344   if (individual != NULL)
2345     {
2346       gchar *text;
2347       GtkWindow *parent;
2348       GList *l, *personas;
2349       guint persona_count = 0;
2350
2351       personas = folks_individual_get_personas (individual);
2352
2353       /* If we have more than one TpfPersona, display a different message
2354        * ensuring the user knows that *all* of the meta-contacts' personas will
2355        * be removed. */
2356       for (l = personas; l != NULL; l = l->next)
2357         {
2358           if (!TPF_IS_PERSONA (l->data))
2359             continue;
2360
2361           persona_count++;
2362           if (persona_count >= 2)
2363             break;
2364         }
2365
2366       if (persona_count < 2)
2367         {
2368           /* Not a meta-contact */
2369           text =
2370               g_strdup_printf (
2371                   _("Do you really want to remove the contact '%s'?"),
2372                   folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2373         }
2374       else
2375         {
2376           /* Meta-contact */
2377           text =
2378               g_strdup_printf (
2379                   _("Do you really want to remove the linked contact '%s'? "
2380                     "Note that this will remove all the contacts which make up "
2381                     "this linked contact."),
2382                   folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
2383         }
2384
2385       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2386
2387       if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2388               text))
2389         {
2390           EmpathyIndividualManager *manager;
2391
2392           manager = empathy_individual_manager_dup_singleton ();
2393           empathy_individual_manager_remove (manager, individual, "");
2394           g_object_unref (G_OBJECT (manager));
2395         }
2396
2397       g_free (text);
2398       g_object_unref (individual);
2399     }
2400 }
2401
2402 static void
2403 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2404     EmpathyLinkingDialog *linking_dialog,
2405     EmpathyIndividualView *self)
2406 {
2407   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2408   EmpathyIndividualLinker *linker;
2409
2410   linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2411   empathy_individual_linker_set_search_text (linker,
2412       empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2413 }
2414
2415 GtkWidget *
2416 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2417 {
2418   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2419   FolksIndividual *individual;
2420   GtkWidget *menu = NULL;
2421   GtkWidget *item;
2422   GtkWidget *image;
2423   gboolean can_remove = FALSE;
2424   GList *l;
2425
2426   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2427
2428   individual = empathy_individual_view_dup_selected (view);
2429   if (individual == NULL)
2430     return NULL;
2431
2432   /* If any of the Individual's personas can be removed, add an option to
2433    * remove. This will act as a best-effort option. If any Personas cannot be
2434    * removed from the server, then this option will just be inactive upon
2435    * subsequent menu openings */
2436   for (l = folks_individual_get_personas (individual); l != NULL; l = l->next)
2437     {
2438       FolksPersona *persona = FOLKS_PERSONA (l->data);
2439       FolksPersonaStore *store = folks_persona_get_store (persona);
2440       FolksMaybeBool maybe_can_remove =
2441           folks_persona_store_get_can_remove_personas (store);
2442
2443       if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2444         {
2445           can_remove = TRUE;
2446           break;
2447         }
2448     }
2449
2450   menu = empathy_individual_menu_new (individual, priv->individual_features);
2451
2452   /* Remove contact */
2453   if ((priv->view_features &
2454       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2455       can_remove)
2456     {
2457       /* create the menu if required, or just add a separator */
2458       if (menu == NULL)
2459         menu = gtk_menu_new ();
2460       else
2461         {
2462           item = gtk_separator_menu_item_new ();
2463           gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2464           gtk_widget_show (item);
2465         }
2466
2467       /* Remove */
2468       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2469       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2470           GTK_ICON_SIZE_MENU);
2471       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2472       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2473       gtk_widget_show (item);
2474       g_signal_connect (item, "activate",
2475           G_CALLBACK (individual_view_remove_activate_cb), view);
2476     }
2477
2478   /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2479    * set the live search text on the new linking dialogue to be the same as
2480    * our own. */
2481   g_signal_connect (menu, "link-contacts-activated",
2482       (GCallback) individual_menu_link_contacts_activated_cb, view);
2483
2484   g_object_unref (individual);
2485
2486   return menu;
2487 }
2488
2489 void
2490 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2491     EmpathyLiveSearch *search)
2492 {
2493   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2494
2495   /* remove old handlers if old search was not null */
2496   if (priv->search_widget != NULL)
2497     {
2498       g_signal_handlers_disconnect_by_func (view,
2499           individual_view_start_search_cb, NULL);
2500
2501       g_signal_handlers_disconnect_by_func (priv->search_widget,
2502           individual_view_search_text_notify_cb, view);
2503       g_signal_handlers_disconnect_by_func (priv->search_widget,
2504           individual_view_search_activate_cb, view);
2505       g_signal_handlers_disconnect_by_func (priv->search_widget,
2506           individual_view_search_key_navigation_cb, view);
2507       g_signal_handlers_disconnect_by_func (priv->search_widget,
2508           individual_view_search_hide_cb, view);
2509       g_signal_handlers_disconnect_by_func (priv->search_widget,
2510           individual_view_search_show_cb, view);
2511       g_object_unref (priv->search_widget);
2512       priv->search_widget = NULL;
2513     }
2514
2515   /* connect handlers if new search is not null */
2516   if (search != NULL)
2517     {
2518       priv->search_widget = g_object_ref (search);
2519
2520       g_signal_connect (view, "start-interactive-search",
2521           G_CALLBACK (individual_view_start_search_cb), NULL);
2522
2523       g_signal_connect (priv->search_widget, "notify::text",
2524           G_CALLBACK (individual_view_search_text_notify_cb), view);
2525       g_signal_connect (priv->search_widget, "activate",
2526           G_CALLBACK (individual_view_search_activate_cb), view);
2527       g_signal_connect (priv->search_widget, "key-navigation",
2528           G_CALLBACK (individual_view_search_key_navigation_cb), view);
2529       g_signal_connect (priv->search_widget, "hide",
2530           G_CALLBACK (individual_view_search_hide_cb), view);
2531       g_signal_connect (priv->search_widget, "show",
2532           G_CALLBACK (individual_view_search_show_cb), view);
2533     }
2534 }
2535
2536 gboolean
2537 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2538 {
2539   EmpathyIndividualViewPriv *priv;
2540
2541   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2542
2543   priv = GET_PRIV (self);
2544
2545   return (priv->search_widget != NULL &&
2546           gtk_widget_get_visible (priv->search_widget));
2547 }
2548
2549 gboolean
2550 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2551 {
2552   EmpathyIndividualViewPriv *priv;
2553
2554   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2555
2556   priv = GET_PRIV (self);
2557
2558   return priv->show_offline;
2559 }
2560
2561 void
2562 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2563     gboolean show_offline)
2564 {
2565   EmpathyIndividualViewPriv *priv;
2566
2567   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2568
2569   priv = GET_PRIV (self);
2570
2571   priv->show_offline = show_offline;
2572
2573   g_object_notify (G_OBJECT (self), "show-offline");
2574   gtk_tree_model_filter_refilter (priv->filter);
2575 }
2576
2577 gboolean
2578 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2579 {
2580   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2581
2582   return GET_PRIV (self)->show_untrusted;
2583 }
2584
2585 void
2586 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2587     gboolean show_untrusted)
2588 {
2589   EmpathyIndividualViewPriv *priv;
2590
2591   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2592
2593   priv = GET_PRIV (self);
2594
2595   priv->show_untrusted = show_untrusted;
2596
2597   g_object_notify (G_OBJECT (self), "show-untrusted");
2598   gtk_tree_model_filter_refilter (priv->filter);
2599 }
2600
2601 EmpathyIndividualStore *
2602 empathy_individual_view_get_store (EmpathyIndividualView *self)
2603 {
2604   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2605
2606   return GET_PRIV (self)->store;
2607 }
2608
2609 void
2610 empathy_individual_view_set_store (EmpathyIndividualView *self,
2611     EmpathyIndividualStore *store)
2612 {
2613   EmpathyIndividualViewPriv *priv;
2614
2615   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2616   g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2617
2618   priv = GET_PRIV (self);
2619
2620   /* Destroy the old filter and remove the old store */
2621   if (priv->store != NULL)
2622     {
2623       g_signal_handlers_disconnect_by_func (priv->store,
2624           individual_view_store_row_changed_cb, self);
2625       g_signal_handlers_disconnect_by_func (priv->store,
2626           individual_view_store_row_deleted_cb, self);
2627
2628       g_signal_handlers_disconnect_by_func (priv->filter,
2629           individual_view_row_has_child_toggled_cb, self);
2630
2631       gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2632     }
2633
2634   tp_clear_object (&priv->filter);
2635   tp_clear_object (&priv->store);
2636
2637   /* Set the new store */
2638   priv->store = store;
2639
2640   if (store != NULL)
2641     {
2642       g_object_ref (store);
2643
2644       /* Create a new filter */
2645       priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2646           GTK_TREE_MODEL (priv->store), NULL));
2647       gtk_tree_model_filter_set_visible_func (priv->filter,
2648           individual_view_filter_visible_func, self, NULL);
2649
2650       g_signal_connect (priv->filter, "row-has-child-toggled",
2651           G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2652       gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2653           GTK_TREE_MODEL (priv->filter));
2654
2655       tp_g_signal_connect_object (priv->store, "row-changed",
2656           G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2657       tp_g_signal_connect_object (priv->store, "row-inserted",
2658           G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2659       tp_g_signal_connect_object (priv->store, "row-deleted",
2660           G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2661     }
2662 }
2663
2664 void
2665 empathy_individual_view_start_search (EmpathyIndividualView *self)
2666 {
2667   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2668
2669   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2670   g_return_if_fail (priv->search_widget != NULL);
2671
2672   if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2673     gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2674   else
2675     gtk_widget_show (GTK_WIDGET (priv->search_widget));
2676 }