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