]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-smiley-manager.c
use the 48x48 version of the local-xmpp icon
[empathy.git] / libempathy-gtk / empathy-smiley-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2008 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library 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  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Dafydd Harrie <dafydd.harries@collabora.co.uk>
20  *          Xavier Claessens <xclaesse@gmail.com>
21  */
22
23 #include <config.h>
24
25 #include <string.h>
26
27 #include <libempathy/empathy-utils.h>
28 #include "empathy-smiley-manager.h"
29 #include "empathy-ui-utils.h"
30
31 typedef struct _SmileyManagerTree SmileyManagerTree;
32
33 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathySmileyManager)
34 typedef struct {
35         SmileyManagerTree *tree;
36         GSList            *smileys;
37 } EmpathySmileyManagerPriv;
38
39 struct _SmileyManagerTree {
40         gunichar     c;
41         GdkPixbuf   *pixbuf;
42         gchar       *path;
43         GSList      *childrens;
44 };
45
46 G_DEFINE_TYPE (EmpathySmileyManager, empathy_smiley_manager, G_TYPE_OBJECT);
47
48 static EmpathySmileyManager *manager_singleton = NULL;
49
50 static SmileyManagerTree *
51 smiley_manager_tree_new (gunichar c)
52 {
53         SmileyManagerTree *tree;
54
55         tree = g_slice_new0 (SmileyManagerTree);
56         tree->c = c;
57         tree->pixbuf = NULL;
58         tree->childrens = NULL;
59         tree->path = NULL;
60
61         return tree;
62 }
63
64 static void
65 smiley_manager_tree_free (SmileyManagerTree *tree)
66 {
67         GSList *l;
68
69         if (!tree) {
70                 return;
71         }
72
73         for (l = tree->childrens; l; l = l->next) {
74                 smiley_manager_tree_free (l->data);
75         }
76
77         if (tree->pixbuf) {
78                 g_object_unref (tree->pixbuf);
79         }
80         g_slist_free (tree->childrens);
81         g_free (tree->path);
82         g_slice_free (SmileyManagerTree, tree);
83 }
84
85 static EmpathySmiley *
86 smiley_new (GdkPixbuf *pixbuf, const gchar *str)
87 {
88         EmpathySmiley *smiley;
89
90         smiley = g_slice_new0 (EmpathySmiley);
91         smiley->pixbuf = g_object_ref (pixbuf);
92         smiley->str = g_strdup (str);
93
94         return smiley;
95 }
96
97 static void
98 smiley_free (EmpathySmiley *smiley)
99 {
100         g_object_unref (smiley->pixbuf);
101         g_free (smiley->str);
102         g_slice_free (EmpathySmiley, smiley);
103 }
104
105 static void
106 smiley_manager_finalize (GObject *object)
107 {
108         EmpathySmileyManagerPriv *priv = GET_PRIV (object);
109
110         smiley_manager_tree_free (priv->tree);
111         g_slist_foreach (priv->smileys, (GFunc) smiley_free, NULL);
112         g_slist_free (priv->smileys);
113 }
114
115 static GObject *
116 smiley_manager_constructor (GType type,
117                             guint n_props,
118                             GObjectConstructParam *props)
119 {
120         GObject *retval;
121
122         if (manager_singleton) {
123                 retval = g_object_ref (manager_singleton);
124         } else {
125                 retval = G_OBJECT_CLASS (empathy_smiley_manager_parent_class)->constructor
126                         (type, n_props, props);
127
128                 manager_singleton = EMPATHY_SMILEY_MANAGER (retval);
129                 g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
130         }
131
132         return retval;
133 }
134
135 static void
136 empathy_smiley_manager_class_init (EmpathySmileyManagerClass *klass)
137 {
138         GObjectClass *object_class = G_OBJECT_CLASS (klass);
139
140         object_class->finalize = smiley_manager_finalize;
141         object_class->constructor = smiley_manager_constructor;
142
143         g_type_class_add_private (object_class, sizeof (EmpathySmileyManagerPriv));
144 }
145
146 static void
147 empathy_smiley_manager_init (EmpathySmileyManager *manager)
148 {
149         EmpathySmileyManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (manager,
150                 EMPATHY_TYPE_SMILEY_MANAGER, EmpathySmileyManagerPriv);
151
152         manager->priv = priv;
153         priv->tree = smiley_manager_tree_new ('\0');
154         priv->smileys = NULL;
155
156         empathy_smiley_manager_load (manager);
157 }
158
159 EmpathySmileyManager *
160 empathy_smiley_manager_dup_singleton (void)
161 {
162         return g_object_new (EMPATHY_TYPE_SMILEY_MANAGER, NULL);
163 }
164
165 static SmileyManagerTree *
166 smiley_manager_tree_find_child (SmileyManagerTree *tree, gunichar c)
167 {
168         GSList *l;
169
170         for (l = tree->childrens; l; l = l->next) {
171                 SmileyManagerTree *child = l->data;
172
173                 if (child->c == c) {
174                         return child;
175                 }
176         }
177
178         return NULL;
179 }
180
181 static SmileyManagerTree *
182 smiley_manager_tree_find_or_insert_child (SmileyManagerTree *tree, gunichar c)
183 {
184         SmileyManagerTree *child;
185
186         child = smiley_manager_tree_find_child (tree, c);
187
188         if (!child) {
189                 child = smiley_manager_tree_new (c);
190                 tree->childrens = g_slist_prepend (tree->childrens, child);
191         }
192
193         return child;
194 }
195
196 static void
197 smiley_manager_tree_insert (SmileyManagerTree *tree,
198                             GdkPixbuf         *pixbuf,
199                             const gchar       *str,
200                             const gchar       *path)
201 {
202         SmileyManagerTree *child;
203
204         child = smiley_manager_tree_find_or_insert_child (tree, g_utf8_get_char (str));
205
206         str = g_utf8_next_char (str);
207         if (*str) {
208                 smiley_manager_tree_insert (child, pixbuf, str, path);
209                 return;
210         }
211
212         child->pixbuf = g_object_ref (pixbuf);
213         child->path = g_strdup (path);
214 }
215
216 static void
217 smiley_manager_add_valist (EmpathySmileyManager *manager,
218                            GdkPixbuf            *pixbuf,
219                            const gchar          *path,
220                            const gchar          *first_str,
221                            va_list               var_args)
222 {
223         EmpathySmileyManagerPriv *priv = GET_PRIV (manager);
224         const gchar              *str;
225         EmpathySmiley            *smiley;
226
227         for (str = first_str; str; str = va_arg (var_args, gchar*)) {
228                 smiley_manager_tree_insert (priv->tree, pixbuf, str, path);
229         }
230
231         g_object_set_data_full (G_OBJECT (pixbuf), "smiley_str",
232                                 g_strdup (first_str), g_free);
233         smiley = smiley_new (pixbuf, first_str);
234         priv->smileys = g_slist_prepend (priv->smileys, smiley);
235 }
236
237 void
238 empathy_smiley_manager_add (EmpathySmileyManager *manager,
239                             const gchar          *icon_name,
240                             const gchar          *first_str,
241                             ...)
242 {
243         GdkPixbuf *pixbuf;
244         va_list    var_args;
245
246         g_return_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager));
247         g_return_if_fail (!EMP_STR_EMPTY (icon_name));
248         g_return_if_fail (!EMP_STR_EMPTY (first_str));
249
250         pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
251         if (pixbuf) {
252                 gchar *path;
253
254                 va_start (var_args, first_str);
255                 path = empathy_filename_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
256                 smiley_manager_add_valist (manager, pixbuf, path, first_str, var_args);
257                 va_end (var_args);
258                 g_object_unref (pixbuf);
259                 g_free (path);
260         }
261 }
262
263 void
264 empathy_smiley_manager_load (EmpathySmileyManager *manager)
265 {
266         g_return_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager));
267
268         /* From fd.o icon-naming spec */
269         empathy_smiley_manager_add (manager, "face-angel",      "O:-)",  "O:)",  NULL);
270         empathy_smiley_manager_add (manager, "face-angry",      "X-(",   ":@",   NULL);
271         empathy_smiley_manager_add (manager, "face-cool",       "B-)",   NULL);
272         empathy_smiley_manager_add (manager, "face-crying",     ":'(",           NULL);
273         empathy_smiley_manager_add (manager, "face-devilish",   ">:-)",  ">:)",  NULL);
274         empathy_smiley_manager_add (manager, "face-embarrassed",":-[",   ":[",   ":-$", ":$", NULL);
275         empathy_smiley_manager_add (manager, "face-kiss",       ":-*",   ":*",   NULL);
276         empathy_smiley_manager_add (manager, "face-laugh",      ":-))",  ":))",  NULL);
277         empathy_smiley_manager_add (manager, "face-monkey",     ":-(|)", ":(|)", NULL);
278         empathy_smiley_manager_add (manager, "face-plain",      ":-|",   ":|",   NULL);
279         empathy_smiley_manager_add (manager, "face-raspberry",  ":-P",   ":P",   ":-p", ":p", NULL);
280         empathy_smiley_manager_add (manager, "face-sad",        ":-(",   ":(",   NULL);
281         empathy_smiley_manager_add (manager, "face-sick",       ":-&",   ":&",   NULL);
282         empathy_smiley_manager_add (manager, "face-smile",      ":-)",   ":)",   NULL);
283         empathy_smiley_manager_add (manager, "face-smile-big",  ":-D",   ":D",   ":-d", ":d", NULL);
284         empathy_smiley_manager_add (manager, "face-smirk",      ":-!",   ":!",   NULL);
285         empathy_smiley_manager_add (manager, "face-surprise",   ":-O",   ":O",   ":-o", ":o", NULL);
286         empathy_smiley_manager_add (manager, "face-tired",      "|-)",   "|)",   NULL);
287         empathy_smiley_manager_add (manager, "face-uncertain",  ":-/",   ":/",   NULL);
288         empathy_smiley_manager_add (manager, "face-wink",       ";-)",   ";)",   NULL);
289         empathy_smiley_manager_add (manager, "face-worried",    ":-S",   ":S",   ":-s", ":s", NULL);
290 }
291
292 static EmpathySmileyHit *
293 smiley_hit_new (SmileyManagerTree *tree,
294                 guint              start,
295                 guint              end)
296 {
297         EmpathySmileyHit *hit;
298
299         hit = g_slice_new (EmpathySmileyHit);
300         hit->pixbuf = tree->pixbuf;
301         hit->path = tree->path;
302         hit->start = start;
303         hit->end = end;
304
305         return hit;
306 }
307
308 void
309 empathy_smiley_hit_free (EmpathySmileyHit *hit)
310 {
311         g_return_if_fail (hit != NULL);
312
313         g_slice_free (EmpathySmileyHit, hit);
314 }
315
316 GSList *
317 empathy_smiley_manager_parse_len (EmpathySmileyManager *manager,
318                                   const gchar          *text,
319                                   gssize                len)
320 {
321         EmpathySmileyManagerPriv *priv = GET_PRIV (manager);
322         EmpathySmileyHit         *hit;
323         GSList                   *hits = NULL;
324         SmileyManagerTree        *cur_tree = priv->tree;
325         const gchar              *cur_str;
326         const gchar              *start = NULL;
327
328         g_return_val_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager), NULL);
329         g_return_val_if_fail (text != NULL, NULL);
330
331         /* If len is negative, parse the string until we find '\0' */
332         if (len < 0) {
333                 len = G_MAXSSIZE;
334         }
335
336         /* Parse the len first bytes of text to find smileys. Each time a smiley
337          * is detected, append a EmpathySmileyHit struct to the returned list,
338          * containing the smiley pixbuf and the position of the text to be
339          * replaced by it.
340          * cur_str is a pointer in the text showing the current position
341          * of the parsing. It is always at the begining of an UTF-8 character,
342          * because we support unicode smileys! For example we could want to
343          * replace ™ by an image. */
344
345         for (cur_str = text;
346              *cur_str != '\0' && cur_str - text < len;
347              cur_str = g_utf8_next_char (cur_str)) {
348                 SmileyManagerTree *child;
349                 gunichar           c;
350
351                 c = g_utf8_get_char (cur_str);
352                 child = smiley_manager_tree_find_child (cur_tree, c);
353
354                 /* If we have a child it means c is part of a smiley */
355                 if (child) {
356                         if (cur_tree == priv->tree) {
357                                 /* c is the first char of some smileys, keep
358                                  * the begining position */
359                                 start = cur_str;
360                         }
361                         cur_tree = child;
362                         continue;
363                 }
364
365                 /* c is not part of a smiley. let's check if we found a smiley
366                  * before it. */
367                 if (cur_tree->pixbuf != NULL) {
368                         /* found! */
369                         hit = smiley_hit_new (cur_tree, start - text,
370                                               cur_str - text);
371                         hits = g_slist_prepend (hits, hit);
372
373                         /* c was not part of this smiley, check if a new smiley
374                          * start with it. */
375                         cur_tree = smiley_manager_tree_find_child (priv->tree, c);
376                         if (cur_tree) {
377                                 start = cur_str;
378                         } else {
379                                 cur_tree = priv->tree;
380                         }
381                 } else if (cur_tree != priv->tree) {
382                         /* We searched a smiley starting at 'start' but we ended
383                          * with no smiley. Look again starting from next char.
384                          *
385                          * For example ">:)" and ":(" are both valid smileys,
386                          * when parsing text ">:(" we first see '>' which could
387                          * be the start of a smiley. 'start' variable is set to
388                          * that position and we parse next char which is ':' and
389                          * is still potential smiley. Then we see '(' which is
390                          * NOT part of the smiley, ">:(" does not exist, so we
391                          * have to start again from ':' to find ":(" which is
392                          * correct smiley. */
393                         cur_str = start;
394                         cur_tree = priv->tree;
395                 }
396         }
397
398         /* Check if last char of the text was the end of a smiley */
399         if (cur_tree->pixbuf != NULL) {
400                 hit = smiley_hit_new (cur_tree, start - text, cur_str - text);
401                 hits = g_slist_prepend (hits, hit);
402         }
403
404         return g_slist_reverse (hits);
405 }
406
407 GSList *
408 empathy_smiley_manager_get_all (EmpathySmileyManager *manager)
409 {
410         EmpathySmileyManagerPriv *priv = GET_PRIV (manager);
411
412         return priv->smileys;
413 }
414
415 typedef struct {
416         EmpathySmileyManager *manager;
417         EmpathySmiley        *smiley;
418         EmpathySmileyMenuFunc func;
419         gpointer              user_data;
420 } ActivateData;
421
422 static void
423 smiley_menu_data_free (gpointer  user_data,
424                        GClosure *closure)
425 {
426         ActivateData *data = (ActivateData *) user_data;
427
428         g_object_unref (data->manager);
429         g_slice_free (ActivateData, data);
430 }
431
432 static void
433 smiley_menu_activate_cb (GtkMenuItem *menuitem,
434                          gpointer     user_data)
435 {
436         ActivateData *data = (ActivateData *) user_data;
437
438         data->func (data->manager, data->smiley, data->user_data);
439 }
440
441 GtkWidget *
442 empathy_smiley_menu_new (EmpathySmileyManager *manager,
443                          EmpathySmileyMenuFunc func,
444                          gpointer              user_data)
445 {
446         EmpathySmileyManagerPriv *priv = GET_PRIV (manager);
447         GSList                   *l;
448         GtkWidget                *menu;
449         gint                      x = 0;
450         gint                      y = 0;
451
452         g_return_val_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager), NULL);
453         g_return_val_if_fail (func != NULL, NULL);
454
455         menu = gtk_menu_new ();
456
457         for (l = priv->smileys; l; l = l->next) {
458                 EmpathySmiley *smiley;
459                 GtkWidget     *item;
460                 GtkWidget     *image;
461                 ActivateData  *data;
462
463                 smiley = l->data;
464                 image = gtk_image_new_from_pixbuf (smiley->pixbuf);
465
466                 item = gtk_image_menu_item_new_with_label ("");
467                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
468                 gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (item), TRUE);
469
470                 gtk_menu_attach (GTK_MENU (menu), item,
471                                  x, x + 1, y, y + 1);
472
473                 gtk_widget_set_tooltip_text (item, smiley->str);
474
475                 data = g_slice_new (ActivateData);
476                 data->manager = g_object_ref (manager);
477                 data->smiley = smiley;
478                 data->func = func;
479                 data->user_data = user_data;
480
481                 g_signal_connect_data (item, "activate",
482                                        G_CALLBACK (smiley_menu_activate_cb),
483                                        data,
484                                        smiley_menu_data_free,
485                                        0);
486
487                 if (x > 3) {
488                         y++;
489                         x = 0;
490                 } else {
491                         x++;
492                 }
493         }
494
495         gtk_widget_show_all (menu);
496
497         return menu;
498 }
499