]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-roster-view.c
add empathy-roster-group
[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
10 G_DEFINE_TYPE (EmpathyRosterView, empathy_roster_view, EGG_TYPE_LIST_BOX)
11
12 enum
13 {
14   PROP_MANAGER = 1,
15   PROP_SHOW_OFFLINE,
16   PROP_SHOW_GROUPS,
17   N_PROPS
18 };
19
20 /*
21 enum
22 {
23   LAST_SIGNAL
24 };
25
26 static guint signals[LAST_SIGNAL];
27 */
28
29 #define NO_GROUP "X-no-group"
30 #define UNGROUPPED _("Ungroupped")
31
32 struct _EmpathyRosterViewPriv
33 {
34   EmpathyIndividualManager *manager;
35
36   /* FolksIndividual (borrowed) -> GHashTable (
37    * (gchar * group_name) -> EmpathyRosterContact (borrowed))
38    *
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.
42    */
43   GHashTable *roster_contacts;
44   /* (gchar *group_name) -> EmpathyRosterGroup (borrowed) */
45   GHashTable *roster_groups;
46
47   gboolean show_offline;
48   gboolean show_groups;
49 };
50
51 static void
52 empathy_roster_view_get_property (GObject *object,
53     guint property_id,
54     GValue *value,
55     GParamSpec *pspec)
56 {
57   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
58
59   switch (property_id)
60     {
61       case PROP_MANAGER:
62         g_value_set_object (value, self->priv->manager);
63         break;
64       case PROP_SHOW_OFFLINE:
65         g_value_set_boolean (value, self->priv->show_offline);
66         break;
67       case PROP_SHOW_GROUPS:
68         g_value_set_boolean (value, self->priv->show_groups);
69         break;
70       default:
71         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
72         break;
73     }
74 }
75
76 static void
77 empathy_roster_view_set_property (GObject *object,
78     guint property_id,
79     const GValue *value,
80     GParamSpec *pspec)
81 {
82   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
83
84   switch (property_id)
85     {
86       case PROP_MANAGER:
87         g_assert (self->priv->manager == NULL); /* construct only */
88         self->priv->manager = g_value_dup_object (value);
89         break;
90       case PROP_SHOW_OFFLINE:
91         empathy_roster_view_show_offline (self, g_value_get_boolean (value));
92         break;
93       case PROP_SHOW_GROUPS:
94         empathy_roster_view_show_groups (self, g_value_get_boolean (value));
95         break;
96       default:
97         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
98         break;
99     }
100 }
101
102 static void
103 roster_contact_changed_cb (GtkWidget *child,
104     GParamSpec *spec,
105     EmpathyRosterView *self)
106 {
107   egg_list_box_child_changed (EGG_LIST_BOX (self), child);
108 }
109
110 static GtkWidget *
111 add_roster_contact (EmpathyRosterView *self,
112     FolksIndividual *individual,
113     const gchar *group)
114 {
115   GtkWidget *contact;
116
117   contact = empathy_roster_contact_new (individual, group);
118
119   /* Need to refilter if online is changed */
120   g_signal_connect (contact, "notify::online",
121       G_CALLBACK (roster_contact_changed_cb), self);
122
123   /* Need to resort if alias is changed */
124   g_signal_connect (contact, "notify::alias",
125       G_CALLBACK (roster_contact_changed_cb), self);
126
127   gtk_widget_show (contact);
128   gtk_container_add (GTK_CONTAINER (self), contact);
129
130   return contact;
131 }
132
133 static void
134 add_to_group (EmpathyRosterView *self,
135     FolksIndividual *individual,
136     const gchar *group)
137 {
138   GtkWidget *contact;
139   GHashTable *contacts;
140
141   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
142   if (contacts == NULL)
143     return;
144
145   /* TODO: ensure group widget */
146   contact = add_roster_contact (self, individual, group);
147   g_hash_table_insert (contacts, g_strdup (group), contact);
148 }
149
150 static void
151 individual_added (EmpathyRosterView *self,
152     FolksIndividual *individual)
153 {
154   GHashTable *contacts;
155
156   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
157   if (contacts != NULL)
158     return;
159
160   contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
161
162   g_hash_table_insert (self->priv->roster_contacts, individual, contacts);
163
164   if (!self->priv->show_groups)
165     {
166       add_to_group (self, individual, NO_GROUP);
167     }
168   else
169     {
170       GeeSet *groups;
171
172       groups = folks_group_details_get_groups (
173           FOLKS_GROUP_DETAILS (individual));
174
175       if (gee_collection_get_size (GEE_COLLECTION (groups)) > 0)
176         {
177           GeeIterator *iter = gee_iterable_iterator (GEE_ITERABLE (groups));
178
179           while (iter != NULL && gee_iterator_next (iter))
180             {
181               gchar *group = gee_iterator_get (iter);
182
183               add_to_group (self, individual, group);
184
185               g_free (group);
186             }
187
188           g_clear_object (&iter);
189         }
190       else
191         {
192           /* No group, adds to Ungroupped */
193           add_to_group (self, individual, UNGROUPPED);
194         }
195     }
196 }
197
198 static void
199 individual_removed (EmpathyRosterView *self,
200     FolksIndividual *individual)
201 {
202   GHashTable *contacts;
203   GHashTableIter iter;
204   gpointer value;
205
206   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
207   if (contacts == NULL)
208     return;
209
210   g_hash_table_iter_init (&iter, contacts);
211   while (g_hash_table_iter_next (&iter, NULL, &value))
212     {
213       GtkWidget *contact = value;
214
215       gtk_container_remove (GTK_CONTAINER (self), contact);
216     }
217
218   g_hash_table_remove (self->priv->roster_contacts, individual);
219 }
220
221 static void
222 members_changed_cb (EmpathyIndividualManager *manager,
223     const gchar *message,
224     GList *added,
225     GList *removed,
226     TpChannelGroupChangeReason reason,
227     EmpathyRosterView *self)
228 {
229   GList *l;
230
231   for (l = added; l != NULL; l = g_list_next (l))
232     {
233       FolksIndividual *individual = l->data;
234
235       individual_added (self, individual);
236     }
237
238   for (l = removed; l != NULL; l = g_list_next (l))
239     {
240       FolksIndividual *individual = l->data;
241
242       individual_removed (self, individual);
243     }
244 }
245
246 static gint
247 roster_view_sort (EmpathyRosterContact *a,
248     EmpathyRosterContact *b,
249     EmpathyRosterView *self)
250 {
251   FolksIndividual *ind_a, *ind_b;
252   const gchar *alias_a, *alias_b;
253
254   ind_a = empathy_roster_contact_get_individual (a);
255   ind_b = empathy_roster_contact_get_individual (b);
256
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));
259
260   return g_ascii_strcasecmp (alias_a, alias_b);
261 }
262
263 static void
264 update_separator (GtkWidget **separator,
265     GtkWidget *child,
266     GtkWidget *before,
267     gpointer user_data)
268 {
269   if (before == NULL)
270     {
271       /* No separator before the first row */
272       g_clear_object (separator);
273       return;
274     }
275
276   if (*separator != NULL)
277     return;
278
279   *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
280   g_object_ref_sink (*separator);
281 }
282
283 static gboolean
284 filter_list (GtkWidget *child,
285     gpointer user_data)
286 {
287   EmpathyRosterView *self = user_data;
288   EmpathyRosterContact *contact = EMPATHY_ROSTER_CONTACT (child);
289
290   if (self->priv->show_offline)
291     return TRUE;
292
293   return empathy_roster_contact_is_online (contact);
294 }
295
296 static void
297 populate_view (EmpathyRosterView *self)
298 {
299   GList *individuals, *l;
300
301   individuals = empathy_individual_manager_get_members (self->priv->manager);
302   for (l = individuals; l != NULL; l = g_list_next (l))
303     {
304       FolksIndividual *individual = l->data;
305
306       individual_added (self, individual);
307     }
308
309   g_list_free (individuals);
310 }
311
312 static void
313 remove_from_group (EmpathyRosterView *self,
314     FolksIndividual *individual,
315     const gchar *group)
316 {
317   GHashTable *contacts;
318   GtkWidget *contact;
319
320   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
321   if (contacts == NULL)
322     return;
323
324   contact = g_hash_table_lookup (contacts, group);
325   if (contact == NULL)
326     return;
327
328   gtk_container_remove (GTK_CONTAINER (self), contact);
329   g_hash_table_remove (contacts, group);
330
331   if (g_hash_table_size (contacts) == 0)
332     {
333       add_to_group (self, individual, UNGROUPPED);
334     }
335 }
336
337 static void
338 groups_changed_cb (EmpathyIndividualManager *manager,
339     FolksIndividual *individual,
340     gchar *group,
341     gboolean is_member,
342     EmpathyRosterView *self)
343 {
344   if (!self->priv->show_groups)
345     return;
346
347   if (is_member)
348     {
349       add_to_group (self, individual, group);
350     }
351   else
352     {
353       remove_from_group (self, individual, group);
354     }
355 }
356
357 static void
358 empathy_roster_view_constructed (GObject *object)
359 {
360   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
361   void (*chain_up) (GObject *) =
362       ((GObjectClass *) empathy_roster_view_parent_class)->constructed;
363
364   if (chain_up != NULL)
365     chain_up (object);
366
367   g_assert (EMPATHY_IS_INDIVIDUAL_MANAGER (self->priv->manager));
368
369   populate_view (self);
370
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);
375
376   egg_list_box_set_sort_func (EGG_LIST_BOX (self),
377       (GCompareDataFunc) roster_view_sort, self, NULL);
378
379   egg_list_box_set_separator_funcs (EGG_LIST_BOX (self), update_separator,
380       self, NULL);
381
382   egg_list_box_set_filter_func (EGG_LIST_BOX (self), filter_list, self, NULL);
383 }
384
385 static void
386 empathy_roster_view_dispose (GObject *object)
387 {
388   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
389   void (*chain_up) (GObject *) =
390       ((GObjectClass *) empathy_roster_view_parent_class)->dispose;
391
392   g_clear_object (&self->priv->manager);
393
394   if (chain_up != NULL)
395     chain_up (object);
396 }
397
398 static void
399 empathy_roster_view_finalize (GObject *object)
400 {
401   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
402   void (*chain_up) (GObject *) =
403       ((GObjectClass *) empathy_roster_view_parent_class)->finalize;
404
405   g_hash_table_unref (self->priv->roster_contacts);
406
407   if (chain_up != NULL)
408     chain_up (object);
409 }
410
411 static void
412 empathy_roster_view_class_init (
413     EmpathyRosterViewClass *klass)
414 {
415   GObjectClass *oclass = G_OBJECT_CLASS (klass);
416   GParamSpec *spec;
417
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;
423
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);
429
430   spec = g_param_spec_boolean ("show-offline", "Show Offline",
431       "Show offline contacts",
432       FALSE,
433       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
434   g_object_class_install_property (oclass, PROP_SHOW_OFFLINE, spec);
435
436   spec = g_param_spec_boolean ("show-groups", "Show Groups",
437       "Show groups",
438       FALSE,
439       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
440   g_object_class_install_property (oclass, PROP_SHOW_GROUPS, spec);
441
442   g_type_class_add_private (klass, sizeof (EmpathyRosterViewPriv));
443 }
444
445 static void
446 empathy_roster_view_init (EmpathyRosterView *self)
447 {
448   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
449       EMPATHY_TYPE_ROSTER_VIEW, EmpathyRosterViewPriv);
450
451   self->priv->roster_contacts = g_hash_table_new_full (NULL, NULL,
452       NULL, (GDestroyNotify) g_hash_table_unref);
453 }
454
455 GtkWidget *
456 empathy_roster_view_new (EmpathyIndividualManager *manager)
457 {
458   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager), NULL);
459
460   return g_object_new (EMPATHY_TYPE_ROSTER_VIEW,
461       "manager", manager,
462       NULL);
463 }
464
465 EmpathyIndividualManager *
466 empathy_roster_view_get_manager (EmpathyRosterView *self)
467 {
468   return self->priv->manager;
469 }
470
471 void
472 empathy_roster_view_show_offline (EmpathyRosterView *self,
473     gboolean show)
474 {
475   if (self->priv->show_offline == show)
476     return;
477
478   self->priv->show_offline = show;
479   egg_list_box_refilter (EGG_LIST_BOX (self));
480
481   g_object_notify (G_OBJECT (self), "show-offline");
482 }
483
484 static void
485 clear_view (EmpathyRosterView *self)
486 {
487   gtk_container_foreach (GTK_CONTAINER (self),
488       (GtkCallback) gtk_widget_destroy, NULL);
489
490   g_hash_table_remove_all (self->priv->roster_contacts);
491 }
492
493 void
494 empathy_roster_view_show_groups (EmpathyRosterView *self,
495     gboolean show)
496 {
497   if (self->priv->show_groups == show)
498     return;
499
500   self->priv->show_groups = show;
501
502   /* TODO: block sort/filter? */
503   clear_view (self);
504   populate_view (self);
505
506   g_object_notify (G_OBJECT (self), "show-groups");
507 }