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