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