]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
c143ef23209e84c6d7902d4b55fb1c382cda82ec
[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 <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
37
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
40
41 #include <libempathy/empathy-connection-aggregator.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-request-util.h>
45 #include <libempathy/empathy-utils.h>
46
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-individual-edit-dialog.h"
51 #include "empathy-individual-dialogs.h"
52 #include "empathy-images.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58
59 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
60 #include <libempathy/empathy-debug.h>
61
62 /* Active users are those which have recently changed state
63  * (e.g. online, offline or from normal to a busy state).
64  */
65
66 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
67 typedef struct
68 {
69   EmpathyIndividualStore *store;
70   GtkTreeRowReference *drag_row;
71   EmpathyIndividualViewFeatureFlags view_features;
72   EmpathyIndividualFeatureFlags individual_features;
73   GtkWidget *tooltip_widget;
74
75   gboolean show_offline;
76   gboolean show_untrusted;
77   gboolean show_uninteresting;
78
79   GtkTreeModelFilter *filter;
80   GtkWidget *search_widget;
81
82   guint expand_groups_idle_handler;
83   /* owned string (group name) -> bool (whether to expand/contract) */
84   GHashTable *expand_groups;
85
86   /* Auto scroll */
87   guint auto_scroll_timeout_id;
88   /* Distance between mouse pointer and the nearby border. Negative when
89      scrolling updards.*/
90   gint distance;
91
92   GtkTreeModelFilterVisibleFunc custom_filter;
93   gpointer custom_filter_data;
94
95   GtkCellRenderer *text_renderer;
96 } EmpathyIndividualViewPriv;
97
98 typedef struct
99 {
100   EmpathyIndividualView *view;
101   GtkTreePath *path;
102   guint timeout_id;
103 } DragMotionData;
104
105 typedef struct
106 {
107   EmpathyIndividualView *view;
108   FolksIndividual *individual;
109   gboolean remove;
110 } ShowActiveData;
111
112 enum
113 {
114   PROP_0,
115   PROP_STORE,
116   PROP_VIEW_FEATURES,
117   PROP_INDIVIDUAL_FEATURES,
118   PROP_SHOW_OFFLINE,
119   PROP_SHOW_UNTRUSTED,
120   PROP_SHOW_UNINTERESTING,
121 };
122
123 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
124  * specific EmpathyContacts (between/in/out of Individuals) */
125 typedef enum
126 {
127   DND_DRAG_TYPE_UNKNOWN = -1,
128   DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
129   DND_DRAG_TYPE_PERSONA_ID,
130   DND_DRAG_TYPE_URI_LIST,
131   DND_DRAG_TYPE_STRING,
132 } DndDragType;
133
134 #define DRAG_TYPE(T,I) \
135   { (gchar *) T, 0, I }
136
137 static const GtkTargetEntry drag_types_dest[] = {
138   DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
139   DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
140   DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
141   DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
142   DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
143   DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
144 };
145
146 static const GtkTargetEntry drag_types_source[] = {
147   DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
148 };
149
150 #undef DRAG_TYPE
151
152 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
153
154 enum
155 {
156   DRAG_INDIVIDUAL_RECEIVED,
157   DRAG_PERSONA_RECEIVED,
158   LAST_SIGNAL
159 };
160
161 static guint signals[LAST_SIGNAL];
162
163 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
164     GTK_TYPE_TREE_VIEW);
165
166 static void
167 individual_view_tooltip_destroy_cb (GtkWidget *widget,
168     EmpathyIndividualView *view)
169 {
170   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
171
172   tp_clear_object (&priv->tooltip_widget);
173 }
174
175 static gboolean
176 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
177     gint x,
178     gint y,
179     gboolean keyboard_mode,
180     GtkTooltip *tooltip,
181     gpointer user_data)
182 {
183   EmpathyIndividualViewPriv *priv;
184   FolksIndividual *individual;
185   GtkTreeModel *model;
186   GtkTreeIter iter;
187   GtkTreePath *path;
188   static gint running = 0;
189   gboolean ret = FALSE;
190
191   priv = GET_PRIV (view);
192
193   /* Avoid an infinite loop. See GNOME bug #574377 */
194   if (running > 0)
195     return FALSE;
196
197   running++;
198
199   /* Don't show the tooltip if there's already a popup menu */
200   if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
201     goto OUT;
202
203   if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
204           keyboard_mode, &model, &path, &iter))
205     goto OUT;
206
207   gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
208   gtk_tree_path_free (path);
209
210   gtk_tree_model_get (model, &iter,
211       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
212       -1);
213   if (individual == NULL)
214     goto OUT;
215
216   if (priv->tooltip_widget == NULL)
217     {
218       priv->tooltip_widget = empathy_individual_widget_new (individual,
219           EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
220           EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
221           EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
222       gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
223       g_object_ref (priv->tooltip_widget);
224
225       tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
226           G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
227
228       gtk_widget_show (priv->tooltip_widget);
229     }
230   else
231     {
232       empathy_individual_widget_set_individual (
233         EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
234     }
235
236   gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
237   ret = TRUE;
238
239   g_object_unref (individual);
240 OUT:
241   running--;
242
243   return ret;
244 }
245
246 static void
247 groups_change_group_cb (GObject *source,
248     GAsyncResult *result,
249     gpointer user_data)
250 {
251   FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
252   GError *error = NULL;
253
254   folks_group_details_change_group_finish (group_details, result, &error);
255   if (error != NULL)
256     {
257       g_warning ("failed to change group: %s", error->message);
258       g_clear_error (&error);
259     }
260 }
261
262 static gboolean
263 group_can_be_modified (const gchar *name,
264     gboolean is_fake_group,
265     gboolean adding)
266 {
267   /* Real groups can always be modified */
268   if (!is_fake_group)
269     return TRUE;
270
271   /* The favorite fake group can be modified so users can
272    * add/remove favorites using DnD */
273   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
274     return TRUE;
275
276   /* We can remove contacts from the 'ungrouped' fake group */
277   if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
278     return TRUE;
279
280   return FALSE;
281 }
282
283 static gboolean
284 individual_view_individual_drag_received (GtkWidget *self,
285     GdkDragContext *context,
286     GtkTreeModel *model,
287     GtkTreePath *path,
288     GtkSelectionData *selection)
289 {
290   EmpathyIndividualViewPriv *priv;
291   EmpathyIndividualManager *manager = NULL;
292   FolksIndividual *individual;
293   GtkTreePath *source_path;
294   const gchar *sel_data;
295   gchar *new_group = NULL;
296   gchar *old_group = NULL;
297   gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
298
299   priv = GET_PRIV (self);
300
301   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
302   new_group = empathy_individual_store_get_parent_group (model, path,
303       NULL, &new_group_is_fake);
304
305   if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
306     goto finished;
307
308   /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
309    * feature. Otherwise, we just add the dropped contact to whichever group
310    * they were dropped in, and don't remove them from their old group. This
311    * allows for Individual views which shouldn't allow Individuals to have
312    * their groups changed, and also for dragging Individuals between Individual
313    * views. */
314   if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
315       priv->drag_row != NULL)
316     {
317       source_path = gtk_tree_row_reference_get_path (priv->drag_row);
318       if (source_path)
319         {
320           old_group =
321               empathy_individual_store_get_parent_group (model, source_path,
322               NULL, &old_group_is_fake);
323           gtk_tree_path_free (source_path);
324         }
325
326       if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
327         goto finished;
328
329       if (!tp_strdiff (old_group, new_group))
330         goto finished;
331     }
332   else if (priv->drag_row != NULL)
333     {
334       /* We don't allow changing Individuals' groups, and this Individual was
335        * dragged from another group in *this* Individual view, so we disallow
336        * the drop. */
337       goto finished;
338     }
339
340   /* XXX: for contacts, we used to ensure the account, create the contact
341    * factory, and then wait on the contacts. But they should already be
342    * created by this point */
343
344   manager = empathy_individual_manager_dup_singleton ();
345   individual = empathy_individual_manager_lookup_member (manager, sel_data);
346
347   if (individual == NULL)
348     {
349       DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
350       goto finished;
351     }
352
353   /* FIXME: We should probably wait for the cb before calling
354    * gtk_drag_finish */
355
356   /* Emit a signal notifying of the drag. We change the Individual's groups in
357    * the default signal handler. */
358   g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
359       gdk_drag_context_get_selected_action (context), individual, new_group,
360       old_group);
361
362   retval = TRUE;
363
364 finished:
365   tp_clear_object (&manager);
366   g_free (old_group);
367   g_free (new_group);
368
369   return retval;
370 }
371
372 static void
373 real_drag_individual_received_cb (EmpathyIndividualView *self,
374     GdkDragAction action,
375     FolksIndividual *individual,
376     const gchar *new_group,
377     const gchar *old_group)
378 {
379   DEBUG ("individual %s dragged from '%s' to '%s'",
380       folks_individual_get_id (individual), old_group, new_group);
381
382   if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
383     {
384       /* Mark contact as favourite */
385       folks_favourite_details_set_is_favourite (
386           FOLKS_FAVOURITE_DETAILS (individual), TRUE);
387       return;
388     }
389
390   if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
391     {
392       /* Remove contact as favourite */
393       folks_favourite_details_set_is_favourite (
394           FOLKS_FAVOURITE_DETAILS (individual), FALSE);
395
396       /* Don't try to remove it */
397       old_group = NULL;
398     }
399
400   if (new_group != NULL)
401     {
402       folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
403           new_group, TRUE, groups_change_group_cb, NULL);
404     }
405
406   if (old_group != NULL && action == GDK_ACTION_MOVE)
407     {
408       folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
409           old_group, FALSE, groups_change_group_cb, NULL);
410     }
411 }
412
413 static gboolean
414 individual_view_persona_drag_received (GtkWidget *self,
415     GdkDragContext *context,
416     GtkTreeModel *model,
417     GtkTreePath *path,
418     GtkSelectionData *selection)
419 {
420   EmpathyIndividualManager *manager = NULL;
421   FolksIndividual *individual = NULL;
422   FolksPersona *persona = NULL;
423   const gchar *persona_uid;
424   GList *individuals, *l;
425   GeeIterator *iter = NULL;
426   gboolean retval = FALSE;
427
428   persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
429
430   /* FIXME: This is slow, but the only way to find the Persona we're having
431    * dropped on us. */
432   manager = empathy_individual_manager_dup_singleton ();
433   individuals = empathy_individual_manager_get_members (manager);
434
435   for (l = individuals; l != NULL; l = l->next)
436     {
437       GeeSet *personas;
438
439       personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
440       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
441       while (gee_iterator_next (iter))
442         {
443           FolksPersona *persona_cur = gee_iterator_get (iter);
444
445           if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
446             {
447               /* takes ownership of the ref */
448               persona = persona_cur;
449               individual = g_object_ref (l->data);
450               goto got_persona;
451             }
452           g_clear_object (&persona_cur);
453         }
454       g_clear_object (&iter);
455     }
456
457 got_persona:
458   g_clear_object (&iter);
459   g_list_free (individuals);
460
461   if (persona == NULL || individual == NULL)
462     {
463       DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
464     }
465   else
466     {
467       /* Emit a signal notifying of the drag. We change the Individual's groups in
468        * the default signal handler. */
469       g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
470           gdk_drag_context_get_selected_action (context), persona, individual,
471           &retval);
472     }
473
474   tp_clear_object (&manager);
475   tp_clear_object (&persona);
476   tp_clear_object (&individual);
477
478   return retval;
479 }
480
481 static gboolean
482 individual_view_file_drag_received (GtkWidget *view,
483     GdkDragContext *context,
484     GtkTreeModel *model,
485     GtkTreePath *path,
486     GtkSelectionData *selection)
487 {
488   GtkTreeIter iter;
489   const gchar *sel_data;
490   FolksIndividual *individual;
491   EmpathyContact *contact;
492
493   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
494
495   gtk_tree_model_get_iter (model, &iter, path);
496   gtk_tree_model_get (model, &iter,
497       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
498   if (individual == NULL)
499     return FALSE;
500
501   contact = empathy_contact_dup_from_folks_individual (individual);
502   empathy_send_file_from_uri_list (contact, sel_data);
503
504   g_object_unref (individual);
505   tp_clear_object (&contact);
506
507   return TRUE;
508 }
509
510 static void
511 individual_view_drag_data_received (GtkWidget *view,
512     GdkDragContext *context,
513     gint x,
514     gint y,
515     GtkSelectionData *selection,
516     guint info,
517     guint time_)
518 {
519   GtkTreeModel *model;
520   gboolean is_row;
521   GtkTreeViewDropPosition position;
522   GtkTreePath *path;
523   gboolean success = TRUE;
524
525   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
526
527   /* Get destination group information. */
528   is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
529       x, y, &path, &position);
530   if (!is_row)
531     {
532       success = FALSE;
533     }
534   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
535     {
536       success = individual_view_individual_drag_received (view,
537           context, model, path, selection);
538     }
539   else if (info == DND_DRAG_TYPE_PERSONA_ID)
540     {
541       success = individual_view_persona_drag_received (view, context, model,
542           path, selection);
543     }
544   else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
545     {
546       success = individual_view_file_drag_received (view,
547           context, model, path, selection);
548     }
549
550   gtk_tree_path_free (path);
551   gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
552 }
553
554 static gboolean
555 individual_view_drag_motion_cb (DragMotionData *data)
556 {
557   if (data->view != NULL)
558     {
559       gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
560       g_object_remove_weak_pointer (G_OBJECT (data->view),
561           (gpointer *) &data->view);
562     }
563
564   data->timeout_id = 0;
565
566   return FALSE;
567 }
568
569 /* Minimum distance between the mouse pointer and a horizontal border when we
570    start auto scrolling. */
571 #define AUTO_SCROLL_MARGIN_SIZE 20
572 /* How far to scroll per one tick. */
573 #define AUTO_SCROLL_PITCH       10
574
575 static gboolean
576 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
577 {
578         EmpathyIndividualViewPriv *priv = GET_PRIV (self);
579         GtkAdjustment         *adj;
580         gdouble                new_value;
581
582         adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
583
584         if (priv->distance < 0)
585                 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
586         else
587                 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
588
589         new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
590                 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
591
592         gtk_adjustment_set_value (adj, new_value);
593
594         return TRUE;
595 }
596
597 static gboolean
598 individual_view_drag_motion (GtkWidget *widget,
599     GdkDragContext *context,
600     gint x,
601     gint y,
602     guint time_)
603 {
604   EmpathyIndividualViewPriv *priv;
605   GtkTreeModel *model;
606   GdkAtom target;
607   GtkTreeIter iter;
608   static DragMotionData *dm = NULL;
609   GtkTreePath *path;
610   gboolean is_row;
611   gboolean is_different = FALSE;
612   gboolean cleanup = TRUE;
613   gboolean retval = TRUE;
614   GtkAllocation allocation;
615   guint i;
616   DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
617
618   priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
619   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
620
621
622   if (priv->auto_scroll_timeout_id != 0)
623     {
624       g_source_remove (priv->auto_scroll_timeout_id);
625       priv->auto_scroll_timeout_id = 0;
626     }
627
628   gtk_widget_get_allocation (widget, &allocation);
629
630   if (y < AUTO_SCROLL_MARGIN_SIZE ||
631       y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
632     {
633       if (y < AUTO_SCROLL_MARGIN_SIZE)
634         priv->distance = MIN (-y, -1);
635       else
636         priv->distance = MAX (allocation.height - y, 1);
637
638       priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
639           (GSourceFunc) individual_view_auto_scroll_cb, widget);
640     }
641
642   is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
643       x, y, &path, NULL, NULL, NULL);
644
645   cleanup &= (dm == NULL);
646
647   if (is_row)
648     {
649       cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
650       is_different = ((dm == NULL) || ((dm != NULL)
651               && gtk_tree_path_compare (dm->path, path) != 0));
652     }
653   else
654     cleanup &= FALSE;
655
656   if (path == NULL)
657     {
658       /* Coordinates don't point to an actual row, so make sure the pointer
659          and highlighting don't indicate that a drag is possible.
660        */
661       gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
662       gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
663       return FALSE;
664     }
665   target = gtk_drag_dest_find_target (widget, context, NULL);
666   gtk_tree_model_get_iter (model, &iter, path);
667
668   /* Determine the DndDragType of the data */
669   for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
670     {
671       if (target == drag_atoms_dest[i])
672         {
673           drag_type = drag_types_dest[i].info;
674           break;
675         }
676     }
677
678   if (drag_type == DND_DRAG_TYPE_URI_LIST ||
679       drag_type == DND_DRAG_TYPE_STRING)
680     {
681       /* This is a file drag, and it can only be dropped on contacts,
682        * not groups.
683        * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
684        * even if we have a valid target. */
685       FolksIndividual *individual = NULL;
686       EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
687
688       if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
689         {
690           gtk_tree_model_get (model, &iter,
691               EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
692               -1);
693         }
694
695       if (individual != NULL)
696         {
697           EmpathyContact *contact = NULL;
698
699           contact = empathy_contact_dup_from_folks_individual (individual);
700           if (contact != NULL)
701             caps = empathy_contact_get_capabilities (contact);
702
703           tp_clear_object (&contact);
704         }
705
706       if (individual != NULL &&
707           folks_presence_details_is_online (
708               FOLKS_PRESENCE_DETAILS (individual)) &&
709           (caps & EMPATHY_CAPABILITIES_FT))
710         {
711           gdk_drag_status (context, GDK_ACTION_COPY, time_);
712           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
713               path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
714         }
715       else
716         {
717           gdk_drag_status (context, 0, time_);
718           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
719           retval = FALSE;
720         }
721
722       if (individual != NULL)
723         g_object_unref (individual);
724     }
725   else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
726       (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
727        priv->drag_row == NULL)) ||
728       (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
729        priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
730     {
731       /* If target != GDK_NONE, then we have a contact (individual or persona)
732          drag.  If we're pointing to a group, highlight it.  Otherwise, if the
733          contact we're pointing to is in a group, highlight that.  Otherwise,
734          set the drag position to before the first row for a drag into
735          the "non-group" at the top.
736          If it's an Individual:
737            We only highlight things if the contact is from a different
738            Individual view, or if this Individual view has
739            FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
740            which don't have FEATURE_GROUPS_CHANGE, but do have
741            FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
742          If it's a Persona:
743            We only highlight things if we have FEATURE_PERSONA_DROP.
744        */
745       GtkTreeIter group_iter;
746       gboolean is_group;
747       GtkTreePath *group_path;
748       gtk_tree_model_get (model, &iter,
749           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
750       if (is_group)
751         {
752           group_iter = iter;
753         }
754       else
755         {
756           if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
757             gtk_tree_model_get (model, &group_iter,
758                 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
759         }
760       if (is_group)
761         {
762           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
763           group_path = gtk_tree_model_get_path (model, &group_iter);
764           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
765               group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
766           gtk_tree_path_free (group_path);
767         }
768       else
769         {
770           group_path = gtk_tree_path_new_first ();
771           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
772           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
773               group_path, GTK_TREE_VIEW_DROP_BEFORE);
774         }
775     }
776
777   if (!is_different && !cleanup)
778     return retval;
779
780   if (dm)
781     {
782       gtk_tree_path_free (dm->path);
783       if (dm->timeout_id)
784         {
785           g_source_remove (dm->timeout_id);
786         }
787
788       g_free (dm);
789
790       dm = NULL;
791     }
792
793   if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
794     {
795       dm = g_new0 (DragMotionData, 1);
796
797       dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
798       g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
799       dm->path = gtk_tree_path_copy (path);
800
801       dm->timeout_id = g_timeout_add_seconds (1,
802           (GSourceFunc) individual_view_drag_motion_cb, dm);
803     }
804
805   return retval;
806 }
807
808 static void
809 individual_view_drag_begin (GtkWidget *widget,
810     GdkDragContext *context)
811 {
812   EmpathyIndividualViewPriv *priv;
813   GtkTreeSelection *selection;
814   GtkTreeModel *model;
815   GtkTreePath *path;
816   GtkTreeIter iter;
817
818   priv = GET_PRIV (widget);
819
820   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
821   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
822     return;
823
824   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
825       context);
826
827   path = gtk_tree_model_get_path (model, &iter);
828   priv->drag_row = gtk_tree_row_reference_new (model, path);
829   gtk_tree_path_free (path);
830 }
831
832 static void
833 individual_view_drag_data_get (GtkWidget *widget,
834     GdkDragContext *context,
835     GtkSelectionData *selection,
836     guint info,
837     guint time_)
838 {
839   EmpathyIndividualViewPriv *priv;
840   GtkTreePath *src_path;
841   GtkTreeIter iter;
842   GtkTreeModel *model;
843   FolksIndividual *individual;
844   const gchar *individual_id;
845
846   priv = GET_PRIV (widget);
847
848   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
849   if (priv->drag_row == NULL)
850     return;
851
852   src_path = gtk_tree_row_reference_get_path (priv->drag_row);
853   if (src_path == NULL)
854     return;
855
856   if (!gtk_tree_model_get_iter (model, &iter, src_path))
857     {
858       gtk_tree_path_free (src_path);
859       return;
860     }
861
862   gtk_tree_path_free (src_path);
863
864   individual =
865       empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
866   if (individual == NULL)
867     return;
868
869   individual_id = folks_individual_get_id (individual);
870
871   if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
872     {
873       gtk_selection_data_set (selection,
874           gdk_atom_intern ("text/x-individual-id", FALSE), 8,
875           (guchar *) individual_id, strlen (individual_id) + 1);
876     }
877
878   g_object_unref (individual);
879 }
880
881 static void
882 individual_view_drag_end (GtkWidget *widget,
883     GdkDragContext *context)
884 {
885   EmpathyIndividualViewPriv *priv;
886
887   priv = GET_PRIV (widget);
888
889   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
890       context);
891
892   if (priv->drag_row)
893     {
894       gtk_tree_row_reference_free (priv->drag_row);
895       priv->drag_row = NULL;
896     }
897
898   if (priv->auto_scroll_timeout_id != 0)
899     {
900       g_source_remove (priv->auto_scroll_timeout_id);
901       priv->auto_scroll_timeout_id = 0;
902     }
903 }
904
905 static gboolean
906 individual_view_drag_drop (GtkWidget *widget,
907     GdkDragContext *drag_context,
908     gint x,
909     gint y,
910     guint time_)
911 {
912   return FALSE;
913 }
914
915 typedef struct
916 {
917   EmpathyIndividualView *view;
918   guint button;
919   guint32 time;
920 } MenuPopupData;
921
922 static void
923 menu_deactivate_cb (GtkMenuShell *menushell,
924     gpointer user_data)
925 {
926   /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
927   g_signal_handlers_disconnect_by_func (menushell,
928       menu_deactivate_cb, user_data);
929
930   gtk_menu_detach (GTK_MENU (menushell));
931 }
932
933 static gboolean
934 individual_view_popup_menu_idle_cb (gpointer user_data)
935 {
936   MenuPopupData *data = user_data;
937   GtkWidget *menu;
938
939   menu = empathy_individual_view_get_individual_menu (data->view);
940   if (menu == NULL)
941     menu = empathy_individual_view_get_group_menu (data->view);
942
943   if (menu != NULL)
944     {
945       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
946           NULL);
947       gtk_widget_show (menu);
948       gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
949           data->time);
950
951       /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
952        * floating ref. We can either wait that the treeview releases its ref
953        * when it will be destroyed (when leaving Empathy) or explicitely
954        * detach the menu when it's not displayed any more.
955        * We go for the latter as we don't want to keep useless menus in memory
956        * during the whole lifetime of Empathy. */
957       g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
958           NULL);
959     }
960
961   g_slice_free (MenuPopupData, data);
962
963   return FALSE;
964 }
965
966 static gboolean
967 individual_view_button_press_event_cb (EmpathyIndividualView *view,
968     GdkEventButton *event,
969     gpointer user_data)
970 {
971   if (event->button == 3)
972     {
973       MenuPopupData *data;
974
975       data = g_slice_new (MenuPopupData);
976       data->view = view;
977       data->button = event->button;
978       data->time = event->time;
979       g_idle_add (individual_view_popup_menu_idle_cb, data);
980     }
981
982   return FALSE;
983 }
984
985 static gboolean
986 individual_view_key_press_event_cb (EmpathyIndividualView *view,
987     GdkEventKey *event,
988     gpointer user_data)
989 {
990   if (event->keyval == GDK_KEY_Menu)
991     {
992       MenuPopupData *data;
993
994       data = g_slice_new (MenuPopupData);
995       data->view = view;
996       data->button = 0;
997       data->time = event->time;
998       g_idle_add (individual_view_popup_menu_idle_cb, data);
999     } else if (event->keyval == GDK_KEY_F2) {
1000         FolksIndividual *individual;
1001
1002         g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
1003
1004         individual = empathy_individual_view_dup_selected (view);
1005         if (individual == NULL)
1006             return FALSE;
1007
1008         empathy_individual_edit_dialog_show (individual, NULL);
1009
1010         g_object_unref (individual);
1011     }
1012
1013   return FALSE;
1014 }
1015
1016 static void
1017 individual_view_row_activated (GtkTreeView *view,
1018     GtkTreePath *path,
1019     GtkTreeViewColumn *column)
1020 {
1021   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1022   FolksIndividual *individual;
1023   EmpathyContact *contact;
1024   GtkTreeModel *model;
1025   GtkTreeIter iter;
1026
1027   if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1028     return;
1029
1030   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1031   gtk_tree_model_get_iter (model, &iter, path);
1032   gtk_tree_model_get (model, &iter,
1033       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1034
1035   if (individual == NULL)
1036     return;
1037
1038   /* Determine which Persona to chat to, by choosing the most available one. */
1039   contact = empathy_contact_dup_best_for_action (individual,
1040       EMPATHY_ACTION_CHAT);
1041
1042   if (contact != NULL)
1043     {
1044       DEBUG ("Starting a chat");
1045
1046       empathy_chat_with_contact (contact,
1047           gtk_get_current_event_time ());
1048     }
1049
1050   g_object_unref (individual);
1051   tp_clear_object (&contact);
1052 }
1053
1054 static void
1055 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1056     const gchar *path_string,
1057     EmpathyIndividualView *view)
1058 {
1059   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1060   GtkWidget *menu;
1061   GtkTreeModel *model;
1062   GtkTreeIter iter;
1063   FolksIndividual *individual;
1064   GdkEventButton *event;
1065   GtkMenuShell *shell;
1066   GtkWidget *item;
1067
1068   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1069     return;
1070
1071   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1072   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1073     return;
1074
1075   gtk_tree_model_get (model, &iter,
1076       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1077   if (individual == NULL)
1078     return;
1079
1080   event = (GdkEventButton *) gtk_get_current_event ();
1081
1082   menu = empathy_context_menu_new (GTK_WIDGET (view));
1083   shell = GTK_MENU_SHELL (menu);
1084
1085   /* audio */
1086   item = empathy_individual_audio_call_menu_item_new (individual);
1087   gtk_menu_shell_append (shell, item);
1088   gtk_widget_show (item);
1089
1090   /* video */
1091   item = empathy_individual_video_call_menu_item_new (individual);
1092   gtk_menu_shell_append (shell, item);
1093   gtk_widget_show (item);
1094
1095   gtk_widget_show (menu);
1096   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1097       event->button, event->time);
1098
1099   g_object_unref (individual);
1100 }
1101
1102 static void
1103 individual_view_cell_set_background (EmpathyIndividualView *view,
1104     GtkCellRenderer *cell,
1105     gboolean is_group,
1106     gboolean is_active)
1107 {
1108   if (!is_group && is_active)
1109     {
1110       GtkStyleContext *style;
1111       GdkRGBA color;
1112
1113       style = gtk_widget_get_style_context (GTK_WIDGET (view));
1114
1115       gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1116           &color);
1117
1118       /* Here we take the current theme colour and add it to
1119        * the colour for white and average the two. This
1120        * gives a colour which is inline with the theme but
1121        * slightly whiter.
1122        */
1123       empathy_make_color_whiter (&color);
1124
1125       g_object_set (cell, "cell-background-rgba", &color, NULL);
1126     }
1127   else
1128     g_object_set (cell, "cell-background-rgba", NULL, NULL);
1129 }
1130
1131 static void
1132 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1133     GtkCellRenderer *cell,
1134     GtkTreeModel *model,
1135     GtkTreeIter *iter,
1136     EmpathyIndividualView *view)
1137 {
1138   GdkPixbuf *pixbuf;
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,
1145       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1146
1147   g_object_set (cell,
1148       "visible", !is_group,
1149       "pixbuf", pixbuf,
1150       NULL);
1151
1152   tp_clear_object (&pixbuf);
1153
1154   individual_view_cell_set_background (view, cell, is_group, is_active);
1155 }
1156
1157 static void
1158 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1159     GtkCellRenderer *cell,
1160     GtkTreeModel *model,
1161     GtkTreeIter *iter,
1162     EmpathyIndividualView *view)
1163 {
1164   GdkPixbuf *pixbuf = NULL;
1165   gboolean is_group;
1166   gchar *name;
1167
1168   gtk_tree_model_get (model, iter,
1169       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1170       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1171
1172   if (!is_group)
1173     goto out;
1174
1175   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1176     {
1177       pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1178           GTK_ICON_SIZE_MENU);
1179     }
1180   else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1181     {
1182       pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1183           GTK_ICON_SIZE_MENU);
1184     }
1185
1186 out:
1187   g_object_set (cell,
1188       "visible", pixbuf != NULL,
1189       "pixbuf", pixbuf,
1190       NULL);
1191
1192   tp_clear_object (&pixbuf);
1193
1194   g_free (name);
1195 }
1196
1197 static void
1198 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1199     GtkCellRenderer *cell,
1200     GtkTreeModel *model,
1201     GtkTreeIter *iter,
1202     EmpathyIndividualView *view)
1203 {
1204   gboolean is_group;
1205   gboolean is_active;
1206   gboolean can_audio, can_video;
1207
1208   gtk_tree_model_get (model, iter,
1209       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1210       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1211       EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1212       EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1213
1214   g_object_set (cell,
1215       "visible", !is_group && (can_audio || can_video),
1216       "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1217       NULL);
1218
1219   individual_view_cell_set_background (view, cell, is_group, is_active);
1220 }
1221
1222 static void
1223 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1224     GtkCellRenderer *cell,
1225     GtkTreeModel *model,
1226     GtkTreeIter *iter,
1227     EmpathyIndividualView *view)
1228 {
1229   GdkPixbuf *pixbuf;
1230   gboolean show_avatar;
1231   gboolean is_group;
1232   gboolean is_active;
1233
1234   gtk_tree_model_get (model, iter,
1235       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1236       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1237       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1238       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1239
1240   g_object_set (cell,
1241       "visible", !is_group && show_avatar,
1242       "pixbuf", pixbuf,
1243       NULL);
1244
1245   tp_clear_object (&pixbuf);
1246
1247   individual_view_cell_set_background (view, cell, is_group, is_active);
1248 }
1249
1250 static void
1251 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1252     GtkCellRenderer *cell,
1253     GtkTreeModel *model,
1254     GtkTreeIter *iter,
1255     EmpathyIndividualView *view)
1256 {
1257   gboolean is_group;
1258   gboolean is_active;
1259
1260   gtk_tree_model_get (model, iter,
1261       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1262       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1263
1264   individual_view_cell_set_background (view, cell, is_group, is_active);
1265 }
1266
1267 static void
1268 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1269     GtkCellRenderer *cell,
1270     GtkTreeModel *model,
1271     GtkTreeIter *iter,
1272     EmpathyIndividualView *view)
1273 {
1274   gboolean is_group;
1275   gboolean is_active;
1276
1277   gtk_tree_model_get (model, iter,
1278       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1279       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1280
1281   if (gtk_tree_model_iter_has_child (model, iter))
1282     {
1283       GtkTreePath *path;
1284       gboolean row_expanded;
1285
1286       path = gtk_tree_model_get_path (model, iter);
1287       row_expanded =
1288           gtk_tree_view_row_expanded (GTK_TREE_VIEW
1289           (gtk_tree_view_column_get_tree_view (column)), path);
1290       gtk_tree_path_free (path);
1291
1292       g_object_set (cell,
1293           "visible", TRUE,
1294           "expander-style",
1295           row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1296           NULL);
1297     }
1298   else
1299     g_object_set (cell, "visible", FALSE, NULL);
1300
1301   individual_view_cell_set_background (view, cell, is_group, is_active);
1302 }
1303
1304 static void
1305 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1306     GtkTreeIter *iter,
1307     GtkTreePath *path,
1308     gpointer user_data)
1309 {
1310   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1311   GtkTreeModel *model;
1312   gchar *name;
1313   gboolean expanded;
1314
1315   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1316     return;
1317
1318   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1319
1320   gtk_tree_model_get (model, iter,
1321       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1322
1323   expanded = GPOINTER_TO_INT (user_data);
1324   empathy_contact_group_set_expanded (name, expanded);
1325
1326   g_free (name);
1327 }
1328
1329 static gboolean
1330 individual_view_start_search_cb (EmpathyIndividualView *view,
1331     gpointer data)
1332 {
1333   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1334
1335   if (priv->search_widget == NULL)
1336     return FALSE;
1337
1338   empathy_individual_view_start_search (view);
1339
1340   return TRUE;
1341 }
1342
1343 static void
1344 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1345     GParamSpec *pspec,
1346     EmpathyIndividualView *view)
1347 {
1348   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1349   GtkTreePath *path;
1350   GtkTreeViewColumn *focus_column;
1351   GtkTreeModel *model;
1352   GtkTreeIter iter;
1353   gboolean set_cursor = FALSE;
1354
1355   gtk_tree_model_filter_refilter (priv->filter);
1356
1357   /* Set cursor on the first contact. If it is already set on a group,
1358    * set it on its first child contact. Note that first child of a group
1359    * is its separator, that's why we actually set to the 2nd
1360    */
1361
1362   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1363   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1364
1365   if (path == NULL)
1366     {
1367       path = gtk_tree_path_new_from_string ("0:1");
1368       set_cursor = TRUE;
1369     }
1370   else if (gtk_tree_path_get_depth (path) < 2)
1371     {
1372       gboolean is_group;
1373
1374       gtk_tree_model_get_iter (model, &iter, path);
1375       gtk_tree_model_get (model, &iter,
1376           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1377           -1);
1378
1379       if (is_group)
1380         {
1381           gtk_tree_path_down (path);
1382           gtk_tree_path_next (path);
1383           set_cursor = TRUE;
1384         }
1385     }
1386
1387   if (set_cursor)
1388     {
1389       /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1390        * valid. */
1391       if (gtk_tree_model_get_iter (model, &iter, path))
1392         {
1393           gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1394               FALSE);
1395         }
1396     }
1397
1398   gtk_tree_path_free (path);
1399 }
1400
1401 static void
1402 individual_view_search_activate_cb (GtkWidget *search,
1403   EmpathyIndividualView *view)
1404 {
1405   GtkTreePath *path;
1406   GtkTreeViewColumn *focus_column;
1407
1408   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1409   if (path != NULL)
1410     {
1411       gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1412       gtk_tree_path_free (path);
1413
1414       gtk_widget_hide (search);
1415     }
1416 }
1417
1418 static gboolean
1419 individual_view_search_key_navigation_cb (GtkWidget *search,
1420   GdkEvent *event,
1421   EmpathyIndividualView *view)
1422 {
1423   GdkEvent *new_event;
1424   gboolean ret = FALSE;
1425
1426   new_event = gdk_event_copy (event);
1427   gtk_widget_grab_focus (GTK_WIDGET (view));
1428   ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1429   gtk_widget_grab_focus (search);
1430
1431   gdk_event_free (new_event);
1432
1433   return ret;
1434 }
1435
1436 static void
1437 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1438     EmpathyIndividualView *view)
1439 {
1440   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1441   GtkTreeModel *model;
1442   GtkTreePath *cursor_path;
1443   GtkTreeIter iter;
1444   gboolean valid = FALSE;
1445
1446   /* block expand or collapse handlers, they would write the
1447    * expand or collapsed setting to file otherwise */
1448   g_signal_handlers_block_by_func (view,
1449       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1450   g_signal_handlers_block_by_func (view,
1451     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1452
1453   /* restore which groups are expanded and which are not */
1454   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1455   for (valid = gtk_tree_model_get_iter_first (model, &iter);
1456        valid; valid = gtk_tree_model_iter_next (model, &iter))
1457     {
1458       gboolean is_group;
1459       gchar *name = NULL;
1460       GtkTreePath *path;
1461
1462       gtk_tree_model_get (model, &iter,
1463           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1464           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1465           -1);
1466
1467       if (!is_group)
1468         {
1469           g_free (name);
1470           continue;
1471         }
1472
1473       path = gtk_tree_model_get_path (model, &iter);
1474       if ((priv->view_features &
1475             EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1476           empathy_contact_group_get_expanded (name))
1477         {
1478           gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1479         }
1480       else
1481         {
1482           gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1483         }
1484
1485       gtk_tree_path_free (path);
1486       g_free (name);
1487     }
1488
1489   /* unblock expand or collapse handlers */
1490   g_signal_handlers_unblock_by_func (view,
1491       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1492   g_signal_handlers_unblock_by_func (view,
1493       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1494
1495   /* keep the selected contact visible */
1496   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1497
1498   if (cursor_path != NULL)
1499     gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1500         FALSE, 0, 0);
1501
1502   gtk_tree_path_free (cursor_path);
1503 }
1504
1505 static void
1506 individual_view_search_show_cb (EmpathyLiveSearch *search,
1507     EmpathyIndividualView *view)
1508 {
1509   /* block expand or collapse handlers during expand all, they would
1510    * write the expand or collapsed setting to file otherwise */
1511   g_signal_handlers_block_by_func (view,
1512       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1513
1514   gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1515
1516   g_signal_handlers_unblock_by_func (view,
1517       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1518 }
1519
1520 static gboolean
1521 expand_idle_foreach_cb (GtkTreeModel *model,
1522     GtkTreePath *path,
1523     GtkTreeIter *iter,
1524     EmpathyIndividualView *self)
1525 {
1526   EmpathyIndividualViewPriv *priv;
1527   gboolean is_group;
1528   gpointer should_expand;
1529   gchar *name;
1530
1531   /* We only want groups */
1532   if (gtk_tree_path_get_depth (path) > 1)
1533     return FALSE;
1534
1535   gtk_tree_model_get (model, iter,
1536       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1537       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1538       -1);
1539
1540   if (!is_group)
1541     {
1542       g_free (name);
1543       return FALSE;
1544     }
1545
1546   priv = GET_PRIV (self);
1547
1548   if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1549       &should_expand))
1550     {
1551       if (GPOINTER_TO_INT (should_expand))
1552         gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1553       else
1554         gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1555
1556       g_hash_table_remove (priv->expand_groups, name);
1557     }
1558
1559   g_free (name);
1560
1561   return FALSE;
1562 }
1563
1564 static gboolean
1565 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1566 {
1567   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1568
1569   g_signal_handlers_block_by_func (self,
1570     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1571   g_signal_handlers_block_by_func (self,
1572     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1573
1574   /* The store/filter could've been removed while we were in the idle queue */
1575   if (priv->filter != NULL)
1576     {
1577       gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1578           (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1579     }
1580
1581   g_signal_handlers_unblock_by_func (self,
1582       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1583   g_signal_handlers_unblock_by_func (self,
1584       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1585
1586   /* Empty the table of groups to expand/contract, since it may contain groups
1587    * which no longer exist in the tree view. This can happen after going
1588    * offline, for example. */
1589   g_hash_table_remove_all (priv->expand_groups);
1590   priv->expand_groups_idle_handler = 0;
1591   g_object_unref (self);
1592
1593   return FALSE;
1594 }
1595
1596 static void
1597 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1598     GtkTreePath *path,
1599     GtkTreeIter *iter,
1600     EmpathyIndividualView *view)
1601 {
1602   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1603   gboolean should_expand, is_group = FALSE;
1604   gchar *name = NULL;
1605   gpointer will_expand;
1606
1607   gtk_tree_model_get (model, iter,
1608       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1609       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1610       -1);
1611
1612   if (!is_group || EMP_STR_EMPTY (name))
1613     {
1614       g_free (name);
1615       return;
1616     }
1617
1618   should_expand = (priv->view_features &
1619           EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1620       (priv->search_widget != NULL &&
1621           gtk_widget_get_visible (priv->search_widget)) ||
1622       empathy_contact_group_get_expanded (name);
1623
1624   /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1625    * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1626    * a hash table, and expand or contract them as appropriate all at once in
1627    * an idle handler which iterates over all the group rows. */
1628   if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1629       &will_expand) ||
1630       GPOINTER_TO_INT (will_expand) != should_expand)
1631     {
1632       g_hash_table_insert (priv->expand_groups, g_strdup (name),
1633           GINT_TO_POINTER (should_expand));
1634
1635       if (priv->expand_groups_idle_handler == 0)
1636         {
1637           priv->expand_groups_idle_handler =
1638               g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1639                   g_object_ref (view));
1640         }
1641     }
1642
1643   g_free (name);
1644 }
1645
1646 static gboolean
1647 individual_view_is_visible_individual (EmpathyIndividualView *self,
1648     FolksIndividual *individual,
1649     gboolean is_online,
1650     gboolean is_searching,
1651     const gchar *group,
1652     gboolean is_fake_group,
1653     guint event_count)
1654 {
1655   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1656   EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1657   GeeSet *personas;
1658   GeeIterator *iter;
1659   gboolean is_favorite;
1660
1661   /* Always display individuals having pending events */
1662   if (event_count > 0)
1663     return TRUE;
1664
1665   /* We're only giving the visibility wrt filtering here, not things like
1666    * presence. */
1667   if (!priv->show_untrusted &&
1668       folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1669     {
1670       return FALSE;
1671     }
1672
1673   if (!priv->show_uninteresting)
1674     {
1675       gboolean contains_interesting_persona = FALSE;
1676
1677       /* Hide all individuals which consist entirely of uninteresting
1678        * personas */
1679       personas = folks_individual_get_personas (individual);
1680       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1681       while (!contains_interesting_persona && gee_iterator_next (iter))
1682         {
1683           FolksPersona *persona = gee_iterator_get (iter);
1684
1685           if (empathy_folks_persona_is_interesting (persona))
1686             contains_interesting_persona = TRUE;
1687
1688           g_clear_object (&persona);
1689         }
1690       g_clear_object (&iter);
1691
1692       if (!contains_interesting_persona)
1693         return FALSE;
1694     }
1695
1696   is_favorite = folks_favourite_details_get_is_favourite (
1697       FOLKS_FAVOURITE_DETAILS (individual));
1698   if (!is_searching) {
1699     if (is_favorite && is_fake_group &&
1700         !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1701         /* Always display favorite contacts in the favorite group */
1702         return TRUE;
1703
1704     return (priv->show_offline || is_online);
1705   }
1706
1707   return empathy_individual_match_string (individual,
1708       empathy_live_search_get_text (live),
1709       empathy_live_search_get_words (live));
1710 }
1711
1712 static gchar *
1713 get_group (GtkTreeModel *model,
1714     GtkTreeIter *iter,
1715     gboolean *is_fake)
1716 {
1717   GtkTreeIter parent_iter;
1718   gchar *name = NULL;
1719
1720   *is_fake = FALSE;
1721
1722   if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1723     return NULL;
1724
1725   gtk_tree_model_get (model, &parent_iter,
1726       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1727       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1728       -1);
1729
1730   return name;
1731 }
1732
1733
1734 static gboolean
1735 individual_view_filter_visible_func (GtkTreeModel *model,
1736     GtkTreeIter *iter,
1737     gpointer user_data)
1738 {
1739   EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1740   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1741   FolksIndividual *individual = NULL;
1742   gboolean is_group, is_separator, valid;
1743   GtkTreeIter child_iter;
1744   gboolean visible, is_online;
1745   gboolean is_searching = TRUE;
1746   guint event_count;
1747
1748   if (priv->custom_filter != NULL)
1749     return priv->custom_filter (model, iter, priv->custom_filter_data);
1750
1751   if (priv->search_widget == NULL ||
1752       !gtk_widget_get_visible (priv->search_widget))
1753      is_searching = FALSE;
1754
1755   gtk_tree_model_get (model, iter,
1756       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1757       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1758       EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1759       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1760       EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1761       -1);
1762
1763   if (individual != NULL)
1764     {
1765       gchar *group;
1766       gboolean is_fake_group;
1767
1768       group = get_group (model, iter, &is_fake_group);
1769
1770       visible = individual_view_is_visible_individual (self, individual,
1771           is_online, is_searching, group, is_fake_group, event_count);
1772
1773       g_object_unref (individual);
1774       g_free (group);
1775
1776       return visible;
1777     }
1778
1779   if (is_separator)
1780     return TRUE;
1781
1782   /* Not a contact, not a separator, must be a group */
1783   g_return_val_if_fail (is_group, FALSE);
1784
1785   /* only show groups which are not empty */
1786   for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1787        valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1788     {
1789       gchar *group;
1790       gboolean is_fake_group;
1791
1792       gtk_tree_model_get (model, &child_iter,
1793         EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1794         EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1795         EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1796         -1);
1797
1798       if (individual == NULL)
1799         continue;
1800
1801       group = get_group (model, &child_iter, &is_fake_group);
1802
1803       visible = individual_view_is_visible_individual (self, individual,
1804           is_online, is_searching, group, is_fake_group, event_count);
1805
1806       g_object_unref (individual);
1807       g_free (group);
1808
1809       /* show group if it has at least one visible contact in it */
1810       if (visible)
1811         return TRUE;
1812     }
1813
1814   return FALSE;
1815 }
1816
1817 static gchar * empathy_individual_view_dup_selected_group (
1818     EmpathyIndividualView *view,
1819     gboolean *is_fake_group);
1820
1821 static void
1822 text_edited_cb (GtkCellRendererText *cellrenderertext,
1823     gchar *path,
1824     gchar *name,
1825     EmpathyIndividualView *self)
1826 {
1827   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1828   gchar *old_name, *new_name;
1829
1830   g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1831
1832   new_name = g_strdup (name);
1833   g_strstrip (new_name);
1834
1835   if (tp_str_empty (new_name))
1836     goto out;
1837
1838   old_name = empathy_individual_view_dup_selected_group (self, NULL);
1839   g_return_if_fail (old_name != NULL);
1840
1841   if (tp_strdiff (old_name, new_name))
1842     {
1843       EmpathyConnectionAggregator *aggregator;
1844
1845       DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1846
1847       aggregator = empathy_connection_aggregator_dup_singleton ();
1848
1849       empathy_connection_aggregator_rename_group (aggregator, old_name,
1850           new_name);
1851       g_object_unref (aggregator);
1852     }
1853
1854   g_free (old_name);
1855 out:
1856   g_free (new_name);
1857 }
1858
1859 static void
1860 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1861     EmpathyIndividualView *self)
1862 {
1863   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1864
1865   g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1866 }
1867
1868 static void
1869 individual_view_constructed (GObject *object)
1870 {
1871   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1872   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1873   GtkCellRenderer *cell;
1874   GtkTreeViewColumn *col;
1875   guint i;
1876
1877   /* Setup view */
1878   g_object_set (view,
1879       "headers-visible", FALSE,
1880       "show-expanders", FALSE,
1881       NULL);
1882
1883   col = gtk_tree_view_column_new ();
1884
1885   /* State */
1886   cell = gtk_cell_renderer_pixbuf_new ();
1887   gtk_tree_view_column_pack_start (col, cell, FALSE);
1888   gtk_tree_view_column_set_cell_data_func (col, cell,
1889       (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1890       view, NULL);
1891
1892   g_object_set (cell,
1893       "xpad", 5,
1894       "ypad", 1,
1895       "visible", FALSE,
1896       NULL);
1897
1898   /* Group icon */
1899   cell = gtk_cell_renderer_pixbuf_new ();
1900   gtk_tree_view_column_pack_start (col, cell, FALSE);
1901   gtk_tree_view_column_set_cell_data_func (col, cell,
1902       (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1903       view, NULL);
1904
1905   g_object_set (cell,
1906       "xpad", 0,
1907       "ypad", 0,
1908       "visible", FALSE,
1909       "width", 16,
1910       "height", 16,
1911       NULL);
1912
1913   /* Name */
1914   priv->text_renderer = empathy_cell_renderer_text_new ();
1915   gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1916   gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1917       (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1918
1919   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1920       "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1921   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1922       "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1923   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1924       "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1925   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1926       "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1927   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1928       "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1929   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1930       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1931   gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1932       "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1933
1934   g_signal_connect (priv->text_renderer, "editing-canceled",
1935       G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1936   g_signal_connect (priv->text_renderer, "edited",
1937       G_CALLBACK (text_edited_cb), view);
1938
1939   /* Audio Call Icon */
1940   cell = empathy_cell_renderer_activatable_new ();
1941   gtk_tree_view_column_pack_start (col, cell, FALSE);
1942   gtk_tree_view_column_set_cell_data_func (col, cell,
1943       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1944       view, NULL);
1945
1946   g_object_set (cell, "visible", FALSE, NULL);
1947
1948   g_signal_connect (cell, "path-activated",
1949       G_CALLBACK (individual_view_call_activated_cb), view);
1950
1951   /* Avatar */
1952   cell = gtk_cell_renderer_pixbuf_new ();
1953   gtk_tree_view_column_pack_start (col, cell, FALSE);
1954   gtk_tree_view_column_set_cell_data_func (col, cell,
1955       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1956       view, NULL);
1957
1958   g_object_set (cell,
1959       "xpad", 0,
1960       "ypad", 0,
1961       "visible", FALSE,
1962       "width", 32,
1963       "height", 32,
1964       NULL);
1965
1966   /* Expander */
1967   cell = empathy_cell_renderer_expander_new ();
1968   gtk_tree_view_column_pack_end (col, cell, FALSE);
1969   gtk_tree_view_column_set_cell_data_func (col, cell,
1970       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1971       view, NULL);
1972
1973   /* Actually add the column now we have added all cell renderers */
1974   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1975
1976   /* Drag & Drop. */
1977   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1978     {
1979       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1980     }
1981 }
1982
1983 static void
1984 individual_view_set_view_features (EmpathyIndividualView *view,
1985     EmpathyIndividualFeatureFlags features)
1986 {
1987   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1988   gboolean has_tooltip;
1989
1990   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1991
1992   priv->view_features = features;
1993
1994   /* Setting reorderable is a hack that gets us row previews as drag icons
1995      for free.  We override all the drag handlers.  It's tricky to get the
1996      position of the drag icon right in drag_begin.  GtkTreeView has special
1997      voodoo for it, so we let it do the voodoo that he do (but only if dragging
1998      is enabled).
1999    */
2000   gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
2001       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
2002
2003   /* Update DnD source/dest */
2004   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
2005     {
2006       gtk_drag_source_set (GTK_WIDGET (view),
2007           GDK_BUTTON1_MASK,
2008           drag_types_source,
2009           G_N_ELEMENTS (drag_types_source),
2010           GDK_ACTION_MOVE | GDK_ACTION_COPY);
2011     }
2012   else
2013     {
2014       gtk_drag_source_unset (GTK_WIDGET (view));
2015
2016     }
2017
2018   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2019     {
2020       gtk_drag_dest_set (GTK_WIDGET (view),
2021           GTK_DEST_DEFAULT_ALL,
2022           drag_types_dest,
2023           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2024     }
2025   else
2026     {
2027       /* FIXME: URI could still be droped depending on FT feature */
2028       gtk_drag_dest_unset (GTK_WIDGET (view));
2029     }
2030
2031   /* Update has-tooltip */
2032   has_tooltip =
2033       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2034   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2035 }
2036
2037 static void
2038 individual_view_dispose (GObject *object)
2039 {
2040   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2041   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2042
2043   tp_clear_object (&priv->store);
2044   tp_clear_object (&priv->filter);
2045   tp_clear_object (&priv->tooltip_widget);
2046
2047   empathy_individual_view_set_live_search (view, NULL);
2048
2049   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2050 }
2051
2052 static void
2053 individual_view_finalize (GObject *object)
2054 {
2055   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2056
2057   if (priv->expand_groups_idle_handler != 0)
2058     g_source_remove (priv->expand_groups_idle_handler);
2059   g_hash_table_unref (priv->expand_groups);
2060
2061   G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2062 }
2063
2064 static void
2065 individual_view_get_property (GObject *object,
2066     guint param_id,
2067     GValue *value,
2068     GParamSpec *pspec)
2069 {
2070   EmpathyIndividualViewPriv *priv;
2071
2072   priv = GET_PRIV (object);
2073
2074   switch (param_id)
2075     {
2076     case PROP_STORE:
2077       g_value_set_object (value, priv->store);
2078       break;
2079     case PROP_VIEW_FEATURES:
2080       g_value_set_flags (value, priv->view_features);
2081       break;
2082     case PROP_INDIVIDUAL_FEATURES:
2083       g_value_set_flags (value, priv->individual_features);
2084       break;
2085     case PROP_SHOW_OFFLINE:
2086       g_value_set_boolean (value, priv->show_offline);
2087       break;
2088     case PROP_SHOW_UNTRUSTED:
2089       g_value_set_boolean (value, priv->show_untrusted);
2090       break;
2091     case PROP_SHOW_UNINTERESTING:
2092       g_value_set_boolean (value, priv->show_uninteresting);
2093       break;
2094     default:
2095       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2096       break;
2097     };
2098 }
2099
2100 static void
2101 individual_view_set_property (GObject *object,
2102     guint param_id,
2103     const GValue *value,
2104     GParamSpec *pspec)
2105 {
2106   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2107   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2108
2109   switch (param_id)
2110     {
2111     case PROP_STORE:
2112       empathy_individual_view_set_store (view, g_value_get_object (value));
2113       break;
2114     case PROP_VIEW_FEATURES:
2115       individual_view_set_view_features (view, g_value_get_flags (value));
2116       break;
2117     case PROP_INDIVIDUAL_FEATURES:
2118       priv->individual_features = g_value_get_flags (value);
2119       break;
2120     case PROP_SHOW_OFFLINE:
2121       empathy_individual_view_set_show_offline (view,
2122           g_value_get_boolean (value));
2123       break;
2124     case PROP_SHOW_UNTRUSTED:
2125       empathy_individual_view_set_show_untrusted (view,
2126           g_value_get_boolean (value));
2127       break;
2128     case PROP_SHOW_UNINTERESTING:
2129       empathy_individual_view_set_show_uninteresting (view,
2130           g_value_get_boolean (value));
2131     default:
2132       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2133       break;
2134     };
2135 }
2136
2137 static void
2138 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2139 {
2140   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2141   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2142   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2143
2144   object_class->constructed = individual_view_constructed;
2145   object_class->dispose = individual_view_dispose;
2146   object_class->finalize = individual_view_finalize;
2147   object_class->get_property = individual_view_get_property;
2148   object_class->set_property = individual_view_set_property;
2149
2150   widget_class->drag_data_received = individual_view_drag_data_received;
2151   widget_class->drag_drop = individual_view_drag_drop;
2152   widget_class->drag_begin = individual_view_drag_begin;
2153   widget_class->drag_data_get = individual_view_drag_data_get;
2154   widget_class->drag_end = individual_view_drag_end;
2155   widget_class->drag_motion = individual_view_drag_motion;
2156
2157   /* We use the class method to let user of this widget to connect to
2158    * the signal and stop emission of the signal so the default handler
2159    * won't be called. */
2160   tree_view_class->row_activated = individual_view_row_activated;
2161
2162   klass->drag_individual_received = real_drag_individual_received_cb;
2163
2164   signals[DRAG_INDIVIDUAL_RECEIVED] =
2165       g_signal_new ("drag-individual-received",
2166       G_OBJECT_CLASS_TYPE (klass),
2167       G_SIGNAL_RUN_LAST,
2168       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2169       NULL, NULL,
2170       g_cclosure_marshal_generic,
2171       G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2172       G_TYPE_STRING, G_TYPE_STRING);
2173
2174   signals[DRAG_PERSONA_RECEIVED] =
2175       g_signal_new ("drag-persona-received",
2176       G_OBJECT_CLASS_TYPE (klass),
2177       G_SIGNAL_RUN_LAST,
2178       G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2179       NULL, NULL,
2180       g_cclosure_marshal_generic,
2181       G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2182
2183   g_object_class_install_property (object_class,
2184       PROP_STORE,
2185       g_param_spec_object ("store",
2186           "The store of the view",
2187           "The store of the view",
2188           EMPATHY_TYPE_INDIVIDUAL_STORE,
2189           G_PARAM_READWRITE));
2190   g_object_class_install_property (object_class,
2191       PROP_VIEW_FEATURES,
2192       g_param_spec_flags ("view-features",
2193           "Features of the view",
2194           "Flags for all enabled features",
2195           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2196           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2197   g_object_class_install_property (object_class,
2198       PROP_INDIVIDUAL_FEATURES,
2199       g_param_spec_flags ("individual-features",
2200           "Features of the individual menu",
2201           "Flags for all enabled features for the menu",
2202           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2203           EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2204   g_object_class_install_property (object_class,
2205       PROP_SHOW_OFFLINE,
2206       g_param_spec_boolean ("show-offline",
2207           "Show Offline",
2208           "Whether contact list should display "
2209           "offline contacts", FALSE, G_PARAM_READWRITE));
2210   g_object_class_install_property (object_class,
2211       PROP_SHOW_UNTRUSTED,
2212       g_param_spec_boolean ("show-untrusted",
2213           "Show Untrusted Individuals",
2214           "Whether the view should display untrusted individuals; "
2215           "those who could not be who they say they are.",
2216           TRUE, G_PARAM_READWRITE));
2217   g_object_class_install_property (object_class,
2218       PROP_SHOW_UNINTERESTING,
2219       g_param_spec_boolean ("show-uninteresting",
2220           "Show Uninteresting Individuals",
2221           "Whether the view should not filter out individuals using "
2222           "empathy_folks_persona_is_interesting.",
2223           FALSE, G_PARAM_READWRITE));
2224
2225   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2226 }
2227
2228 static void
2229 empathy_individual_view_init (EmpathyIndividualView *view)
2230 {
2231   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2232       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2233
2234   view->priv = priv;
2235
2236   priv->show_untrusted = TRUE;
2237   priv->show_uninteresting = FALSE;
2238
2239   /* Get saved group states. */
2240   empathy_contact_groups_get_all ();
2241
2242   priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2243       (GDestroyNotify) g_free, NULL);
2244
2245   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2246       empathy_individual_store_row_separator_func, NULL, NULL);
2247
2248   /* Connect to tree view signals rather than override. */
2249   g_signal_connect (view, "button-press-event",
2250       G_CALLBACK (individual_view_button_press_event_cb), NULL);
2251   g_signal_connect (view, "key-press-event",
2252       G_CALLBACK (individual_view_key_press_event_cb), NULL);
2253   g_signal_connect (view, "row-expanded",
2254       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2255       GINT_TO_POINTER (TRUE));
2256   g_signal_connect (view, "row-collapsed",
2257       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2258       GINT_TO_POINTER (FALSE));
2259   g_signal_connect (view, "query-tooltip",
2260       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2261 }
2262
2263 EmpathyIndividualView *
2264 empathy_individual_view_new (EmpathyIndividualStore *store,
2265     EmpathyIndividualViewFeatureFlags view_features,
2266     EmpathyIndividualFeatureFlags individual_features)
2267 {
2268   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2269
2270   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2271       "store", store,
2272       "individual-features", individual_features,
2273       "view-features", view_features, NULL);
2274 }
2275
2276 FolksIndividual *
2277 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2278 {
2279   GtkTreeSelection *selection;
2280   GtkTreeIter iter;
2281   GtkTreeModel *model;
2282   FolksIndividual *individual;
2283
2284   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2285
2286   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2287   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2288     return NULL;
2289
2290   gtk_tree_model_get (model, &iter,
2291       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2292
2293   return individual;
2294 }
2295
2296 static gchar *
2297 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2298     gboolean *is_fake_group)
2299 {
2300   GtkTreeSelection *selection;
2301   GtkTreeIter iter;
2302   GtkTreeModel *model;
2303   gboolean is_group;
2304   gchar *name;
2305   gboolean fake;
2306
2307   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2308
2309   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2310   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2311     return NULL;
2312
2313   gtk_tree_model_get (model, &iter,
2314       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2315       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2316       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2317
2318   if (!is_group)
2319     {
2320       g_free (name);
2321       return NULL;
2322     }
2323
2324   if (is_fake_group != NULL)
2325     *is_fake_group = fake;
2326
2327   return name;
2328 }
2329
2330 enum
2331 {
2332   REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2333   REMOVE_DIALOG_RESPONSE_DELETE,
2334 };
2335
2336 static int
2337 individual_view_remove_dialog_show (GtkWindow *parent,
2338     const gchar *message,
2339     const gchar *secondary_text)
2340 {
2341   GtkWidget *dialog;
2342   gboolean res;
2343
2344   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2345       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2346
2347   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2348       GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2349       GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2350   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2351       "%s", secondary_text);
2352
2353   gtk_widget_show (dialog);
2354
2355   res = gtk_dialog_run (GTK_DIALOG (dialog));
2356   gtk_widget_destroy (dialog);
2357
2358   return res;
2359 }
2360
2361 static void
2362 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2363     EmpathyIndividualView *view)
2364 {
2365   gchar *group;
2366
2367   group = empathy_individual_view_dup_selected_group (view, NULL);
2368   if (group != NULL)
2369     {
2370       gchar *text;
2371       GtkWindow *parent;
2372
2373       text =
2374           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2375           group);
2376       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2377       if (individual_view_remove_dialog_show (parent, _("Removing group"),
2378               text) == REMOVE_DIALOG_RESPONSE_DELETE)
2379         {
2380           EmpathyIndividualManager *manager =
2381               empathy_individual_manager_dup_singleton ();
2382           empathy_individual_manager_remove_group (manager, group);
2383           g_object_unref (G_OBJECT (manager));
2384         }
2385
2386       g_free (text);
2387     }
2388
2389   g_free (group);
2390 }
2391
2392 static void
2393 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2394     EmpathyIndividualView *self)
2395 {
2396   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2397   GtkTreePath *path;
2398   GtkTreeIter iter;
2399   GtkTreeSelection *selection;
2400   GtkTreeModel *model;
2401
2402   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2403   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2404     return;
2405   path = gtk_tree_model_get_path (model, &iter);
2406
2407   g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2408
2409   gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2410   gtk_widget_grab_focus (GTK_WIDGET (self));
2411   gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2412       gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2413
2414   gtk_tree_path_free (path);
2415 }
2416
2417 GtkWidget *
2418 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2419 {
2420   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2421   gchar *group;
2422   GtkWidget *menu;
2423   GtkWidget *item;
2424   GtkWidget *image;
2425   gboolean is_fake_group;
2426
2427   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2428
2429   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2430               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2431     return NULL;
2432
2433   group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2434   if (!group || is_fake_group)
2435     {
2436       /* We can't alter fake groups */
2437       g_free (group);
2438       return NULL;
2439     }
2440
2441   menu = gtk_menu_new ();
2442
2443   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2444     {
2445        item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2446        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2447        gtk_widget_show (item);
2448        g_signal_connect (item, "activate",
2449            G_CALLBACK (individual_view_group_rename_activate_cb), view);
2450      }
2451
2452   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2453     {
2454       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2455       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2456           GTK_ICON_SIZE_MENU);
2457       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2458       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2459       gtk_widget_show (item);
2460       g_signal_connect (item, "activate",
2461           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2462     }
2463
2464   g_free (group);
2465
2466   return menu;
2467 }
2468
2469 GtkWidget *
2470 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2471 {
2472   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2473   FolksIndividual *individual;
2474   GtkWidget *menu = NULL;
2475
2476   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2477
2478   if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2479     /* No need to create a context menu */
2480     return NULL;
2481
2482   individual = empathy_individual_view_dup_selected (view);
2483   if (individual == NULL)
2484     return NULL;
2485
2486   if (!empathy_folks_individual_contains_contact (individual))
2487     goto out;
2488
2489   menu = empathy_individual_menu_new (individual, priv->individual_features,
2490       priv->store);
2491
2492 out:
2493   g_object_unref (individual);
2494
2495   return menu;
2496 }
2497
2498 void
2499 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2500     EmpathyLiveSearch *search)
2501 {
2502   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2503
2504   /* remove old handlers if old search was not null */
2505   if (priv->search_widget != NULL)
2506     {
2507       g_signal_handlers_disconnect_by_func (view,
2508           individual_view_start_search_cb, NULL);
2509
2510       g_signal_handlers_disconnect_by_func (priv->search_widget,
2511           individual_view_search_text_notify_cb, view);
2512       g_signal_handlers_disconnect_by_func (priv->search_widget,
2513           individual_view_search_activate_cb, view);
2514       g_signal_handlers_disconnect_by_func (priv->search_widget,
2515           individual_view_search_key_navigation_cb, view);
2516       g_signal_handlers_disconnect_by_func (priv->search_widget,
2517           individual_view_search_hide_cb, view);
2518       g_signal_handlers_disconnect_by_func (priv->search_widget,
2519           individual_view_search_show_cb, view);
2520       g_object_unref (priv->search_widget);
2521       priv->search_widget = NULL;
2522     }
2523
2524   /* connect handlers if new search is not null */
2525   if (search != NULL)
2526     {
2527       priv->search_widget = g_object_ref (search);
2528
2529       g_signal_connect (view, "start-interactive-search",
2530           G_CALLBACK (individual_view_start_search_cb), NULL);
2531
2532       g_signal_connect (priv->search_widget, "notify::text",
2533           G_CALLBACK (individual_view_search_text_notify_cb), view);
2534       g_signal_connect (priv->search_widget, "activate",
2535           G_CALLBACK (individual_view_search_activate_cb), view);
2536       g_signal_connect (priv->search_widget, "key-navigation",
2537           G_CALLBACK (individual_view_search_key_navigation_cb), view);
2538       g_signal_connect (priv->search_widget, "hide",
2539           G_CALLBACK (individual_view_search_hide_cb), view);
2540       g_signal_connect (priv->search_widget, "show",
2541           G_CALLBACK (individual_view_search_show_cb), view);
2542     }
2543 }
2544
2545 gboolean
2546 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2547 {
2548   EmpathyIndividualViewPriv *priv;
2549
2550   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2551
2552   priv = GET_PRIV (self);
2553
2554   return (priv->search_widget != NULL &&
2555           gtk_widget_get_visible (priv->search_widget));
2556 }
2557
2558 gboolean
2559 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2560 {
2561   EmpathyIndividualViewPriv *priv;
2562
2563   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2564
2565   priv = GET_PRIV (self);
2566
2567   return priv->show_offline;
2568 }
2569
2570 void
2571 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2572     gboolean show_offline)
2573 {
2574   EmpathyIndividualViewPriv *priv;
2575
2576   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2577
2578   priv = GET_PRIV (self);
2579
2580   priv->show_offline = show_offline;
2581
2582   g_object_notify (G_OBJECT (self), "show-offline");
2583   gtk_tree_model_filter_refilter (priv->filter);
2584 }
2585
2586 gboolean
2587 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2588 {
2589   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2590
2591   return GET_PRIV (self)->show_untrusted;
2592 }
2593
2594 void
2595 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2596     gboolean show_untrusted)
2597 {
2598   EmpathyIndividualViewPriv *priv;
2599
2600   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2601
2602   priv = GET_PRIV (self);
2603
2604   priv->show_untrusted = show_untrusted;
2605
2606   g_object_notify (G_OBJECT (self), "show-untrusted");
2607   gtk_tree_model_filter_refilter (priv->filter);
2608 }
2609
2610 EmpathyIndividualStore *
2611 empathy_individual_view_get_store (EmpathyIndividualView *self)
2612 {
2613   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2614
2615   return GET_PRIV (self)->store;
2616 }
2617
2618 void
2619 empathy_individual_view_set_store (EmpathyIndividualView *self,
2620     EmpathyIndividualStore *store)
2621 {
2622   EmpathyIndividualViewPriv *priv;
2623
2624   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2625   g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2626
2627   priv = GET_PRIV (self);
2628
2629   /* Destroy the old filter and remove the old store */
2630   if (priv->store != NULL)
2631     {
2632       g_signal_handlers_disconnect_by_func (priv->filter,
2633           individual_view_row_has_child_toggled_cb, self);
2634
2635       gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2636     }
2637
2638   tp_clear_object (&priv->filter);
2639   tp_clear_object (&priv->store);
2640
2641   /* Set the new store */
2642   priv->store = store;
2643
2644   if (store != NULL)
2645     {
2646       g_object_ref (store);
2647
2648       /* Create a new filter */
2649       priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2650           GTK_TREE_MODEL (priv->store), NULL));
2651       gtk_tree_model_filter_set_visible_func (priv->filter,
2652           individual_view_filter_visible_func, self, NULL);
2653
2654       g_signal_connect (priv->filter, "row-has-child-toggled",
2655           G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2656       gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2657           GTK_TREE_MODEL (priv->filter));
2658     }
2659 }
2660
2661 void
2662 empathy_individual_view_start_search (EmpathyIndividualView *self)
2663 {
2664   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2665
2666   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2667   g_return_if_fail (priv->search_widget != NULL);
2668
2669   if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2670     gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2671   else
2672     gtk_widget_show (GTK_WIDGET (priv->search_widget));
2673 }
2674
2675 void
2676 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2677     GtkTreeModelFilterVisibleFunc filter,
2678     gpointer data)
2679 {
2680   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2681
2682   priv->custom_filter = filter;
2683   priv->custom_filter_data = data;
2684 }
2685
2686 void
2687 empathy_individual_view_refilter (EmpathyIndividualView *self)
2688 {
2689   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2690
2691   gtk_tree_model_filter_refilter (priv->filter);
2692 }
2693
2694 void
2695 empathy_individual_view_select_first (EmpathyIndividualView *self)
2696 {
2697   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2698   GtkTreeIter iter;
2699
2700   gtk_tree_model_filter_refilter (priv->filter);
2701
2702   if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2703     {
2704       GtkTreeSelection *selection = gtk_tree_view_get_selection (
2705           GTK_TREE_VIEW (self));
2706
2707       gtk_tree_selection_select_iter (selection, &iter);
2708     }
2709 }
2710
2711 void
2712 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2713     gboolean show_uninteresting)
2714 {
2715   EmpathyIndividualViewPriv *priv;
2716
2717   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2718
2719   priv = GET_PRIV (self);
2720
2721   priv->show_uninteresting = show_uninteresting;
2722
2723   g_object_notify (G_OBJECT (self), "show-uninteresting");
2724   gtk_tree_model_filter_refilter (priv->filter);
2725 }