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