]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
Catch up with convention for getting the view's model.
[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
1493   GtkCellRenderer *cell;
1494   GtkTreeViewColumn *col;
1495   guint i;
1496
1497   priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1498       GTK_TREE_MODEL (priv->store), NULL));
1499   gtk_tree_model_filter_set_visible_func (priv->filter,
1500       individual_view_filter_visible_func, view, NULL);
1501
1502   g_signal_connect (priv->store, "row-has-child-toggled",
1503       G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1504   gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1505       GTK_TREE_MODEL (priv->filter));
1506
1507   tp_g_signal_connect_object (priv->store, "row-changed",
1508       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1509   tp_g_signal_connect_object (priv->store, "row-inserted",
1510       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1511   tp_g_signal_connect_object (priv->store, "row-deleted",
1512       G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1513
1514   /* Setup view */
1515   /* Setting reorderable is a hack that gets us row previews as drag icons
1516      for free.  We override all the drag handlers.  It's tricky to get the
1517      position of the drag icon right in drag_begin.  GtkTreeView has special
1518      voodoo for it, so we let it do the voodoo that he do.
1519    */
1520   g_object_set (view,
1521       "headers-visible", FALSE,
1522       "reorderable", TRUE,
1523       "show-expanders", FALSE,
1524       NULL);
1525
1526   col = gtk_tree_view_column_new ();
1527
1528   /* State */
1529   cell = gtk_cell_renderer_pixbuf_new ();
1530   gtk_tree_view_column_pack_start (col, cell, FALSE);
1531   gtk_tree_view_column_set_cell_data_func (col, cell,
1532       (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1533       view, NULL);
1534
1535   g_object_set (cell,
1536       "xpad", 5,
1537       "ypad", 1,
1538       "visible", FALSE,
1539       NULL);
1540
1541   /* Group icon */
1542   cell = gtk_cell_renderer_pixbuf_new ();
1543   gtk_tree_view_column_pack_start (col, cell, FALSE);
1544   gtk_tree_view_column_set_cell_data_func (col, cell,
1545       (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1546       view, NULL);
1547
1548   g_object_set (cell,
1549       "xpad", 0,
1550       "ypad", 0,
1551       "visible", FALSE,
1552       "width", 16,
1553       "height", 16,
1554       NULL);
1555
1556   /* Name */
1557   cell = empathy_cell_renderer_text_new ();
1558   gtk_tree_view_column_pack_start (col, cell, TRUE);
1559   gtk_tree_view_column_set_cell_data_func (col, cell,
1560       (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1561
1562   gtk_tree_view_column_add_attribute (col, cell,
1563       "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1564   gtk_tree_view_column_add_attribute (col, cell,
1565       "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1566   gtk_tree_view_column_add_attribute (col, cell,
1567       "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1568   gtk_tree_view_column_add_attribute (col, cell,
1569       "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1570   gtk_tree_view_column_add_attribute (col, cell,
1571       "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1572   gtk_tree_view_column_add_attribute (col, cell,
1573       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1574
1575   /* Audio Call Icon */
1576   cell = empathy_cell_renderer_activatable_new ();
1577   gtk_tree_view_column_pack_start (col, cell, FALSE);
1578   gtk_tree_view_column_set_cell_data_func (col, cell,
1579       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1580       view, NULL);
1581
1582   g_object_set (cell, "visible", FALSE, NULL);
1583
1584   g_signal_connect (cell, "path-activated",
1585       G_CALLBACK (individual_view_call_activated_cb), view);
1586
1587   /* Avatar */
1588   cell = gtk_cell_renderer_pixbuf_new ();
1589   gtk_tree_view_column_pack_start (col, cell, FALSE);
1590   gtk_tree_view_column_set_cell_data_func (col, cell,
1591       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1592       view, NULL);
1593
1594   g_object_set (cell,
1595       "xpad", 0,
1596       "ypad", 0,
1597       "visible", FALSE,
1598       "width", 32,
1599       "height", 32,
1600       NULL);
1601
1602   /* Expander */
1603   cell = empathy_cell_renderer_expander_new ();
1604   gtk_tree_view_column_pack_end (col, cell, FALSE);
1605   gtk_tree_view_column_set_cell_data_func (col, cell,
1606       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1607       view, NULL);
1608
1609   /* Actually add the column now we have added all cell renderers */
1610   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1611
1612   /* Drag & Drop. */
1613   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1614     {
1615       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1616     }
1617
1618   for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1619     {
1620       drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1621           FALSE);
1622     }
1623 }
1624
1625 static void
1626 individual_view_set_view_features (EmpathyIndividualView *view,
1627     EmpathyIndividualFeatureFlags features)
1628 {
1629   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1630   gboolean has_tooltip;
1631
1632   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1633
1634   priv->view_features = features;
1635
1636   /* Update DnD source/dest */
1637   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1638     {
1639       gtk_drag_source_set (GTK_WIDGET (view),
1640           GDK_BUTTON1_MASK,
1641           drag_types_source,
1642           G_N_ELEMENTS (drag_types_source),
1643           GDK_ACTION_MOVE | GDK_ACTION_COPY);
1644     }
1645   else
1646     {
1647       gtk_drag_source_unset (GTK_WIDGET (view));
1648
1649     }
1650
1651   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1652     {
1653       gtk_drag_dest_set (GTK_WIDGET (view),
1654           GTK_DEST_DEFAULT_ALL,
1655           drag_types_dest,
1656           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1657     }
1658   else
1659     {
1660       /* FIXME: URI could still be droped depending on FT feature */
1661       gtk_drag_dest_unset (GTK_WIDGET (view));
1662     }
1663
1664   /* Update has-tooltip */
1665   has_tooltip =
1666       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1667   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1668 }
1669
1670 static void
1671 individual_view_dispose (GObject *object)
1672 {
1673   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1674   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1675
1676   if (priv->store != NULL)
1677     {
1678       g_object_unref (priv->store);
1679       priv->store = NULL;
1680     }
1681   if (priv->filter != NULL)
1682     {
1683       g_object_unref (priv->filter);
1684       priv->filter = NULL;
1685     }
1686   if (priv->tooltip_widget != NULL)
1687     {
1688       gtk_widget_destroy (priv->tooltip_widget);
1689       priv->tooltip_widget = NULL;
1690     }
1691   if (priv->file_targets != NULL)
1692     {
1693       gtk_target_list_unref (priv->file_targets);
1694       priv->file_targets = NULL;
1695     }
1696
1697   empathy_individual_view_set_live_search (view, NULL);
1698
1699   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1700 }
1701
1702 static void
1703 individual_view_get_property (GObject *object,
1704     guint param_id,
1705     GValue *value,
1706     GParamSpec *pspec)
1707 {
1708   EmpathyIndividualViewPriv *priv;
1709
1710   priv = GET_PRIV (object);
1711
1712   switch (param_id)
1713     {
1714     case PROP_STORE:
1715       g_value_set_object (value, priv->store);
1716       break;
1717     case PROP_VIEW_FEATURES:
1718       g_value_set_flags (value, priv->view_features);
1719       break;
1720     case PROP_INDIVIDUAL_FEATURES:
1721       g_value_set_flags (value, priv->individual_features);
1722       break;
1723     default:
1724       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1725       break;
1726     };
1727 }
1728
1729 static void
1730 individual_view_set_property (GObject *object,
1731     guint param_id,
1732     const GValue *value,
1733     GParamSpec *pspec)
1734 {
1735   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1736   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1737
1738   switch (param_id)
1739     {
1740     case PROP_STORE:
1741       priv->store = g_value_dup_object (value);
1742       break;
1743     case PROP_VIEW_FEATURES:
1744       individual_view_set_view_features (view, g_value_get_flags (value));
1745       break;
1746     case PROP_INDIVIDUAL_FEATURES:
1747       priv->individual_features = g_value_get_flags (value);
1748       break;
1749     default:
1750       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1751       break;
1752     };
1753 }
1754
1755 static void
1756 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1757 {
1758   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1759   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1760   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1761
1762   object_class->constructed = individual_view_constructed;
1763   object_class->dispose = individual_view_dispose;
1764   object_class->get_property = individual_view_get_property;
1765   object_class->set_property = individual_view_set_property;
1766
1767   widget_class->drag_data_received = individual_view_drag_data_received;
1768   widget_class->drag_drop = individual_view_drag_drop;
1769   widget_class->drag_begin = individual_view_drag_begin;
1770   widget_class->drag_data_get = individual_view_drag_data_get;
1771   widget_class->drag_end = individual_view_drag_end;
1772   widget_class->drag_motion = individual_view_drag_motion;
1773
1774   /* We use the class method to let user of this widget to connect to
1775    * the signal and stop emission of the signal so the default handler
1776    * won't be called. */
1777   tree_view_class->row_activated = individual_view_row_activated;
1778
1779   signals[DRAG_CONTACT_RECEIVED] =
1780       g_signal_new ("drag-contact-received",
1781       G_OBJECT_CLASS_TYPE (klass),
1782       G_SIGNAL_RUN_LAST,
1783       0,
1784       NULL, NULL,
1785       _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1786       G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1787
1788   g_object_class_install_property (object_class,
1789       PROP_STORE,
1790       g_param_spec_object ("store",
1791           "The store of the view",
1792           "The store of the view",
1793           EMPATHY_TYPE_INDIVIDUAL_STORE,
1794           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1795   g_object_class_install_property (object_class,
1796       PROP_VIEW_FEATURES,
1797       g_param_spec_flags ("view-features",
1798           "Features of the view",
1799           "Flags for all enabled features",
1800           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1801           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1802   g_object_class_install_property (object_class,
1803       PROP_INDIVIDUAL_FEATURES,
1804       g_param_spec_flags ("individual-features",
1805           "Features of the contact menu",
1806           "Flags for all enabled features for the menu",
1807           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1808           EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1809
1810   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1811 }
1812
1813 static void
1814 empathy_individual_view_init (EmpathyIndividualView *view)
1815 {
1816   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1817       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1818
1819   view->priv = priv;
1820   /* Get saved group states. */
1821   empathy_contact_groups_get_all ();
1822
1823   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1824       empathy_individual_store_row_separator_func, NULL, NULL);
1825
1826   /* Set up drag target lists. */
1827   priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1828       G_N_ELEMENTS (drag_types_dest_file));
1829
1830   /* Connect to tree view signals rather than override. */
1831   g_signal_connect (view, "button-press-event",
1832       G_CALLBACK (individual_view_button_press_event_cb), NULL);
1833   g_signal_connect (view, "key-press-event",
1834       G_CALLBACK (individual_view_key_press_event_cb), NULL);
1835   g_signal_connect (view, "row-expanded",
1836       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1837       GINT_TO_POINTER (TRUE));
1838   g_signal_connect (view, "row-collapsed",
1839       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1840       GINT_TO_POINTER (FALSE));
1841   g_signal_connect (view, "query-tooltip",
1842       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1843 }
1844
1845 EmpathyIndividualView *
1846 empathy_individual_view_new (EmpathyIndividualStore *store,
1847     EmpathyIndividualViewFeatureFlags view_features,
1848     EmpathyIndividualFeatureFlags individual_features)
1849 {
1850   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1851
1852   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1853       "store", store,
1854       "individual-features", individual_features,
1855       "view-features", view_features, NULL);
1856 }
1857
1858 FolksIndividual *
1859 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1860 {
1861   EmpathyIndividualViewPriv *priv;
1862   GtkTreeSelection *selection;
1863   GtkTreeIter iter;
1864   GtkTreeModel *model;
1865   FolksIndividual *individual;
1866
1867   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1868
1869   priv = GET_PRIV (view);
1870
1871   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1872   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1873     {
1874       return NULL;
1875     }
1876
1877   gtk_tree_model_get (model, &iter,
1878       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1879
1880   return individual;
1881 }
1882
1883 EmpathyIndividualManagerFlags
1884 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1885 {
1886   EmpathyIndividualViewPriv *priv;
1887   GtkTreeSelection *selection;
1888   GtkTreeIter iter;
1889   GtkTreeModel *model;
1890   EmpathyIndividualFeatureFlags flags;
1891
1892   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1893
1894   priv = GET_PRIV (view);
1895
1896   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1897   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1898     {
1899       return 0;
1900     }
1901
1902   gtk_tree_model_get (model, &iter,
1903       EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1904
1905   return flags;
1906 }
1907
1908 gchar *
1909 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1910     gboolean *is_fake_group)
1911 {
1912   EmpathyIndividualViewPriv *priv;
1913   GtkTreeSelection *selection;
1914   GtkTreeIter iter;
1915   GtkTreeModel *model;
1916   gboolean is_group;
1917   gchar *name;
1918   gboolean fake;
1919
1920   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1921
1922   priv = GET_PRIV (view);
1923
1924   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1925   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1926     {
1927       return NULL;
1928     }
1929
1930   gtk_tree_model_get (model, &iter,
1931       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1932       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1933       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1934
1935   if (!is_group)
1936     {
1937       g_free (name);
1938       return NULL;
1939     }
1940
1941   if (is_fake_group != NULL)
1942     *is_fake_group = fake;
1943
1944   return name;
1945 }
1946
1947 static gboolean
1948 individual_view_remove_dialog_show (GtkWindow *parent,
1949     const gchar *message,
1950     const gchar *secondary_text)
1951 {
1952   GtkWidget *dialog;
1953   gboolean res;
1954
1955   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1956       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1957   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1958       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1959       GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1960   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1961       "%s", secondary_text);
1962
1963   gtk_widget_show (dialog);
1964
1965   res = gtk_dialog_run (GTK_DIALOG (dialog));
1966   gtk_widget_destroy (dialog);
1967
1968   return (res == GTK_RESPONSE_YES);
1969 }
1970
1971 static void
1972 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1973     EmpathyIndividualView *view)
1974 {
1975   gchar *group;
1976
1977   group = empathy_individual_view_get_selected_group (view, NULL);
1978   if (group)
1979     {
1980       gchar *text;
1981       GtkWindow *parent;
1982
1983       text =
1984           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1985           group);
1986       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1987       if (individual_view_remove_dialog_show (parent, _("Removing group"),
1988               text))
1989         {
1990           /* TODO: implement */
1991           DEBUG ("removing group unimplemented");
1992         }
1993
1994       g_free (text);
1995     }
1996
1997   g_free (group);
1998 }
1999
2000 GtkWidget *
2001 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2002 {
2003   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2004   gchar *group;
2005   GtkWidget *menu;
2006   GtkWidget *item;
2007   GtkWidget *image;
2008   gboolean is_fake_group;
2009
2010   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2011
2012   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2013               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2014     {
2015       return NULL;
2016     }
2017
2018   group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2019   if (!group || is_fake_group)
2020     {
2021       /* We can't alter fake groups */
2022       return NULL;
2023     }
2024
2025   menu = gtk_menu_new ();
2026
2027   /* TODO: implement
2028      if (priv->view_features &
2029      EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2030      item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2031      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2032      gtk_widget_show (item);
2033      g_signal_connect (item, "activate",
2034      G_CALLBACK (individual_view_group_rename_activate_cb),
2035      view);
2036      }
2037    */
2038
2039   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2040     {
2041       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2042       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2043           GTK_ICON_SIZE_MENU);
2044       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2045       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2046       gtk_widget_show (item);
2047       g_signal_connect (item, "activate",
2048           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2049     }
2050
2051   g_free (group);
2052
2053   return menu;
2054 }
2055
2056 static void
2057 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2058     EmpathyIndividualView *view)
2059 {
2060   FolksIndividual *individual;
2061
2062   individual = empathy_individual_view_dup_selected (view);
2063
2064   if (individual)
2065     {
2066       gchar *text;
2067       GtkWindow *parent;
2068
2069       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2070       text =
2071           g_strdup_printf (_
2072           ("Do you really want to remove the contact '%s'?"),
2073           folks_individual_get_alias (individual));
2074       if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2075               text))
2076         {
2077           EmpathyIndividualManager *manager;
2078
2079           manager = empathy_individual_manager_dup_singleton ();
2080           empathy_individual_manager_remove (manager, individual, "");
2081           g_object_unref (G_OBJECT (manager));
2082         }
2083
2084       g_free (text);
2085       g_object_unref (individual);
2086     }
2087 }
2088
2089 GtkWidget *
2090 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2091 {
2092   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2093   FolksIndividual *individual;
2094   GtkWidget *menu = NULL;
2095   GtkWidget *item;
2096   GtkWidget *image;
2097   EmpathyIndividualManagerFlags flags;
2098
2099   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2100
2101   individual = empathy_individual_view_dup_selected (view);
2102   if (!individual)
2103     {
2104       return NULL;
2105     }
2106   flags = empathy_individual_view_get_flags (view);
2107
2108   menu = empathy_individual_menu_new (individual, priv->individual_features);
2109
2110   /* Remove contact */
2111   if (priv->view_features &
2112       EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2113       flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2114     {
2115
2116       /* create the menu if required, or just add a separator */
2117       if (!menu)
2118         {
2119           menu = gtk_menu_new ();
2120         }
2121       else
2122         {
2123           item = gtk_separator_menu_item_new ();
2124           gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2125           gtk_widget_show (item);
2126         }
2127
2128       /* Remove */
2129       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2130       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2131           GTK_ICON_SIZE_MENU);
2132       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2133       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2134       gtk_widget_show (item);
2135       g_signal_connect (item, "activate",
2136           G_CALLBACK (individual_view_remove_activate_cb), view);
2137     }
2138
2139   g_object_unref (individual);
2140
2141   return menu;
2142 }
2143
2144 void
2145 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2146     EmpathyLiveSearch *search)
2147 {
2148   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2149
2150   /* remove old handlers if old search was not null */
2151   if (priv->search_widget != NULL)
2152     {
2153       g_signal_handlers_disconnect_by_func (view,
2154           individual_view_start_search_cb, NULL);
2155
2156       g_signal_handlers_disconnect_by_func (priv->search_widget,
2157           individual_view_search_text_notify_cb, view);
2158       g_signal_handlers_disconnect_by_func (priv->search_widget,
2159           individual_view_search_activate_cb, view);
2160       g_signal_handlers_disconnect_by_func (priv->search_widget,
2161           individual_view_search_hide_cb, view);
2162       g_signal_handlers_disconnect_by_func (priv->search_widget,
2163           individual_view_search_show_cb, view);
2164       g_object_unref (priv->search_widget);
2165       priv->search_widget = NULL;
2166     }
2167
2168   /* connect handlers if new search is not null */
2169   if (search != NULL)
2170     {
2171       priv->search_widget = g_object_ref (search);
2172
2173       g_signal_connect (view, "start-interactive-search",
2174           G_CALLBACK (individual_view_start_search_cb), NULL);
2175
2176       g_signal_connect (priv->search_widget, "notify::text",
2177           G_CALLBACK (individual_view_search_text_notify_cb), view);
2178       g_signal_connect (priv->search_widget, "activate",
2179           G_CALLBACK (individual_view_search_activate_cb), view);
2180       g_signal_connect (priv->search_widget, "hide",
2181           G_CALLBACK (individual_view_search_hide_cb), view);
2182       g_signal_connect (priv->search_widget, "show",
2183           G_CALLBACK (individual_view_search_show_cb), view);
2184     }
2185 }