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