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