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