]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
Updated Spanish Translation
[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 (NULL, individual);
1075   gtk_menu_shell_append (shell, item);
1076   gtk_widget_show (item);
1077
1078   /* video */
1079   item = empathy_individual_video_call_menu_item_new (NULL, individual);
1080   gtk_menu_shell_append (shell, item);
1081   gtk_widget_show (item);
1082
1083   gtk_widget_show (menu);
1084   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1085       event->button, event->time);
1086
1087   g_object_unref (individual);
1088 }
1089
1090 static void
1091 individual_view_cell_set_background (EmpathyIndividualView *view,
1092     GtkCellRenderer *cell,
1093     gboolean is_group,
1094     gboolean is_active)
1095 {
1096   if (!is_group && is_active)
1097     {
1098       GtkStyleContext *style;
1099       GdkRGBA color;
1100
1101       style = gtk_widget_get_style_context (GTK_WIDGET (view));
1102
1103       gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1104           &color);
1105
1106       /* Here we take the current theme colour and add it to
1107        * the colour for white and average the two. This
1108        * gives a colour which is inline with the theme but
1109        * slightly whiter.
1110        */
1111       empathy_make_color_whiter (&color);
1112
1113       g_object_set (cell, "cell-background-rgba", &color, NULL);
1114     }
1115   else
1116     g_object_set (cell, "cell-background-rgba", NULL, NULL);
1117 }
1118
1119 static void
1120 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1121     GtkCellRenderer *cell,
1122     GtkTreeModel *model,
1123     GtkTreeIter *iter,
1124     EmpathyIndividualView *view)
1125 {
1126   GdkPixbuf *pixbuf;
1127   gboolean is_group;
1128   gboolean is_active;
1129
1130   gtk_tree_model_get (model, iter,
1131       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1132       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1133       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1134
1135   g_object_set (cell,
1136       "visible", !is_group,
1137       "pixbuf", pixbuf,
1138       NULL);
1139
1140   tp_clear_object (&pixbuf);
1141
1142   individual_view_cell_set_background (view, cell, is_group, is_active);
1143 }
1144
1145 static void
1146 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1147     GtkCellRenderer *cell,
1148     GtkTreeModel *model,
1149     GtkTreeIter *iter,
1150     EmpathyIndividualView *view)
1151 {
1152   GdkPixbuf *pixbuf = NULL;
1153   gboolean is_group;
1154   gchar *name;
1155
1156   gtk_tree_model_get (model, iter,
1157       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1158       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1159
1160   if (!is_group)
1161     goto out;
1162
1163   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1164     {
1165       pixbuf = tpaw_pixbuf_from_icon_name ("emblem-favorite",
1166           GTK_ICON_SIZE_MENU);
1167     }
1168   else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1169     {
1170       pixbuf = tpaw_pixbuf_from_icon_name ("im-local-xmpp",
1171           GTK_ICON_SIZE_MENU);
1172     }
1173
1174 out:
1175   g_object_set (cell,
1176       "visible", pixbuf != NULL,
1177       "pixbuf", pixbuf,
1178       NULL);
1179
1180   tp_clear_object (&pixbuf);
1181
1182   g_free (name);
1183 }
1184
1185 static void
1186 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1187     GtkCellRenderer *cell,
1188     GtkTreeModel *model,
1189     GtkTreeIter *iter,
1190     EmpathyIndividualView *view)
1191 {
1192   gboolean is_group;
1193   gboolean is_active;
1194   gboolean can_audio, can_video;
1195
1196   gtk_tree_model_get (model, iter,
1197       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1198       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1199       EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1200       EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1201
1202   g_object_set (cell,
1203       "visible", !is_group && (can_audio || can_video),
1204       "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1205       NULL);
1206
1207   individual_view_cell_set_background (view, cell, is_group, is_active);
1208 }
1209
1210 static void
1211 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1212     GtkCellRenderer *cell,
1213     GtkTreeModel *model,
1214     GtkTreeIter *iter,
1215     EmpathyIndividualView *view)
1216 {
1217   GdkPixbuf *pixbuf;
1218   gboolean show_avatar;
1219   gboolean is_group;
1220   gboolean is_active;
1221
1222   gtk_tree_model_get (model, iter,
1223       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1224       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1225       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1226       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1227
1228   g_object_set (cell,
1229       "visible", !is_group && show_avatar,
1230       "pixbuf", pixbuf,
1231       NULL);
1232
1233   tp_clear_object (&pixbuf);
1234
1235   individual_view_cell_set_background (view, cell, is_group, is_active);
1236 }
1237
1238 static void
1239 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1240     GtkCellRenderer *cell,
1241     GtkTreeModel *model,
1242     GtkTreeIter *iter,
1243     EmpathyIndividualView *view)
1244 {
1245   gboolean is_group;
1246   gboolean is_active;
1247
1248   gtk_tree_model_get (model, iter,
1249       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1250       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1251
1252   individual_view_cell_set_background (view, cell, is_group, is_active);
1253 }
1254
1255 static void
1256 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1257     GtkCellRenderer *cell,
1258     GtkTreeModel *model,
1259     GtkTreeIter *iter,
1260     EmpathyIndividualView *view)
1261 {
1262   gboolean is_group;
1263   gboolean is_active;
1264
1265   gtk_tree_model_get (model, iter,
1266       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1267       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1268
1269   if (gtk_tree_model_iter_has_child (model, iter))
1270     {
1271       GtkTreePath *path;
1272       gboolean row_expanded;
1273
1274       path = gtk_tree_model_get_path (model, iter);
1275       row_expanded =
1276           gtk_tree_view_row_expanded (GTK_TREE_VIEW
1277           (gtk_tree_view_column_get_tree_view (column)), path);
1278       gtk_tree_path_free (path);
1279
1280       g_object_set (cell,
1281           "visible", TRUE,
1282           "expander-style",
1283           row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1284           NULL);
1285     }
1286   else
1287     g_object_set (cell, "visible", FALSE, NULL);
1288
1289   individual_view_cell_set_background (view, cell, is_group, is_active);
1290 }
1291
1292 static void
1293 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1294     GtkTreeIter *iter,
1295     GtkTreePath *path,
1296     gpointer user_data)
1297 {
1298   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1299   GtkTreeModel *model;
1300   gchar *name;
1301   gboolean expanded;
1302
1303   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1304     return;
1305
1306   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1307
1308   gtk_tree_model_get (model, iter,
1309       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1310
1311   expanded = GPOINTER_TO_INT (user_data);
1312   empathy_contact_group_set_expanded (name, expanded);
1313
1314   g_free (name);
1315 }
1316
1317 static gboolean
1318 individual_view_start_search_cb (EmpathyIndividualView *view,
1319     gpointer data)
1320 {
1321   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1322
1323   if (priv->search_widget == NULL)
1324     return FALSE;
1325
1326   empathy_individual_view_start_search (view);
1327
1328   return TRUE;
1329 }
1330
1331 static void
1332 individual_view_search_text_notify_cb (TpawLiveSearch *search,
1333     GParamSpec *pspec,
1334     EmpathyIndividualView *view)
1335 {
1336   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1337   GtkTreePath *path;
1338   GtkTreeViewColumn *focus_column;
1339   GtkTreeModel *model;
1340   GtkTreeIter iter;
1341   gboolean set_cursor = FALSE;
1342
1343   gtk_tree_model_filter_refilter (priv->filter);
1344
1345   /* Set cursor on the first contact. If it is already set on a group,
1346    * set it on its first child contact. Note that first child of a group
1347    * is its separator, that's why we actually set to the 2nd
1348    */
1349
1350   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1351   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1352
1353   if (path == NULL)
1354     {
1355       path = gtk_tree_path_new_from_string ("0:1");
1356       set_cursor = TRUE;
1357     }
1358   else if (gtk_tree_path_get_depth (path) < 2)
1359     {
1360       gboolean is_group;
1361
1362       gtk_tree_model_get_iter (model, &iter, path);
1363       gtk_tree_model_get (model, &iter,
1364           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1365           -1);
1366
1367       if (is_group)
1368         {
1369           gtk_tree_path_down (path);
1370           gtk_tree_path_next (path);
1371           set_cursor = TRUE;
1372         }
1373     }
1374
1375   if (set_cursor)
1376     {
1377       /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1378        * valid. */
1379       if (gtk_tree_model_get_iter (model, &iter, path))
1380         {
1381           gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1382               FALSE);
1383         }
1384     }
1385
1386   gtk_tree_path_free (path);
1387 }
1388
1389 static void
1390 individual_view_search_activate_cb (GtkWidget *search,
1391   EmpathyIndividualView *view)
1392 {
1393   GtkTreePath *path;
1394   GtkTreeViewColumn *focus_column;
1395
1396   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1397   if (path != NULL)
1398     {
1399       gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1400       gtk_tree_path_free (path);
1401
1402       gtk_widget_hide (search);
1403     }
1404 }
1405
1406 static gboolean
1407 individual_view_search_key_navigation_cb (GtkWidget *search,
1408   GdkEvent *event,
1409   EmpathyIndividualView *view)
1410 {
1411   GdkEvent *new_event;
1412   gboolean ret = FALSE;
1413
1414   new_event = gdk_event_copy (event);
1415   gtk_widget_grab_focus (GTK_WIDGET (view));
1416   ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1417   gtk_widget_grab_focus (search);
1418
1419   gdk_event_free (new_event);
1420
1421   return ret;
1422 }
1423
1424 static void
1425 individual_view_search_hide_cb (TpawLiveSearch *search,
1426     EmpathyIndividualView *view)
1427 {
1428   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1429   GtkTreeModel *model;
1430   GtkTreePath *cursor_path;
1431   GtkTreeIter iter;
1432   gboolean valid = FALSE;
1433
1434   /* block expand or collapse handlers, they would write the
1435    * expand or collapsed setting to file otherwise */
1436   g_signal_handlers_block_by_func (view,
1437       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1438   g_signal_handlers_block_by_func (view,
1439     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1440
1441   /* restore which groups are expanded and which are not */
1442   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1443   for (valid = gtk_tree_model_get_iter_first (model, &iter);
1444        valid; valid = gtk_tree_model_iter_next (model, &iter))
1445     {
1446       gboolean is_group;
1447       gchar *name = NULL;
1448       GtkTreePath *path;
1449
1450       gtk_tree_model_get (model, &iter,
1451           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1452           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1453           -1);
1454
1455       if (!is_group)
1456         {
1457           g_free (name);
1458           continue;
1459         }
1460
1461       path = gtk_tree_model_get_path (model, &iter);
1462       if ((priv->view_features &
1463             EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1464           empathy_contact_group_get_expanded (name))
1465         {
1466           gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1467         }
1468       else
1469         {
1470           gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1471         }
1472
1473       gtk_tree_path_free (path);
1474       g_free (name);
1475     }
1476
1477   /* unblock expand or collapse handlers */
1478   g_signal_handlers_unblock_by_func (view,
1479       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1480   g_signal_handlers_unblock_by_func (view,
1481       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1482
1483   /* keep the selected contact visible */
1484   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1485
1486   if (cursor_path != NULL)
1487     gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1488         FALSE, 0, 0);
1489
1490   gtk_tree_path_free (cursor_path);
1491 }
1492
1493 static void
1494 individual_view_search_show_cb (TpawLiveSearch *search,
1495     EmpathyIndividualView *view)
1496 {
1497   /* block expand or collapse handlers during expand all, they would
1498    * write the expand or collapsed setting to file otherwise */
1499   g_signal_handlers_block_by_func (view,
1500       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1501
1502   gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1503
1504   g_signal_handlers_unblock_by_func (view,
1505       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1506 }
1507
1508 static gboolean
1509 expand_idle_foreach_cb (GtkTreeModel *model,
1510     GtkTreePath *path,
1511     GtkTreeIter *iter,
1512     EmpathyIndividualView *self)
1513 {
1514   EmpathyIndividualViewPriv *priv;
1515   gboolean is_group;
1516   gpointer should_expand;
1517   gchar *name;
1518
1519   /* We only want groups */
1520   if (gtk_tree_path_get_depth (path) > 1)
1521     return FALSE;
1522
1523   gtk_tree_model_get (model, iter,
1524       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1525       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1526       -1);
1527
1528   if (!is_group)
1529     {
1530       g_free (name);
1531       return FALSE;
1532     }
1533
1534   priv = GET_PRIV (self);
1535
1536   if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1537       &should_expand))
1538     {
1539       if (GPOINTER_TO_INT (should_expand))
1540         gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1541       else
1542         gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1543
1544       g_hash_table_remove (priv->expand_groups, name);
1545     }
1546
1547   g_free (name);
1548
1549   return FALSE;
1550 }
1551
1552 static gboolean
1553 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1554 {
1555   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1556
1557   g_signal_handlers_block_by_func (self,
1558     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1559   g_signal_handlers_block_by_func (self,
1560     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1561
1562   /* The store/filter could've been removed while we were in the idle queue */
1563   if (priv->filter != NULL)
1564     {
1565       gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1566           (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1567     }
1568
1569   g_signal_handlers_unblock_by_func (self,
1570       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1571   g_signal_handlers_unblock_by_func (self,
1572       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1573
1574   /* Empty the table of groups to expand/contract, since it may contain groups
1575    * which no longer exist in the tree view. This can happen after going
1576    * offline, for example. */
1577   g_hash_table_remove_all (priv->expand_groups);
1578   priv->expand_groups_idle_handler = 0;
1579   g_object_unref (self);
1580
1581   return FALSE;
1582 }
1583
1584 static void
1585 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1586     GtkTreePath *path,
1587     GtkTreeIter *iter,
1588     EmpathyIndividualView *view)
1589 {
1590   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1591   gboolean should_expand, is_group = FALSE;
1592   gchar *name = NULL;
1593   gpointer will_expand;
1594
1595   gtk_tree_model_get (model, iter,
1596       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1597       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1598       -1);
1599
1600   if (!is_group || TPAW_STR_EMPTY (name))
1601     {
1602       g_free (name);
1603       return;
1604     }
1605
1606   should_expand = (priv->view_features &
1607           EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1608       (priv->search_widget != NULL &&
1609           gtk_widget_get_visible (priv->search_widget)) ||
1610       empathy_contact_group_get_expanded (name);
1611
1612   /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1613    * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1614    * a hash table, and expand or contract them as appropriate all at once in
1615    * an idle handler which iterates over all the group rows. */
1616   if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1617       &will_expand) ||
1618       GPOINTER_TO_INT (will_expand) != should_expand)
1619     {
1620       g_hash_table_insert (priv->expand_groups, g_strdup (name),
1621           GINT_TO_POINTER (should_expand));
1622
1623       if (priv->expand_groups_idle_handler == 0)
1624         {
1625           priv->expand_groups_idle_handler =
1626               g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1627                   g_object_ref (view));
1628         }
1629     }
1630
1631   g_free (name);
1632 }
1633
1634 static gboolean
1635 individual_view_is_visible_individual (EmpathyIndividualView *self,
1636     FolksIndividual *individual,
1637     gboolean is_online,
1638     gboolean is_searching,
1639     const gchar *group,
1640     gboolean is_fake_group,
1641     guint event_count)
1642 {
1643   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1644   TpawLiveSearch *live = TPAW_LIVE_SEARCH (priv->search_widget);
1645   GeeSet *personas;
1646   GeeIterator *iter;
1647   gboolean is_favorite;
1648
1649   /* Always display individuals having pending events */
1650   if (event_count > 0)
1651     return TRUE;
1652
1653   /* We're only giving the visibility wrt filtering here, not things like
1654    * presence. */
1655   if (!priv->show_untrusted &&
1656       folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1657     {
1658       return FALSE;
1659     }
1660
1661   if (!priv->show_uninteresting)
1662     {
1663       gboolean contains_interesting_persona = FALSE;
1664
1665       /* Hide all individuals which consist entirely of uninteresting
1666        * personas */
1667       personas = folks_individual_get_personas (individual);
1668       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1669       while (!contains_interesting_persona && gee_iterator_next (iter))
1670         {
1671           FolksPersona *persona = gee_iterator_get (iter);
1672
1673           if (empathy_folks_persona_is_interesting (persona))
1674             contains_interesting_persona = TRUE;
1675
1676           g_clear_object (&persona);
1677         }
1678       g_clear_object (&iter);
1679
1680       if (!contains_interesting_persona)
1681         return FALSE;
1682     }
1683
1684   is_favorite = folks_favourite_details_get_is_favourite (
1685       FOLKS_FAVOURITE_DETAILS (individual));
1686   if (!is_searching) {
1687     if (is_favorite && is_fake_group &&
1688         !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1689         /* Always display favorite contacts in the favorite group */
1690         return TRUE;
1691
1692     return (priv->show_offline || is_online);
1693   }
1694
1695   return empathy_individual_match_string (individual,
1696       tpaw_live_search_get_text (live),
1697       tpaw_live_search_get_words (live));
1698 }
1699
1700 static gchar *
1701 get_group (GtkTreeModel *model,
1702     GtkTreeIter *iter,
1703     gboolean *is_fake)
1704 {
1705   GtkTreeIter parent_iter;
1706   gchar *name = NULL;
1707
1708   *is_fake = FALSE;
1709
1710   if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1711     return NULL;
1712
1713   gtk_tree_model_get (model, &parent_iter,
1714       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1715       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1716       -1);
1717
1718   return name;
1719 }
1720
1721
1722 static gboolean
1723 individual_view_filter_visible_func (GtkTreeModel *model,
1724     GtkTreeIter *iter,
1725     gpointer user_data)
1726 {
1727   EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1728   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1729   FolksIndividual *individual = NULL;
1730   gboolean is_group, is_separator, valid;
1731   GtkTreeIter child_iter;
1732   gboolean visible, is_online;
1733   gboolean is_searching = TRUE;
1734   guint event_count;
1735
1736   if (priv->custom_filter != NULL)
1737     return priv->custom_filter (model, iter, priv->custom_filter_data);
1738
1739   if (priv->search_widget == NULL ||
1740       !gtk_widget_get_visible (priv->search_widget))
1741      is_searching = FALSE;
1742
1743   gtk_tree_model_get (model, iter,
1744       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1745       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1746       EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1747       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1748       EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1749       -1);
1750
1751   if (individual != NULL)
1752     {
1753       gchar *group;
1754       gboolean is_fake_group;
1755
1756       group = get_group (model, iter, &is_fake_group);
1757
1758       visible = individual_view_is_visible_individual (self, individual,
1759           is_online, is_searching, group, is_fake_group, event_count);
1760
1761       g_object_unref (individual);
1762       g_free (group);
1763
1764       return visible;
1765     }
1766
1767   if (is_separator)
1768     return TRUE;
1769
1770   /* Not a contact, not a separator, must be a group */
1771   g_return_val_if_fail (is_group, FALSE);
1772
1773   /* only show groups which are not empty */
1774   for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1775        valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1776     {
1777       gchar *group;
1778       gboolean is_fake_group;
1779
1780       gtk_tree_model_get (model, &child_iter,
1781         EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1782         EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1783         EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1784         -1);
1785
1786       if (individual == NULL)
1787         continue;
1788
1789       group = get_group (model, &child_iter, &is_fake_group);
1790
1791       visible = individual_view_is_visible_individual (self, individual,
1792           is_online, is_searching, group, is_fake_group, event_count);
1793
1794       g_object_unref (individual);
1795       g_free (group);
1796
1797       /* show group if it has at least one visible contact in it */
1798       if (visible)
1799         return TRUE;
1800     }
1801
1802   return FALSE;
1803 }
1804
1805 static gchar * empathy_individual_view_dup_selected_group (
1806     EmpathyIndividualView *view,
1807     gboolean *is_fake_group);
1808
1809 static void
1810 text_edited_cb (GtkCellRendererText *cellrenderertext,
1811     gchar *path,
1812     gchar *name,
1813     EmpathyIndividualView *self)
1814 {
1815   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1816   gchar *old_name, *new_name;
1817
1818   g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1819
1820   new_name = g_strdup (name);
1821   g_strstrip (new_name);
1822
1823   if (tp_str_empty (new_name))
1824     goto out;
1825
1826   old_name = empathy_individual_view_dup_selected_group (self, NULL);
1827   g_return_if_fail (old_name != NULL);
1828
1829   if (tp_strdiff (old_name, new_name))
1830     {
1831       EmpathyConnectionAggregator *aggregator;
1832
1833       DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1834
1835       aggregator = empathy_connection_aggregator_dup_singleton ();
1836
1837       empathy_connection_aggregator_rename_group (aggregator, old_name,
1838           new_name);
1839       g_object_unref (aggregator);
1840     }
1841
1842   g_free (old_name);
1843 out:
1844   g_free (new_name);
1845 }
1846
1847 static void
1848 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1849     EmpathyIndividualView *self)
1850 {
1851   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1852
1853   g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1854 }
1855
1856 static void
1857 individual_view_constructed (GObject *object)
1858 {
1859   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1860   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1861   GtkCellRenderer *cell;
1862   GtkTreeViewColumn *col;
1863   guint i;
1864
1865   /* Setup view */
1866   g_object_set (view,
1867       "headers-visible", FALSE,
1868       "show-expanders", FALSE,
1869       NULL);
1870
1871   col = gtk_tree_view_column_new ();
1872
1873   /* State */
1874   cell = gtk_cell_renderer_pixbuf_new ();
1875   gtk_tree_view_column_pack_start (col, cell, FALSE);
1876   gtk_tree_view_column_set_cell_data_func (col, cell,
1877       (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1878       view, NULL);
1879
1880   g_object_set (cell,
1881       "xpad", 5,
1882       "ypad", 1,
1883       "visible", FALSE,
1884       NULL);
1885
1886   /* Group icon */
1887   cell = gtk_cell_renderer_pixbuf_new ();
1888   gtk_tree_view_column_pack_start (col, cell, FALSE);
1889   gtk_tree_view_column_set_cell_data_func (col, cell,
1890       (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1891       view, NULL);
1892
1893   g_object_set (cell,
1894       "xpad", 0,
1895       "ypad", 0,
1896       "visible", FALSE,
1897       "width", 16,
1898       "height", 16,
1899       NULL);
1900
1901   /* Name */
1902   priv->text_renderer = empathy_cell_renderer_text_new ();
1903   gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1904   gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1905       (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1906
1907   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1908       "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1909   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1910       "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1911   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1912       "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1913   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1914       "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1915   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1916       "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1917   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1918       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1919   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1920       "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1921
1922   g_signal_connect (priv->text_renderer, "editing-canceled",
1923       G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1924   g_signal_connect (priv->text_renderer, "edited",
1925       G_CALLBACK (text_edited_cb), view);
1926
1927   /* Audio Call Icon */
1928   cell = empathy_cell_renderer_activatable_new ();
1929   gtk_tree_view_column_pack_start (col, cell, FALSE);
1930   gtk_tree_view_column_set_cell_data_func (col, cell,
1931       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1932       view, NULL);
1933
1934   g_object_set (cell, "visible", FALSE, NULL);
1935
1936   g_signal_connect (cell, "path-activated",
1937       G_CALLBACK (individual_view_call_activated_cb), view);
1938
1939   /* Avatar */
1940   cell = gtk_cell_renderer_pixbuf_new ();
1941   gtk_tree_view_column_pack_start (col, cell, FALSE);
1942   gtk_tree_view_column_set_cell_data_func (col, cell,
1943       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1944       view, NULL);
1945
1946   g_object_set (cell,
1947       "xpad", 0,
1948       "ypad", 0,
1949       "visible", FALSE,
1950       "width", 32,
1951       "height", 32,
1952       NULL);
1953
1954   /* Expander */
1955   cell = empathy_cell_renderer_expander_new ();
1956   gtk_tree_view_column_pack_end (col, cell, FALSE);
1957   gtk_tree_view_column_set_cell_data_func (col, cell,
1958       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1959       view, NULL);
1960
1961   /* Actually add the column now we have added all cell renderers */
1962   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1963
1964   /* Drag & Drop. */
1965   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1966     {
1967       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1968     }
1969 }
1970
1971 static void
1972 individual_view_set_view_features (EmpathyIndividualView *view,
1973     EmpathyIndividualViewFeatureFlags features)
1974 {
1975   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1976   gboolean has_tooltip;
1977
1978   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1979
1980   priv->view_features = features;
1981
1982   /* Setting reorderable is a hack that gets us row previews as drag icons
1983      for free.  We override all the drag handlers.  It's tricky to get the
1984      position of the drag icon right in drag_begin.  GtkTreeView has special
1985      voodoo for it, so we let it do the voodoo that he do (but only if dragging
1986      is enabled).
1987    */
1988   gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1989       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1990
1991   /* Update DnD source/dest */
1992   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1993     {
1994       gtk_drag_source_set (GTK_WIDGET (view),
1995           GDK_BUTTON1_MASK,
1996           drag_types_source,
1997           G_N_ELEMENTS (drag_types_source),
1998           GDK_ACTION_MOVE | GDK_ACTION_COPY);
1999     }
2000   else
2001     {
2002       gtk_drag_source_unset (GTK_WIDGET (view));
2003
2004     }
2005
2006   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2007     {
2008       gtk_drag_dest_set (GTK_WIDGET (view),
2009           GTK_DEST_DEFAULT_ALL,
2010           drag_types_dest,
2011           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2012     }
2013   else
2014     {
2015       /* FIXME: URI could still be droped depending on FT feature */
2016       gtk_drag_dest_unset (GTK_WIDGET (view));
2017     }
2018
2019   /* Update has-tooltip */
2020   has_tooltip =
2021       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2022   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2023 }
2024
2025 static void
2026 individual_view_dispose (GObject *object)
2027 {
2028   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2029   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2030
2031   tp_clear_object (&priv->store);
2032   tp_clear_object (&priv->filter);
2033   tp_clear_object (&priv->tooltip_widget);
2034
2035   empathy_individual_view_set_live_search (view, NULL);
2036
2037   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2038 }
2039
2040 static void
2041 individual_view_finalize (GObject *object)
2042 {
2043   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2044
2045   if (priv->expand_groups_idle_handler != 0)
2046     g_source_remove (priv->expand_groups_idle_handler);
2047   g_hash_table_unref (priv->expand_groups);
2048
2049   G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2050 }
2051
2052 static void
2053 individual_view_get_property (GObject *object,
2054     guint param_id,
2055     GValue *value,
2056     GParamSpec *pspec)
2057 {
2058   EmpathyIndividualViewPriv *priv;
2059
2060   priv = GET_PRIV (object);
2061
2062   switch (param_id)
2063     {
2064     case PROP_STORE:
2065       g_value_set_object (value, priv->store);
2066       break;
2067     case PROP_VIEW_FEATURES:
2068       g_value_set_flags (value, priv->view_features);
2069       break;
2070     case PROP_INDIVIDUAL_FEATURES:
2071       g_value_set_flags (value, priv->individual_features);
2072       break;
2073     case PROP_SHOW_OFFLINE:
2074       g_value_set_boolean (value, priv->show_offline);
2075       break;
2076     case PROP_SHOW_UNTRUSTED:
2077       g_value_set_boolean (value, priv->show_untrusted);
2078       break;
2079     case PROP_SHOW_UNINTERESTING:
2080       g_value_set_boolean (value, priv->show_uninteresting);
2081       break;
2082     default:
2083       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2084       break;
2085     };
2086 }
2087
2088 static void
2089 individual_view_set_property (GObject *object,
2090     guint param_id,
2091     const GValue *value,
2092     GParamSpec *pspec)
2093 {
2094   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2095   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2096
2097   switch (param_id)
2098     {
2099     case PROP_STORE:
2100       empathy_individual_view_set_store (view, g_value_get_object (value));
2101       break;
2102     case PROP_VIEW_FEATURES:
2103       individual_view_set_view_features (view, g_value_get_flags (value));
2104       break;
2105     case PROP_INDIVIDUAL_FEATURES:
2106       priv->individual_features = g_value_get_flags (value);
2107       break;
2108     case PROP_SHOW_OFFLINE:
2109       empathy_individual_view_set_show_offline (view,
2110           g_value_get_boolean (value));
2111       break;
2112     case PROP_SHOW_UNTRUSTED:
2113       empathy_individual_view_set_show_untrusted (view,
2114           g_value_get_boolean (value));
2115       break;
2116     case PROP_SHOW_UNINTERESTING:
2117       empathy_individual_view_set_show_uninteresting (view,
2118           g_value_get_boolean (value));
2119     default:
2120       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2121       break;
2122     };
2123 }
2124
2125 static void
2126 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2127 {
2128   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2129   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2130   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2131
2132   object_class->constructed = individual_view_constructed;
2133   object_class->dispose = individual_view_dispose;
2134   object_class->finalize = individual_view_finalize;
2135   object_class->get_property = individual_view_get_property;
2136   object_class->set_property = individual_view_set_property;
2137
2138   widget_class->drag_data_received = individual_view_drag_data_received;
2139   widget_class->drag_drop = individual_view_drag_drop;
2140   widget_class->drag_begin = individual_view_drag_begin;
2141   widget_class->drag_data_get = individual_view_drag_data_get;
2142   widget_class->drag_end = individual_view_drag_end;
2143   widget_class->drag_motion = individual_view_drag_motion;
2144
2145   /* We use the class method to let user of this widget to connect to
2146    * the signal and stop emission of the signal so the default handler
2147    * won't be called. */
2148   tree_view_class->row_activated = individual_view_row_activated;
2149
2150   klass->drag_individual_received = real_drag_individual_received_cb;
2151
2152   signals[DRAG_INDIVIDUAL_RECEIVED] =
2153       g_signal_new ("drag-individual-received",
2154       G_OBJECT_CLASS_TYPE (klass),
2155       G_SIGNAL_RUN_LAST,
2156       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2157       NULL, NULL,
2158       g_cclosure_marshal_generic,
2159       G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2160       G_TYPE_STRING, G_TYPE_STRING);
2161
2162   signals[DRAG_PERSONA_RECEIVED] =
2163       g_signal_new ("drag-persona-received",
2164       G_OBJECT_CLASS_TYPE (klass),
2165       G_SIGNAL_RUN_LAST,
2166       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2167       NULL, NULL,
2168       g_cclosure_marshal_generic,
2169       G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2170
2171   g_object_class_install_property (object_class,
2172       PROP_STORE,
2173       g_param_spec_object ("store",
2174           "The store of the view",
2175           "The store of the view",
2176           EMPATHY_TYPE_INDIVIDUAL_STORE,
2177           G_PARAM_READWRITE));
2178   g_object_class_install_property (object_class,
2179       PROP_VIEW_FEATURES,
2180       g_param_spec_flags ("view-features",
2181           "Features of the view",
2182           "Flags for all enabled features",
2183           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2184           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2185   g_object_class_install_property (object_class,
2186       PROP_INDIVIDUAL_FEATURES,
2187       g_param_spec_flags ("individual-features",
2188           "Features of the individual menu",
2189           "Flags for all enabled features for the menu",
2190           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2191           EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2192   g_object_class_install_property (object_class,
2193       PROP_SHOW_OFFLINE,
2194       g_param_spec_boolean ("show-offline",
2195           "Show Offline",
2196           "Whether contact list should display "
2197           "offline contacts", FALSE, G_PARAM_READWRITE));
2198   g_object_class_install_property (object_class,
2199       PROP_SHOW_UNTRUSTED,
2200       g_param_spec_boolean ("show-untrusted",
2201           "Show Untrusted Individuals",
2202           "Whether the view should display untrusted individuals; "
2203           "those who could not be who they say they are.",
2204           TRUE, G_PARAM_READWRITE));
2205   g_object_class_install_property (object_class,
2206       PROP_SHOW_UNINTERESTING,
2207       g_param_spec_boolean ("show-uninteresting",
2208           "Show Uninteresting Individuals",
2209           "Whether the view should not filter out individuals using "
2210           "empathy_folks_persona_is_interesting.",
2211           FALSE, G_PARAM_READWRITE));
2212
2213   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2214 }
2215
2216 static void
2217 empathy_individual_view_init (EmpathyIndividualView *view)
2218 {
2219   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2220       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2221
2222   view->priv = priv;
2223
2224   priv->show_untrusted = TRUE;
2225   priv->show_uninteresting = FALSE;
2226
2227   /* Get saved group states. */
2228   empathy_contact_groups_get_all ();
2229
2230   priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2231       (GDestroyNotify) g_free, NULL);
2232
2233   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2234       empathy_individual_store_row_separator_func, NULL, NULL);
2235
2236   /* Connect to tree view signals rather than override. */
2237   g_signal_connect (view, "button-press-event",
2238       G_CALLBACK (individual_view_button_press_event_cb), NULL);
2239   g_signal_connect (view, "key-press-event",
2240       G_CALLBACK (individual_view_key_press_event_cb), NULL);
2241   g_signal_connect (view, "row-expanded",
2242       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2243       GINT_TO_POINTER (TRUE));
2244   g_signal_connect (view, "row-collapsed",
2245       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2246       GINT_TO_POINTER (FALSE));
2247   g_signal_connect (view, "query-tooltip",
2248       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2249 }
2250
2251 EmpathyIndividualView *
2252 empathy_individual_view_new (EmpathyIndividualStore *store,
2253     EmpathyIndividualViewFeatureFlags view_features,
2254     EmpathyIndividualFeatureFlags individual_features)
2255 {
2256   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2257
2258   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2259       "store", store,
2260       "individual-features", individual_features,
2261       "view-features", view_features, NULL);
2262 }
2263
2264 FolksIndividual *
2265 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2266 {
2267   GtkTreeSelection *selection;
2268   GtkTreeIter iter;
2269   GtkTreeModel *model;
2270   FolksIndividual *individual;
2271
2272   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2273
2274   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2275   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2276     return NULL;
2277
2278   gtk_tree_model_get (model, &iter,
2279       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2280
2281   return individual;
2282 }
2283
2284 static gchar *
2285 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2286     gboolean *is_fake_group)
2287 {
2288   GtkTreeSelection *selection;
2289   GtkTreeIter iter;
2290   GtkTreeModel *model;
2291   gboolean is_group;
2292   gchar *name;
2293   gboolean fake;
2294
2295   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2296
2297   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2298   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2299     return NULL;
2300
2301   gtk_tree_model_get (model, &iter,
2302       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2303       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2304       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2305
2306   if (!is_group)
2307     {
2308       g_free (name);
2309       return NULL;
2310     }
2311
2312   if (is_fake_group != NULL)
2313     *is_fake_group = fake;
2314
2315   return name;
2316 }
2317
2318 enum
2319 {
2320   REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2321   REMOVE_DIALOG_RESPONSE_DELETE,
2322 };
2323
2324 static int
2325 individual_view_remove_dialog_show (GtkWindow *parent,
2326     const gchar *message,
2327     const gchar *secondary_text)
2328 {
2329   GtkWidget *dialog;
2330   gboolean res;
2331
2332   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2333       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2334
2335   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2336       GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2337       GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2338   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2339       "%s", secondary_text);
2340
2341   gtk_widget_show (dialog);
2342
2343   res = gtk_dialog_run (GTK_DIALOG (dialog));
2344   gtk_widget_destroy (dialog);
2345
2346   return res;
2347 }
2348
2349 static void
2350 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2351     EmpathyIndividualView *view)
2352 {
2353   gchar *group;
2354
2355   group = empathy_individual_view_dup_selected_group (view, NULL);
2356   if (group != NULL)
2357     {
2358       gchar *text;
2359       GtkWindow *parent;
2360
2361       text =
2362           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2363           group);
2364       parent = tpaw_get_toplevel_window (GTK_WIDGET (view));
2365       if (individual_view_remove_dialog_show (parent, _("Removing group"),
2366               text) == REMOVE_DIALOG_RESPONSE_DELETE)
2367         {
2368           EmpathyIndividualManager *manager =
2369               empathy_individual_manager_dup_singleton ();
2370           empathy_individual_manager_remove_group (manager, group);
2371           g_object_unref (G_OBJECT (manager));
2372         }
2373
2374       g_free (text);
2375     }
2376
2377   g_free (group);
2378 }
2379
2380 static void
2381 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2382     EmpathyIndividualView *self)
2383 {
2384   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2385   GtkTreePath *path;
2386   GtkTreeIter iter;
2387   GtkTreeSelection *selection;
2388   GtkTreeModel *model;
2389
2390   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2391   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2392     return;
2393   path = gtk_tree_model_get_path (model, &iter);
2394
2395   g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2396
2397   gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2398   gtk_widget_grab_focus (GTK_WIDGET (self));
2399   gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2400       gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2401
2402   gtk_tree_path_free (path);
2403 }
2404
2405 GtkWidget *
2406 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2407 {
2408   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2409   gchar *group;
2410   GtkWidget *menu;
2411   GtkWidget *item;
2412   GtkWidget *image;
2413   gboolean is_fake_group;
2414
2415   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2416
2417   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2418               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2419     return NULL;
2420
2421   group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2422   if (!group || is_fake_group)
2423     {
2424       /* We can't alter fake groups */
2425       g_free (group);
2426       return NULL;
2427     }
2428
2429   menu = gtk_menu_new ();
2430
2431   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2432     {
2433        item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2434        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2435        gtk_widget_show (item);
2436        g_signal_connect (item, "activate",
2437            G_CALLBACK (individual_view_group_rename_activate_cb), view);
2438      }
2439
2440   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2441     {
2442       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2443       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2444           GTK_ICON_SIZE_MENU);
2445       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2446       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2447       gtk_widget_show (item);
2448       g_signal_connect (item, "activate",
2449           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2450     }
2451
2452   g_free (group);
2453
2454   return menu;
2455 }
2456
2457 GtkWidget *
2458 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2459 {
2460   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2461   FolksIndividual *individual;
2462   GtkWidget *menu = NULL;
2463
2464   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2465
2466   if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2467     /* No need to create a context menu */
2468     return NULL;
2469
2470   individual = empathy_individual_view_dup_selected (view);
2471   if (individual == NULL)
2472     return NULL;
2473
2474   if (!empathy_folks_individual_contains_contact (individual))
2475     goto out;
2476
2477   menu = empathy_individual_menu_new (individual, NULL,
2478       priv->individual_features, priv->store);
2479
2480 out:
2481   g_object_unref (individual);
2482
2483   return menu;
2484 }
2485
2486 void
2487 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2488     TpawLiveSearch *search)
2489 {
2490   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2491
2492   /* remove old handlers if old search was not null */
2493   if (priv->search_widget != NULL)
2494     {
2495       g_signal_handlers_disconnect_by_func (view,
2496           individual_view_start_search_cb, NULL);
2497
2498       g_signal_handlers_disconnect_by_func (priv->search_widget,
2499           individual_view_search_text_notify_cb, view);
2500       g_signal_handlers_disconnect_by_func (priv->search_widget,
2501           individual_view_search_activate_cb, view);
2502       g_signal_handlers_disconnect_by_func (priv->search_widget,
2503           individual_view_search_key_navigation_cb, view);
2504       g_signal_handlers_disconnect_by_func (priv->search_widget,
2505           individual_view_search_hide_cb, view);
2506       g_signal_handlers_disconnect_by_func (priv->search_widget,
2507           individual_view_search_show_cb, view);
2508       g_object_unref (priv->search_widget);
2509       priv->search_widget = NULL;
2510     }
2511
2512   /* connect handlers if new search is not null */
2513   if (search != NULL)
2514     {
2515       priv->search_widget = g_object_ref (search);
2516
2517       g_signal_connect (view, "start-interactive-search",
2518           G_CALLBACK (individual_view_start_search_cb), NULL);
2519
2520       g_signal_connect (priv->search_widget, "notify::text",
2521           G_CALLBACK (individual_view_search_text_notify_cb), view);
2522       g_signal_connect (priv->search_widget, "activate",
2523           G_CALLBACK (individual_view_search_activate_cb), view);
2524       g_signal_connect (priv->search_widget, "key-navigation",
2525           G_CALLBACK (individual_view_search_key_navigation_cb), view);
2526       g_signal_connect (priv->search_widget, "hide",
2527           G_CALLBACK (individual_view_search_hide_cb), view);
2528       g_signal_connect (priv->search_widget, "show",
2529           G_CALLBACK (individual_view_search_show_cb), view);
2530     }
2531 }
2532
2533 gboolean
2534 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2535 {
2536   EmpathyIndividualViewPriv *priv;
2537
2538   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2539
2540   priv = GET_PRIV (self);
2541
2542   return (priv->search_widget != NULL &&
2543           gtk_widget_get_visible (priv->search_widget));
2544 }
2545
2546 gboolean
2547 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2548 {
2549   EmpathyIndividualViewPriv *priv;
2550
2551   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2552
2553   priv = GET_PRIV (self);
2554
2555   return priv->show_offline;
2556 }
2557
2558 void
2559 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2560     gboolean show_offline)
2561 {
2562   EmpathyIndividualViewPriv *priv;
2563
2564   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2565
2566   priv = GET_PRIV (self);
2567
2568   priv->show_offline = show_offline;
2569
2570   g_object_notify (G_OBJECT (self), "show-offline");
2571   gtk_tree_model_filter_refilter (priv->filter);
2572 }
2573
2574 gboolean
2575 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2576 {
2577   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2578
2579   return GET_PRIV (self)->show_untrusted;
2580 }
2581
2582 void
2583 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2584     gboolean show_untrusted)
2585 {
2586   EmpathyIndividualViewPriv *priv;
2587
2588   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2589
2590   priv = GET_PRIV (self);
2591
2592   priv->show_untrusted = show_untrusted;
2593
2594   g_object_notify (G_OBJECT (self), "show-untrusted");
2595   gtk_tree_model_filter_refilter (priv->filter);
2596 }
2597
2598 EmpathyIndividualStore *
2599 empathy_individual_view_get_store (EmpathyIndividualView *self)
2600 {
2601   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2602
2603   return GET_PRIV (self)->store;
2604 }
2605
2606 void
2607 empathy_individual_view_set_store (EmpathyIndividualView *self,
2608     EmpathyIndividualStore *store)
2609 {
2610   EmpathyIndividualViewPriv *priv;
2611
2612   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2613   g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2614
2615   priv = GET_PRIV (self);
2616
2617   /* Destroy the old filter and remove the old store */
2618   if (priv->store != NULL)
2619     {
2620       g_signal_handlers_disconnect_by_func (priv->filter,
2621           individual_view_row_has_child_toggled_cb, self);
2622
2623       gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2624     }
2625
2626   tp_clear_object (&priv->filter);
2627   tp_clear_object (&priv->store);
2628
2629   /* Set the new store */
2630   priv->store = store;
2631
2632   if (store != NULL)
2633     {
2634       g_object_ref (store);
2635
2636       /* Create a new filter */
2637       priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2638           GTK_TREE_MODEL (priv->store), NULL));
2639       gtk_tree_model_filter_set_visible_func (priv->filter,
2640           individual_view_filter_visible_func, self, NULL);
2641
2642       g_signal_connect (priv->filter, "row-has-child-toggled",
2643           G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2644       gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2645           GTK_TREE_MODEL (priv->filter));
2646     }
2647 }
2648
2649 void
2650 empathy_individual_view_start_search (EmpathyIndividualView *self)
2651 {
2652   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2653
2654   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2655   g_return_if_fail (priv->search_widget != NULL);
2656
2657   if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2658     gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2659   else
2660     gtk_widget_show (GTK_WIDGET (priv->search_widget));
2661 }
2662
2663 void
2664 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2665     GtkTreeModelFilterVisibleFunc filter,
2666     gpointer data)
2667 {
2668   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2669
2670   priv->custom_filter = filter;
2671   priv->custom_filter_data = data;
2672 }
2673
2674 void
2675 empathy_individual_view_refilter (EmpathyIndividualView *self)
2676 {
2677   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2678
2679   gtk_tree_model_filter_refilter (priv->filter);
2680 }
2681
2682 void
2683 empathy_individual_view_select_first (EmpathyIndividualView *self)
2684 {
2685   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2686   GtkTreeIter iter;
2687
2688   gtk_tree_model_filter_refilter (priv->filter);
2689
2690   if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2691     {
2692       GtkTreeSelection *selection = gtk_tree_view_get_selection (
2693           GTK_TREE_VIEW (self));
2694
2695       gtk_tree_selection_select_iter (selection, &iter);
2696     }
2697 }
2698
2699 void
2700 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2701     gboolean show_uninteresting)
2702 {
2703   EmpathyIndividualViewPriv *priv;
2704
2705   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2706
2707   priv = GET_PRIV (self);
2708
2709   priv->show_uninteresting = show_uninteresting;
2710
2711   g_object_notify (G_OBJECT (self), "show-uninteresting");
2712   gtk_tree_model_filter_refilter (priv->filter);
2713 }