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