]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-roster-view.c
roster-view: allow to store more than one widget per Individual
[empathy.git] / libempathy-gtk / empathy-roster-view.c
1
2 #include "config.h"
3
4 #include "empathy-roster-view.h"
5
6 #include <libempathy-gtk/empathy-roster-contact.h>
7
8 G_DEFINE_TYPE (EmpathyRosterView, empathy_roster_view, EGG_TYPE_LIST_BOX)
9
10 enum
11 {
12   PROP_MANAGER = 1,
13   PROP_SHOW_OFFLINE,
14   PROP_SHOW_GROUPS,
15   N_PROPS
16 };
17
18 /*
19 enum
20 {
21   LAST_SIGNAL
22 };
23
24 static guint signals[LAST_SIGNAL];
25 */
26
27 struct _EmpathyRosterViewPriv
28 {
29   EmpathyIndividualManager *manager;
30
31   /* FolksIndividual (borrowed) -> GHashTable (used as a set) of
32    * EmpathyRosterContact (borrowed) */
33   GHashTable *roster_contacts;
34
35   gboolean show_offline;
36   gboolean show_groups;
37 };
38
39 static void
40 empathy_roster_view_get_property (GObject *object,
41     guint property_id,
42     GValue *value,
43     GParamSpec *pspec)
44 {
45   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
46
47   switch (property_id)
48     {
49       case PROP_MANAGER:
50         g_value_set_object (value, self->priv->manager);
51         break;
52       case PROP_SHOW_OFFLINE:
53         g_value_set_boolean (value, self->priv->show_offline);
54         break;
55       case PROP_SHOW_GROUPS:
56         g_value_set_boolean (value, self->priv->show_groups);
57         break;
58       default:
59         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
60         break;
61     }
62 }
63
64 static void
65 empathy_roster_view_set_property (GObject *object,
66     guint property_id,
67     const GValue *value,
68     GParamSpec *pspec)
69 {
70   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
71
72   switch (property_id)
73     {
74       case PROP_MANAGER:
75         g_assert (self->priv->manager == NULL); /* construct only */
76         self->priv->manager = g_value_dup_object (value);
77         break;
78       case PROP_SHOW_OFFLINE:
79         empathy_roster_view_show_offline (self, g_value_get_boolean (value));
80         break;
81       case PROP_SHOW_GROUPS:
82         empathy_roster_view_show_groups (self, g_value_get_boolean (value));
83         break;
84       default:
85         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
86         break;
87     }
88 }
89
90 static void
91 roster_contact_changed_cb (GtkWidget *child,
92     GParamSpec *spec,
93     EmpathyRosterView *self)
94 {
95   egg_list_box_child_changed (EGG_LIST_BOX (self), child);
96 }
97
98 static GtkWidget *
99 add_roster_contact (EmpathyRosterView *self,
100     FolksIndividual *individual)
101 {
102   GtkWidget *contact;
103
104   contact = empathy_roster_contact_new (individual);
105
106   /* Need to refilter if online is changed */
107   g_signal_connect (contact, "notify::online",
108       G_CALLBACK (roster_contact_changed_cb), self);
109
110   /* Need to resort if alias is changed */
111   g_signal_connect (contact, "notify::alias",
112       G_CALLBACK (roster_contact_changed_cb), self);
113
114   gtk_widget_show (contact);
115   gtk_container_add (GTK_CONTAINER (self), contact);
116
117   return contact;
118 }
119
120 static void
121 individual_added (EmpathyRosterView *self,
122     FolksIndividual *individual)
123 {
124   GtkWidget *contact;
125   GHashTable *contacts;
126
127   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
128   if (contacts != NULL)
129     return;
130
131   contacts = g_hash_table_new (NULL, NULL);
132
133   contact = add_roster_contact (self, individual);
134   g_hash_table_add (contacts, contact);
135
136   g_hash_table_insert (self->priv->roster_contacts, individual, contacts);
137 }
138
139 static void
140 individual_removed (EmpathyRosterView *self,
141     FolksIndividual *individual)
142 {
143   GHashTable *contacts;
144   GHashTableIter iter;
145   gpointer key;
146
147   contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
148   if (contacts == NULL)
149     return;
150
151   g_hash_table_iter_init (&iter, contacts);
152   while (g_hash_table_iter_next (&iter, &key, NULL))
153     {
154       GtkWidget *contact = key;
155
156       gtk_container_remove (GTK_CONTAINER (self), contact);
157     }
158
159   g_hash_table_remove (self->priv->roster_contacts, individual);
160 }
161
162 static void
163 members_changed_cb (EmpathyIndividualManager *manager,
164     const gchar *message,
165     GList *added,
166     GList *removed,
167     TpChannelGroupChangeReason reason,
168     EmpathyRosterView *self)
169 {
170   GList *l;
171
172   for (l = added; l != NULL; l = g_list_next (l))
173     {
174       FolksIndividual *individual = l->data;
175
176       individual_added (self, individual);
177     }
178
179   for (l = removed; l != NULL; l = g_list_next (l))
180     {
181       FolksIndividual *individual = l->data;
182
183       individual_removed (self, individual);
184     }
185 }
186
187 static gint
188 roster_view_sort (EmpathyRosterContact *a,
189     EmpathyRosterContact *b,
190     EmpathyRosterView *self)
191 {
192   FolksIndividual *ind_a, *ind_b;
193   const gchar *alias_a, *alias_b;
194
195   ind_a = empathy_roster_contact_get_individual (a);
196   ind_b = empathy_roster_contact_get_individual (b);
197
198   alias_a = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_a));
199   alias_b = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_b));
200
201   return g_ascii_strcasecmp (alias_a, alias_b);
202 }
203
204 static void
205 update_separator (GtkWidget **separator,
206     GtkWidget *child,
207     GtkWidget *before,
208     gpointer user_data)
209 {
210   if (before == NULL)
211     {
212       /* No separator before the first row */
213       g_clear_object (separator);
214       return;
215     }
216
217   if (*separator != NULL)
218     return;
219
220   *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
221   g_object_ref_sink (*separator);
222 }
223
224 static gboolean
225 filter_list (GtkWidget *child,
226     gpointer user_data)
227 {
228   EmpathyRosterView *self = user_data;
229   EmpathyRosterContact *contact = EMPATHY_ROSTER_CONTACT (child);
230
231   if (self->priv->show_offline)
232     return TRUE;
233
234   return empathy_roster_contact_is_online (contact);
235 }
236
237 static void
238 populate_view (EmpathyRosterView *self)
239 {
240   GList *individuals, *l;
241
242   individuals = empathy_individual_manager_get_members (self->priv->manager);
243   for (l = individuals; l != NULL; l = g_list_next (l))
244     {
245       FolksIndividual *individual = l->data;
246
247       individual_added (self, individual);
248     }
249
250   g_list_free (individuals);
251 }
252
253 static void
254 empathy_roster_view_constructed (GObject *object)
255 {
256   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
257   void (*chain_up) (GObject *) =
258       ((GObjectClass *) empathy_roster_view_parent_class)->constructed;
259
260   if (chain_up != NULL)
261     chain_up (object);
262
263   g_assert (EMPATHY_IS_INDIVIDUAL_MANAGER (self->priv->manager));
264
265   populate_view (self);
266
267   tp_g_signal_connect_object (self->priv->manager, "members-changed",
268       G_CALLBACK (members_changed_cb), self, 0);
269
270   egg_list_box_set_sort_func (EGG_LIST_BOX (self),
271       (GCompareDataFunc) roster_view_sort, self, NULL);
272
273   egg_list_box_set_separator_funcs (EGG_LIST_BOX (self), update_separator,
274       self, NULL);
275
276   egg_list_box_set_filter_func (EGG_LIST_BOX (self), filter_list, self, NULL);
277 }
278
279 static void
280 empathy_roster_view_dispose (GObject *object)
281 {
282   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
283   void (*chain_up) (GObject *) =
284       ((GObjectClass *) empathy_roster_view_parent_class)->dispose;
285
286   g_clear_object (&self->priv->manager);
287
288   if (chain_up != NULL)
289     chain_up (object);
290 }
291
292 static void
293 empathy_roster_view_finalize (GObject *object)
294 {
295   EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
296   void (*chain_up) (GObject *) =
297       ((GObjectClass *) empathy_roster_view_parent_class)->finalize;
298
299   g_hash_table_unref (self->priv->roster_contacts);
300
301   if (chain_up != NULL)
302     chain_up (object);
303 }
304
305 static void
306 empathy_roster_view_class_init (
307     EmpathyRosterViewClass *klass)
308 {
309   GObjectClass *oclass = G_OBJECT_CLASS (klass);
310   GParamSpec *spec;
311
312   oclass->get_property = empathy_roster_view_get_property;
313   oclass->set_property = empathy_roster_view_set_property;
314   oclass->constructed = empathy_roster_view_constructed;
315   oclass->dispose = empathy_roster_view_dispose;
316   oclass->finalize = empathy_roster_view_finalize;
317
318   spec = g_param_spec_object ("manager", "Manager",
319       "EmpathyIndividualManager",
320       EMPATHY_TYPE_INDIVIDUAL_MANAGER,
321       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
322   g_object_class_install_property (oclass, PROP_MANAGER, spec);
323
324   spec = g_param_spec_boolean ("show-offline", "Show Offline",
325       "Show offline contacts",
326       FALSE,
327       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
328   g_object_class_install_property (oclass, PROP_SHOW_OFFLINE, spec);
329
330   spec = g_param_spec_boolean ("show-groups", "Show Groups",
331       "Show groups",
332       FALSE,
333       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
334   g_object_class_install_property (oclass, PROP_SHOW_GROUPS, spec);
335
336   g_type_class_add_private (klass, sizeof (EmpathyRosterViewPriv));
337 }
338
339 static void
340 empathy_roster_view_init (EmpathyRosterView *self)
341 {
342   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
343       EMPATHY_TYPE_ROSTER_VIEW, EmpathyRosterViewPriv);
344
345   self->priv->roster_contacts = g_hash_table_new_full (NULL, NULL,
346       NULL, (GDestroyNotify) g_hash_table_unref);
347 }
348
349 GtkWidget *
350 empathy_roster_view_new (EmpathyIndividualManager *manager)
351 {
352   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager), NULL);
353
354   return g_object_new (EMPATHY_TYPE_ROSTER_VIEW,
355       "manager", manager,
356       NULL);
357 }
358
359 EmpathyIndividualManager *
360 empathy_roster_view_get_manager (EmpathyRosterView *self)
361 {
362   return self->priv->manager;
363 }
364
365 void
366 empathy_roster_view_show_offline (EmpathyRosterView *self,
367     gboolean show)
368 {
369   if (self->priv->show_offline == show)
370     return;
371
372   self->priv->show_offline = show;
373   egg_list_box_refilter (EGG_LIST_BOX (self));
374
375   g_object_notify (G_OBJECT (self), "show-offline");
376 }
377
378 static void
379 clear_view (EmpathyRosterView *self)
380 {
381   gtk_container_foreach (GTK_CONTAINER (self),
382       (GtkCallback) gtk_widget_destroy, NULL);
383
384   g_hash_table_remove_all (self->priv->roster_contacts);
385 }
386
387 void
388 empathy_roster_view_show_groups (EmpathyRosterView *self,
389     gboolean show)
390 {
391   if (self->priv->show_groups == show)
392     return;
393
394   self->priv->show_groups = show;
395
396   clear_view (self);
397   populate_view (self);
398
399   g_object_notify (G_OBJECT (self), "show-groups");
400 }