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 add_to_group (EmpathyRosterView *self,
135 FolksIndividual *individual,
139 GHashTable *contacts;
141 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
142 if (contacts == NULL)
145 /* TODO: ensure group widget */
146 contact = add_roster_contact (self, individual, group);
147 g_hash_table_insert (contacts, g_strdup (group), contact);
151 individual_added (EmpathyRosterView *self,
152 FolksIndividual *individual)
154 GHashTable *contacts;
156 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
157 if (contacts != NULL)
160 contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
162 g_hash_table_insert (self->priv->roster_contacts, individual, contacts);
164 if (!self->priv->show_groups)
166 add_to_group (self, individual, NO_GROUP);
172 groups = folks_group_details_get_groups (
173 FOLKS_GROUP_DETAILS (individual));
175 if (gee_collection_get_size (GEE_COLLECTION (groups)) > 0)
177 GeeIterator *iter = gee_iterable_iterator (GEE_ITERABLE (groups));
179 while (iter != NULL && gee_iterator_next (iter))
181 gchar *group = gee_iterator_get (iter);
183 add_to_group (self, individual, group);
188 g_clear_object (&iter);
192 /* No group, adds to Ungroupped */
193 add_to_group (self, individual, UNGROUPPED);
199 individual_removed (EmpathyRosterView *self,
200 FolksIndividual *individual)
202 GHashTable *contacts;
206 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
207 if (contacts == NULL)
210 g_hash_table_iter_init (&iter, contacts);
211 while (g_hash_table_iter_next (&iter, NULL, &value))
213 GtkWidget *contact = value;
215 gtk_container_remove (GTK_CONTAINER (self), contact);
218 g_hash_table_remove (self->priv->roster_contacts, individual);
222 members_changed_cb (EmpathyIndividualManager *manager,
223 const gchar *message,
226 TpChannelGroupChangeReason reason,
227 EmpathyRosterView *self)
231 for (l = added; l != NULL; l = g_list_next (l))
233 FolksIndividual *individual = l->data;
235 individual_added (self, individual);
238 for (l = removed; l != NULL; l = g_list_next (l))
240 FolksIndividual *individual = l->data;
242 individual_removed (self, individual);
247 roster_view_sort (EmpathyRosterContact *a,
248 EmpathyRosterContact *b,
249 EmpathyRosterView *self)
251 FolksIndividual *ind_a, *ind_b;
252 const gchar *alias_a, *alias_b;
254 ind_a = empathy_roster_contact_get_individual (a);
255 ind_b = empathy_roster_contact_get_individual (b);
257 alias_a = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_a));
258 alias_b = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_b));
260 return g_ascii_strcasecmp (alias_a, alias_b);
264 update_separator (GtkWidget **separator,
271 /* No separator before the first row */
272 g_clear_object (separator);
276 if (*separator != NULL)
279 *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
280 g_object_ref_sink (*separator);
284 filter_list (GtkWidget *child,
287 EmpathyRosterView *self = user_data;
288 EmpathyRosterContact *contact = EMPATHY_ROSTER_CONTACT (child);
290 if (self->priv->show_offline)
293 return empathy_roster_contact_is_online (contact);
297 populate_view (EmpathyRosterView *self)
299 GList *individuals, *l;
301 individuals = empathy_individual_manager_get_members (self->priv->manager);
302 for (l = individuals; l != NULL; l = g_list_next (l))
304 FolksIndividual *individual = l->data;
306 individual_added (self, individual);
309 g_list_free (individuals);
313 remove_from_group (EmpathyRosterView *self,
314 FolksIndividual *individual,
317 GHashTable *contacts;
320 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
321 if (contacts == NULL)
324 contact = g_hash_table_lookup (contacts, group);
328 gtk_container_remove (GTK_CONTAINER (self), contact);
329 g_hash_table_remove (contacts, group);
331 if (g_hash_table_size (contacts) == 0)
333 add_to_group (self, individual, UNGROUPPED);
338 groups_changed_cb (EmpathyIndividualManager *manager,
339 FolksIndividual *individual,
342 EmpathyRosterView *self)
344 if (!self->priv->show_groups)
349 add_to_group (self, individual, group);
353 remove_from_group (self, individual, group);
358 empathy_roster_view_constructed (GObject *object)
360 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
361 void (*chain_up) (GObject *) =
362 ((GObjectClass *) empathy_roster_view_parent_class)->constructed;
364 if (chain_up != NULL)
367 g_assert (EMPATHY_IS_INDIVIDUAL_MANAGER (self->priv->manager));
369 populate_view (self);
371 tp_g_signal_connect_object (self->priv->manager, "members-changed",
372 G_CALLBACK (members_changed_cb), self, 0);
373 tp_g_signal_connect_object (self->priv->manager, "groups-changed",
374 G_CALLBACK (groups_changed_cb), self, 0);
376 egg_list_box_set_sort_func (EGG_LIST_BOX (self),
377 (GCompareDataFunc) roster_view_sort, self, NULL);
379 egg_list_box_set_separator_funcs (EGG_LIST_BOX (self), update_separator,
382 egg_list_box_set_filter_func (EGG_LIST_BOX (self), filter_list, self, NULL);
386 empathy_roster_view_dispose (GObject *object)
388 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
389 void (*chain_up) (GObject *) =
390 ((GObjectClass *) empathy_roster_view_parent_class)->dispose;
392 g_clear_object (&self->priv->manager);
394 if (chain_up != NULL)
399 empathy_roster_view_finalize (GObject *object)
401 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
402 void (*chain_up) (GObject *) =
403 ((GObjectClass *) empathy_roster_view_parent_class)->finalize;
405 g_hash_table_unref (self->priv->roster_contacts);
407 if (chain_up != NULL)
412 empathy_roster_view_class_init (
413 EmpathyRosterViewClass *klass)
415 GObjectClass *oclass = G_OBJECT_CLASS (klass);
418 oclass->get_property = empathy_roster_view_get_property;
419 oclass->set_property = empathy_roster_view_set_property;
420 oclass->constructed = empathy_roster_view_constructed;
421 oclass->dispose = empathy_roster_view_dispose;
422 oclass->finalize = empathy_roster_view_finalize;
424 spec = g_param_spec_object ("manager", "Manager",
425 "EmpathyIndividualManager",
426 EMPATHY_TYPE_INDIVIDUAL_MANAGER,
427 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
428 g_object_class_install_property (oclass, PROP_MANAGER, spec);
430 spec = g_param_spec_boolean ("show-offline", "Show Offline",
431 "Show offline contacts",
433 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
434 g_object_class_install_property (oclass, PROP_SHOW_OFFLINE, spec);
436 spec = g_param_spec_boolean ("show-groups", "Show Groups",
439 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
440 g_object_class_install_property (oclass, PROP_SHOW_GROUPS, spec);
442 g_type_class_add_private (klass, sizeof (EmpathyRosterViewPriv));
446 empathy_roster_view_init (EmpathyRosterView *self)
448 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
449 EMPATHY_TYPE_ROSTER_VIEW, EmpathyRosterViewPriv);
451 self->priv->roster_contacts = g_hash_table_new_full (NULL, NULL,
452 NULL, (GDestroyNotify) g_hash_table_unref);
456 empathy_roster_view_new (EmpathyIndividualManager *manager)
458 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager), NULL);
460 return g_object_new (EMPATHY_TYPE_ROSTER_VIEW,
465 EmpathyIndividualManager *
466 empathy_roster_view_get_manager (EmpathyRosterView *self)
468 return self->priv->manager;
472 empathy_roster_view_show_offline (EmpathyRosterView *self,
475 if (self->priv->show_offline == show)
478 self->priv->show_offline = show;
479 egg_list_box_refilter (EGG_LIST_BOX (self));
481 g_object_notify (G_OBJECT (self), "show-offline");
485 clear_view (EmpathyRosterView *self)
487 gtk_container_foreach (GTK_CONTAINER (self),
488 (GtkCallback) gtk_widget_destroy, NULL);
490 g_hash_table_remove_all (self->priv->roster_contacts);
494 empathy_roster_view_show_groups (EmpathyRosterView *self,
497 if (self->priv->show_groups == show)
500 self->priv->show_groups = show;
502 /* TODO: block sort/filter? */
504 populate_view (self);
506 g_object_notify (G_OBJECT (self), "show-groups");