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