finch: Mark unused variable.
[pidgin-git.git] / pidgin / gtksmiley-theme.c
blob65ba0fe751d28e4347f3cd1e01919017fd118951
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_MAX_LINES 1024
34 #define PIDGIN_SMILEY_THEME_MAX_TOKENS 1024
36 /**
37 * PidginSmileyTheme:
39 * An implementation of a smiley theme.
41 struct _PidginSmileyTheme
43 PurpleSmileyTheme parent;
46 typedef struct
48 gchar *path;
50 gchar *name;
51 gchar *desc;
52 gchar *icon;
53 gchar *author;
55 GdkPixbuf *icon_pixbuf;
57 GHashTable *smiley_lists_map;
58 } PidginSmileyThemePrivate;
60 static gchar **probe_dirs;
61 static GList *smiley_themes = NULL;
63 typedef struct
65 gchar *name;
66 gchar *desc;
67 gchar *icon;
68 gchar *author;
70 GList *protocols;
71 } PidginSmileyThemeIndex;
73 typedef struct
75 gchar *name;
76 GList *smileys;
77 } PidginSmileyThemeIndexProtocol;
79 typedef struct
81 gchar *file;
82 gboolean hidden;
83 GList *shortcuts;
84 } PidginSmileyThemeIndexSmiley;
86 G_DEFINE_TYPE_WITH_PRIVATE(PidginSmileyTheme, pidgin_smiley_theme,
87 PURPLE_TYPE_SMILEY_THEME);
89 /*******************************************************************************
90 * Theme index parsing
91 ******************************************************************************/
93 static void
94 pidgin_smiley_theme_index_free(PidginSmileyThemeIndex *index)
96 GList *it, *it2;
98 g_return_if_fail(index != NULL);
100 g_free(index->name);
101 g_free(index->desc);
102 g_free(index->icon);
103 g_free(index->author);
105 for (it = index->protocols; it; it = g_list_next(it)) {
106 PidginSmileyThemeIndexProtocol *proto = it->data;
108 g_free(proto->name);
109 for (it2 = proto->smileys; it2; it2 = g_list_next(it2)) {
110 PidginSmileyThemeIndexSmiley *smiley = it2->data;
112 g_free(smiley->file);
113 g_list_free_full(smiley->shortcuts, g_free);
114 g_free(smiley);
116 g_list_free(proto->smileys);
117 g_free(proto);
119 g_list_free(index->protocols);
121 g_free(index);
124 static PidginSmileyThemeIndex *
125 pidgin_smiley_theme_index_parse(const gchar *theme_path, gboolean load_contents)
127 PidginSmileyThemeIndex *index;
128 PidginSmileyThemeIndexProtocol *proto = NULL;
129 gchar *index_path;
130 FILE *file;
131 int line_no = 0;
132 gboolean inv_frm = FALSE;
134 index_path = g_build_filename(theme_path, "theme", NULL);
135 file = g_fopen(index_path, "r");
136 if (!file) {
137 purple_debug_error("gtksmiley-theme",
138 "Failed to open index file %s", index_path);
139 g_free(index_path);
140 return NULL;
143 index = g_new0(PidginSmileyThemeIndex, 1);
145 while (!feof(file)) {
146 PidginSmileyThemeIndexSmiley *smiley;
147 gchar buff[1024];
148 gchar *line, *eqchr;
149 gchar **split;
150 int i;
152 if (++line_no > PIDGIN_SMILEY_THEME_MAX_LINES) {
153 purple_debug_warning("gtksmiley-theme", "file too big");
154 break;
157 if (!fgets(buff, sizeof(buff), file))
158 break;
160 /* strip comments */
161 if (buff[0] == '#')
162 continue;
164 g_strstrip(buff);
165 if (buff[0] == '\0')
166 continue;
168 if (!g_utf8_validate(buff, -1, NULL)) {
169 purple_debug_error("gtksmiley-theme",
170 "%s:%d is invalid UTF-8",
171 index_path, line_no);
172 continue;
175 line = buff;
177 if (line[0] == '[') {
178 gchar *end;
180 if (!load_contents)
181 break;
182 line++;
183 end = strchr(line, ']');
184 if (!end) {
185 inv_frm = TRUE;
186 break;
189 if (proto)
190 proto->smileys = g_list_reverse(proto->smileys);
192 proto = g_new0(PidginSmileyThemeIndexProtocol, 1);
193 proto->name = g_strndup(line, end - line);
195 index->protocols =
196 g_list_prepend(index->protocols, proto);
198 continue;
201 if ((eqchr = strchr(line, '='))) {
202 *eqchr = '\0';
203 if (g_ascii_strcasecmp(line, "name") == 0) {
204 g_free(index->name);
205 index->name = g_strdup(eqchr + 1);
206 g_strchug(index->name);
207 continue;
208 } else if (g_ascii_strcasecmp(line, "description") == 0) {
209 g_free(index->desc);
210 index->desc = g_strdup(eqchr + 1);
211 g_strchug(index->desc);
212 continue;
213 } else if (g_ascii_strcasecmp(line, "icon") == 0) {
214 g_free(index->icon);
215 index->icon = g_strdup(eqchr + 1);
216 g_strchug(index->icon);
217 continue;
218 } else if (g_ascii_strcasecmp(line, "author") == 0) {
219 g_free(index->author);
220 index->author = g_strdup(eqchr + 1);
221 g_strchug(index->author);
222 continue;
224 *eqchr = '=';
227 /* parsing section content */
229 if (proto == NULL) {
230 inv_frm = FALSE;
231 break;
234 smiley = g_new0(PidginSmileyThemeIndexSmiley, 1);
235 proto->smileys = g_list_prepend(proto->smileys, smiley);
237 smiley->hidden = FALSE;
238 if (line[0] == '!') {
239 smiley->hidden = TRUE;
240 line++;
243 split = g_strsplit_set(line, " \t",
244 PIDGIN_SMILEY_THEME_MAX_TOKENS);
245 for (i = 0; split[i]; i++) {
246 gchar *token = split[i];
248 if (token[0] == '\0')
249 continue;
250 if (i == PIDGIN_SMILEY_THEME_MAX_TOKENS - 1)
251 break;
253 if (!smiley->file) {
254 smiley->file = g_strdup(token);
255 continue;
258 smiley->shortcuts = g_list_prepend(smiley->shortcuts,
259 g_strdup(token));
261 g_strfreev(split);
262 smiley->shortcuts = g_list_reverse(smiley->shortcuts);
265 if (proto)
266 proto->smileys = g_list_reverse(proto->smileys);
268 fclose(file);
270 if (inv_frm) {
271 purple_debug_error("gtksmiley-theme", "%s:%d"
272 " invalid format", index_path, line_no);
273 pidgin_smiley_theme_index_free(index);
274 index = NULL;
277 g_free(index_path);
278 return index;
281 /*******************************************************************************
282 * Theme loading
283 ******************************************************************************/
285 static void
286 pidgin_smiley_theme_load(const gchar *theme_path)
288 PidginSmileyTheme *theme;
289 PidginSmileyThemePrivate *priv;
290 PidginSmileyThemeIndex *index;
291 GList *it;
293 /* it's not super-efficient, but we don't expect huge amount of
294 * installed themes */
295 for (it = smiley_themes; it; it = g_list_next(it)) {
296 PidginSmileyThemePrivate *priv =
297 pidgin_smiley_theme_get_instance_private(it->data);
299 /* theme is already loaded */
300 if (g_strcmp0(priv->path, theme_path) == 0)
301 return;
304 theme = g_object_new(PIDGIN_TYPE_SMILEY_THEME, NULL);
305 priv = pidgin_smiley_theme_get_instance_private(theme);
307 priv->path = g_strdup(theme_path);
309 index = pidgin_smiley_theme_index_parse(theme_path, FALSE);
311 if (!index->name || index->name[0] == '\0') {
312 purple_debug_warning("gtksmiley-theme",
313 "incomplete theme %s", theme_path);
314 pidgin_smiley_theme_index_free(index);
315 g_object_unref(theme);
316 return;
319 priv->name = g_strdup(index->name);
320 if (index->desc && index->desc[0])
321 priv->desc = g_strdup(index->desc);
322 if (index->icon && index->icon[0])
323 priv->icon = g_strdup(index->icon);
324 if (index->author && index->author[0])
325 priv->author = g_strdup(index->author);
327 pidgin_smiley_theme_index_free(index);
329 smiley_themes = g_list_append(smiley_themes, theme);
332 static void
333 pidgin_smiley_theme_probe(void)
335 GList *it, *next;
336 int i;
338 /* remove non-existing themes */
339 for (it = smiley_themes; it; it = next) {
340 PidginSmileyTheme *theme = it->data;
341 PidginSmileyThemePrivate *priv =
342 pidgin_smiley_theme_get_instance_private(theme);
344 next = g_list_next(it);
346 if (g_file_test(priv->path, G_FILE_TEST_EXISTS))
347 continue;
348 smiley_themes = g_list_delete_link(smiley_themes, it);
349 g_object_unref(theme);
352 /* scan for themes */
353 for (i = 0; probe_dirs[i]; i++) {
354 GDir *dir = g_dir_open(probe_dirs[i], 0, NULL);
355 const gchar *theme_dir_name;
357 if (!dir)
358 continue;
360 while ((theme_dir_name = g_dir_read_name(dir))) {
361 gchar *theme_path;
363 /* Ignore Pidgin 2.x.y "none" theme. */
364 if (g_strcmp0(theme_dir_name, "none") == 0)
365 continue;
367 theme_path = g_build_filename(
368 probe_dirs[i], theme_dir_name, NULL);
370 if (g_file_test(theme_path, G_FILE_TEST_IS_DIR))
371 pidgin_smiley_theme_load(theme_path);
373 g_free(theme_path);
376 g_dir_close(dir);
381 /*******************************************************************************
382 * API implementation
383 ******************************************************************************/
385 const gchar *
386 pidgin_smiley_theme_get_name(PidginSmileyTheme *theme)
388 PidginSmileyThemePrivate *priv = NULL;
390 g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
392 priv = pidgin_smiley_theme_get_instance_private(theme);
393 return priv->name;
396 const gchar *
397 pidgin_smiley_theme_get_description(PidginSmileyTheme *theme)
399 PidginSmileyThemePrivate *priv = NULL;
401 g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
403 priv = pidgin_smiley_theme_get_instance_private(theme);
404 return priv->desc;
407 GdkPixbuf *
408 pidgin_smiley_theme_get_icon(PidginSmileyTheme *theme)
410 PidginSmileyThemePrivate *priv = NULL;
412 g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
413 priv = pidgin_smiley_theme_get_instance_private(theme);
415 if (priv->icon == NULL)
416 return NULL;
418 if (!priv->icon_pixbuf) {
419 gchar *icon_path = g_build_filename(
420 priv->path, priv->icon, NULL);
421 priv->icon_pixbuf = pidgin_pixbuf_new_from_file(icon_path);
422 g_free(icon_path);
425 return priv->icon_pixbuf;
428 const gchar *
429 pidgin_smiley_theme_get_author(PidginSmileyTheme *theme)
431 PidginSmileyThemePrivate *priv = NULL;
433 g_return_val_if_fail(PIDGIN_IS_SMILEY_THEME(theme), NULL);
435 priv = pidgin_smiley_theme_get_instance_private(theme);
436 return priv->author;
439 PurpleSmileyList *
440 pidgin_smiley_theme_for_conv(PurpleConversation *conv)
442 PurpleAccount *acc = NULL;
443 PurpleSmileyTheme *theme;
444 const gchar *proto_name = NULL;
446 theme = purple_smiley_theme_get_current();
447 if (theme == NULL)
448 return NULL;
450 if (conv)
451 acc = purple_conversation_get_account(conv);
452 if (acc)
453 proto_name = purple_account_get_protocol_name(acc);
455 return purple_smiley_theme_get_smileys(theme, (gpointer)proto_name);
458 static void
459 pidgin_smiley_theme_activate_impl(PurpleSmileyTheme *theme)
461 PidginSmileyThemePrivate *priv =
462 pidgin_smiley_theme_get_instance_private(
463 PIDGIN_SMILEY_THEME(theme));
464 PidginSmileyThemeIndex *index;
465 GHashTable *smap;
466 GList *it, *it2, *it3;
468 if (priv->smiley_lists_map)
469 return;
471 priv->smiley_lists_map = smap = g_hash_table_new_full(
472 g_str_hash, g_str_equal, g_free, g_object_unref);
474 index = pidgin_smiley_theme_index_parse(priv->path, TRUE);
476 for (it = index->protocols; it; it = g_list_next(it)) {
477 PidginSmileyThemeIndexProtocol *proto_idx = it->data;
478 PurpleSmileyList *proto_smileys;
480 proto_smileys = g_hash_table_lookup(smap, proto_idx->name);
481 if (!proto_smileys) {
482 proto_smileys = purple_smiley_list_new();
483 g_hash_table_insert(smap,
484 g_strdup(proto_idx->name), proto_smileys);
487 for (it2 = proto_idx->smileys; it2; it2 = g_list_next(it2)) {
488 PidginSmileyThemeIndexSmiley *smiley_idx = it2->data;
489 gchar *smiley_path;
491 smiley_path = g_build_filename(
492 priv->path, smiley_idx->file, NULL);
493 if (!g_file_test(smiley_path, G_FILE_TEST_EXISTS)) {
494 purple_debug_warning("gtksmiley-theme",
495 "Smiley %s is missing", smiley_path);
496 g_free(smiley_path);
497 continue;
500 for (it3 = smiley_idx->shortcuts; it3;
501 it3 = g_list_next(it3))
503 PurpleSmiley *smiley;
504 gchar *shortcut = it3->data;
506 smiley = purple_smiley_new(
507 shortcut, smiley_path);
508 g_object_set_data(G_OBJECT(smiley),
509 "pidgin-smiley-hidden",
510 GINT_TO_POINTER(smiley_idx->hidden));
511 purple_smiley_list_add(proto_smileys, smiley);
512 g_object_unref(smiley);
515 g_free(smiley_path);
519 pidgin_smiley_theme_index_free(index);
522 static PurpleSmileyList *
523 pidgin_smiley_theme_get_smileys_impl(PurpleSmileyTheme *theme, gpointer ui_data)
525 PidginSmileyThemePrivate *priv =
526 pidgin_smiley_theme_get_instance_private(
527 PIDGIN_SMILEY_THEME(theme));
528 PurpleSmileyList *smileys = NULL;
530 pidgin_smiley_theme_activate_impl(theme);
532 if (ui_data)
533 smileys = g_hash_table_lookup(priv->smiley_lists_map, ui_data);
534 if (smileys != NULL)
535 return smileys;
537 return g_hash_table_lookup(priv->smiley_lists_map, "default");
540 GList *
541 pidgin_smiley_theme_get_all(void)
543 pidgin_smiley_theme_probe();
545 return smiley_themes;
548 void
549 _pidgin_smiley_theme_init(void)
551 GList *it;
552 const gchar *user_smileys_dir;
553 const gchar *theme_name;
555 probe_dirs = g_new0(gchar*, 3);
556 probe_dirs[0] = g_build_filename(
557 PURPLE_DATADIR, "pixmaps", "pidgin", "emotes", NULL);
558 user_smileys_dir = probe_dirs[1] = g_build_filename(
559 purple_user_dir(), "smileys", NULL);
561 if (!g_file_test(user_smileys_dir, G_FILE_TEST_IS_DIR)) {
562 if (g_mkdir(user_smileys_dir, S_IRUSR | S_IWUSR | S_IXUSR) == 0) {
563 purple_debug_error("gtksmiley-theme",
564 "Failed to create user smileys dir");
568 /* setting theme by name (copy-paste from gtkprefs) */
569 pidgin_smiley_theme_probe();
570 theme_name = purple_prefs_get_string(
571 PIDGIN_PREFS_ROOT "/smileys/theme");
572 for (it = smiley_themes; it; it = g_list_next(it)) {
573 PidginSmileyTheme *theme = it->data;
575 if (g_strcmp0(pidgin_smiley_theme_get_name(theme), theme_name))
576 continue;
578 purple_smiley_theme_set_current(PURPLE_SMILEY_THEME(theme));
582 void
583 _pidgin_smiley_theme_uninit(void)
585 g_strfreev(probe_dirs);
588 /*******************************************************************************
589 * Object stuff
590 ******************************************************************************/
592 static void
593 pidgin_smiley_theme_finalize(GObject *obj)
595 PidginSmileyThemePrivate *priv =
596 pidgin_smiley_theme_get_instance_private(
597 PIDGIN_SMILEY_THEME(obj));
599 g_free(priv->path);
600 g_free(priv->name);
601 g_free(priv->desc);
602 g_free(priv->icon);
603 g_free(priv->author);
604 if (priv->icon_pixbuf)
605 g_object_unref(priv->icon_pixbuf);
606 if (priv->smiley_lists_map)
607 g_hash_table_destroy(priv->smiley_lists_map);
609 G_OBJECT_CLASS(pidgin_smiley_theme_parent_class)->finalize(obj);
612 static void
613 pidgin_smiley_theme_class_init(PidginSmileyThemeClass *klass)
615 GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
616 PurpleSmileyThemeClass *pst_class = PURPLE_SMILEY_THEME_CLASS(klass);
618 gobj_class->finalize = pidgin_smiley_theme_finalize;
620 pst_class->get_smileys = pidgin_smiley_theme_get_smileys_impl;
621 pst_class->activate = pidgin_smiley_theme_activate_impl;
624 static void
625 pidgin_smiley_theme_init(PidginSmileyTheme *theme)