]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-store-channel.c
Be more compatible with Facebook emoticon codes
[empathy.git] / libempathy-gtk / empathy-individual-store-channel.c
1 /*
2  * Copyright (C) 2005-2007 Imendio AB
3  * Copyright (C) 2007-2011 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  *          Xavier Claessens <xclaesse@gmail.com>
23  *          Travis Reitter <travis.reitter@collabora.co.uk>
24  */
25
26 #include "config.h"
27 #include "empathy-individual-store-channel.h"
28
29 #include <tp-account-widgets/tpaw-pixbuf-utils.h>
30
31 #include "empathy-utils.h"
32 #include "empathy-ui-utils.h"
33 #include "empathy-images.h"
34
35 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
36 #include "empathy-debug.h"
37
38 struct _EmpathyIndividualStoreChannelPriv
39 {
40   TpChannel *channel;
41
42   /* TpContact => FolksIndividual
43    * We keep the individuals we have added to the store so can easily remove
44    * them when their TpContact leaves the channel. */
45   GHashTable *individuals;
46 };
47
48 enum
49 {
50   PROP_0,
51   PROP_CHANNEL,
52 };
53
54
55 G_DEFINE_TYPE (EmpathyIndividualStoreChannel, empathy_individual_store_channel,
56     EMPATHY_TYPE_INDIVIDUAL_STORE);
57
58 static void
59 add_members (EmpathyIndividualStoreChannel *self,
60     GPtrArray *members)
61 {
62   EmpathyIndividualStore *store = (EmpathyIndividualStore *) self;
63   guint i;
64
65   for (i = 0; i < members->len; i++)
66     {
67       TpContact *contact = g_ptr_array_index (members, i);
68       FolksIndividual *individual;
69
70       if (g_hash_table_lookup (self->priv->individuals, contact) != NULL)
71         continue;
72
73       individual = empathy_ensure_individual_from_tp_contact (contact);
74       if (individual == NULL)
75         return;
76
77       DEBUG ("%s joined channel %s", tp_contact_get_identifier (contact),
78           tp_proxy_get_object_path (self->priv->channel));
79
80       individual_store_add_individual_and_connect (store, individual);
81
82       /* Pass the individual reference to the hash table */
83       g_hash_table_insert (self->priv->individuals, g_object_ref (contact),
84           individual);
85     }
86 }
87
88 static void
89 remove_members (EmpathyIndividualStoreChannel *self,
90     GPtrArray *members)
91 {
92   EmpathyIndividualStore *store = (EmpathyIndividualStore *) self;
93   guint i;
94
95   for (i = 0; i < members->len; i++)
96     {
97       TpContact *contact = g_ptr_array_index (members, i);
98       FolksIndividual *individual;
99
100       individual = g_hash_table_lookup (self->priv->individuals, contact);
101       if (individual == NULL)
102         continue;
103
104       DEBUG ("%s left channel %s", tp_contact_get_identifier (contact),
105           tp_proxy_get_object_path (self->priv->channel));
106
107       individual_store_remove_individual_and_disconnect (store, individual);
108
109       g_hash_table_remove (self->priv->individuals, contact);
110     }
111 }
112
113 static void
114 group_contacts_changed_cb (TpChannel *channel,
115     GPtrArray *added,
116     GPtrArray *removed,
117     GPtrArray *local_pending,
118     GPtrArray *remote_pending,
119     TpContact *actor,
120     GHashTable *details,
121     gpointer user_data)
122 {
123   EmpathyIndividualStoreChannel *self = EMPATHY_INDIVIDUAL_STORE_CHANNEL (
124       user_data);
125
126   remove_members (self, removed);
127   add_members (self, added);
128 }
129
130 static void
131 individual_store_channel_contact_chat_state_changed (TpTextChannel *channel,
132     TpContact *tp_contact,
133     TpChannelChatState state,
134     EmpathyIndividualStoreChannel *self)
135 {
136   FolksIndividual *individual;
137   EmpathyContact *contact = NULL;
138   GList *iters, *l;
139   GdkPixbuf *pixbuf;
140
141   contact = empathy_contact_dup_from_tp_contact (tp_contact);
142   if (empathy_contact_is_user (contact))
143     {
144       /* We don't care about our own chat composing states */
145       goto finally;
146     }
147
148   DEBUG ("Contact %s entered chat state %d",
149       tp_contact_get_identifier (tp_contact), state);
150
151   individual = g_hash_table_lookup (self->priv->individuals, tp_contact);
152   if (individual == NULL)
153     {
154       g_warning ("individual is NULL");
155       goto finally;
156     }
157
158   iters = empathy_individual_store_find_contact (
159       EMPATHY_INDIVIDUAL_STORE (self), individual);
160
161   if (state == TP_CHANNEL_CHAT_STATE_COMPOSING)
162     {
163       gchar *icon_filename =
164           tpaw_filename_from_icon_name (EMPATHY_IMAGE_TYPING,
165             GTK_ICON_SIZE_MENU);
166
167       pixbuf = gdk_pixbuf_new_from_file (icon_filename, NULL);
168       g_free (icon_filename);
169     }
170   else
171     {
172        pixbuf = empathy_individual_store_get_individual_status_icon (
173            EMPATHY_INDIVIDUAL_STORE (self), individual);
174
175        /* Take a ref as the 'if' blocks creates a new pixbuf */
176        g_object_ref (pixbuf);
177     }
178
179   for (l = iters; l != NULL; l = l->next)
180     {
181       gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
182           EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf,
183           -1);
184     }
185   /* Store takes it's own ref */
186   g_object_unref (pixbuf);
187
188   empathy_individual_store_free_iters (iters);
189
190   finally:
191     g_object_unref (contact);
192 }
193
194 static void
195 individual_store_channel_set_individual_channel (
196     EmpathyIndividualStoreChannel *self,
197     TpChannel *channel)
198 {
199   GPtrArray *members;
200
201   g_assert (self->priv->channel == NULL); /* construct only */
202   self->priv->channel = g_object_ref (channel);
203
204   /* Add initial members */
205   members = tp_channel_group_dup_members_contacts (channel);
206   if (members != NULL)
207     {
208       add_members (self, members);
209       g_ptr_array_unref (members);
210     }
211
212   tp_g_signal_connect_object (channel, "group-contacts-changed",
213       G_CALLBACK (group_contacts_changed_cb), self, 0);
214
215   tp_g_signal_connect_object (channel, "contact-chat-state-changed",
216       G_CALLBACK (individual_store_channel_contact_chat_state_changed),
217       self, 0);
218 }
219
220 static void
221 individual_store_channel_dispose (GObject *object)
222 {
223   EmpathyIndividualStoreChannel *self = EMPATHY_INDIVIDUAL_STORE_CHANNEL (
224       object);
225   EmpathyIndividualStore *store = EMPATHY_INDIVIDUAL_STORE (object);
226   GHashTableIter iter;
227   gpointer v;
228
229   g_hash_table_iter_init (&iter, self->priv->individuals);
230   while (g_hash_table_iter_next (&iter, NULL, &v))
231     {
232       FolksIndividual *individual = v;
233
234       empathy_individual_store_disconnect_individual (store, individual);
235     }
236
237   tp_clear_pointer (&self->priv->individuals, g_hash_table_unref);
238   g_clear_object (&self->priv->channel);
239
240   G_OBJECT_CLASS (empathy_individual_store_channel_parent_class)->dispose (
241       object);
242 }
243
244 static void
245 individual_store_channel_get_property (GObject *object,
246     guint param_id,
247     GValue *value,
248     GParamSpec *pspec)
249 {
250   EmpathyIndividualStoreChannel *self = EMPATHY_INDIVIDUAL_STORE_CHANNEL (
251       object);
252
253   switch (param_id)
254     {
255     case PROP_CHANNEL:
256       g_value_set_object (value, self->priv->channel);
257       break;
258     default:
259       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
260       break;
261     };
262 }
263
264 static void
265 individual_store_channel_set_property (GObject *object,
266     guint param_id,
267     const GValue *value,
268     GParamSpec *pspec)
269 {
270   switch (param_id)
271     {
272     case PROP_CHANNEL:
273       individual_store_channel_set_individual_channel (
274           EMPATHY_INDIVIDUAL_STORE_CHANNEL (object),
275           g_value_get_object (value));
276       break;
277     default:
278       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
279       break;
280     };
281 }
282
283 static void
284 individual_store_channel_reload_individuals (EmpathyIndividualStore *store)
285 {
286   EmpathyIndividualStoreChannel *self = EMPATHY_INDIVIDUAL_STORE_CHANNEL (
287       store);
288   GPtrArray *members;
289   GList *list, *l;
290
291   /* remove all. The list returned by g_hash_table_get_keys() is valid until
292    * the hash table is modified so we can't remove the contact directly in the
293    * iteration. */
294   members = g_ptr_array_new_with_free_func (g_object_unref);
295
296   list = g_hash_table_get_keys (self->priv->individuals);
297   for (l = list; l != NULL; l = g_list_next (l))
298     {
299       g_ptr_array_add (members, g_object_ref (l->data));
300     }
301
302   remove_members (self, members);
303
304   g_list_free (list);
305   g_ptr_array_unref (members);
306
307   /* re-add members */
308   members = tp_channel_group_dup_members_contacts (self->priv->channel);
309   if (members == NULL)
310     return;
311
312   add_members (self, members);
313   g_ptr_array_unref (members);
314 }
315
316 static gboolean
317 individual_store_channel_initial_loading (EmpathyIndividualStore *store)
318 {
319   EmpathyIndividualStoreChannel *self = EMPATHY_INDIVIDUAL_STORE_CHANNEL (
320       store);
321
322   return !tp_proxy_is_prepared (self->priv->channel,
323       TP_CHANNEL_FEATURE_CONTACTS);
324 }
325
326 static void
327 empathy_individual_store_channel_class_init (
328     EmpathyIndividualStoreChannelClass *klass)
329 {
330   GObjectClass *object_class = G_OBJECT_CLASS (klass);
331   EmpathyIndividualStoreClass *store_class = EMPATHY_INDIVIDUAL_STORE_CLASS (
332       klass);
333
334   object_class->dispose = individual_store_channel_dispose;
335   object_class->get_property = individual_store_channel_get_property;
336   object_class->set_property = individual_store_channel_set_property;
337
338   store_class->reload_individuals = individual_store_channel_reload_individuals;
339   store_class->initial_loading = individual_store_channel_initial_loading;
340
341   g_object_class_install_property (object_class,
342       PROP_CHANNEL,
343       g_param_spec_object ("individual-channel",
344           "Individual channel",
345           "Individual channel",
346           TP_TYPE_CHANNEL,
347           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
348
349   g_type_class_add_private (object_class,
350       sizeof (EmpathyIndividualStoreChannelPriv));
351 }
352
353 static void
354 empathy_individual_store_channel_init (EmpathyIndividualStoreChannel *self)
355 {
356   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
357       EMPATHY_TYPE_INDIVIDUAL_STORE_CHANNEL, EmpathyIndividualStoreChannelPriv);
358
359   self->priv->individuals = g_hash_table_new_full (NULL, NULL, g_object_unref,
360       g_object_unref);
361 }
362
363 EmpathyIndividualStoreChannel *
364 empathy_individual_store_channel_new (TpChannel *channel)
365 {
366   g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL);
367
368   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_STORE_CHANNEL,
369       "individual-channel", channel, NULL);
370 }
371
372 TpChannel *
373 empathy_individual_store_channel_get_channel (
374     EmpathyIndividualStoreChannel *self)
375 {
376   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (self), FALSE);
377
378   return self->priv->channel;
379 }