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