]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
5c8e96b80f2369140e730085a9dfb8bf2f70e881
[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 <folks/folks.h>
36 #include <telepathy-glib/account-manager.h>
37 #include <telepathy-glib/util.h>
38
39 #include <libempathy/empathy-call-factory.h>
40 #include <libempathy/empathy-individual-manager.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
44
45 #include "empathy-individual-view.h"
46 #include "empathy-individual-menu.h"
47 #include "empathy-individual-store.h"
48 #include "empathy-images.h"
49 #include "empathy-cell-renderer-expander.h"
50 #include "empathy-cell-renderer-text.h"
51 #include "empathy-cell-renderer-activatable.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-gtk-enum-types.h"
54 #include "empathy-gtk-marshal.h"
55
56 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
57 #include <libempathy/empathy-debug.h>
58
59 /* Active users are those which have recently changed state
60  * (e.g. online, offline or from normal to a busy state).
61  */
62
63 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
64 typedef struct
65 {
66   EmpathyIndividualStore *store;
67   GtkTreeRowReference *drag_row;
68   EmpathyIndividualViewFeatureFlags view_features;
69   EmpathyContactFeatureFlags individual_features;
70   GtkWidget *tooltip_widget;
71   GtkTargetList *file_targets;
72
73   GtkTreeModelFilter *filter;
74   GtkWidget *search_widget;
75 } EmpathyIndividualViewPriv;
76
77 typedef struct
78 {
79   EmpathyIndividualView *view;
80   GtkTreePath *path;
81   guint timeout_id;
82 } DragMotionData;
83
84 typedef struct
85 {
86   EmpathyIndividualView *view;
87   FolksIndividual *individual;
88   gboolean remove;
89 } ShowActiveData;
90
91 enum
92 {
93   PROP_0,
94   PROP_STORE,
95   PROP_VIEW_FEATURES,
96   PROP_INDIVIDUAL_FEATURES,
97 };
98
99 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
100  * specific EmpathyContacts (between/in/out of Individuals) */
101 enum DndDragType
102 {
103   DND_DRAG_TYPE_INDIVIDUAL_ID,
104   DND_DRAG_TYPE_URI_LIST,
105   DND_DRAG_TYPE_STRING,
106 };
107
108 static const GtkTargetEntry drag_types_dest[] = {
109   {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
110   {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
111   {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
112   {"text/plain", 0, DND_DRAG_TYPE_STRING},
113   {"STRING", 0, DND_DRAG_TYPE_STRING},
114 };
115
116 static const GtkTargetEntry drag_types_dest_file[] = {
117   {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
118   {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
119 };
120
121 static const GtkTargetEntry drag_types_source[] = {
122   {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
123 };
124
125 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
126 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
127
128 enum
129 {
130   DRAG_CONTACT_RECEIVED,
131   LAST_SIGNAL
132 };
133
134 static guint signals[LAST_SIGNAL];
135
136 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
137     GTK_TYPE_TREE_VIEW);
138
139 static gboolean
140 individual_view_is_visible_individual (EmpathyIndividualView *self,
141     FolksIndividual *individual)
142 {
143   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
144   EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
145   const gchar *str;
146   const gchar *p;
147   gchar *dup_str = NULL;
148   gboolean visible;
149   GList *personas, *l;
150
151   g_assert (live != NULL);
152
153   /* check alias name */
154   str = folks_individual_get_alias (individual);
155   if (empathy_live_search_match (live, str))
156     return TRUE;
157
158   /* check contact id, remove the @server.com part */
159   personas = folks_individual_get_personas (individual);
160   for (l = personas; l; l = l->next)
161     {
162       str = folks_persona_get_uid (l->data);
163       p = strstr (str, "@");
164       if (p != NULL)
165         str = dup_str = g_strndup (str, p - str);
166
167       visible = empathy_live_search_match (live, str);
168       g_free (dup_str);
169       if (visible)
170         return TRUE;
171     }
172
173   /* FIXME: Add more rules here, we could check phone numbers in
174    * contact's vCard for example. */
175
176   return FALSE;
177 }
178
179 static gboolean
180 individual_view_filter_visible_func (GtkTreeModel *model,
181     GtkTreeIter *iter,
182     gpointer user_data)
183 {
184   EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
185   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
186   FolksIndividual *individual = NULL;
187   gboolean is_group, is_separator, valid;
188   GtkTreeIter child_iter;
189   gboolean visible;
190
191   if (priv->search_widget == NULL ||
192       !gtk_widget_get_visible (priv->search_widget))
193     return TRUE;
194
195   gtk_tree_model_get (model, iter,
196       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
197       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
198       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
199       -1);
200
201   if (individual != NULL)
202     {
203       visible = individual_view_is_visible_individual (self, individual);
204       g_object_unref (individual);
205       return visible;
206     }
207
208   if (is_separator)
209     return TRUE;
210
211   /* Not a contact, not a separator, must be a group */
212   g_return_val_if_fail (is_group, FALSE);
213
214   /* only show groups which are not empty */
215   for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
216        valid; valid = gtk_tree_model_iter_next (model, &child_iter))
217     {
218       gtk_tree_model_get (model, &child_iter,
219         EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
220         -1);
221
222       if (individual == NULL)
223         continue;
224
225       visible = individual_view_is_visible_individual (self, individual);
226       g_object_unref (individual);
227
228       /* show group if it has at least one visible contact in it */
229       if (visible)
230         return TRUE;
231     }
232
233   return FALSE;
234 }
235
236 static void
237 individual_view_tooltip_destroy_cb (GtkWidget *widget,
238     EmpathyIndividualView *view)
239 {
240   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
241
242   if (priv->tooltip_widget != NULL)
243     {
244       DEBUG ("Tooltip destroyed");
245       g_object_unref (priv->tooltip_widget);
246       priv->tooltip_widget = NULL;
247     }
248 }
249
250 static gboolean
251 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
252     gint x,
253     gint y,
254     gboolean keyboard_mode,
255     GtkTooltip *tooltip,
256     gpointer user_data)
257 {
258   EmpathyIndividualViewPriv *priv;
259   FolksIndividual *individual;
260   GtkTreeModel *model;
261   GtkTreeIter iter;
262   GtkTreePath *path;
263   static gint running = 0;
264   gboolean ret = FALSE;
265   EmpathyContact *contact;
266
267   priv = GET_PRIV (view);
268
269   /* Avoid an infinite loop. See GNOME bug #574377 */
270   if (running > 0)
271     {
272       return FALSE;
273     }
274   running++;
275
276   /* Don't show the tooltip if there's already a popup menu */
277   if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
278     {
279       goto OUT;
280     }
281
282   if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
283           keyboard_mode, &model, &path, &iter))
284     {
285       goto OUT;
286     }
287
288   gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
289   gtk_tree_path_free (path);
290
291   gtk_tree_model_get (model, &iter,
292       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
293       -1);
294   if (individual == NULL)
295     {
296       goto OUT;
297     }
298   else
299     {
300       contact = empathy_contact_from_folks_individual (individual);
301       if (contact == NULL)
302         goto OUT;
303     }
304
305   if (!priv->tooltip_widget)
306     {
307       priv->tooltip_widget = empathy_contact_widget_new (contact,
308           EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
309           EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
310       gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
311       g_object_ref (priv->tooltip_widget);
312       g_signal_connect (priv->tooltip_widget, "destroy",
313           G_CALLBACK (individual_view_tooltip_destroy_cb), view);
314       gtk_widget_show (priv->tooltip_widget);
315     }
316   else
317     {
318       empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
319     }
320
321   gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
322   ret = TRUE;
323
324   g_object_unref (contact);
325   g_object_unref (individual);
326 OUT:
327   running--;
328
329   return ret;
330 }
331
332 static void
333 individual_view_handle_drag (EmpathyIndividualView *self,
334     FolksIndividual *individual,
335     const gchar *old_group,
336     const gchar *new_group,
337     GdkDragAction action)
338 {
339   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
340   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
341
342   DEBUG ("individual %s dragged from '%s' to '%s'",
343       folks_individual_get_id (individual), old_group, new_group);
344
345   if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
346     {
347       /* Mark contact as favourite */
348       folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
349       return;
350     }
351
352   if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
353     {
354       /* Remove contact as favourite */
355       folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
356
357       /* Don't try to remove it */
358       old_group = NULL;
359     }
360
361   if (new_group != NULL)
362     folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
363
364   if (old_group != NULL && action == GDK_ACTION_MOVE)
365     folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
366 }
367
368 static gboolean
369 group_can_be_modified (const gchar *name,
370     gboolean is_fake_group,
371     gboolean adding)
372 {
373   /* Real groups can always be modified */
374   if (!is_fake_group)
375     return TRUE;
376
377   /* The favorite fake group can be modified so users can
378    * add/remove favorites using DnD */
379   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
380     return TRUE;
381
382   /* We can remove contacts from the 'ungrouped' fake group */
383   if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
384     return TRUE;
385
386   return FALSE;
387 }
388
389 static gboolean
390 individual_view_contact_drag_received (GtkWidget *self,
391     GdkDragContext *context,
392     GtkTreeModel *model,
393     GtkTreePath *path,
394     GtkSelectionData *selection)
395 {
396   EmpathyIndividualViewPriv *priv;
397   EmpathyIndividualManager *manager;
398   FolksIndividual *individual;
399   GtkTreePath *source_path;
400   const gchar *sel_data;
401   gchar *new_group = NULL;
402   gchar *old_group = NULL;
403   gboolean new_group_is_fake, old_group_is_fake = TRUE;
404
405   priv = GET_PRIV (self);
406
407   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
408   new_group = empathy_individual_store_get_parent_group (model, path,
409       NULL, &new_group_is_fake);
410
411   if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
412     return FALSE;
413
414   /* Get source group information. */
415   if (priv->drag_row)
416     {
417       source_path = gtk_tree_row_reference_get_path (priv->drag_row);
418       if (source_path)
419         {
420           old_group =
421               empathy_individual_store_get_parent_group (model, source_path,
422               NULL, &old_group_is_fake);
423           gtk_tree_path_free (source_path);
424         }
425     }
426
427   if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
428     return FALSE;
429
430   if (!tp_strdiff (old_group, new_group))
431     {
432       g_free (new_group);
433       g_free (old_group);
434       return FALSE;
435     }
436
437   /* XXX: for contacts, we used to ensure the account, create the contact
438    * factory, and then wait on the contacts. But they should already be
439    * created by this point */
440
441   manager = empathy_individual_manager_dup_singleton ();
442   individual = empathy_individual_manager_lookup_member (manager, sel_data);
443
444   if (individual == NULL)
445     {
446       DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
447
448       g_object_unref (manager);
449
450       return FALSE;
451     }
452
453   /* FIXME: We should probably wait for the cb before calling
454    * gtk_drag_finish */
455
456   individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
457       old_group, new_group, gdk_drag_context_get_selected_action (context));
458
459   g_object_unref (G_OBJECT (manager));
460   g_free (old_group);
461   g_free (new_group);
462
463   return TRUE;
464 }
465
466 static gboolean
467 individual_view_file_drag_received (GtkWidget *view,
468     GdkDragContext *context,
469     GtkTreeModel *model,
470     GtkTreePath *path,
471     GtkSelectionData *selection)
472 {
473   GtkTreeIter iter;
474   const gchar *sel_data;
475   FolksIndividual *individual;
476   EmpathyContact *contact;
477
478   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
479
480   gtk_tree_model_get_iter (model, &iter, path);
481   gtk_tree_model_get (model, &iter,
482       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
483   if (!individual)
484     {
485       return FALSE;
486     }
487
488   contact = empathy_contact_from_folks_individual (individual);
489   empathy_send_file_from_uri_list (contact, sel_data);
490
491   g_object_unref (individual);
492
493   return TRUE;
494 }
495
496 static void
497 individual_view_drag_data_received (GtkWidget *view,
498     GdkDragContext *context,
499     gint x,
500     gint y,
501     GtkSelectionData *selection,
502     guint info,
503     guint time_)
504 {
505   GtkTreeModel *model;
506   gboolean is_row;
507   GtkTreeViewDropPosition position;
508   GtkTreePath *path;
509   gboolean success = TRUE;
510
511   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
512
513   /* Get destination group information. */
514   is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
515       x, y, &path, &position);
516   if (!is_row)
517     {
518       success = FALSE;
519     }
520   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
521       || info == DND_DRAG_TYPE_STRING)
522     {
523       success = individual_view_contact_drag_received (view,
524           context, model, path, selection);
525     }
526   else if (info == DND_DRAG_TYPE_URI_LIST)
527     {
528       success = individual_view_file_drag_received (view,
529           context, model, path, selection);
530     }
531
532   gtk_tree_path_free (path);
533   gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
534 }
535
536 static gboolean
537 individual_view_drag_motion_cb (DragMotionData *data)
538 {
539   gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
540
541   data->timeout_id = 0;
542
543   return FALSE;
544 }
545
546 static gboolean
547 individual_view_drag_motion (GtkWidget *widget,
548     GdkDragContext *context,
549     gint x,
550     gint y,
551     guint time_)
552 {
553   EmpathyIndividualViewPriv *priv;
554   GtkTreeModel *model;
555   GdkAtom target;
556   GtkTreeIter iter;
557   static DragMotionData *dm = NULL;
558   GtkTreePath *path;
559   gboolean is_row;
560   gboolean is_different = FALSE;
561   gboolean cleanup = TRUE;
562   gboolean retval = TRUE;
563
564   priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
565   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
566
567   is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
568       x, y, &path, NULL, NULL, NULL);
569
570   cleanup &= (!dm);
571
572   if (is_row)
573     {
574       cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
575       is_different = (!dm || (dm
576               && gtk_tree_path_compare (dm->path, path) != 0));
577     }
578   else
579     {
580       cleanup &= FALSE;
581     }
582
583   if (path == NULL)
584     {
585       /* Coordinates don't point to an actual row, so make sure the pointer
586          and highlighting don't indicate that a drag is possible.
587        */
588       gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
589       gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
590       return FALSE;
591     }
592   target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
593   gtk_tree_model_get_iter (model, &iter, path);
594
595   if (target == GDK_NONE)
596     {
597       /* If target == GDK_NONE, then we don't have a target that can be
598          dropped on a contact.  This means a contact drag.  If we're
599          pointing to a group, highlight it.  Otherwise, if the contact
600          we're pointing to is in a group, highlight that.  Otherwise,
601          set the drag position to before the first row for a drag into
602          the "non-group" at the top.
603        */
604       GtkTreeIter group_iter;
605       gboolean is_group;
606       GtkTreePath *group_path;
607       gtk_tree_model_get (model, &iter,
608           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
609       if (is_group)
610         {
611           group_iter = iter;
612         }
613       else
614         {
615           if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
616             gtk_tree_model_get (model, &group_iter,
617                 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
618         }
619       if (is_group)
620         {
621           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
622           group_path = gtk_tree_model_get_path (model, &group_iter);
623           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
624               group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
625           gtk_tree_path_free (group_path);
626         }
627       else
628         {
629           group_path = gtk_tree_path_new_first ();
630           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
631           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
632               group_path, GTK_TREE_VIEW_DROP_BEFORE);
633         }
634     }
635   else
636     {
637       /* This is a file drag, and it can only be dropped on contacts,
638          not groups.
639        */
640       FolksIndividual *individual;
641       gtk_tree_model_get (model, &iter,
642           EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
643       if (individual != NULL &&
644           folks_individual_is_online (individual) &&
645           (folks_individual_get_capabilities (individual) &
646               FOLKS_CAPABILITIES_FLAGS_FILE_TRANSFER))
647         {
648           gdk_drag_status (context, GDK_ACTION_COPY, time_);
649           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
650               path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
651           g_object_unref (individual);
652         }
653       else
654         {
655           gdk_drag_status (context, 0, time_);
656           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
657           retval = FALSE;
658         }
659     }
660
661   if (!is_different && !cleanup)
662     {
663       return retval;
664     }
665
666   if (dm)
667     {
668       gtk_tree_path_free (dm->path);
669       if (dm->timeout_id)
670         {
671           g_source_remove (dm->timeout_id);
672         }
673
674       g_free (dm);
675
676       dm = NULL;
677     }
678
679   if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
680     {
681       dm = g_new0 (DragMotionData, 1);
682
683       dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
684       dm->path = gtk_tree_path_copy (path);
685
686       dm->timeout_id = g_timeout_add_seconds (1,
687           (GSourceFunc) individual_view_drag_motion_cb, dm);
688     }
689
690   return retval;
691 }
692
693 static void
694 individual_view_drag_begin (GtkWidget *widget,
695     GdkDragContext *context)
696 {
697   EmpathyIndividualViewPriv *priv;
698   GtkTreeSelection *selection;
699   GtkTreeModel *model;
700   GtkTreePath *path;
701   GtkTreeIter iter;
702
703   priv = GET_PRIV (widget);
704
705   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
706       context);
707
708   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
709   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
710     {
711       return;
712     }
713
714   path = gtk_tree_model_get_path (model, &iter);
715   priv->drag_row = gtk_tree_row_reference_new (model, path);
716   gtk_tree_path_free (path);
717 }
718
719 static void
720 individual_view_drag_data_get (GtkWidget *widget,
721     GdkDragContext *context,
722     GtkSelectionData *selection,
723     guint info,
724     guint time_)
725 {
726   EmpathyIndividualViewPriv *priv;
727   GtkTreePath *src_path;
728   GtkTreeIter iter;
729   GtkTreeModel *model;
730   FolksIndividual *individual;
731   const gchar *individual_id;
732
733   priv = GET_PRIV (widget);
734
735   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
736   if (!priv->drag_row)
737     {
738       return;
739     }
740
741   src_path = gtk_tree_row_reference_get_path (priv->drag_row);
742   if (!src_path)
743     {
744       return;
745     }
746
747   if (!gtk_tree_model_get_iter (model, &iter, src_path))
748     {
749       gtk_tree_path_free (src_path);
750       return;
751     }
752
753   gtk_tree_path_free (src_path);
754
755   individual =
756       empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
757   if (!individual)
758     {
759       return;
760     }
761
762   individual_id = folks_individual_get_id (individual);
763
764   switch (info)
765     {
766     case DND_DRAG_TYPE_INDIVIDUAL_ID:
767       gtk_selection_data_set (selection, drag_atoms_source[info], 8,
768           (guchar *) individual_id, strlen (individual_id) + 1);
769       break;
770     }
771 }
772
773 static void
774 individual_view_drag_end (GtkWidget *widget,
775     GdkDragContext *context)
776 {
777   EmpathyIndividualViewPriv *priv;
778
779   priv = GET_PRIV (widget);
780
781   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
782       context);
783
784   if (priv->drag_row)
785     {
786       gtk_tree_row_reference_free (priv->drag_row);
787       priv->drag_row = NULL;
788     }
789 }
790
791 static gboolean
792 individual_view_drag_drop (GtkWidget *widget,
793     GdkDragContext *drag_context,
794     gint x,
795     gint y,
796     guint time_)
797 {
798   return FALSE;
799 }
800
801 typedef struct
802 {
803   EmpathyIndividualView *view;
804   guint button;
805   guint32 time;
806 } MenuPopupData;
807
808 static gboolean
809 individual_view_popup_menu_idle_cb (gpointer user_data)
810 {
811   MenuPopupData *data = user_data;
812   GtkWidget *menu;
813
814   menu = empathy_individual_view_get_individual_menu (data->view);
815   if (menu == NULL)
816     menu = empathy_individual_view_get_group_menu (data->view);
817
818   if (menu != NULL)
819     {
820       g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
821       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
822           NULL);
823       gtk_widget_show (menu);
824       gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
825           data->time);
826       g_object_ref_sink (menu);
827       g_object_unref (menu);
828     }
829
830   g_slice_free (MenuPopupData, data);
831
832   return FALSE;
833 }
834
835 static gboolean
836 individual_view_button_press_event_cb (EmpathyIndividualView *view,
837     GdkEventButton *event,
838     gpointer user_data)
839 {
840   if (event->button == 3)
841     {
842       MenuPopupData *data;
843
844       data = g_slice_new (MenuPopupData);
845       data->view = view;
846       data->button = event->button;
847       data->time = event->time;
848       g_idle_add (individual_view_popup_menu_idle_cb, data);
849     }
850
851   return FALSE;
852 }
853
854 static gboolean
855 individual_view_key_press_event_cb (EmpathyIndividualView *view,
856     GdkEventKey *event,
857     gpointer user_data)
858 {
859   if (event->keyval == GDK_Menu)
860     {
861       MenuPopupData *data;
862
863       data = g_slice_new (MenuPopupData);
864       data->view = view;
865       data->button = 0;
866       data->time = event->time;
867       g_idle_add (individual_view_popup_menu_idle_cb, data);
868     }
869
870   return FALSE;
871 }
872
873 static void
874 individual_view_row_activated (GtkTreeView *view,
875     GtkTreePath *path,
876     GtkTreeViewColumn *column)
877 {
878   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
879   FolksIndividual *individual;
880   EmpathyContact *contact = NULL;
881   GtkTreeModel *model;
882   GtkTreeIter iter;
883
884   if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
885     {
886       return;
887     }
888
889   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
890   gtk_tree_model_get_iter (model, &iter, path);
891   gtk_tree_model_get (model, &iter,
892       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
893
894   if (!individual)
895     return;
896
897   contact = empathy_contact_from_folks_individual (individual);
898
899   if (contact)
900     {
901       DEBUG ("Starting a chat");
902
903       empathy_dispatcher_chat_with_contact (contact,
904           gtk_get_current_event_time (), NULL, NULL);
905     }
906
907   g_object_unref (individual);
908 }
909
910 static void
911 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
912     const gchar *path_string,
913     EmpathyIndividualView *view)
914 {
915   GtkWidget *menu;
916   GtkTreeModel *model;
917   GtkTreeIter iter;
918   FolksIndividual *individual;
919   GdkEventButton *event;
920   GtkMenuShell *shell;
921   GtkWidget *item;
922
923   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
924   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
925     return;
926
927   gtk_tree_model_get (model, &iter,
928       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
929   if (individual == NULL)
930     return;
931
932   event = (GdkEventButton *) gtk_get_current_event ();
933
934   menu = gtk_menu_new ();
935   shell = GTK_MENU_SHELL (menu);
936
937   /* audio */
938   item = empathy_individual_audio_call_menu_item_new (individual);
939   gtk_menu_shell_append (shell, item);
940   gtk_widget_show (item);
941
942   /* video */
943   item = empathy_individual_video_call_menu_item_new (individual);
944   gtk_menu_shell_append (shell, item);
945   gtk_widget_show (item);
946
947   g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
948   gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
949   gtk_widget_show (menu);
950   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
951       event->button, event->time);
952   g_object_ref_sink (menu);
953   g_object_unref (menu);
954
955   g_object_unref (individual);
956 }
957
958 static void
959 individual_view_cell_set_background (EmpathyIndividualView *view,
960     GtkCellRenderer *cell,
961     gboolean is_group,
962     gboolean is_active)
963 {
964   GdkColor color;
965   GtkStyle *style;
966
967   style = gtk_widget_get_style (GTK_WIDGET (view));
968
969   if (!is_group && is_active)
970     {
971       color = style->bg[GTK_STATE_SELECTED];
972
973       /* Here we take the current theme colour and add it to
974        * the colour for white and average the two. This
975        * gives a colour which is inline with the theme but
976        * slightly whiter.
977        */
978       color.red = (color.red + (style->white).red) / 2;
979       color.green = (color.green + (style->white).green) / 2;
980       color.blue = (color.blue + (style->white).blue) / 2;
981
982       g_object_set (cell, "cell-background-gdk", &color, NULL);
983     }
984   else
985     {
986       g_object_set (cell, "cell-background-gdk", NULL, NULL);
987     }
988 }
989
990 static void
991 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
992     GtkCellRenderer *cell,
993     GtkTreeModel *model,
994     GtkTreeIter *iter,
995     EmpathyIndividualView *view)
996 {
997   GdkPixbuf *pixbuf;
998   gboolean is_group;
999   gboolean is_active;
1000
1001   gtk_tree_model_get (model, iter,
1002       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1003       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1004       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1005
1006   g_object_set (cell,
1007       "visible", !is_group,
1008       "pixbuf", pixbuf,
1009       NULL);
1010
1011   if (pixbuf != NULL)
1012     {
1013       g_object_unref (pixbuf);
1014     }
1015
1016   individual_view_cell_set_background (view, cell, is_group, is_active);
1017 }
1018
1019 static void
1020 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1021     GtkCellRenderer *cell,
1022     GtkTreeModel *model,
1023     GtkTreeIter *iter,
1024     EmpathyIndividualView *view)
1025 {
1026   GdkPixbuf *pixbuf = NULL;
1027   gboolean is_group;
1028   gchar *name;
1029
1030   gtk_tree_model_get (model, iter,
1031       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1032       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1033
1034   if (!is_group)
1035     goto out;
1036
1037   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1038     {
1039       pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1040           GTK_ICON_SIZE_MENU);
1041     }
1042   else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1043     {
1044       pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1045           GTK_ICON_SIZE_MENU);
1046     }
1047
1048 out:
1049   g_object_set (cell,
1050       "visible", pixbuf != NULL,
1051       "pixbuf", pixbuf,
1052       NULL);
1053
1054   if (pixbuf != NULL)
1055     g_object_unref (pixbuf);
1056
1057   g_free (name);
1058 }
1059
1060 static void
1061 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1062     GtkCellRenderer *cell,
1063     GtkTreeModel *model,
1064     GtkTreeIter *iter,
1065     EmpathyIndividualView *view)
1066 {
1067   gboolean is_group;
1068   gboolean is_active;
1069   gboolean can_audio, can_video;
1070
1071   gtk_tree_model_get (model, iter,
1072       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1073       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1074       EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1075       EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1076
1077   g_object_set (cell,
1078       "visible", !is_group && (can_audio || can_video),
1079       "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1080       NULL);
1081
1082   individual_view_cell_set_background (view, cell, is_group, is_active);
1083 }
1084
1085 static void
1086 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1087     GtkCellRenderer *cell,
1088     GtkTreeModel *model,
1089     GtkTreeIter *iter,
1090     EmpathyIndividualView *view)
1091 {
1092   GdkPixbuf *pixbuf;
1093   gboolean show_avatar;
1094   gboolean is_group;
1095   gboolean is_active;
1096
1097   gtk_tree_model_get (model, iter,
1098       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1099       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1100       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1101       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1102
1103   g_object_set (cell,
1104       "visible", !is_group && show_avatar,
1105       "pixbuf", pixbuf,
1106       NULL);
1107
1108   if (pixbuf)
1109     {
1110       g_object_unref (pixbuf);
1111     }
1112
1113   individual_view_cell_set_background (view, cell, is_group, is_active);
1114 }
1115
1116 static void
1117 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1118     GtkCellRenderer *cell,
1119     GtkTreeModel *model,
1120     GtkTreeIter *iter,
1121     EmpathyIndividualView *view)
1122 {
1123   gboolean is_group;
1124   gboolean is_active;
1125
1126   gtk_tree_model_get (model, iter,
1127       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1128       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1129
1130   individual_view_cell_set_background (view, cell, is_group, is_active);
1131 }
1132
1133 static void
1134 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1135     GtkCellRenderer *cell,
1136     GtkTreeModel *model,
1137     GtkTreeIter *iter,
1138     EmpathyIndividualView *view)
1139 {
1140   gboolean is_group;
1141   gboolean is_active;
1142
1143   gtk_tree_model_get (model, iter,
1144       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1145       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1146
1147   if (gtk_tree_model_iter_has_child (model, iter))
1148     {
1149       GtkTreePath *path;
1150       gboolean row_expanded;
1151
1152       path = gtk_tree_model_get_path (model, iter);
1153       row_expanded =
1154           gtk_tree_view_row_expanded (GTK_TREE_VIEW
1155           (gtk_tree_view_column_get_tree_view (column)), path);
1156       gtk_tree_path_free (path);
1157
1158       g_object_set (cell,
1159           "visible", TRUE,
1160           "expander-style",
1161           row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1162           NULL);
1163     }
1164   else
1165     {
1166       g_object_set (cell, "visible", FALSE, NULL);
1167     }
1168
1169   individual_view_cell_set_background (view, cell, is_group, is_active);
1170 }
1171
1172 static void
1173 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1174     GtkTreeIter *iter,
1175     GtkTreePath *path,
1176     gpointer user_data)
1177 {
1178   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1179   GtkTreeModel *model;
1180   gchar *name;
1181   gboolean expanded;
1182
1183   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1184     return;
1185
1186   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1187
1188   gtk_tree_model_get (model, iter,
1189       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1190
1191   expanded = GPOINTER_TO_INT (user_data);
1192   empathy_contact_group_set_expanded (name, expanded);
1193
1194   g_free (name);
1195 }
1196
1197 static gboolean
1198 individual_view_start_search_cb (EmpathyIndividualView *view,
1199     gpointer data)
1200 {
1201   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1202
1203   if (priv->search_widget == NULL)
1204     return FALSE;
1205
1206   if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1207     gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1208   else
1209     gtk_widget_show (GTK_WIDGET (priv->search_widget));
1210
1211   return TRUE;
1212 }
1213
1214 static void
1215 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1216     GParamSpec *pspec,
1217     EmpathyIndividualView *view)
1218 {
1219   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1220   GtkTreePath *path;
1221   GtkTreeViewColumn *focus_column;
1222   GtkTreeModel *model;
1223   GtkTreeIter iter;
1224   gboolean set_cursor = FALSE;
1225
1226   gtk_tree_model_filter_refilter (priv->filter);
1227
1228   /* Set cursor on the first contact. If it is already set on a group,
1229    * set it on its first child contact. Note that first child of a group
1230    * is its separator, that's why we actually set to the 2nd
1231    */
1232
1233   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1234   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1235
1236   if (path == NULL)
1237     {
1238       path = gtk_tree_path_new_from_string ("0:1");
1239       set_cursor = TRUE;
1240     }
1241   else if (gtk_tree_path_get_depth (path) < 2)
1242     {
1243       gboolean is_group;
1244
1245       gtk_tree_model_get_iter (model, &iter, path);
1246       gtk_tree_model_get (model, &iter,
1247           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1248           -1);
1249
1250       if (is_group)
1251         {
1252           gtk_tree_path_down (path);
1253           gtk_tree_path_next (path);
1254           set_cursor = TRUE;
1255         }
1256     }
1257
1258   if (set_cursor)
1259     {
1260       /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1261        * valid. */
1262       if (gtk_tree_model_get_iter (model, &iter, path))
1263         {
1264           gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1265               FALSE);
1266         }
1267     }
1268
1269   gtk_tree_path_free (path);
1270 }
1271
1272 static void
1273 individual_view_search_activate_cb (GtkWidget *search,
1274   EmpathyIndividualView *view)
1275 {
1276   GtkTreePath *path;
1277   GtkTreeViewColumn *focus_column;
1278
1279   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1280   if (path != NULL)
1281     {
1282       gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1283       gtk_tree_path_free (path);
1284
1285       gtk_widget_hide (search);
1286     }
1287 }
1288
1289 static void
1290 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1291     EmpathyIndividualView *view)
1292 {
1293   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1294   GtkTreeModel *model;
1295   GtkTreeIter iter;
1296   gboolean valid = FALSE;
1297
1298   /* block expand or collapse handlers, they would write the
1299    * expand or collapsed setting to file otherwise */
1300   g_signal_handlers_block_by_func (view,
1301       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1302   g_signal_handlers_block_by_func (view,
1303     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1304
1305   /* restore which groups are expanded and which are not */
1306   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1307   for (valid = gtk_tree_model_get_iter_first (model, &iter);
1308        valid; valid = gtk_tree_model_iter_next (model, &iter))
1309     {
1310       gboolean is_group;
1311       gchar *name = NULL;
1312       GtkTreePath *path;
1313
1314       gtk_tree_model_get (model, &iter,
1315           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1316           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1317           -1);
1318
1319       if (!is_group)
1320         {
1321           g_free (name);
1322           continue;
1323         }
1324
1325       path = gtk_tree_model_get_path (model, &iter);
1326       if ((priv->view_features &
1327             EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1328           empathy_contact_group_get_expanded (name))
1329         {
1330           gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1331         }
1332       else
1333         {
1334           gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1335         }
1336
1337       gtk_tree_path_free (path);
1338       g_free (name);
1339     }
1340
1341   /* unblock expand or collapse handlers */
1342   g_signal_handlers_unblock_by_func (view,
1343       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1344   g_signal_handlers_unblock_by_func (view,
1345       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1346 }
1347
1348 static void
1349 individual_view_search_show_cb (EmpathyLiveSearch *search,
1350     EmpathyIndividualView *view)
1351 {
1352   /* block expand or collapse handlers during expand all, they would
1353    * write the expand or collapsed setting to file otherwise */
1354   g_signal_handlers_block_by_func (view,
1355       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1356
1357   gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1358
1359   g_signal_handlers_unblock_by_func (view,
1360       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1361 }
1362
1363 typedef struct {
1364   EmpathyIndividualView *view;
1365   GtkTreeRowReference *row_ref;
1366   gboolean expand;
1367 } ExpandData;
1368
1369 static gboolean
1370 individual_view_expand_idle_cb (gpointer user_data)
1371 {
1372   ExpandData *data = user_data;
1373   GtkTreePath *path;
1374
1375   path = gtk_tree_row_reference_get_path (data->row_ref);
1376   if (path == NULL)
1377     goto done;
1378
1379   g_signal_handlers_block_by_func (data->view,
1380     individual_view_row_expand_or_collapse_cb,
1381     GINT_TO_POINTER (data->expand));
1382
1383   if (data->expand)
1384     gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1385   else
1386     gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1387
1388   gtk_tree_path_free (path);
1389
1390   g_signal_handlers_unblock_by_func (data->view,
1391       individual_view_row_expand_or_collapse_cb,
1392       GINT_TO_POINTER (data->expand));
1393
1394 done:
1395   g_object_unref (data->view);
1396   gtk_tree_row_reference_free (data->row_ref);
1397   g_slice_free (ExpandData, data);
1398
1399   return FALSE;
1400 }
1401
1402 static void
1403 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1404     GtkTreePath *path,
1405     GtkTreeIter *iter,
1406     EmpathyIndividualView *view)
1407 {
1408   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1409   gboolean is_group = FALSE;
1410   gchar *name = NULL;
1411   ExpandData *data;
1412
1413   gtk_tree_model_get (model, iter,
1414       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1415       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1416       -1);
1417
1418   if (!is_group || EMP_STR_EMPTY (name))
1419     {
1420       g_free (name);
1421       return;
1422     }
1423
1424   data = g_slice_new0 (ExpandData);
1425   data->view = g_object_ref (view);
1426   data->row_ref = gtk_tree_row_reference_new (model, path);
1427   data->expand =
1428       (priv->view_features &
1429           EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1430       (priv->search_widget != NULL &&
1431           gtk_widget_get_visible (priv->search_widget)) ||
1432       empathy_contact_group_get_expanded (name);
1433
1434   /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1435    * gtk_tree_model_filter_refilter () */
1436   g_idle_add (individual_view_expand_idle_cb, data);
1437
1438   g_free (name);
1439 }
1440
1441 static void
1442 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1443     GtkTreePath *path)
1444 {
1445   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1446   GtkTreeModel *model;
1447   GtkTreePath *parent_path;
1448   GtkTreeIter parent_iter;
1449
1450   if (gtk_tree_path_get_depth (path) < 2)
1451     return;
1452
1453   /* A group row is visible if and only if at least one if its child is visible.
1454    * So when a row is inserted/deleted/changed in the base model, that could
1455    * modify the visibility of its parent in the filter model.
1456   */
1457
1458   model = GTK_TREE_MODEL (priv->store);
1459   parent_path = gtk_tree_path_copy (path);
1460   gtk_tree_path_up (parent_path);
1461   if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1462     {
1463       /* This tells the filter to verify the visibility of that row, and
1464        * show/hide it if necessary */
1465       gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1466               parent_path, &parent_iter);
1467     }
1468   gtk_tree_path_free (parent_path);
1469 }
1470
1471 static void
1472 individual_view_store_row_changed_cb (GtkTreeModel *model,
1473   GtkTreePath *path,
1474   GtkTreeIter *iter,
1475   EmpathyIndividualView *view)
1476 {
1477   individual_view_verify_group_visibility (view, path);
1478 }
1479
1480 static void
1481 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1482   GtkTreePath *path,
1483   EmpathyIndividualView *view)
1484 {
1485   individual_view_verify_group_visibility (view, path);
1486 }
1487
1488 static void
1489 individual_view_constructed (GObject *object)
1490 {
1491   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1492   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1493   GtkCellRenderer *cell;
1494   GtkTreeViewColumn *col;
1495   guint i;
1496
1497   priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1498       GTK_TREE_MODEL (priv->store), NULL));
1499   gtk_tree_model_filter_set_visible_func (priv->filter,
1500       individual_view_filter_visible_func, view, NULL);
1501
1502   g_signal_connect (priv->store, "row-has-child-toggled",
1503       G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1504   gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1505       GTK_TREE_MODEL (priv->filter));
1506
1507   tp_g_signal_connect_object (priv->store, "row-changed",
1508       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1509   tp_g_signal_connect_object (priv->store, "row-inserted",
1510       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1511   tp_g_signal_connect_object (priv->store, "row-deleted",
1512       G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1513
1514   tp_g_signal_connect_object (priv->store, "row-changed",
1515     G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1516   tp_g_signal_connect_object (priv->store, "row-inserted",
1517     G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1518   tp_g_signal_connect_object (priv->store, "row-deleted",
1519     G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1520
1521   /* Setup view */
1522   /* Setting reorderable is a hack that gets us row previews as drag icons
1523      for free.  We override all the drag handlers.  It's tricky to get the
1524      position of the drag icon right in drag_begin.  GtkTreeView has special
1525      voodoo for it, so we let it do the voodoo that he do.
1526    */
1527   g_object_set (view,
1528       "headers-visible", FALSE,
1529       "reorderable", TRUE,
1530       "show-expanders", FALSE,
1531       NULL);
1532
1533   col = gtk_tree_view_column_new ();
1534
1535   /* State */
1536   cell = gtk_cell_renderer_pixbuf_new ();
1537   gtk_tree_view_column_pack_start (col, cell, FALSE);
1538   gtk_tree_view_column_set_cell_data_func (col, cell,
1539       (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1540       view, NULL);
1541
1542   g_object_set (cell,
1543       "xpad", 5,
1544       "ypad", 1,
1545       "visible", FALSE,
1546       NULL);
1547
1548   /* Group icon */
1549   cell = gtk_cell_renderer_pixbuf_new ();
1550   gtk_tree_view_column_pack_start (col, cell, FALSE);
1551   gtk_tree_view_column_set_cell_data_func (col, cell,
1552       (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1553       view, NULL);
1554
1555   g_object_set (cell,
1556       "xpad", 0,
1557       "ypad", 0,
1558       "visible", FALSE,
1559       "width", 16,
1560       "height", 16,
1561       NULL);
1562
1563   /* Name */
1564   cell = empathy_cell_renderer_text_new ();
1565   gtk_tree_view_column_pack_start (col, cell, TRUE);
1566   gtk_tree_view_column_set_cell_data_func (col, cell,
1567       (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1568
1569   gtk_tree_view_column_add_attribute (col, cell,
1570       "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1571   gtk_tree_view_column_add_attribute (col, cell,
1572       "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1573   gtk_tree_view_column_add_attribute (col, cell,
1574       "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1575   gtk_tree_view_column_add_attribute (col, cell,
1576       "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1577   gtk_tree_view_column_add_attribute (col, cell,
1578       "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1579   gtk_tree_view_column_add_attribute (col, cell,
1580       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1581
1582   /* Audio Call Icon */
1583   cell = empathy_cell_renderer_activatable_new ();
1584   gtk_tree_view_column_pack_start (col, cell, FALSE);
1585   gtk_tree_view_column_set_cell_data_func (col, cell,
1586       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1587       view, NULL);
1588
1589   g_object_set (cell, "visible", FALSE, NULL);
1590
1591   g_signal_connect (cell, "path-activated",
1592       G_CALLBACK (individual_view_call_activated_cb), view);
1593
1594   /* Avatar */
1595   cell = gtk_cell_renderer_pixbuf_new ();
1596   gtk_tree_view_column_pack_start (col, cell, FALSE);
1597   gtk_tree_view_column_set_cell_data_func (col, cell,
1598       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1599       view, NULL);
1600
1601   g_object_set (cell,
1602       "xpad", 0,
1603       "ypad", 0,
1604       "visible", FALSE,
1605       "width", 32,
1606       "height", 32,
1607       NULL);
1608
1609   /* Expander */
1610   cell = empathy_cell_renderer_expander_new ();
1611   gtk_tree_view_column_pack_end (col, cell, FALSE);
1612   gtk_tree_view_column_set_cell_data_func (col, cell,
1613       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1614       view, NULL);
1615
1616   /* Actually add the column now we have added all cell renderers */
1617   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1618
1619   /* Drag & Drop. */
1620   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1621     {
1622       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1623     }
1624
1625   for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1626     {
1627       drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1628           FALSE);
1629     }
1630 }
1631
1632 static void
1633 individual_view_set_view_features (EmpathyIndividualView *view,
1634     EmpathyIndividualFeatureFlags features)
1635 {
1636   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1637   gboolean has_tooltip;
1638
1639   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1640
1641   priv->view_features = features;
1642
1643   /* Update DnD source/dest */
1644   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1645     {
1646       gtk_drag_source_set (GTK_WIDGET (view),
1647           GDK_BUTTON1_MASK,
1648           drag_types_source,
1649           G_N_ELEMENTS (drag_types_source),
1650           GDK_ACTION_MOVE | GDK_ACTION_COPY);
1651     }
1652   else
1653     {
1654       gtk_drag_source_unset (GTK_WIDGET (view));
1655
1656     }
1657
1658   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1659     {
1660       gtk_drag_dest_set (GTK_WIDGET (view),
1661           GTK_DEST_DEFAULT_ALL,
1662           drag_types_dest,
1663           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1664     }
1665   else
1666     {
1667       /* FIXME: URI could still be droped depending on FT feature */
1668       gtk_drag_dest_unset (GTK_WIDGET (view));
1669     }
1670
1671   /* Update has-tooltip */
1672   has_tooltip =
1673       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1674   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1675 }
1676
1677 static void
1678 individual_view_dispose (GObject *object)
1679 {
1680   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1681   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1682
1683   if (priv->store != NULL)
1684     {
1685       g_object_unref (priv->store);
1686       priv->store = NULL;
1687     }
1688   if (priv->filter != NULL)
1689     {
1690       g_object_unref (priv->filter);
1691       priv->filter = NULL;
1692     }
1693   if (priv->tooltip_widget != NULL)
1694     {
1695       gtk_widget_destroy (priv->tooltip_widget);
1696       priv->tooltip_widget = NULL;
1697     }
1698   if (priv->file_targets != NULL)
1699     {
1700       gtk_target_list_unref (priv->file_targets);
1701       priv->file_targets = NULL;
1702     }
1703
1704   empathy_individual_view_set_live_search (view, NULL);
1705
1706   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1707 }
1708
1709 static void
1710 individual_view_get_property (GObject *object,
1711     guint param_id,
1712     GValue *value,
1713     GParamSpec *pspec)
1714 {
1715   EmpathyIndividualViewPriv *priv;
1716
1717   priv = GET_PRIV (object);
1718
1719   switch (param_id)
1720     {
1721     case PROP_STORE:
1722       g_value_set_object (value, priv->store);
1723       break;
1724     case PROP_VIEW_FEATURES:
1725       g_value_set_flags (value, priv->view_features);
1726       break;
1727     case PROP_INDIVIDUAL_FEATURES:
1728       g_value_set_flags (value, priv->individual_features);
1729       break;
1730     default:
1731       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1732       break;
1733     };
1734 }
1735
1736 static void
1737 individual_view_set_property (GObject *object,
1738     guint param_id,
1739     const GValue *value,
1740     GParamSpec *pspec)
1741 {
1742   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1743   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1744
1745   switch (param_id)
1746     {
1747     case PROP_STORE:
1748       priv->store = g_value_dup_object (value);
1749       break;
1750     case PROP_VIEW_FEATURES:
1751       individual_view_set_view_features (view, g_value_get_flags (value));
1752       break;
1753     case PROP_INDIVIDUAL_FEATURES:
1754       priv->individual_features = g_value_get_flags (value);
1755       break;
1756     default:
1757       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1758       break;
1759     };
1760 }
1761
1762 static void
1763 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1764 {
1765   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1766   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1767   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1768
1769   object_class->constructed = individual_view_constructed;
1770   object_class->dispose = individual_view_dispose;
1771   object_class->get_property = individual_view_get_property;
1772   object_class->set_property = individual_view_set_property;
1773
1774   widget_class->drag_data_received = individual_view_drag_data_received;
1775   widget_class->drag_drop = individual_view_drag_drop;
1776   widget_class->drag_begin = individual_view_drag_begin;
1777   widget_class->drag_data_get = individual_view_drag_data_get;
1778   widget_class->drag_end = individual_view_drag_end;
1779   widget_class->drag_motion = individual_view_drag_motion;
1780
1781   /* We use the class method to let user of this widget to connect to
1782    * the signal and stop emission of the signal so the default handler
1783    * won't be called. */
1784   tree_view_class->row_activated = individual_view_row_activated;
1785
1786   signals[DRAG_CONTACT_RECEIVED] =
1787       g_signal_new ("drag-contact-received",
1788       G_OBJECT_CLASS_TYPE (klass),
1789       G_SIGNAL_RUN_LAST,
1790       0,
1791       NULL, NULL,
1792       _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1793       G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1794
1795   g_object_class_install_property (object_class,
1796       PROP_STORE,
1797       g_param_spec_object ("store",
1798           "The store of the view",
1799           "The store of the view",
1800           EMPATHY_TYPE_INDIVIDUAL_STORE,
1801           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1802   g_object_class_install_property (object_class,
1803       PROP_VIEW_FEATURES,
1804       g_param_spec_flags ("view-features",
1805           "Features of the view",
1806           "Flags for all enabled features",
1807           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1808           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1809   g_object_class_install_property (object_class,
1810       PROP_INDIVIDUAL_FEATURES,
1811       g_param_spec_flags ("individual-features",
1812           "Features of the contact menu",
1813           "Flags for all enabled features for the menu",
1814           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1815           EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1816
1817   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1818 }
1819
1820 static void
1821 empathy_individual_view_init (EmpathyIndividualView *view)
1822 {
1823   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1824       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1825
1826   view->priv = priv;
1827   /* Get saved group states. */
1828   empathy_contact_groups_get_all ();
1829
1830   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1831       empathy_individual_store_row_separator_func, NULL, NULL);
1832
1833   /* Set up drag target lists. */
1834   priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1835       G_N_ELEMENTS (drag_types_dest_file));
1836
1837   /* Connect to tree view signals rather than override. */
1838   g_signal_connect (view, "button-press-event",
1839       G_CALLBACK (individual_view_button_press_event_cb), NULL);
1840   g_signal_connect (view, "key-press-event",
1841       G_CALLBACK (individual_view_key_press_event_cb), NULL);
1842   g_signal_connect (view, "row-expanded",
1843       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1844       GINT_TO_POINTER (TRUE));
1845   g_signal_connect (view, "row-collapsed",
1846       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1847       GINT_TO_POINTER (FALSE));
1848   g_signal_connect (view, "query-tooltip",
1849       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1850 }
1851
1852 EmpathyIndividualView *
1853 empathy_individual_view_new (EmpathyIndividualStore *store,
1854     EmpathyIndividualViewFeatureFlags view_features,
1855     EmpathyIndividualFeatureFlags individual_features)
1856 {
1857   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1858
1859   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1860       "store", store,
1861       "individual-features", individual_features,
1862       "view-features", view_features, NULL);
1863 }
1864
1865 FolksIndividual *
1866 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1867 {
1868   EmpathyIndividualViewPriv *priv;
1869   GtkTreeSelection *selection;
1870   GtkTreeIter iter;
1871   GtkTreeModel *model;
1872   FolksIndividual *individual;
1873
1874   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1875
1876   priv = GET_PRIV (view);
1877
1878   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1879   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1880     {
1881       return NULL;
1882     }
1883
1884   gtk_tree_model_get (model, &iter,
1885       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1886
1887   return individual;
1888 }
1889
1890 EmpathyIndividualManagerFlags
1891 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1892 {
1893   EmpathyIndividualViewPriv *priv;
1894   GtkTreeSelection *selection;
1895   GtkTreeIter iter;
1896   GtkTreeModel *model;
1897   EmpathyIndividualFeatureFlags flags;
1898
1899   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1900
1901   priv = GET_PRIV (view);
1902
1903   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1904   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1905     {
1906       return 0;
1907     }
1908
1909   gtk_tree_model_get (model, &iter,
1910       EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1911
1912   return flags;
1913 }
1914
1915 gchar *
1916 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1917     gboolean *is_fake_group)
1918 {
1919   EmpathyIndividualViewPriv *priv;
1920   GtkTreeSelection *selection;
1921   GtkTreeIter iter;
1922   GtkTreeModel *model;
1923   gboolean is_group;
1924   gchar *name;
1925   gboolean fake;
1926
1927   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1928
1929   priv = GET_PRIV (view);
1930
1931   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1932   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1933     {
1934       return NULL;
1935     }
1936
1937   gtk_tree_model_get (model, &iter,
1938       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1939       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1940       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1941
1942   if (!is_group)
1943     {
1944       g_free (name);
1945       return NULL;
1946     }
1947
1948   if (is_fake_group != NULL)
1949     *is_fake_group = fake;
1950
1951   return name;
1952 }
1953
1954 static gboolean
1955 individual_view_remove_dialog_show (GtkWindow *parent,
1956     const gchar *message,
1957     const gchar *secondary_text)
1958 {
1959   GtkWidget *dialog;
1960   gboolean res;
1961
1962   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1963       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1964   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1965       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1966       GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1967   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1968       "%s", secondary_text);
1969
1970   gtk_widget_show (dialog);
1971
1972   res = gtk_dialog_run (GTK_DIALOG (dialog));
1973   gtk_widget_destroy (dialog);
1974
1975   return (res == GTK_RESPONSE_YES);
1976 }
1977
1978 static void
1979 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1980     EmpathyIndividualView *view)
1981 {
1982   gchar *group;
1983
1984   group = empathy_individual_view_get_selected_group (view, NULL);
1985   if (group)
1986     {
1987       gchar *text;
1988       GtkWindow *parent;
1989
1990       text =
1991           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1992           group);
1993       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1994       if (individual_view_remove_dialog_show (parent, _("Removing group"),
1995               text))
1996         {
1997           /* TODO: implement */
1998           DEBUG ("removing group unimplemented");
1999         }
2000
2001       g_free (text);
2002     }
2003
2004   g_free (group);
2005 }
2006
2007 GtkWidget *
2008 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2009 {
2010   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2011   gchar *group;
2012   GtkWidget *menu;
2013   GtkWidget *item;
2014   GtkWidget *image;
2015   gboolean is_fake_group;
2016
2017   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2018
2019   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2020               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2021     {
2022       return NULL;
2023     }
2024
2025   group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2026   if (!group || is_fake_group)
2027     {
2028       /* We can't alter fake groups */
2029       return NULL;
2030     }
2031
2032   menu = gtk_menu_new ();
2033
2034   /* TODO: implement
2035      if (priv->view_features &
2036      EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2037      item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2038      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2039      gtk_widget_show (item);
2040      g_signal_connect (item, "activate",
2041      G_CALLBACK (individual_view_group_rename_activate_cb),
2042      view);
2043      }
2044    */
2045
2046   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2047     {
2048       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2049       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2050           GTK_ICON_SIZE_MENU);
2051       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2052       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2053       gtk_widget_show (item);
2054       g_signal_connect (item, "activate",
2055           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2056     }
2057
2058   g_free (group);
2059
2060   return menu;
2061 }
2062
2063 static void
2064 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2065     EmpathyIndividualView *view)
2066 {
2067   FolksIndividual *individual;
2068
2069   individual = empathy_individual_view_dup_selected (view);
2070
2071   if (individual)
2072     {
2073       gchar *text;
2074       GtkWindow *parent;
2075
2076       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2077       text =
2078           g_strdup_printf (_
2079           ("Do you really want to remove the contact '%s'?"),
2080           folks_individual_get_alias (individual));
2081       if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2082               text))
2083         {
2084           EmpathyIndividualManager *manager;
2085
2086           manager = empathy_individual_manager_dup_singleton ();
2087           empathy_individual_manager_remove (manager, individual, "");
2088           g_object_unref (G_OBJECT (manager));
2089         }
2090
2091       g_free (text);
2092       g_object_unref (individual);
2093     }
2094 }
2095
2096 GtkWidget *
2097 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2098 {
2099   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2100   FolksIndividual *individual;
2101   GtkWidget *menu = NULL;
2102   GtkWidget *item;
2103   GtkWidget *image;
2104   EmpathyIndividualManagerFlags flags;
2105
2106   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2107
2108   individual = empathy_individual_view_dup_selected (view);
2109   if (!individual)
2110     {
2111       return NULL;
2112     }
2113   flags = empathy_individual_view_get_flags (view);
2114
2115   menu = empathy_individual_menu_new (individual, priv->individual_features);
2116
2117   /* Remove contact */
2118   if (priv->view_features &
2119       EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2120       flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2121     {
2122
2123       /* create the menu if required, or just add a separator */
2124       if (!menu)
2125         {
2126           menu = gtk_menu_new ();
2127         }
2128       else
2129         {
2130           item = gtk_separator_menu_item_new ();
2131           gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2132           gtk_widget_show (item);
2133         }
2134
2135       /* Remove */
2136       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2137       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2138           GTK_ICON_SIZE_MENU);
2139       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2140       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2141       gtk_widget_show (item);
2142       g_signal_connect (item, "activate",
2143           G_CALLBACK (individual_view_remove_activate_cb), view);
2144     }
2145
2146   g_object_unref (individual);
2147
2148   return menu;
2149 }
2150
2151 void
2152 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2153     EmpathyLiveSearch *search)
2154 {
2155   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2156
2157   /* remove old handlers if old search was not null */
2158   if (priv->search_widget != NULL)
2159     {
2160       g_signal_handlers_disconnect_by_func (view,
2161           individual_view_start_search_cb, NULL);
2162
2163       g_signal_handlers_disconnect_by_func (priv->search_widget,
2164           individual_view_search_text_notify_cb, view);
2165       g_signal_handlers_disconnect_by_func (priv->search_widget,
2166           individual_view_search_activate_cb, view);
2167       g_signal_handlers_disconnect_by_func (priv->search_widget,
2168           individual_view_search_hide_cb, view);
2169       g_signal_handlers_disconnect_by_func (priv->search_widget,
2170           individual_view_search_show_cb, view);
2171       g_object_unref (priv->search_widget);
2172       priv->search_widget = NULL;
2173     }
2174
2175   /* connect handlers if new search is not null */
2176   if (search != NULL)
2177     {
2178       priv->search_widget = g_object_ref (search);
2179
2180       g_signal_connect (view, "start-interactive-search",
2181           G_CALLBACK (individual_view_start_search_cb), NULL);
2182
2183       g_signal_connect (priv->search_widget, "notify::text",
2184           G_CALLBACK (individual_view_search_text_notify_cb), view);
2185       g_signal_connect (priv->search_widget, "activate",
2186           G_CALLBACK (individual_view_search_activate_cb), view);
2187       g_signal_connect (priv->search_widget, "hide",
2188           G_CALLBACK (individual_view_search_hide_cb), view);
2189       g_signal_connect (priv->search_widget, "show",
2190           G_CALLBACK (individual_view_search_show_cb), view);
2191     }
2192 }