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