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