3 #include "empathy-roster-view.h"
5 #include <glib/gi18n-lib.h>
7 #include <libempathy-gtk/empathy-roster-contact.h>
8 #include <libempathy-gtk/empathy-roster-group.h>
10 G_DEFINE_TYPE (EmpathyRosterView, empathy_roster_view, EGG_TYPE_LIST_BOX)
26 static guint signals[LAST_SIGNAL];
29 #define NO_GROUP "X-no-group"
30 #define UNGROUPPED _("Ungroupped")
32 struct _EmpathyRosterViewPriv
34 EmpathyIndividualManager *manager;
36 /* FolksIndividual (borrowed) -> GHashTable (
37 * (gchar * group_name) -> EmpathyRosterContact (borrowed))
39 * When not using groups, this hash just have one element mapped
40 * from the special NO_GROUP key. We could use it as a set but
41 * I prefer to stay coherent in the way this hash is managed.
43 GHashTable *roster_contacts;
44 /* (gchar *group_name) -> EmpathyRosterGroup (borrowed) */
45 GHashTable *roster_groups;
47 gboolean show_offline;
52 empathy_roster_view_get_property (GObject *object,
57 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
62 g_value_set_object (value, self->priv->manager);
64 case PROP_SHOW_OFFLINE:
65 g_value_set_boolean (value, self->priv->show_offline);
67 case PROP_SHOW_GROUPS:
68 g_value_set_boolean (value, self->priv->show_groups);
71 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
77 empathy_roster_view_set_property (GObject *object,
82 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
87 g_assert (self->priv->manager == NULL); /* construct only */
88 self->priv->manager = g_value_dup_object (value);
90 case PROP_SHOW_OFFLINE:
91 empathy_roster_view_show_offline (self, g_value_get_boolean (value));
93 case PROP_SHOW_GROUPS:
94 empathy_roster_view_show_groups (self, g_value_get_boolean (value));
97 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
103 roster_contact_changed_cb (GtkWidget *child,
105 EmpathyRosterView *self)
107 egg_list_box_child_changed (EGG_LIST_BOX (self), child);
111 add_roster_contact (EmpathyRosterView *self,
112 FolksIndividual *individual,
117 contact = empathy_roster_contact_new (individual, group);
119 /* Need to refilter if online is changed */
120 g_signal_connect (contact, "notify::online",
121 G_CALLBACK (roster_contact_changed_cb), self);
123 /* Need to resort if alias is changed */
124 g_signal_connect (contact, "notify::alias",
125 G_CALLBACK (roster_contact_changed_cb), self);
127 gtk_widget_show (contact);
128 gtk_container_add (GTK_CONTAINER (self), contact);
134 group_expanded_cb (EmpathyRosterGroup *group,
136 EmpathyRosterView *self)
140 widgets = empathy_roster_group_get_widgets (group);
141 for (l = widgets; l != NULL; l = g_list_next (l))
143 egg_list_box_child_changed (EGG_LIST_BOX (self), l->data);
146 g_list_free (widgets);
149 static EmpathyRosterGroup *
150 lookup_roster_group (EmpathyRosterView *self,
153 return g_hash_table_lookup (self->priv->roster_groups, group);
157 ensure_roster_group (EmpathyRosterView *self,
160 GtkWidget *roster_group;
162 roster_group = (GtkWidget *) lookup_roster_group (self, group);
163 if (roster_group != NULL)
166 roster_group = empathy_roster_group_new (group);
168 g_signal_connect (roster_group, "notify::expanded",
169 G_CALLBACK (group_expanded_cb), self);
171 gtk_widget_show (roster_group);
172 gtk_container_add (GTK_CONTAINER (self), roster_group);
174 g_hash_table_insert (self->priv->roster_groups, g_strdup (group),
179 add_to_group (EmpathyRosterView *self,
180 FolksIndividual *individual,
184 GHashTable *contacts;
186 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
187 if (contacts == NULL)
190 if (tp_strdiff (group, NO_GROUP))
191 ensure_roster_group (self, group);
193 contact = add_roster_contact (self, individual, group);
194 g_hash_table_insert (contacts, g_strdup (group), contact);
198 individual_added (EmpathyRosterView *self,
199 FolksIndividual *individual)
201 GHashTable *contacts;
203 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
204 if (contacts != NULL)
207 contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
209 g_hash_table_insert (self->priv->roster_contacts, individual, contacts);
211 if (!self->priv->show_groups)
213 add_to_group (self, individual, NO_GROUP);
219 groups = folks_group_details_get_groups (
220 FOLKS_GROUP_DETAILS (individual));
222 if (gee_collection_get_size (GEE_COLLECTION (groups)) > 0)
224 GeeIterator *iter = gee_iterable_iterator (GEE_ITERABLE (groups));
226 while (iter != NULL && gee_iterator_next (iter))
228 gchar *group = gee_iterator_get (iter);
230 add_to_group (self, individual, group);
235 g_clear_object (&iter);
239 /* No group, adds to Ungroupped */
240 add_to_group (self, individual, UNGROUPPED);
246 update_group_widgets_count (EmpathyRosterView *self,
247 EmpathyRosterGroup *group,
248 EmpathyRosterContact *contact,
253 if (empathy_roster_group_add_widget (group, GTK_WIDGET (contact)) == 1)
255 egg_list_box_child_changed (EGG_LIST_BOX (self),
261 if (empathy_roster_group_remove_widget (group, GTK_WIDGET (contact)) == 0)
263 egg_list_box_child_changed (EGG_LIST_BOX (self),
270 individual_removed (EmpathyRosterView *self,
271 FolksIndividual *individual)
273 GHashTable *contacts;
277 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
278 if (contacts == NULL)
281 g_hash_table_iter_init (&iter, contacts);
282 while (g_hash_table_iter_next (&iter, &key, &value))
284 const gchar *group_name = key;
285 GtkWidget *contact = value;
286 EmpathyRosterGroup *group;
288 group = lookup_roster_group (self, group_name);
291 update_group_widgets_count (self, group,
292 EMPATHY_ROSTER_CONTACT (contact), FALSE);
295 gtk_container_remove (GTK_CONTAINER (self), contact);
298 g_hash_table_remove (self->priv->roster_contacts, individual);
302 members_changed_cb (EmpathyIndividualManager *manager,
303 const gchar *message,
306 TpChannelGroupChangeReason reason,
307 EmpathyRosterView *self)
311 for (l = added; l != NULL; l = g_list_next (l))
313 FolksIndividual *individual = l->data;
315 individual_added (self, individual);
318 for (l = removed; l != NULL; l = g_list_next (l))
320 FolksIndividual *individual = l->data;
322 individual_removed (self, individual);
327 compare_roster_contacts_by_alias (EmpathyRosterContact *a,
328 EmpathyRosterContact *b)
330 FolksIndividual *ind_a, *ind_b;
331 const gchar *alias_a, *alias_b;
333 ind_a = empathy_roster_contact_get_individual (a);
334 ind_b = empathy_roster_contact_get_individual (b);
336 alias_a = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_a));
337 alias_b = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_b));
339 return g_ascii_strcasecmp (alias_a, alias_b);
343 compare_roster_contacts_no_group (EmpathyRosterView *self,
344 EmpathyRosterContact *a,
345 EmpathyRosterContact *b)
347 return compare_roster_contacts_by_alias (a, b);
351 compare_group_names (const gchar *group_a,
352 const gchar *group_b)
354 return g_ascii_strcasecmp (group_a, group_b);
358 compare_roster_contacts_with_groups (EmpathyRosterView *self,
359 EmpathyRosterContact *a,
360 EmpathyRosterContact *b)
362 const gchar *group_a, *group_b;
364 group_a = empathy_roster_contact_get_group (a);
365 group_b = empathy_roster_contact_get_group (b);
367 if (!tp_strdiff (group_a, group_b))
368 /* Same group, compare the contacts */
369 return compare_roster_contacts_by_alias (a, b);
372 return compare_group_names (group_a, group_b);
376 compare_roster_contacts (EmpathyRosterView *self,
377 EmpathyRosterContact *a,
378 EmpathyRosterContact *b)
380 if (!self->priv->show_groups)
381 return compare_roster_contacts_no_group (self, a, b);
383 return compare_roster_contacts_with_groups (self, a, b);
387 compare_roster_groups (EmpathyRosterGroup *a,
388 EmpathyRosterGroup *b)
390 const gchar *name_a, *name_b;
392 name_a = empathy_roster_group_get_name (a);
393 name_b = empathy_roster_group_get_name (b);
395 return compare_group_names (name_a, name_b);
399 compare_contact_group (EmpathyRosterContact *contact,
400 EmpathyRosterGroup *group)
402 const char *contact_group, *group_name;
404 contact_group = empathy_roster_contact_get_group (contact);
405 group_name = empathy_roster_group_get_name (group);
407 if (!tp_strdiff (contact_group, group_name))
408 /* @contact is in @group, @group has to be displayed first */
411 /* @contact is in a different group, sort by group name */
412 return compare_group_names (contact_group, group_name);
416 roster_view_sort (gconstpointer a,
420 EmpathyRosterView *self = user_data;
422 if (EMPATHY_IS_ROSTER_CONTACT (a) && EMPATHY_IS_ROSTER_CONTACT (b))
423 return compare_roster_contacts (self, EMPATHY_ROSTER_CONTACT (a),
424 EMPATHY_ROSTER_CONTACT (b));
425 else if (EMPATHY_IS_ROSTER_GROUP (a) && EMPATHY_IS_ROSTER_GROUP (b))
426 return compare_roster_groups (EMPATHY_ROSTER_GROUP (a),
427 EMPATHY_ROSTER_GROUP (b));
428 else if (EMPATHY_IS_ROSTER_CONTACT (a) && EMPATHY_IS_ROSTER_GROUP (b))
429 return compare_contact_group (EMPATHY_ROSTER_CONTACT (a),
430 EMPATHY_ROSTER_GROUP (b));
431 else if (EMPATHY_IS_ROSTER_GROUP (a) && EMPATHY_IS_ROSTER_CONTACT (b))
432 return -1 * compare_contact_group (EMPATHY_ROSTER_CONTACT (b),
433 EMPATHY_ROSTER_GROUP (a));
435 g_return_val_if_reached (0);
439 update_separator (GtkWidget **separator,
446 /* No separator before the first row */
447 g_clear_object (separator);
451 if (*separator != NULL)
454 *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
455 g_object_ref_sink (*separator);
459 filter_contact (EmpathyRosterView *self,
460 EmpathyRosterContact *contact)
464 if (self->priv->show_offline)
470 displayed = empathy_roster_contact_is_online (contact);
473 if (self->priv->show_groups)
475 const gchar *group_name;
476 EmpathyRosterGroup *group;
478 group_name = empathy_roster_contact_get_group (contact);
479 group = lookup_roster_group (self, group_name);
483 update_group_widgets_count (self, group, contact, displayed);
485 if (!gtk_expander_get_expanded (GTK_EXPANDER (group)))
494 filter_group (EmpathyRosterView *self,
495 EmpathyRosterGroup *group)
497 return empathy_roster_group_get_widgets_count (group);
501 filter_list (GtkWidget *child,
504 EmpathyRosterView *self = user_data;
506 if (EMPATHY_IS_ROSTER_CONTACT (child))
507 return filter_contact (self, EMPATHY_ROSTER_CONTACT (child));
509 else if (EMPATHY_IS_ROSTER_GROUP (child))
510 return filter_group (self, EMPATHY_ROSTER_GROUP (child));
512 g_return_val_if_reached (FALSE);
516 populate_view (EmpathyRosterView *self)
518 GList *individuals, *l;
520 individuals = empathy_individual_manager_get_members (self->priv->manager);
521 for (l = individuals; l != NULL; l = g_list_next (l))
523 FolksIndividual *individual = l->data;
525 individual_added (self, individual);
528 g_list_free (individuals);
532 remove_from_group (EmpathyRosterView *self,
533 FolksIndividual *individual,
536 GHashTable *contacts;
538 EmpathyRosterGroup *roster_group;
540 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
541 if (contacts == NULL)
544 contact = g_hash_table_lookup (contacts, group);
548 g_hash_table_remove (contacts, group);
550 if (g_hash_table_size (contacts) == 0)
552 add_to_group (self, individual, UNGROUPPED);
555 roster_group = lookup_roster_group (self, group);
557 if (roster_group != NULL)
559 update_group_widgets_count (self, roster_group,
560 EMPATHY_ROSTER_CONTACT (contact), FALSE);
563 gtk_container_remove (GTK_CONTAINER (self), contact);
567 groups_changed_cb (EmpathyIndividualManager *manager,
568 FolksIndividual *individual,
571 EmpathyRosterView *self)
573 if (!self->priv->show_groups)
578 add_to_group (self, individual, group);
582 remove_from_group (self, individual, group);
587 empathy_roster_view_constructed (GObject *object)
589 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
590 void (*chain_up) (GObject *) =
591 ((GObjectClass *) empathy_roster_view_parent_class)->constructed;
593 if (chain_up != NULL)
596 g_assert (EMPATHY_IS_INDIVIDUAL_MANAGER (self->priv->manager));
598 populate_view (self);
600 tp_g_signal_connect_object (self->priv->manager, "members-changed",
601 G_CALLBACK (members_changed_cb), self, 0);
602 tp_g_signal_connect_object (self->priv->manager, "groups-changed",
603 G_CALLBACK (groups_changed_cb), self, 0);
605 egg_list_box_set_sort_func (EGG_LIST_BOX (self),
606 roster_view_sort, self, NULL);
608 egg_list_box_set_separator_funcs (EGG_LIST_BOX (self), update_separator,
611 egg_list_box_set_filter_func (EGG_LIST_BOX (self), filter_list, self, NULL);
615 empathy_roster_view_dispose (GObject *object)
617 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
618 void (*chain_up) (GObject *) =
619 ((GObjectClass *) empathy_roster_view_parent_class)->dispose;
621 g_clear_object (&self->priv->manager);
623 if (chain_up != NULL)
628 empathy_roster_view_finalize (GObject *object)
630 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
631 void (*chain_up) (GObject *) =
632 ((GObjectClass *) empathy_roster_view_parent_class)->finalize;
634 g_hash_table_unref (self->priv->roster_contacts);
635 g_hash_table_unref (self->priv->roster_groups);
637 if (chain_up != NULL)
642 empathy_roster_view_class_init (
643 EmpathyRosterViewClass *klass)
645 GObjectClass *oclass = G_OBJECT_CLASS (klass);
648 oclass->get_property = empathy_roster_view_get_property;
649 oclass->set_property = empathy_roster_view_set_property;
650 oclass->constructed = empathy_roster_view_constructed;
651 oclass->dispose = empathy_roster_view_dispose;
652 oclass->finalize = empathy_roster_view_finalize;
654 spec = g_param_spec_object ("manager", "Manager",
655 "EmpathyIndividualManager",
656 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
657 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
658 g_object_class_install_property (oclass, PROP_MANAGER, spec);
660 spec = g_param_spec_boolean ("show-offline", "Show Offline",
661 "Show offline contacts",
663 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
664 g_object_class_install_property (oclass, PROP_SHOW_OFFLINE, spec);
666 spec = g_param_spec_boolean ("show-groups", "Show Groups",
669 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
670 g_object_class_install_property (oclass, PROP_SHOW_GROUPS, spec);
672 g_type_class_add_private (klass, sizeof (EmpathyRosterViewPriv));
676 empathy_roster_view_init (EmpathyRosterView *self)
678 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
679 EMPATHY_TYPE_ROSTER_VIEW, EmpathyRosterViewPriv);
681 self->priv->roster_contacts = g_hash_table_new_full (NULL, NULL,
682 NULL, (GDestroyNotify) g_hash_table_unref);
683 self->priv->roster_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
688 empathy_roster_view_new (EmpathyIndividualManager *manager)
690 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager), NULL);
692 return g_object_new (EMPATHY_TYPE_ROSTER_VIEW,
697 EmpathyIndividualManager *
698 empathy_roster_view_get_manager (EmpathyRosterView *self)
700 return self->priv->manager;
704 empathy_roster_view_show_offline (EmpathyRosterView *self,
707 if (self->priv->show_offline == show)
710 self->priv->show_offline = show;
711 egg_list_box_refilter (EGG_LIST_BOX (self));
713 g_object_notify (G_OBJECT (self), "show-offline");
717 clear_view (EmpathyRosterView *self)
719 gtk_container_foreach (GTK_CONTAINER (self),
720 (GtkCallback) gtk_widget_destroy, NULL);
722 g_hash_table_remove_all (self->priv->roster_contacts);
726 empathy_roster_view_show_groups (EmpathyRosterView *self,
729 if (self->priv->show_groups == show)
732 self->priv->show_groups = show;
734 /* TODO: block sort/filter? */
736 populate_view (self);
738 g_object_notify (G_OBJECT (self), "show-groups");