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