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