]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-roster-view.c
roster-view: add live search support
[empathy.git] / libempathy-gtk / empathy-roster-view.c
1 #include "config.h"
2
3 #include "empathy-roster-view.h"
4
5 #include <glib/gi18n-lib.h>
6
7 #include <libempathy-gtk/empathy-roster-contact.h>
8 #include <libempathy-gtk/empathy-roster-group.h>
9 #include <libempathy-gtk/empathy-ui-utils.h>
10
11 G_DEFINE_TYPE (EmpathyRosterView, empathy_roster_view, EGG_TYPE_LIST_BOX)
12
13 enum
14 {
15   PROP_MANAGER = 1,
16   PROP_SHOW_OFFLINE,
17   PROP_SHOW_GROUPS,
18   N_PROPS
19 };
20
21 /*
22 enum
23 {
24   LAST_SIGNAL
25 };
26
27 static guint signals[LAST_SIGNAL];
28 */
29
30 #define NO_GROUP "X-no-group"
31 #define UNGROUPPED _("Ungroupped")
32
33 struct _EmpathyRosterViewPriv
34 {
35   EmpathyIndividualManager *manager;
36
37   /* FolksIndividual (borrowed) -> GHashTable (
38    * (gchar * group_name) -> EmpathyRosterContact (borrowed))
39    *
40    * When not using groups, this hash just have one element mapped
41    * from the special NO_GROUP key. We could use it as a set but
42    * I prefer to stay coherent in the way this hash is managed.
43    */
44   GHashTable *roster_contacts;
45   /* (gchar *group_name) -> EmpathyRosterGroup (borrowed) */
46   GHashTable *roster_groups;
47
48   gboolean show_offline;
49   gboolean show_groups;
50
51   EmpathyLiveSearch *search;
52 };
53
54 static void
55 empathy_roster_view_get_property (GObject *object,
56     guint property_id,
57     GValue *value,
58     GParamSpec *pspec)
59 {
60   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
61
62   switch (property_id)
63     {
64       case PROP_MANAGER:
65         g_value_set_object (value, self->priv->manager);
66         break;
67       case PROP_SHOW_OFFLINE:
68         g_value_set_boolean (value, self->priv->show_offline);
69         break;
70       case PROP_SHOW_GROUPS:
71         g_value_set_boolean (value, self->priv->show_groups);
72         break;
73       default:
74         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
75         break;
76     }
77 }
78
79 static void
80 empathy_roster_view_set_property (GObject *object,
81     guint property_id,
82     const GValue *value,
83     GParamSpec *pspec)
84 {
85   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
86
87   switch (property_id)
88     {
89       case PROP_MANAGER:
90         g_assert (self->priv->manager == NULL); /* construct only */
91         self->priv->manager = g_value_dup_object (value);
92         break;
93       case PROP_SHOW_OFFLINE:
94         empathy_roster_view_show_offline (self, g_value_get_boolean (value));
95         break;
96       case PROP_SHOW_GROUPS:
97         empathy_roster_view_show_groups (self, g_value_get_boolean (value));
98         break;
99       default:
100         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
101         break;
102     }
103 }
104
105 static void
106 roster_contact_changed_cb (GtkWidget *child,
107     GParamSpec *spec,
108     EmpathyRosterView *self)
109 {
110   egg_list_box_child_changed (EGG_LIST_BOX (self), child);
111 }
112
113 static GtkWidget *
114 add_roster_contact (EmpathyRosterView *self,
115     FolksIndividual *individual,
116     const gchar *group)
117 {
118   GtkWidget *contact;
119
120   contact = empathy_roster_contact_new (individual, group);
121
122   /* Need to refilter if online is changed */
123   g_signal_connect (contact, "notify::online",
124       G_CALLBACK (roster_contact_changed_cb), self);
125
126   /* Need to resort if alias is changed */
127   g_signal_connect (contact, "notify::alias",
128       G_CALLBACK (roster_contact_changed_cb), self);
129
130   gtk_widget_show (contact);
131   gtk_container_add (GTK_CONTAINER (self), contact);
132
133   return contact;
134 }
135
136 static void
137 group_expanded_cb (EmpathyRosterGroup *group,
138     GParamSpec *spec,
139     EmpathyRosterView *self)
140 {
141   GList *widgets, *l;
142
143   widgets = empathy_roster_group_get_widgets (group);
144   for (l = widgets; l != NULL; l = g_list_next (l))
145     {
146       egg_list_box_child_changed (EGG_LIST_BOX (self), l->data);
147     }
148
149   g_list_free (widgets);
150 }
151
152 static EmpathyRosterGroup *
153 lookup_roster_group (EmpathyRosterView *self,
154     const gchar *group)
155 {
156   return g_hash_table_lookup (self->priv->roster_groups, group);
157 }
158
159 static void
160 ensure_roster_group (EmpathyRosterView *self,
161     const gchar *group)
162 {
163   GtkWidget *roster_group;
164
165   roster_group = (GtkWidget *) lookup_roster_group (self, group);
166   if (roster_group != NULL)
167     return;
168
169   roster_group = empathy_roster_group_new (group);
170
171   g_signal_connect (roster_group, "notify::expanded",
172       G_CALLBACK (group_expanded_cb), self);
173
174   gtk_widget_show (roster_group);
175   gtk_container_add (GTK_CONTAINER (self), roster_group);
176
177   g_hash_table_insert (self->priv->roster_groups, g_strdup (group),
178       roster_group);
179 }
180
181 static void
182 add_to_group (EmpathyRosterView *self,
183     FolksIndividual *individual,
184     const gchar *group)
185 {
186   GtkWidget *contact;
187   GHashTable *contacts;
188
189   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
190   if (contacts == NULL)
191     return;
192
193   if (tp_strdiff (group, NO_GROUP))
194     ensure_roster_group (self, group);
195
196   contact = add_roster_contact (self, individual, group);
197   g_hash_table_insert (contacts, g_strdup (group), contact);
198 }
199
200 static void
201 individual_added (EmpathyRosterView *self,
202     FolksIndividual *individual)
203 {
204   GHashTable *contacts;
205
206   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
207   if (contacts != NULL)
208     return;
209
210   contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
211
212   g_hash_table_insert (self->priv->roster_contacts, individual, contacts);
213
214   if (!self->priv->show_groups)
215     {
216       add_to_group (self, individual, NO_GROUP);
217     }
218   else
219     {
220       GeeSet *groups;
221
222       groups = folks_group_details_get_groups (
223           FOLKS_GROUP_DETAILS (individual));
224
225       if (gee_collection_get_size (GEE_COLLECTION (groups)) > 0)
226         {
227           GeeIterator *iter = gee_iterable_iterator (GEE_ITERABLE (groups));
228
229           while (iter != NULL && gee_iterator_next (iter))
230             {
231               gchar *group = gee_iterator_get (iter);
232
233               add_to_group (self, individual, group);
234
235               g_free (group);
236             }
237
238           g_clear_object (&iter);
239         }
240       else
241         {
242           /* No group, adds to Ungroupped */
243           add_to_group (self, individual, UNGROUPPED);
244         }
245     }
246 }
247
248 static void
249 update_group_widgets_count (EmpathyRosterView *self,
250     EmpathyRosterGroup *group,
251     EmpathyRosterContact *contact,
252     gboolean displayed)
253 {
254   if (displayed)
255     {
256       if (empathy_roster_group_add_widget (group, GTK_WIDGET (contact)) == 1)
257         {
258           egg_list_box_child_changed (EGG_LIST_BOX (self),
259               GTK_WIDGET (group));
260         }
261     }
262   else
263     {
264       if (empathy_roster_group_remove_widget (group, GTK_WIDGET (contact)) == 0)
265         {
266           egg_list_box_child_changed (EGG_LIST_BOX (self),
267               GTK_WIDGET (group));
268         }
269     }
270 }
271
272 static void
273 individual_removed (EmpathyRosterView *self,
274     FolksIndividual *individual)
275 {
276   GHashTable *contacts;
277   GHashTableIter iter;
278   gpointer key, value;
279
280   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
281   if (contacts == NULL)
282     return;
283
284   g_hash_table_iter_init (&iter, contacts);
285   while (g_hash_table_iter_next (&iter, &key, &value))
286     {
287       const gchar *group_name = key;
288       GtkWidget *contact = value;
289       EmpathyRosterGroup *group;
290
291       group = lookup_roster_group (self, group_name);
292       if (group != NULL)
293         {
294           update_group_widgets_count (self, group,
295               EMPATHY_ROSTER_CONTACT (contact), FALSE);
296         }
297
298       gtk_container_remove (GTK_CONTAINER (self), contact);
299     }
300
301   g_hash_table_remove (self->priv->roster_contacts, individual);
302 }
303
304 static void
305 members_changed_cb (EmpathyIndividualManager *manager,
306     const gchar *message,
307     GList *added,
308     GList *removed,
309     TpChannelGroupChangeReason reason,
310     EmpathyRosterView *self)
311 {
312   GList *l;
313
314   for (l = added; l != NULL; l = g_list_next (l))
315     {
316       FolksIndividual *individual = l->data;
317
318       individual_added (self, individual);
319     }
320
321   for (l = removed; l != NULL; l = g_list_next (l))
322     {
323       FolksIndividual *individual = l->data;
324
325       individual_removed (self, individual);
326     }
327 }
328
329 static gint
330 compare_roster_contacts_by_alias (EmpathyRosterContact *a,
331     EmpathyRosterContact *b)
332 {
333   FolksIndividual *ind_a, *ind_b;
334   const gchar *alias_a, *alias_b;
335
336   ind_a = empathy_roster_contact_get_individual (a);
337   ind_b = empathy_roster_contact_get_individual (b);
338
339   alias_a = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_a));
340   alias_b = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_b));
341
342   return g_ascii_strcasecmp (alias_a, alias_b);
343 }
344
345 static gint
346 compare_roster_contacts_no_group (EmpathyRosterView *self,
347     EmpathyRosterContact *a,
348     EmpathyRosterContact *b)
349 {
350   return compare_roster_contacts_by_alias (a, b);
351 }
352
353 static gint
354 compare_group_names (const gchar *group_a,
355     const gchar *group_b)
356 {
357   return g_ascii_strcasecmp (group_a, group_b);
358 }
359
360 static gint
361 compare_roster_contacts_with_groups (EmpathyRosterView *self,
362     EmpathyRosterContact *a,
363     EmpathyRosterContact *b)
364 {
365   const gchar *group_a, *group_b;
366
367   group_a = empathy_roster_contact_get_group (a);
368   group_b = empathy_roster_contact_get_group (b);
369
370   if (!tp_strdiff (group_a, group_b))
371     /* Same group, compare the contacts */
372     return compare_roster_contacts_by_alias (a, b);
373
374   /* Sort by group */
375   return compare_group_names (group_a, group_b);
376 }
377
378 static gint
379 compare_roster_contacts (EmpathyRosterView *self,
380     EmpathyRosterContact *a,
381     EmpathyRosterContact *b)
382 {
383   if (!self->priv->show_groups)
384     return compare_roster_contacts_no_group (self, a, b);
385   else
386     return compare_roster_contacts_with_groups (self, a, b);
387 }
388
389 static gint
390 compare_roster_groups (EmpathyRosterGroup *a,
391     EmpathyRosterGroup *b)
392 {
393   const gchar *name_a, *name_b;
394
395   name_a = empathy_roster_group_get_name (a);
396   name_b = empathy_roster_group_get_name (b);
397
398   return compare_group_names (name_a, name_b);
399 }
400
401 static gint
402 compare_contact_group (EmpathyRosterContact *contact,
403     EmpathyRosterGroup *group)
404 {
405   const char *contact_group, *group_name;
406
407   contact_group = empathy_roster_contact_get_group (contact);
408   group_name = empathy_roster_group_get_name (group);
409
410   if (!tp_strdiff (contact_group, group_name))
411     /* @contact is in @group, @group has to be displayed first */
412     return 1;
413
414   /* @contact is in a different group, sort by group name */
415   return compare_group_names (contact_group, group_name);
416 }
417
418 static gint
419 roster_view_sort (gconstpointer a,
420     gconstpointer b,
421     gpointer user_data)
422 {
423   EmpathyRosterView *self = user_data;
424
425   if (EMPATHY_IS_ROSTER_CONTACT (a) && EMPATHY_IS_ROSTER_CONTACT (b))
426     return compare_roster_contacts (self, EMPATHY_ROSTER_CONTACT (a),
427         EMPATHY_ROSTER_CONTACT (b));
428   else if (EMPATHY_IS_ROSTER_GROUP (a) && EMPATHY_IS_ROSTER_GROUP (b))
429     return compare_roster_groups (EMPATHY_ROSTER_GROUP (a),
430         EMPATHY_ROSTER_GROUP (b));
431   else if (EMPATHY_IS_ROSTER_CONTACT (a) && EMPATHY_IS_ROSTER_GROUP (b))
432     return compare_contact_group (EMPATHY_ROSTER_CONTACT (a),
433         EMPATHY_ROSTER_GROUP (b));
434   else if (EMPATHY_IS_ROSTER_GROUP (a) && EMPATHY_IS_ROSTER_CONTACT (b))
435     return -1 * compare_contact_group (EMPATHY_ROSTER_CONTACT (b),
436         EMPATHY_ROSTER_GROUP (a));
437
438   g_return_val_if_reached (0);
439 }
440
441 static void
442 update_separator (GtkWidget **separator,
443     GtkWidget *child,
444     GtkWidget *before,
445     gpointer user_data)
446 {
447   if (before == NULL)
448     {
449       /* No separator before the first row */
450       g_clear_object (separator);
451       return;
452     }
453
454   if (*separator != NULL)
455     return;
456
457   *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
458   g_object_ref_sink (*separator);
459 }
460
461 static gboolean
462 is_searching (EmpathyRosterView *self)
463 {
464   if (self->priv->search == NULL)
465     return FALSE;
466
467   return gtk_widget_get_visible (GTK_WIDGET (self->priv->search));
468 }
469
470 static gboolean
471 filter_contact (EmpathyRosterView *self,
472     EmpathyRosterContact *contact)
473 {
474   gboolean displayed;
475
476   if (is_searching (self))
477     {
478       FolksIndividual *individual;
479
480       individual = empathy_roster_contact_get_individual (contact);
481
482       displayed = empathy_individual_match_string (individual,
483           empathy_live_search_get_text (self->priv->search),
484           empathy_live_search_get_words (self->priv->search));
485     }
486   else
487     {
488       if (self->priv->show_offline)
489         {
490           displayed = TRUE;
491         }
492       else
493         {
494           displayed = empathy_roster_contact_is_online (contact);
495         }
496     }
497
498   if (self->priv->show_groups)
499     {
500       const gchar *group_name;
501       EmpathyRosterGroup *group;
502
503       group_name = empathy_roster_contact_get_group (contact);
504       group = lookup_roster_group (self, group_name);
505
506       if (group != NULL)
507         {
508           update_group_widgets_count (self, group, contact, displayed);
509
510           /* When searching, always display even if the group is closed */
511           if (!is_searching (self) &&
512               !gtk_expander_get_expanded (GTK_EXPANDER (group)))
513             return FALSE;
514         }
515     }
516
517   return displayed;
518 }
519
520 static gboolean
521 filter_group (EmpathyRosterView *self,
522     EmpathyRosterGroup *group)
523 {
524   return empathy_roster_group_get_widgets_count (group);
525 }
526
527 static gboolean
528 filter_list (GtkWidget *child,
529     gpointer user_data)
530 {
531   EmpathyRosterView *self = user_data;
532
533   if (EMPATHY_IS_ROSTER_CONTACT (child))
534     return filter_contact (self, EMPATHY_ROSTER_CONTACT (child));
535
536   else if (EMPATHY_IS_ROSTER_GROUP (child))
537     return filter_group (self, EMPATHY_ROSTER_GROUP (child));
538
539   g_return_val_if_reached (FALSE);
540 }
541
542 static void
543 populate_view (EmpathyRosterView *self)
544 {
545   GList *individuals, *l;
546
547   individuals = empathy_individual_manager_get_members (self->priv->manager);
548   for (l = individuals; l != NULL; l = g_list_next (l))
549     {
550       FolksIndividual *individual = l->data;
551
552       individual_added (self, individual);
553     }
554
555   g_list_free (individuals);
556 }
557
558 static void
559 remove_from_group (EmpathyRosterView *self,
560     FolksIndividual *individual,
561     const gchar *group)
562 {
563   GHashTable *contacts;
564   GtkWidget *contact;
565   EmpathyRosterGroup *roster_group;
566
567   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
568   if (contacts == NULL)
569     return;
570
571   contact = g_hash_table_lookup (contacts, group);
572   if (contact == NULL)
573     return;
574
575   g_hash_table_remove (contacts, group);
576
577   if (g_hash_table_size (contacts) == 0)
578     {
579       add_to_group (self, individual, UNGROUPPED);
580     }
581
582   roster_group = lookup_roster_group (self, group);
583
584   if (roster_group != NULL)
585     {
586       update_group_widgets_count (self, roster_group,
587           EMPATHY_ROSTER_CONTACT (contact), FALSE);
588     }
589
590   gtk_container_remove (GTK_CONTAINER (self), contact);
591 }
592
593 static void
594 groups_changed_cb (EmpathyIndividualManager *manager,
595     FolksIndividual *individual,
596     gchar *group,
597     gboolean is_member,
598     EmpathyRosterView *self)
599 {
600   if (!self->priv->show_groups)
601     return;
602
603   if (is_member)
604     {
605       add_to_group (self, individual, group);
606     }
607   else
608     {
609       remove_from_group (self, individual, group);
610     }
611 }
612
613 static void
614 empathy_roster_view_constructed (GObject *object)
615 {
616   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
617   void (*chain_up) (GObject *) =
618       ((GObjectClass *) empathy_roster_view_parent_class)->constructed;
619
620   if (chain_up != NULL)
621     chain_up (object);
622
623   g_assert (EMPATHY_IS_INDIVIDUAL_MANAGER (self->priv->manager));
624
625   populate_view (self);
626
627   tp_g_signal_connect_object (self->priv->manager, "members-changed",
628       G_CALLBACK (members_changed_cb), self, 0);
629   tp_g_signal_connect_object (self->priv->manager, "groups-changed",
630       G_CALLBACK (groups_changed_cb), self, 0);
631
632   egg_list_box_set_sort_func (EGG_LIST_BOX (self),
633       roster_view_sort, self, NULL);
634
635   egg_list_box_set_separator_funcs (EGG_LIST_BOX (self), update_separator,
636       self, NULL);
637
638   egg_list_box_set_filter_func (EGG_LIST_BOX (self), filter_list, self, NULL);
639 }
640
641 static void
642 empathy_roster_view_dispose (GObject *object)
643 {
644   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
645   void (*chain_up) (GObject *) =
646       ((GObjectClass *) empathy_roster_view_parent_class)->dispose;
647
648   empathy_roster_view_set_live_search (self, NULL);
649   g_clear_object (&self->priv->manager);
650
651   if (chain_up != NULL)
652     chain_up (object);
653 }
654
655 static void
656 empathy_roster_view_finalize (GObject *object)
657 {
658   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
659   void (*chain_up) (GObject *) =
660       ((GObjectClass *) empathy_roster_view_parent_class)->finalize;
661
662   g_hash_table_unref (self->priv->roster_contacts);
663   g_hash_table_unref (self->priv->roster_groups);
664
665   if (chain_up != NULL)
666     chain_up (object);
667 }
668
669 static void
670 empathy_roster_view_class_init (
671     EmpathyRosterViewClass *klass)
672 {
673   GObjectClass *oclass = G_OBJECT_CLASS (klass);
674   GParamSpec *spec;
675
676   oclass->get_property = empathy_roster_view_get_property;
677   oclass->set_property = empathy_roster_view_set_property;
678   oclass->constructed = empathy_roster_view_constructed;
679   oclass->dispose = empathy_roster_view_dispose;
680   oclass->finalize = empathy_roster_view_finalize;
681
682   spec = g_param_spec_object ("manager", "Manager",
683       "EmpathyIndividualManager",
684       EMPATHY_TYPE_INDIVIDUAL_MANAGER,
685       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
686   g_object_class_install_property (oclass, PROP_MANAGER, spec);
687
688   spec = g_param_spec_boolean ("show-offline", "Show Offline",
689       "Show offline contacts",
690       FALSE,
691       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
692   g_object_class_install_property (oclass, PROP_SHOW_OFFLINE, spec);
693
694   spec = g_param_spec_boolean ("show-groups", "Show Groups",
695       "Show groups",
696       FALSE,
697       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
698   g_object_class_install_property (oclass, PROP_SHOW_GROUPS, spec);
699
700   g_type_class_add_private (klass, sizeof (EmpathyRosterViewPriv));
701 }
702
703 static void
704 empathy_roster_view_init (EmpathyRosterView *self)
705 {
706   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
707       EMPATHY_TYPE_ROSTER_VIEW, EmpathyRosterViewPriv);
708
709   self->priv->roster_contacts = g_hash_table_new_full (NULL, NULL,
710       NULL, (GDestroyNotify) g_hash_table_unref);
711   self->priv->roster_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
712       g_free, NULL);
713 }
714
715 GtkWidget *
716 empathy_roster_view_new (EmpathyIndividualManager *manager)
717 {
718   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager), NULL);
719
720   return g_object_new (EMPATHY_TYPE_ROSTER_VIEW,
721       "manager", manager,
722       NULL);
723 }
724
725 EmpathyIndividualManager *
726 empathy_roster_view_get_manager (EmpathyRosterView *self)
727 {
728   return self->priv->manager;
729 }
730
731 void
732 empathy_roster_view_show_offline (EmpathyRosterView *self,
733     gboolean show)
734 {
735   if (self->priv->show_offline == show)
736     return;
737
738   self->priv->show_offline = show;
739   egg_list_box_refilter (EGG_LIST_BOX (self));
740
741   g_object_notify (G_OBJECT (self), "show-offline");
742 }
743
744 static void
745 clear_view (EmpathyRosterView *self)
746 {
747   gtk_container_foreach (GTK_CONTAINER (self),
748       (GtkCallback) gtk_widget_destroy, NULL);
749
750   g_hash_table_remove_all (self->priv->roster_contacts);
751 }
752
753 void
754 empathy_roster_view_show_groups (EmpathyRosterView *self,
755     gboolean show)
756 {
757   if (self->priv->show_groups == show)
758     return;
759
760   self->priv->show_groups = show;
761
762   /* TODO: block sort/filter? */
763   clear_view (self);
764   populate_view (self);
765
766   g_object_notify (G_OBJECT (self), "show-groups");
767 }
768
769 static void
770 select_first_contact (EmpathyRosterView *self)
771 {
772   GList *children, *l;
773
774   children = gtk_container_get_children (GTK_CONTAINER (self));
775   for (l = children; l != NULL; l = g_list_next (l))
776     {
777       GtkWidget *child = l->data;
778
779       if (!gtk_widget_get_child_visible (child))
780         continue;
781
782       if (!EMPATHY_IS_ROSTER_CONTACT (child))
783         continue;
784
785       egg_list_box_select_child (EGG_LIST_BOX (self), child);
786       break;
787     }
788
789   g_list_free (children);
790 }
791
792 static void
793 search_text_notify_cb (EmpathyLiveSearch *search,
794     GParamSpec *pspec,
795     EmpathyRosterView *self)
796 {
797   egg_list_box_refilter (EGG_LIST_BOX (self));
798
799   select_first_contact (self);
800 }
801
802 static void
803 search_activate_cb (GtkWidget *search,
804   EmpathyRosterView *self)
805 {
806   /* TODO */
807 }
808
809 void
810 empathy_roster_view_set_live_search (EmpathyRosterView *self,
811     EmpathyLiveSearch *search)
812 {
813   if (self->priv->search != NULL)
814     {
815       g_signal_handlers_disconnect_by_func (self->priv->search,
816           search_text_notify_cb, self);
817       g_signal_handlers_disconnect_by_func (self->priv->search,
818           search_activate_cb, self);
819
820       g_clear_object (&self->priv->search);
821     }
822
823   if (search == NULL)
824     return;
825
826   self->priv->search = g_object_ref (search);
827
828   g_signal_connect (self->priv->search, "notify::text",
829       G_CALLBACK (search_text_notify_cb), self);
830   g_signal_connect (self->priv->search, "activate",
831       G_CALLBACK (search_activate_cb), self);
832 }