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