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