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