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