]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
Use explicit '!= NULL' for pointer boolean expressions.
[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
299   contact = empathy_contact_dup_from_folks_individual (individual);
300   g_object_unref (individual);
301
302   if (contact == NULL)
303     goto OUT;
304
305   if (priv->tooltip_widget == NULL)
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 OUT:
326   running--;
327
328   return ret;
329 }
330
331 static void
332 individual_view_handle_drag (EmpathyIndividualView *self,
333     FolksIndividual *individual,
334     const gchar *old_group,
335     const gchar *new_group,
336     GdkDragAction action)
337 {
338   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
339   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
340
341   DEBUG ("individual %s dragged from '%s' to '%s'",
342       folks_individual_get_id (individual), old_group, new_group);
343
344   if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
345     {
346       /* Mark contact as favourite */
347       folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
348       return;
349     }
350
351   if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
352     {
353       /* Remove contact as favourite */
354       folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
355
356       /* Don't try to remove it */
357       old_group = NULL;
358     }
359
360   if (new_group != NULL)
361     folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
362
363   if (old_group != NULL && action == GDK_ACTION_MOVE)
364     folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
365 }
366
367 static gboolean
368 group_can_be_modified (const gchar *name,
369     gboolean is_fake_group,
370     gboolean adding)
371 {
372   /* Real groups can always be modified */
373   if (!is_fake_group)
374     return TRUE;
375
376   /* The favorite fake group can be modified so users can
377    * add/remove favorites using DnD */
378   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379     return TRUE;
380
381   /* We can remove contacts from the 'ungrouped' fake group */
382   if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
383     return TRUE;
384
385   return FALSE;
386 }
387
388 static gboolean
389 individual_view_contact_drag_received (GtkWidget *self,
390     GdkDragContext *context,
391     GtkTreeModel *model,
392     GtkTreePath *path,
393     GtkSelectionData *selection)
394 {
395   EmpathyIndividualViewPriv *priv;
396   EmpathyIndividualManager *manager;
397   FolksIndividual *individual;
398   GtkTreePath *source_path;
399   const gchar *sel_data;
400   gchar *new_group = NULL;
401   gchar *old_group = NULL;
402   gboolean new_group_is_fake, old_group_is_fake = TRUE;
403
404   priv = GET_PRIV (self);
405
406   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
407   new_group = empathy_individual_store_get_parent_group (model, path,
408       NULL, &new_group_is_fake);
409
410   if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
411     return FALSE;
412
413   /* Get source group information. */
414   if (priv->drag_row)
415     {
416       source_path = gtk_tree_row_reference_get_path (priv->drag_row);
417       if (source_path)
418         {
419           old_group =
420               empathy_individual_store_get_parent_group (model, source_path,
421               NULL, &old_group_is_fake);
422           gtk_tree_path_free (source_path);
423         }
424     }
425
426   if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
427     return FALSE;
428
429   if (!tp_strdiff (old_group, new_group))
430     {
431       g_free (new_group);
432       g_free (old_group);
433       return FALSE;
434     }
435
436   /* XXX: for contacts, we used to ensure the account, create the contact
437    * factory, and then wait on the contacts. But they should already be
438    * created by this point */
439
440   manager = empathy_individual_manager_dup_singleton ();
441   individual = empathy_individual_manager_lookup_member (manager, sel_data);
442
443   if (individual == NULL)
444     {
445       DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
446
447       g_object_unref (manager);
448
449       return FALSE;
450     }
451
452   /* FIXME: We should probably wait for the cb before calling
453    * gtk_drag_finish */
454
455   individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
456       old_group, new_group, gdk_drag_context_get_selected_action (context));
457
458   g_object_unref (G_OBJECT (manager));
459   g_free (old_group);
460   g_free (new_group);
461
462   return TRUE;
463 }
464
465 static gboolean
466 individual_view_file_drag_received (GtkWidget *view,
467     GdkDragContext *context,
468     GtkTreeModel *model,
469     GtkTreePath *path,
470     GtkSelectionData *selection)
471 {
472   GtkTreeIter iter;
473   const gchar *sel_data;
474   FolksIndividual *individual;
475   EmpathyContact *contact;
476
477   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
478
479   gtk_tree_model_get_iter (model, &iter, path);
480   gtk_tree_model_get (model, &iter,
481       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
482   if (individual == NULL)
483     {
484       return FALSE;
485     }
486
487   contact = empathy_contact_dup_from_folks_individual (individual);
488   empathy_send_file_from_uri_list (contact, sel_data);
489
490   g_object_unref (individual);
491   tp_clear_object (&contact);
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 == NULL);
571
572   if (is_row)
573     {
574       cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
575       is_different = ((dm == NULL) || ((dm != NULL)
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         }
652       else
653         {
654           gdk_drag_status (context, 0, time_);
655           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
656           retval = FALSE;
657         }
658
659       if (individual != NULL)
660         g_object_unref (individual);
661     }
662
663   if (!is_different && !cleanup)
664     {
665       return retval;
666     }
667
668   if (dm)
669     {
670       gtk_tree_path_free (dm->path);
671       if (dm->timeout_id)
672         {
673           g_source_remove (dm->timeout_id);
674         }
675
676       g_free (dm);
677
678       dm = NULL;
679     }
680
681   if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
682     {
683       dm = g_new0 (DragMotionData, 1);
684
685       dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
686       dm->path = gtk_tree_path_copy (path);
687
688       dm->timeout_id = g_timeout_add_seconds (1,
689           (GSourceFunc) individual_view_drag_motion_cb, dm);
690     }
691
692   return retval;
693 }
694
695 static void
696 individual_view_drag_begin (GtkWidget *widget,
697     GdkDragContext *context)
698 {
699   EmpathyIndividualViewPriv *priv;
700   GtkTreeSelection *selection;
701   GtkTreeModel *model;
702   GtkTreePath *path;
703   GtkTreeIter iter;
704
705   priv = GET_PRIV (widget);
706
707   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
708       context);
709
710   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
711   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
712     {
713       return;
714     }
715
716   path = gtk_tree_model_get_path (model, &iter);
717   priv->drag_row = gtk_tree_row_reference_new (model, path);
718   gtk_tree_path_free (path);
719 }
720
721 static void
722 individual_view_drag_data_get (GtkWidget *widget,
723     GdkDragContext *context,
724     GtkSelectionData *selection,
725     guint info,
726     guint time_)
727 {
728   EmpathyIndividualViewPriv *priv;
729   GtkTreePath *src_path;
730   GtkTreeIter iter;
731   GtkTreeModel *model;
732   FolksIndividual *individual;
733   const gchar *individual_id;
734
735   priv = GET_PRIV (widget);
736
737   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
738   if (priv->drag_row == NULL)
739     {
740       return;
741     }
742
743   src_path = gtk_tree_row_reference_get_path (priv->drag_row);
744   if (src_path == NULL)
745     {
746       return;
747     }
748
749   if (!gtk_tree_model_get_iter (model, &iter, src_path))
750     {
751       gtk_tree_path_free (src_path);
752       return;
753     }
754
755   gtk_tree_path_free (src_path);
756
757   individual =
758       empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
759   if (individual == NULL)
760     {
761       return;
762     }
763
764   individual_id = folks_individual_get_id (individual);
765
766   switch (info)
767     {
768     case DND_DRAG_TYPE_INDIVIDUAL_ID:
769       gtk_selection_data_set (selection, drag_atoms_source[info], 8,
770           (guchar *) individual_id, strlen (individual_id) + 1);
771       break;
772     }
773
774   g_object_unref (individual);
775 }
776
777 static void
778 individual_view_drag_end (GtkWidget *widget,
779     GdkDragContext *context)
780 {
781   EmpathyIndividualViewPriv *priv;
782
783   priv = GET_PRIV (widget);
784
785   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
786       context);
787
788   if (priv->drag_row)
789     {
790       gtk_tree_row_reference_free (priv->drag_row);
791       priv->drag_row = NULL;
792     }
793 }
794
795 static gboolean
796 individual_view_drag_drop (GtkWidget *widget,
797     GdkDragContext *drag_context,
798     gint x,
799     gint y,
800     guint time_)
801 {
802   return FALSE;
803 }
804
805 typedef struct
806 {
807   EmpathyIndividualView *view;
808   guint button;
809   guint32 time;
810 } MenuPopupData;
811
812 static gboolean
813 individual_view_popup_menu_idle_cb (gpointer user_data)
814 {
815   MenuPopupData *data = user_data;
816   GtkWidget *menu;
817
818   menu = empathy_individual_view_get_individual_menu (data->view);
819   if (menu == NULL)
820     menu = empathy_individual_view_get_group_menu (data->view);
821
822   if (menu != NULL)
823     {
824       g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
825       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
826           NULL);
827       gtk_widget_show (menu);
828       gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
829           data->time);
830       g_object_ref_sink (menu);
831       g_object_unref (menu);
832     }
833
834   g_slice_free (MenuPopupData, data);
835
836   return FALSE;
837 }
838
839 static gboolean
840 individual_view_button_press_event_cb (EmpathyIndividualView *view,
841     GdkEventButton *event,
842     gpointer user_data)
843 {
844   if (event->button == 3)
845     {
846       MenuPopupData *data;
847
848       data = g_slice_new (MenuPopupData);
849       data->view = view;
850       data->button = event->button;
851       data->time = event->time;
852       g_idle_add (individual_view_popup_menu_idle_cb, data);
853     }
854
855   return FALSE;
856 }
857
858 static gboolean
859 individual_view_key_press_event_cb (EmpathyIndividualView *view,
860     GdkEventKey *event,
861     gpointer user_data)
862 {
863   if (event->keyval == GDK_Menu)
864     {
865       MenuPopupData *data;
866
867       data = g_slice_new (MenuPopupData);
868       data->view = view;
869       data->button = 0;
870       data->time = event->time;
871       g_idle_add (individual_view_popup_menu_idle_cb, data);
872     }
873
874   return FALSE;
875 }
876
877 static void
878 individual_view_row_activated (GtkTreeView *view,
879     GtkTreePath *path,
880     GtkTreeViewColumn *column)
881 {
882   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
883   FolksIndividual *individual;
884   EmpathyContact *contact = NULL;
885   GtkTreeModel *model;
886   GtkTreeIter iter;
887
888   if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
889     {
890       return;
891     }
892
893   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
894   gtk_tree_model_get_iter (model, &iter, path);
895   gtk_tree_model_get (model, &iter,
896       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
897
898   if (individual == NULL)
899     return;
900
901   contact = empathy_contact_dup_from_folks_individual (individual);
902   if (contact != NULL)
903     {
904       DEBUG ("Starting a chat");
905
906       empathy_dispatcher_chat_with_contact (contact,
907           gtk_get_current_event_time (), NULL, NULL);
908     }
909
910   g_object_unref (individual);
911   tp_clear_object (&contact);
912 }
913
914 static void
915 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
916     const gchar *path_string,
917     EmpathyIndividualView *view)
918 {
919   GtkWidget *menu;
920   GtkTreeModel *model;
921   GtkTreeIter iter;
922   FolksIndividual *individual;
923   GdkEventButton *event;
924   GtkMenuShell *shell;
925   GtkWidget *item;
926
927   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
928   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
929     return;
930
931   gtk_tree_model_get (model, &iter,
932       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
933   if (individual == NULL)
934     return;
935
936   event = (GdkEventButton *) gtk_get_current_event ();
937
938   menu = gtk_menu_new ();
939   shell = GTK_MENU_SHELL (menu);
940
941   /* audio */
942   item = empathy_individual_audio_call_menu_item_new (individual);
943   gtk_menu_shell_append (shell, item);
944   gtk_widget_show (item);
945
946   /* video */
947   item = empathy_individual_video_call_menu_item_new (individual);
948   gtk_menu_shell_append (shell, item);
949   gtk_widget_show (item);
950
951   g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
952   gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
953   gtk_widget_show (menu);
954   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
955       event->button, event->time);
956   g_object_ref_sink (menu);
957   g_object_unref (menu);
958
959   g_object_unref (individual);
960 }
961
962 static void
963 individual_view_cell_set_background (EmpathyIndividualView *view,
964     GtkCellRenderer *cell,
965     gboolean is_group,
966     gboolean is_active)
967 {
968   GdkColor color;
969   GtkStyle *style;
970
971   style = gtk_widget_get_style (GTK_WIDGET (view));
972
973   if (!is_group && is_active)
974     {
975       color = style->bg[GTK_STATE_SELECTED];
976
977       /* Here we take the current theme colour and add it to
978        * the colour for white and average the two. This
979        * gives a colour which is inline with the theme but
980        * slightly whiter.
981        */
982       color.red = (color.red + (style->white).red) / 2;
983       color.green = (color.green + (style->white).green) / 2;
984       color.blue = (color.blue + (style->white).blue) / 2;
985
986       g_object_set (cell, "cell-background-gdk", &color, NULL);
987     }
988   else
989     {
990       g_object_set (cell, "cell-background-gdk", NULL, NULL);
991     }
992 }
993
994 static void
995 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
996     GtkCellRenderer *cell,
997     GtkTreeModel *model,
998     GtkTreeIter *iter,
999     EmpathyIndividualView *view)
1000 {
1001   GdkPixbuf *pixbuf;
1002   gboolean is_group;
1003   gboolean is_active;
1004
1005   gtk_tree_model_get (model, iter,
1006       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1007       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1008       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1009
1010   g_object_set (cell,
1011       "visible", !is_group,
1012       "pixbuf", pixbuf,
1013       NULL);
1014
1015   tp_clear_object (&pixbuf);
1016
1017   individual_view_cell_set_background (view, cell, is_group, is_active);
1018 }
1019
1020 static void
1021 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1022     GtkCellRenderer *cell,
1023     GtkTreeModel *model,
1024     GtkTreeIter *iter,
1025     EmpathyIndividualView *view)
1026 {
1027   GdkPixbuf *pixbuf = NULL;
1028   gboolean is_group;
1029   gchar *name;
1030
1031   gtk_tree_model_get (model, iter,
1032       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1033       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1034
1035   if (!is_group)
1036     goto out;
1037
1038   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1039     {
1040       pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1041           GTK_ICON_SIZE_MENU);
1042     }
1043   else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1044     {
1045       pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1046           GTK_ICON_SIZE_MENU);
1047     }
1048
1049 out:
1050   g_object_set (cell,
1051       "visible", pixbuf != NULL,
1052       "pixbuf", pixbuf,
1053       NULL);
1054
1055   tp_clear_object (&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   tp_clear_object (&pixbuf);
1109
1110   individual_view_cell_set_background (view, cell, is_group, is_active);
1111 }
1112
1113 static void
1114 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1115     GtkCellRenderer *cell,
1116     GtkTreeModel *model,
1117     GtkTreeIter *iter,
1118     EmpathyIndividualView *view)
1119 {
1120   gboolean is_group;
1121   gboolean is_active;
1122
1123   gtk_tree_model_get (model, iter,
1124       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1125       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1126
1127   individual_view_cell_set_background (view, cell, is_group, is_active);
1128 }
1129
1130 static void
1131 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1132     GtkCellRenderer *cell,
1133     GtkTreeModel *model,
1134     GtkTreeIter *iter,
1135     EmpathyIndividualView *view)
1136 {
1137   gboolean is_group;
1138   gboolean is_active;
1139
1140   gtk_tree_model_get (model, iter,
1141       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1142       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1143
1144   if (gtk_tree_model_iter_has_child (model, iter))
1145     {
1146       GtkTreePath *path;
1147       gboolean row_expanded;
1148
1149       path = gtk_tree_model_get_path (model, iter);
1150       row_expanded =
1151           gtk_tree_view_row_expanded (GTK_TREE_VIEW
1152           (gtk_tree_view_column_get_tree_view (column)), path);
1153       gtk_tree_path_free (path);
1154
1155       g_object_set (cell,
1156           "visible", TRUE,
1157           "expander-style",
1158           row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1159           NULL);
1160     }
1161   else
1162     {
1163       g_object_set (cell, "visible", FALSE, NULL);
1164     }
1165
1166   individual_view_cell_set_background (view, cell, is_group, is_active);
1167 }
1168
1169 static void
1170 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1171     GtkTreeIter *iter,
1172     GtkTreePath *path,
1173     gpointer user_data)
1174 {
1175   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1176   GtkTreeModel *model;
1177   gchar *name;
1178   gboolean expanded;
1179
1180   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1181     return;
1182
1183   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1184
1185   gtk_tree_model_get (model, iter,
1186       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1187
1188   expanded = GPOINTER_TO_INT (user_data);
1189   empathy_contact_group_set_expanded (name, expanded);
1190
1191   g_free (name);
1192 }
1193
1194 static gboolean
1195 individual_view_start_search_cb (EmpathyIndividualView *view,
1196     gpointer data)
1197 {
1198   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1199
1200   if (priv->search_widget == NULL)
1201     return FALSE;
1202
1203   if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1204     gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1205   else
1206     gtk_widget_show (GTK_WIDGET (priv->search_widget));
1207
1208   return TRUE;
1209 }
1210
1211 static void
1212 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1213     GParamSpec *pspec,
1214     EmpathyIndividualView *view)
1215 {
1216   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1217   GtkTreePath *path;
1218   GtkTreeViewColumn *focus_column;
1219   GtkTreeModel *model;
1220   GtkTreeIter iter;
1221   gboolean set_cursor = FALSE;
1222
1223   gtk_tree_model_filter_refilter (priv->filter);
1224
1225   /* Set cursor on the first contact. If it is already set on a group,
1226    * set it on its first child contact. Note that first child of a group
1227    * is its separator, that's why we actually set to the 2nd
1228    */
1229
1230   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1231   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1232
1233   if (path == NULL)
1234     {
1235       path = gtk_tree_path_new_from_string ("0:1");
1236       set_cursor = TRUE;
1237     }
1238   else if (gtk_tree_path_get_depth (path) < 2)
1239     {
1240       gboolean is_group;
1241
1242       gtk_tree_model_get_iter (model, &iter, path);
1243       gtk_tree_model_get (model, &iter,
1244           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1245           -1);
1246
1247       if (is_group)
1248         {
1249           gtk_tree_path_down (path);
1250           gtk_tree_path_next (path);
1251           set_cursor = TRUE;
1252         }
1253     }
1254
1255   if (set_cursor)
1256     {
1257       /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1258        * valid. */
1259       if (gtk_tree_model_get_iter (model, &iter, path))
1260         {
1261           gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1262               FALSE);
1263         }
1264     }
1265
1266   gtk_tree_path_free (path);
1267 }
1268
1269 static void
1270 individual_view_search_activate_cb (GtkWidget *search,
1271   EmpathyIndividualView *view)
1272 {
1273   GtkTreePath *path;
1274   GtkTreeViewColumn *focus_column;
1275
1276   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1277   if (path != NULL)
1278     {
1279       gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1280       gtk_tree_path_free (path);
1281
1282       gtk_widget_hide (search);
1283     }
1284 }
1285
1286 static void
1287 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1288     EmpathyIndividualView *view)
1289 {
1290   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1291   GtkTreeModel *model;
1292   GtkTreeIter iter;
1293   gboolean valid = FALSE;
1294
1295   /* block expand or collapse handlers, they would write the
1296    * expand or collapsed setting to file otherwise */
1297   g_signal_handlers_block_by_func (view,
1298       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1299   g_signal_handlers_block_by_func (view,
1300     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1301
1302   /* restore which groups are expanded and which are not */
1303   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1304   for (valid = gtk_tree_model_get_iter_first (model, &iter);
1305        valid; valid = gtk_tree_model_iter_next (model, &iter))
1306     {
1307       gboolean is_group;
1308       gchar *name = NULL;
1309       GtkTreePath *path;
1310
1311       gtk_tree_model_get (model, &iter,
1312           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1313           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1314           -1);
1315
1316       if (!is_group)
1317         {
1318           g_free (name);
1319           continue;
1320         }
1321
1322       path = gtk_tree_model_get_path (model, &iter);
1323       if ((priv->view_features &
1324             EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1325           empathy_contact_group_get_expanded (name))
1326         {
1327           gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1328         }
1329       else
1330         {
1331           gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1332         }
1333
1334       gtk_tree_path_free (path);
1335       g_free (name);
1336     }
1337
1338   /* unblock expand or collapse handlers */
1339   g_signal_handlers_unblock_by_func (view,
1340       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1341   g_signal_handlers_unblock_by_func (view,
1342       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1343 }
1344
1345 static void
1346 individual_view_search_show_cb (EmpathyLiveSearch *search,
1347     EmpathyIndividualView *view)
1348 {
1349   /* block expand or collapse handlers during expand all, they would
1350    * write the expand or collapsed setting to file otherwise */
1351   g_signal_handlers_block_by_func (view,
1352       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1353
1354   gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1355
1356   g_signal_handlers_unblock_by_func (view,
1357       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1358 }
1359
1360 typedef struct {
1361   EmpathyIndividualView *view;
1362   GtkTreeRowReference *row_ref;
1363   gboolean expand;
1364 } ExpandData;
1365
1366 static gboolean
1367 individual_view_expand_idle_cb (gpointer user_data)
1368 {
1369   ExpandData *data = user_data;
1370   GtkTreePath *path;
1371
1372   path = gtk_tree_row_reference_get_path (data->row_ref);
1373   if (path == NULL)
1374     goto done;
1375
1376   g_signal_handlers_block_by_func (data->view,
1377     individual_view_row_expand_or_collapse_cb,
1378     GINT_TO_POINTER (data->expand));
1379
1380   if (data->expand)
1381     gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1382   else
1383     gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1384
1385   gtk_tree_path_free (path);
1386
1387   g_signal_handlers_unblock_by_func (data->view,
1388       individual_view_row_expand_or_collapse_cb,
1389       GINT_TO_POINTER (data->expand));
1390
1391 done:
1392   g_object_unref (data->view);
1393   gtk_tree_row_reference_free (data->row_ref);
1394   g_slice_free (ExpandData, data);
1395
1396   return FALSE;
1397 }
1398
1399 static void
1400 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1401     GtkTreePath *path,
1402     GtkTreeIter *iter,
1403     EmpathyIndividualView *view)
1404 {
1405   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1406   gboolean is_group = FALSE;
1407   gchar *name = NULL;
1408   ExpandData *data;
1409
1410   gtk_tree_model_get (model, iter,
1411       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1412       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1413       -1);
1414
1415   if (!is_group || EMP_STR_EMPTY (name))
1416     {
1417       g_free (name);
1418       return;
1419     }
1420
1421   data = g_slice_new0 (ExpandData);
1422   data->view = g_object_ref (view);
1423   data->row_ref = gtk_tree_row_reference_new (model, path);
1424   data->expand =
1425       (priv->view_features &
1426           EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1427       (priv->search_widget != NULL &&
1428           gtk_widget_get_visible (priv->search_widget)) ||
1429       empathy_contact_group_get_expanded (name);
1430
1431   /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1432    * gtk_tree_model_filter_refilter () */
1433   g_idle_add (individual_view_expand_idle_cb, data);
1434
1435   g_free (name);
1436 }
1437
1438 static void
1439 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1440     GtkTreePath *path)
1441 {
1442   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1443   GtkTreeModel *model;
1444   GtkTreePath *parent_path;
1445   GtkTreeIter parent_iter;
1446
1447   if (gtk_tree_path_get_depth (path) < 2)
1448     return;
1449
1450   /* A group row is visible if and only if at least one if its child is visible.
1451    * So when a row is inserted/deleted/changed in the base model, that could
1452    * modify the visibility of its parent in the filter model.
1453   */
1454
1455   model = GTK_TREE_MODEL (priv->store);
1456   parent_path = gtk_tree_path_copy (path);
1457   gtk_tree_path_up (parent_path);
1458   if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1459     {
1460       /* This tells the filter to verify the visibility of that row, and
1461        * show/hide it if necessary */
1462       gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1463               parent_path, &parent_iter);
1464     }
1465   gtk_tree_path_free (parent_path);
1466 }
1467
1468 static void
1469 individual_view_store_row_changed_cb (GtkTreeModel *model,
1470   GtkTreePath *path,
1471   GtkTreeIter *iter,
1472   EmpathyIndividualView *view)
1473 {
1474   individual_view_verify_group_visibility (view, path);
1475 }
1476
1477 static void
1478 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1479   GtkTreePath *path,
1480   EmpathyIndividualView *view)
1481 {
1482   individual_view_verify_group_visibility (view, path);
1483 }
1484
1485 static void
1486 individual_view_constructed (GObject *object)
1487 {
1488   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1489   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1490   GtkCellRenderer *cell;
1491   GtkTreeViewColumn *col;
1492   guint i;
1493
1494   priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1495       GTK_TREE_MODEL (priv->store), NULL));
1496   gtk_tree_model_filter_set_visible_func (priv->filter,
1497       individual_view_filter_visible_func, view, NULL);
1498
1499   g_signal_connect (priv->store, "row-has-child-toggled",
1500       G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1501   gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1502       GTK_TREE_MODEL (priv->filter));
1503
1504   tp_g_signal_connect_object (priv->store, "row-changed",
1505       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1506   tp_g_signal_connect_object (priv->store, "row-inserted",
1507       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1508   tp_g_signal_connect_object (priv->store, "row-deleted",
1509       G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1510
1511   tp_g_signal_connect_object (priv->store, "row-changed",
1512     G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1513   tp_g_signal_connect_object (priv->store, "row-inserted",
1514     G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1515   tp_g_signal_connect_object (priv->store, "row-deleted",
1516     G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1517
1518   /* Setup view */
1519   /* Setting reorderable is a hack that gets us row previews as drag icons
1520      for free.  We override all the drag handlers.  It's tricky to get the
1521      position of the drag icon right in drag_begin.  GtkTreeView has special
1522      voodoo for it, so we let it do the voodoo that he do.
1523    */
1524   g_object_set (view,
1525       "headers-visible", FALSE,
1526       "reorderable", TRUE,
1527       "show-expanders", FALSE,
1528       NULL);
1529
1530   col = gtk_tree_view_column_new ();
1531
1532   /* State */
1533   cell = gtk_cell_renderer_pixbuf_new ();
1534   gtk_tree_view_column_pack_start (col, cell, FALSE);
1535   gtk_tree_view_column_set_cell_data_func (col, cell,
1536       (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1537       view, NULL);
1538
1539   g_object_set (cell,
1540       "xpad", 5,
1541       "ypad", 1,
1542       "visible", FALSE,
1543       NULL);
1544
1545   /* Group icon */
1546   cell = gtk_cell_renderer_pixbuf_new ();
1547   gtk_tree_view_column_pack_start (col, cell, FALSE);
1548   gtk_tree_view_column_set_cell_data_func (col, cell,
1549       (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1550       view, NULL);
1551
1552   g_object_set (cell,
1553       "xpad", 0,
1554       "ypad", 0,
1555       "visible", FALSE,
1556       "width", 16,
1557       "height", 16,
1558       NULL);
1559
1560   /* Name */
1561   cell = empathy_cell_renderer_text_new ();
1562   gtk_tree_view_column_pack_start (col, cell, TRUE);
1563   gtk_tree_view_column_set_cell_data_func (col, cell,
1564       (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1565
1566   gtk_tree_view_column_add_attribute (col, cell,
1567       "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1568   gtk_tree_view_column_add_attribute (col, cell,
1569       "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1570   gtk_tree_view_column_add_attribute (col, cell,
1571       "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1572   gtk_tree_view_column_add_attribute (col, cell,
1573       "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1574   gtk_tree_view_column_add_attribute (col, cell,
1575       "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1576   gtk_tree_view_column_add_attribute (col, cell,
1577       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1578
1579   /* Audio Call Icon */
1580   cell = empathy_cell_renderer_activatable_new ();
1581   gtk_tree_view_column_pack_start (col, cell, FALSE);
1582   gtk_tree_view_column_set_cell_data_func (col, cell,
1583       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1584       view, NULL);
1585
1586   g_object_set (cell, "visible", FALSE, NULL);
1587
1588   g_signal_connect (cell, "path-activated",
1589       G_CALLBACK (individual_view_call_activated_cb), view);
1590
1591   /* Avatar */
1592   cell = gtk_cell_renderer_pixbuf_new ();
1593   gtk_tree_view_column_pack_start (col, cell, FALSE);
1594   gtk_tree_view_column_set_cell_data_func (col, cell,
1595       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1596       view, NULL);
1597
1598   g_object_set (cell,
1599       "xpad", 0,
1600       "ypad", 0,
1601       "visible", FALSE,
1602       "width", 32,
1603       "height", 32,
1604       NULL);
1605
1606   /* Expander */
1607   cell = empathy_cell_renderer_expander_new ();
1608   gtk_tree_view_column_pack_end (col, cell, FALSE);
1609   gtk_tree_view_column_set_cell_data_func (col, cell,
1610       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1611       view, NULL);
1612
1613   /* Actually add the column now we have added all cell renderers */
1614   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1615
1616   /* Drag & Drop. */
1617   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1618     {
1619       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1620     }
1621
1622   for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1623     {
1624       drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1625           FALSE);
1626     }
1627 }
1628
1629 static void
1630 individual_view_set_view_features (EmpathyIndividualView *view,
1631     EmpathyIndividualFeatureFlags features)
1632 {
1633   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1634   gboolean has_tooltip;
1635
1636   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1637
1638   priv->view_features = features;
1639
1640   /* Update DnD source/dest */
1641   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1642     {
1643       gtk_drag_source_set (GTK_WIDGET (view),
1644           GDK_BUTTON1_MASK,
1645           drag_types_source,
1646           G_N_ELEMENTS (drag_types_source),
1647           GDK_ACTION_MOVE | GDK_ACTION_COPY);
1648     }
1649   else
1650     {
1651       gtk_drag_source_unset (GTK_WIDGET (view));
1652
1653     }
1654
1655   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1656     {
1657       gtk_drag_dest_set (GTK_WIDGET (view),
1658           GTK_DEST_DEFAULT_ALL,
1659           drag_types_dest,
1660           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1661     }
1662   else
1663     {
1664       /* FIXME: URI could still be droped depending on FT feature */
1665       gtk_drag_dest_unset (GTK_WIDGET (view));
1666     }
1667
1668   /* Update has-tooltip */
1669   has_tooltip =
1670       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1671   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1672 }
1673
1674 static void
1675 individual_view_dispose (GObject *object)
1676 {
1677   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1678   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1679
1680   tp_clear_object (&priv->store);
1681   tp_clear_object (&priv->filter);
1682   tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1683   tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1684
1685   empathy_individual_view_set_live_search (view, NULL);
1686
1687   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1688 }
1689
1690 static void
1691 individual_view_get_property (GObject *object,
1692     guint param_id,
1693     GValue *value,
1694     GParamSpec *pspec)
1695 {
1696   EmpathyIndividualViewPriv *priv;
1697
1698   priv = GET_PRIV (object);
1699
1700   switch (param_id)
1701     {
1702     case PROP_STORE:
1703       g_value_set_object (value, priv->store);
1704       break;
1705     case PROP_VIEW_FEATURES:
1706       g_value_set_flags (value, priv->view_features);
1707       break;
1708     case PROP_INDIVIDUAL_FEATURES:
1709       g_value_set_flags (value, priv->individual_features);
1710       break;
1711     default:
1712       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1713       break;
1714     };
1715 }
1716
1717 static void
1718 individual_view_set_property (GObject *object,
1719     guint param_id,
1720     const GValue *value,
1721     GParamSpec *pspec)
1722 {
1723   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1724   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1725
1726   switch (param_id)
1727     {
1728     case PROP_STORE:
1729       priv->store = g_value_dup_object (value);
1730       break;
1731     case PROP_VIEW_FEATURES:
1732       individual_view_set_view_features (view, g_value_get_flags (value));
1733       break;
1734     case PROP_INDIVIDUAL_FEATURES:
1735       priv->individual_features = g_value_get_flags (value);
1736       break;
1737     default:
1738       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1739       break;
1740     };
1741 }
1742
1743 static void
1744 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1745 {
1746   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1747   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1748   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1749
1750   object_class->constructed = individual_view_constructed;
1751   object_class->dispose = individual_view_dispose;
1752   object_class->get_property = individual_view_get_property;
1753   object_class->set_property = individual_view_set_property;
1754
1755   widget_class->drag_data_received = individual_view_drag_data_received;
1756   widget_class->drag_drop = individual_view_drag_drop;
1757   widget_class->drag_begin = individual_view_drag_begin;
1758   widget_class->drag_data_get = individual_view_drag_data_get;
1759   widget_class->drag_end = individual_view_drag_end;
1760   widget_class->drag_motion = individual_view_drag_motion;
1761
1762   /* We use the class method to let user of this widget to connect to
1763    * the signal and stop emission of the signal so the default handler
1764    * won't be called. */
1765   tree_view_class->row_activated = individual_view_row_activated;
1766
1767   signals[DRAG_CONTACT_RECEIVED] =
1768       g_signal_new ("drag-contact-received",
1769       G_OBJECT_CLASS_TYPE (klass),
1770       G_SIGNAL_RUN_LAST,
1771       0,
1772       NULL, NULL,
1773       _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1774       G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1775
1776   g_object_class_install_property (object_class,
1777       PROP_STORE,
1778       g_param_spec_object ("store",
1779           "The store of the view",
1780           "The store of the view",
1781           EMPATHY_TYPE_INDIVIDUAL_STORE,
1782           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1783   g_object_class_install_property (object_class,
1784       PROP_VIEW_FEATURES,
1785       g_param_spec_flags ("view-features",
1786           "Features of the view",
1787           "Flags for all enabled features",
1788           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1789           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1790   g_object_class_install_property (object_class,
1791       PROP_INDIVIDUAL_FEATURES,
1792       g_param_spec_flags ("individual-features",
1793           "Features of the contact menu",
1794           "Flags for all enabled features for the menu",
1795           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1796           EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1797
1798   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1799 }
1800
1801 static void
1802 empathy_individual_view_init (EmpathyIndividualView *view)
1803 {
1804   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1805       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1806
1807   view->priv = priv;
1808   /* Get saved group states. */
1809   empathy_contact_groups_get_all ();
1810
1811   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1812       empathy_individual_store_row_separator_func, NULL, NULL);
1813
1814   /* Set up drag target lists. */
1815   priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1816       G_N_ELEMENTS (drag_types_dest_file));
1817
1818   /* Connect to tree view signals rather than override. */
1819   g_signal_connect (view, "button-press-event",
1820       G_CALLBACK (individual_view_button_press_event_cb), NULL);
1821   g_signal_connect (view, "key-press-event",
1822       G_CALLBACK (individual_view_key_press_event_cb), NULL);
1823   g_signal_connect (view, "row-expanded",
1824       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1825       GINT_TO_POINTER (TRUE));
1826   g_signal_connect (view, "row-collapsed",
1827       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1828       GINT_TO_POINTER (FALSE));
1829   g_signal_connect (view, "query-tooltip",
1830       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1831 }
1832
1833 EmpathyIndividualView *
1834 empathy_individual_view_new (EmpathyIndividualStore *store,
1835     EmpathyIndividualViewFeatureFlags view_features,
1836     EmpathyIndividualFeatureFlags individual_features)
1837 {
1838   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1839
1840   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1841       "store", store,
1842       "individual-features", individual_features,
1843       "view-features", view_features, NULL);
1844 }
1845
1846 FolksIndividual *
1847 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1848 {
1849   EmpathyIndividualViewPriv *priv;
1850   GtkTreeSelection *selection;
1851   GtkTreeIter iter;
1852   GtkTreeModel *model;
1853   FolksIndividual *individual;
1854
1855   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1856
1857   priv = GET_PRIV (view);
1858
1859   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1860   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1861     {
1862       return NULL;
1863     }
1864
1865   gtk_tree_model_get (model, &iter,
1866       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1867
1868   return individual;
1869 }
1870
1871 EmpathyIndividualManagerFlags
1872 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1873 {
1874   EmpathyIndividualViewPriv *priv;
1875   GtkTreeSelection *selection;
1876   GtkTreeIter iter;
1877   GtkTreeModel *model;
1878   EmpathyIndividualFeatureFlags flags;
1879
1880   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1881
1882   priv = GET_PRIV (view);
1883
1884   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1885   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1886     {
1887       return 0;
1888     }
1889
1890   gtk_tree_model_get (model, &iter,
1891       EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1892
1893   return flags;
1894 }
1895
1896 gchar *
1897 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1898     gboolean *is_fake_group)
1899 {
1900   EmpathyIndividualViewPriv *priv;
1901   GtkTreeSelection *selection;
1902   GtkTreeIter iter;
1903   GtkTreeModel *model;
1904   gboolean is_group;
1905   gchar *name;
1906   gboolean fake;
1907
1908   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1909
1910   priv = GET_PRIV (view);
1911
1912   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1913   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1914     {
1915       return NULL;
1916     }
1917
1918   gtk_tree_model_get (model, &iter,
1919       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1920       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1921       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1922
1923   if (!is_group)
1924     {
1925       g_free (name);
1926       return NULL;
1927     }
1928
1929   if (is_fake_group != NULL)
1930     *is_fake_group = fake;
1931
1932   return name;
1933 }
1934
1935 static gboolean
1936 individual_view_remove_dialog_show (GtkWindow *parent,
1937     const gchar *message,
1938     const gchar *secondary_text)
1939 {
1940   GtkWidget *dialog;
1941   gboolean res;
1942
1943   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1944       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1945   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1946       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1947       GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1948   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1949       "%s", secondary_text);
1950
1951   gtk_widget_show (dialog);
1952
1953   res = gtk_dialog_run (GTK_DIALOG (dialog));
1954   gtk_widget_destroy (dialog);
1955
1956   return (res == GTK_RESPONSE_YES);
1957 }
1958
1959 static void
1960 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1961     EmpathyIndividualView *view)
1962 {
1963   gchar *group;
1964
1965   group = empathy_individual_view_get_selected_group (view, NULL);
1966   if (group != NULL)
1967     {
1968       gchar *text;
1969       GtkWindow *parent;
1970
1971       text =
1972           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1973           group);
1974       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1975       if (individual_view_remove_dialog_show (parent, _("Removing group"),
1976               text))
1977         {
1978           EmpathyIndividualManager *manager =
1979               empathy_individual_manager_dup_singleton ();
1980           empathy_individual_manager_remove_group (manager, group);
1981           g_object_unref (G_OBJECT (manager));
1982         }
1983
1984       g_free (text);
1985     }
1986
1987   g_free (group);
1988 }
1989
1990 GtkWidget *
1991 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
1992 {
1993   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1994   gchar *group;
1995   GtkWidget *menu;
1996   GtkWidget *item;
1997   GtkWidget *image;
1998   gboolean is_fake_group;
1999
2000   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2001
2002   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2003               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2004     {
2005       return NULL;
2006     }
2007
2008   group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2009   if (!group || is_fake_group)
2010     {
2011       /* We can't alter fake groups */
2012       return NULL;
2013     }
2014
2015   menu = gtk_menu_new ();
2016
2017   /* TODO: implement
2018      if (priv->view_features &
2019      EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2020      item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2021      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2022      gtk_widget_show (item);
2023      g_signal_connect (item, "activate",
2024      G_CALLBACK (individual_view_group_rename_activate_cb),
2025      view);
2026      }
2027    */
2028
2029   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2030     {
2031       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2032       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2033           GTK_ICON_SIZE_MENU);
2034       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2035       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2036       gtk_widget_show (item);
2037       g_signal_connect (item, "activate",
2038           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2039     }
2040
2041   g_free (group);
2042
2043   return menu;
2044 }
2045
2046 static void
2047 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2048     EmpathyIndividualView *view)
2049 {
2050   FolksIndividual *individual;
2051
2052   individual = empathy_individual_view_dup_selected (view);
2053
2054   if (individual != NULL)
2055     {
2056       gchar *text;
2057       GtkWindow *parent;
2058
2059       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2060       text =
2061           g_strdup_printf (_
2062           ("Do you really want to remove the contact '%s'?"),
2063           folks_individual_get_alias (individual));
2064       if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2065               text))
2066         {
2067           EmpathyIndividualManager *manager;
2068
2069           manager = empathy_individual_manager_dup_singleton ();
2070           empathy_individual_manager_remove (manager, individual, "");
2071           g_object_unref (G_OBJECT (manager));
2072         }
2073
2074       g_free (text);
2075       g_object_unref (individual);
2076     }
2077 }
2078
2079 GtkWidget *
2080 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2081 {
2082   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2083   FolksIndividual *individual;
2084   GtkWidget *menu = NULL;
2085   GtkWidget *item;
2086   GtkWidget *image;
2087   EmpathyIndividualManagerFlags flags;
2088
2089   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2090
2091   individual = empathy_individual_view_dup_selected (view);
2092   if (individual == NULL)
2093     {
2094       return NULL;
2095     }
2096
2097   flags = empathy_individual_view_get_flags (view);
2098
2099   menu = empathy_individual_menu_new (individual, priv->individual_features);
2100
2101   /* Remove contact */
2102   if (priv->view_features &
2103       EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2104       flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2105     {
2106
2107       /* create the menu if required, or just add a separator */
2108       if (menu == NULL)
2109         {
2110           menu = gtk_menu_new ();
2111         }
2112       else
2113         {
2114           item = gtk_separator_menu_item_new ();
2115           gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2116           gtk_widget_show (item);
2117         }
2118
2119       /* Remove */
2120       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2121       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2122           GTK_ICON_SIZE_MENU);
2123       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2124       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2125       gtk_widget_show (item);
2126       g_signal_connect (item, "activate",
2127           G_CALLBACK (individual_view_remove_activate_cb), view);
2128     }
2129
2130   g_object_unref (individual);
2131
2132   return menu;
2133 }
2134
2135 void
2136 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2137     EmpathyLiveSearch *search)
2138 {
2139   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2140
2141   /* remove old handlers if old search was not null */
2142   if (priv->search_widget != NULL)
2143     {
2144       g_signal_handlers_disconnect_by_func (view,
2145           individual_view_start_search_cb, NULL);
2146
2147       g_signal_handlers_disconnect_by_func (priv->search_widget,
2148           individual_view_search_text_notify_cb, view);
2149       g_signal_handlers_disconnect_by_func (priv->search_widget,
2150           individual_view_search_activate_cb, view);
2151       g_signal_handlers_disconnect_by_func (priv->search_widget,
2152           individual_view_search_hide_cb, view);
2153       g_signal_handlers_disconnect_by_func (priv->search_widget,
2154           individual_view_search_show_cb, view);
2155       g_object_unref (priv->search_widget);
2156       priv->search_widget = NULL;
2157     }
2158
2159   /* connect handlers if new search is not null */
2160   if (search != NULL)
2161     {
2162       priv->search_widget = g_object_ref (search);
2163
2164       g_signal_connect (view, "start-interactive-search",
2165           G_CALLBACK (individual_view_start_search_cb), NULL);
2166
2167       g_signal_connect (priv->search_widget, "notify::text",
2168           G_CALLBACK (individual_view_search_text_notify_cb), view);
2169       g_signal_connect (priv->search_widget, "activate",
2170           G_CALLBACK (individual_view_search_activate_cb), view);
2171       g_signal_connect (priv->search_widget, "hide",
2172           G_CALLBACK (individual_view_search_hide_cb), view);
2173       g_signal_connect (priv->search_widget, "show",
2174           G_CALLBACK (individual_view_search_show_cb), view);
2175     }
2176 }