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