Merged pidgin/main into default
[pidgin-git.git] / pidgin / gtksmiley-theme.c
blobcc6bfd1a4efb07aba8ddedc0d3084ade5ac4b8d3
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "gtksmiley-theme.h"
24 #include "internal.h"
25 #include "glibcompat.h"
27 #include "debug.h"
29 #include "gtkutils.h"
31 #include <glib/gstdio.h>
33 #define PIDGIN_SMILEY_THEME_GET_PRIVATE(obj) \
34 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PIDGIN_TYPE_SMILEY_THEME, \
35 PidginSmileyThemePrivate))
37 #define PIDGIN_SMILEY_THEME_MAX_LINES 1024
38 #define PIDGIN_SMILEY_THEME_MAX_TOKENS 1024
40 typedef struct
42 gchar *path;
44 gchar *name;
45 gchar *desc;
46 gchar *icon;
47 gchar *author;
49 GdkPixbuf *icon_pixbuf;
51 GHashTable *smiley_lists_map;
52 } PidginSmileyThemePrivate;
54 static GObjectClass *parent_class;
56 static gchar **probe_dirs;
57 static GList *smiley_themes = NULL;
59 typedef struct
61 gchar *name;
62 gchar *desc;
63 gchar *icon;
64 gchar *author;
66 GList *protocols;
67 } PidginSmileyThemeIndex;
69 typedef struct
71 gchar *name;
72 GList *smileys;
73 } PidginSmileyThemeIndexProtocol;
75 typedef struct
77 gchar *file;
78 gboolean hidden;
79 GList *shortcuts;
80 } PidginSmileyThemeIndexSmiley;
82 /*******************************************************************************
83 * Theme index parsing
84 ******************************************************************************/
86 static void
87 pidgin_smiley_theme_index_free(PidginSmileyThemeIndex *index)
89 GList *it, *it2;
91 g_return_if_fail(index != NULL);
93 g_free(index->name);
94 g_free(index->desc);
95 g_free(index->icon);
96 g_free(index->author);
98 for (it = index->protocols; it; it = g_list_next(it)) {
99 PidginSmileyThemeIndexProtocol *proto = it->data;
101 g_free(proto->name);
102 for (it2 = proto->smileys; it2; it2 = g_list_next(it2)) {
103 PidginSmileyThemeIndexSmiley *smiley = it2->data;
105 g_free(smiley->file);
106 g_list_free_full(smiley->shortcuts, g_free);
107 g_free(smiley);
109 g_list_free(proto->smileys);
110 g_free(proto);
112 g_list_free(index->protocols);
115 static PidginSmileyThemeIndex *
116 pidgin_smiley_theme_index_parse(const gchar *theme_path, gboolean load_contents)
118 PidginSmileyThemeIndex *index;
119 PidginSmileyThemeIndexProtocol *proto = NULL;
120 gchar *index_path;
121 FILE *file;
122 int line_no = 0;
123 gboolean inv_frm = FALSE;
125 index_path = g_build_filename(theme_path, "theme", NULL);
126 file = g_fopen(index_path, "r");
127 if (!file) {
128 purple_debug_error("gtksmiley-theme",
129 "Failed to open index file %s", index_path);
130 g_free(index_path);
131 return NULL;
134 index = g_new0(PidginSmileyThemeIndex, 1);
136 while (!feof(file)) {
137 PidginSmileyThemeIndexSmiley *smiley;
138 gchar buff[1024];
139 gchar *line, *eqchr;
140 gchar **split;
141 int i;
143 if (++line_no > PIDGIN_SMILEY_THEME_MAX_LINES) {
144 purple_debug_warning("gtksmiley-theme", "file too big");
145 break;
148 if (!fgets(buff, sizeof(buff), file))
149 break;
151 /* strip comments */
152 if (buff[0] == '#')
153 continue;
155 g_strstrip(buff);
156 if (buff[0] == '\0')
157 continue;
159 if (!g_utf8_validate(buff, -1, NULL)) {
160 purple_debug_error("gtksmiley-theme",
161 "%s:%d is invalid UTF-8",
162 index_path, line_no);
163 continue;
166 line = buff;
168 if (line[0] == '[') {
169 gchar *end;
171 if (!load_contents)
172 break;
173 line++;
174 end = strchr(line, ']');
175 if (!end) {
176 inv_frm = TRUE;
177 break;
180 if (proto)
181 proto->smileys = g_list_reverse(proto->smileys);
183 proto = g_new0(PidginSmileyThemeIndexProtocol, 1);
184 proto->name = g_strndup(line, end - line);
186 index->protocols =
187 g_list_prepend(index->protocols, proto);
189 continue;
192 if ((eqchr = strchr(line, '='))) {
193 *eqchr = '\0';
194 if (g_ascii_strcasecmp(line, "name") == 0) {
195 g_free(index->name);
196 index->name = g_strdup(eqchr + 1);
197 g_strchug(index->name);
198 continue;
199 } else if (g_ascii_strcasecmp(line, "description") == 0) {
200 g_free(index->desc);
201 index->desc = g_strdup(eqchr + 1);
202 g_strchug(index->desc);
203 continue;
204 } else if (g_ascii_strcasecmp(line, "icon") == 0) {
205 g_free(index->icon);
206 index->icon = g_strdup(eqchr + 1);
207 g_strchug(index->icon);
208 continue;
209 } else if (g_ascii_strcasecmp(line, "author") == 0) {
210 g_free(index->author);
211 index->author = g_strdup(eqchr + 1);
212 g_strchug(index->author);
213 continue;
215 *eqchr = '=';
218 /* parsing section content */
220 if (proto == NULL) {
221 inv_frm = FALSE;
222 break;
225 smiley = g_new0(PidginSmileyThemeIndexSmiley, 1);
226 proto->smileys = g_list_prepend(proto->smileys, smiley);
228 smiley->hidden = FALSE;
229 if (line[0] == '!') {
230 smiley->hidden = TRUE;
231 line++;
234 split = g_strsplit_set(line, " \t",
235 PIDGIN_SMILEY_THEME_MAX_TOKENS);
236 for (i = 0; split[i]; i++) {
237 gchar *token = split[i];
239 if (token[0] == '\0')
240 continue;
241 if (i == PIDGIN_SMILEY_THEME_MAX_TOKENS - 1)
242 break;
244 if (!smiley->file) {
245 smiley->file = g_strdup(token);
246 continue;
249 smiley->shortcuts = g_list_prepend(smiley->shortcuts,
250 g_strdup(token));
252 g_strfreev(split);
253 smiley->shortcuts = g_list_reverse(smiley->shortcuts);
256 if (proto)
257 proto->smileys = g_list_reverse(proto->smileys);
259 fclose(file);
261 if (inv_frm) {
262 purple_debug_error("gtksmiley-theme", "%s:%d"
263 " invalid format", index_path, line_no);
264 pidgin_smiley_theme_index_free(index);
265 index = NULL;
268 g_free(index_path);
269 return index;
272 /*******************************************************************************
273 * Theme loading
274 ******************************************************************************/
276 static void
277 pidgin_smiley_theme_load(const gchar *theme_path)
279 PidginSmileyTheme *theme;
280 PidginSmileyThemePrivate *priv;
281 PidginSmileyThemeIndex *index;
282 GList *it;
284 /* it's not super-efficient, but we don't expect huge amount of
285 * installed themes */
286 for (it = smiley_themes; it; it = g_list_next(it)) {
287 PidginSmileyThemePrivate *priv =
288 PIDGIN_SMILEY_THEME_GET_PRIVATE(it->data);
290 /* theme is already loaded */
291 if (g_strcmp0(priv->path, theme_path) == 0)
292 return;
295 theme = g_object_new(PIDGIN_TYPE_SMILEY_THEME, NULL);
296 priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
298 priv->path = g_strdup(theme_path);
300 index = pidgin_smiley_theme_index_parse(theme_path, FALSE);
302 if (!index->name || index->name[0] == '\0') {
303 purple_debug_warning("gtksmiley-theme",
304 "incomplete theme %s", theme_path);
305 pidgin_smiley_theme_index_free(index);
306 g_object_unref(theme);
307 return;
310 priv->name = g_strdup(index->name);
311 if (index->desc && index->desc[0])
312 priv->desc = g_strdup(index->desc);
313 if (index->icon && index->icon[0])
314 priv->icon = g_strdup(index->icon);
315 if (index->author && index->author[0])
316 priv->author = g_strdup(index->author);
318 pidgin_smiley_theme_index_free(index);
320 smiley_themes = g_list_append(smiley_themes, theme);
323 static void
324 pidgin_smiley_theme_probe(void)
326 GList *it, *next;
327 int i;
329 /* remove non-existing themes */
330 for (it = smiley_themes; it; it = next) {
331 PidginSmileyTheme *theme = it->data;
332 PidginSmileyThemePrivate *priv =
333 PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
335 next = g_list_next(it);
337 if (g_file_test(priv->path, G_FILE_TEST_EXISTS))
338 continue;
339 smiley_themes = g_list_delete_link(smiley_themes, it);
340 g_object_unref(theme);
343 /* scan for themes */
344 for (i = 0; probe_dirs[i]; i++) {
345 GDir *dir = g_dir_open(probe_dirs[i], 0, NULL);
346 const gchar *theme_dir_name;
348 if (!dir)
349 continue;
351 while ((theme_dir_name = g_dir_read_name(dir))) {
352 gchar *theme_path;
354 /* Ignore Pidgin 2.x.y "none" theme. */
355 if (g_strcmp0(theme_dir_name, "none") == 0)
356 continue;
358 theme_path = g_build_filename(
359 probe_dirs[i], theme_dir_name, NULL);
361 if (g_file_test(theme_path, G_FILE_TEST_IS_DIR))
362 pidgin_smiley_theme_load(theme_path);
364 g_free(theme_path);
367 g_dir_close(dir);
372 /*******************************************************************************
373 * API implementation
374 ******************************************************************************/
376 const gchar *
377 pidgin_smiley_theme_get_name(PidginSmileyTheme *theme)
379 PidginSmileyThemePrivate *priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
381 g_return_val_if_fail(priv != NULL, NULL);
383 return priv->name;
386 const gchar *
387 pidgin_smiley_theme_get_description(PidginSmileyTheme *theme)
389 PidginSmileyThemePrivate *priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
391 g_return_val_if_fail(priv != NULL, NULL);
393 return priv->desc;
396 GdkPixbuf *
397 pidgin_smiley_theme_get_icon(PidginSmileyTheme *theme)
399 PidginSmileyThemePrivate *priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
401 g_return_val_if_fail(priv != NULL, NULL);
403 if (priv->icon == NULL)
404 return NULL;
406 if (!priv->icon_pixbuf) {
407 gchar *icon_path = g_build_filename(
408 priv->path, priv->icon, NULL);
409 priv->icon_pixbuf = pidgin_pixbuf_new_from_file(icon_path);
410 g_free(icon_path);
413 return priv->icon_pixbuf;
416 const gchar *
417 pidgin_smiley_theme_get_author(PidginSmileyTheme *theme)
419 PidginSmileyThemePrivate *priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
421 g_return_val_if_fail(priv != NULL, NULL);
423 return priv->author;
426 PurpleSmileyList *
427 pidgin_smiley_theme_for_conv(PurpleConversation *conv)
429 PurpleAccount *acc = NULL;
430 PurpleSmileyTheme *theme;
431 const gchar *proto_name = NULL;
433 theme = purple_smiley_theme_get_current();
434 if (theme == NULL)
435 return NULL;
437 if (conv)
438 acc = purple_conversation_get_account(conv);
439 if (acc)
440 proto_name = purple_account_get_protocol_name(acc);
442 return purple_smiley_theme_get_smileys(theme, (gpointer)proto_name);
445 static void
446 pidgin_smiley_theme_activate_impl(PurpleSmileyTheme *theme)
448 PidginSmileyThemePrivate *priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
449 PidginSmileyThemeIndex *index;
450 GHashTable *smap;
451 GList *it, *it2, *it3;
453 g_return_if_fail(priv != NULL);
455 if (priv->smiley_lists_map)
456 return;
458 priv->smiley_lists_map = smap = g_hash_table_new_full(
459 g_str_hash, g_str_equal, g_free, g_object_unref);
461 index = pidgin_smiley_theme_index_parse(priv->path, TRUE);
463 for (it = index->protocols; it; it = g_list_next(it)) {
464 PidginSmileyThemeIndexProtocol *proto_idx = it->data;
465 PurpleSmileyList *proto_smileys;
467 proto_smileys = g_hash_table_lookup(smap, proto_idx->name);
468 if (!proto_smileys) {
469 proto_smileys = purple_smiley_list_new();
470 g_hash_table_insert(smap,
471 g_strdup(proto_idx->name), proto_smileys);
474 for (it2 = proto_idx->smileys; it2; it2 = g_list_next(it2)) {
475 PidginSmileyThemeIndexSmiley *smiley_idx = it2->data;
476 gchar *smiley_path;
478 smiley_path = g_build_filename(
479 priv->path, smiley_idx->file, NULL);
480 if (!g_file_test(smiley_path, G_FILE_TEST_EXISTS)) {
481 purple_debug_warning("gtksmiley-theme",
482 "Smiley %s is missing", smiley_path);
483 continue;
486 for (it3 = smiley_idx->shortcuts; it3;
487 it3 = g_list_next(it3))
489 PurpleSmiley *smiley;
490 gchar *shortcut = it3->data;
492 smiley = purple_smiley_new(
493 shortcut, smiley_path);
494 g_object_set_data(G_OBJECT(smiley),
495 "pidgin-smiley-hidden",
496 GINT_TO_POINTER(smiley_idx->hidden));
497 purple_smiley_list_add(proto_smileys, smiley);
498 g_object_unref(smiley);
503 pidgin_smiley_theme_index_free(index);
506 static PurpleSmileyList *
507 pidgin_smiley_theme_get_smileys_impl(PurpleSmileyTheme *theme, gpointer ui_data)
509 PidginSmileyThemePrivate *priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(theme);
510 PurpleSmileyList *smileys = NULL;
512 pidgin_smiley_theme_activate_impl(theme);
514 if (ui_data)
515 smileys = g_hash_table_lookup(priv->smiley_lists_map, ui_data);
516 if (smileys != NULL)
517 return smileys;
519 return g_hash_table_lookup(priv->smiley_lists_map, "default");
522 GList *
523 pidgin_smiley_theme_get_all(void)
525 pidgin_smiley_theme_probe();
527 return smiley_themes;
530 void
531 _pidgin_smiley_theme_init(void)
533 GList *it;
534 const gchar *user_smileys_dir;
535 const gchar *theme_name;
537 probe_dirs = g_new0(gchar*, 3);
538 probe_dirs[0] = g_build_filename(
539 PURPLE_DATADIR, "pixmaps", "pidgin", "emotes", NULL);
540 user_smileys_dir = probe_dirs[1] = g_build_filename(
541 purple_user_dir(), "smileys", NULL);
543 if (!g_file_test(user_smileys_dir, G_FILE_TEST_IS_DIR)) {
544 if (g_mkdir(user_smileys_dir, S_IRUSR | S_IWUSR | S_IXUSR) == 0) {
545 purple_debug_error("gtksmiley-theme",
546 "Failed to create user smileys dir");
550 /* setting theme by name (copy-paste from gtkprefs) */
551 pidgin_smiley_theme_probe();
552 theme_name = purple_prefs_get_string(
553 PIDGIN_PREFS_ROOT "/smileys/theme");
554 for (it = smiley_themes; it; it = g_list_next(it)) {
555 PidginSmileyTheme *theme = it->data;
557 if (g_strcmp0(pidgin_smiley_theme_get_name(theme), theme_name))
558 continue;
560 purple_smiley_theme_set_current(PURPLE_SMILEY_THEME(theme));
564 void
565 _pidgin_smiley_theme_uninit(void)
567 g_strfreev(probe_dirs);
570 /*******************************************************************************
571 * Object stuff
572 ******************************************************************************/
574 static void
575 pidgin_smiley_theme_finalize(GObject *obj)
577 PidginSmileyThemePrivate *priv = PIDGIN_SMILEY_THEME_GET_PRIVATE(obj);
579 g_free(priv->path);
580 g_free(priv->name);
581 g_free(priv->desc);
582 g_free(priv->icon);
583 g_free(priv->author);
584 if (priv->icon_pixbuf)
585 g_object_unref(priv->icon_pixbuf);
586 if (priv->smiley_lists_map)
587 g_hash_table_destroy(priv->smiley_lists_map);
589 G_OBJECT_CLASS(parent_class)->finalize(obj);
592 static void
593 pidgin_smiley_theme_class_init(PidginSmileyThemeClass *klass)
595 GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
596 PurpleSmileyThemeClass *pst_class = PURPLE_SMILEY_THEME_CLASS(klass);
598 parent_class = g_type_class_peek_parent(klass);
600 g_type_class_add_private(klass, sizeof(PidginSmileyThemePrivate));
602 gobj_class->finalize = pidgin_smiley_theme_finalize;
604 pst_class->get_smileys = pidgin_smiley_theme_get_smileys_impl;
605 pst_class->activate = pidgin_smiley_theme_activate_impl;
608 GType
609 pidgin_smiley_theme_get_type(void)
611 static GType type = 0;
613 if (G_UNLIKELY(type == 0)) {
614 static const GTypeInfo info = {
615 .class_size = sizeof(PidginSmileyThemeClass),
616 .class_init = (GClassInitFunc)pidgin_smiley_theme_class_init,
617 .instance_size = sizeof(PidginSmileyTheme),
620 type = g_type_register_static(PURPLE_TYPE_SMILEY_THEME,
621 "PidginSmileyTheme", &info, 0);
624 return type;