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