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