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
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"
25 #include "glibcompat.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
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
;
67 } PidginSmileyThemeIndex
;
73 } PidginSmileyThemeIndexProtocol
;
80 } PidginSmileyThemeIndexSmiley
;
82 /*******************************************************************************
84 ******************************************************************************/
87 pidgin_smiley_theme_index_free(PidginSmileyThemeIndex
*index
)
91 g_return_if_fail(index
!= NULL
);
96 g_free(index
->author
);
98 for (it
= index
->protocols
; it
; it
= g_list_next(it
)) {
99 PidginSmileyThemeIndexProtocol
*proto
= it
->data
;
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
);
109 g_list_free(proto
->smileys
);
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
;
123 gboolean inv_frm
= FALSE
;
125 index_path
= g_build_filename(theme_path
, "theme", NULL
);
126 file
= g_fopen(index_path
, "r");
128 purple_debug_error("gtksmiley-theme",
129 "Failed to open index file %s", index_path
);
134 index
= g_new0(PidginSmileyThemeIndex
, 1);
136 while (!feof(file
)) {
137 PidginSmileyThemeIndexSmiley
*smiley
;
143 if (++line_no
> PIDGIN_SMILEY_THEME_MAX_LINES
) {
144 purple_debug_warning("gtksmiley-theme", "file too big");
148 if (!fgets(buff
, sizeof(buff
), file
))
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
);
168 if (line
[0] == '[') {
174 end
= strchr(line
, ']');
181 proto
->smileys
= g_list_reverse(proto
->smileys
);
183 proto
= g_new0(PidginSmileyThemeIndexProtocol
, 1);
184 proto
->name
= g_strndup(line
, end
- line
);
187 g_list_prepend(index
->protocols
, proto
);
192 if ((eqchr
= strchr(line
, '='))) {
194 if (g_ascii_strcasecmp(line
, "name") == 0) {
196 index
->name
= g_strdup(eqchr
+ 1);
197 g_strchug(index
->name
);
199 } else if (g_ascii_strcasecmp(line
, "description") == 0) {
201 index
->desc
= g_strdup(eqchr
+ 1);
202 g_strchug(index
->desc
);
204 } else if (g_ascii_strcasecmp(line
, "icon") == 0) {
206 index
->icon
= g_strdup(eqchr
+ 1);
207 g_strchug(index
->icon
);
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
);
218 /* parsing section content */
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
;
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')
241 if (i
== PIDGIN_SMILEY_THEME_MAX_TOKENS
- 1)
245 smiley
->file
= g_strdup(token
);
249 smiley
->shortcuts
= g_list_prepend(smiley
->shortcuts
,
253 smiley
->shortcuts
= g_list_reverse(smiley
->shortcuts
);
257 proto
->smileys
= g_list_reverse(proto
->smileys
);
262 purple_debug_error("gtksmiley-theme", "%s:%d"
263 " invalid format", index_path
, line_no
);
264 pidgin_smiley_theme_index_free(index
);
272 /*******************************************************************************
274 ******************************************************************************/
277 pidgin_smiley_theme_load(const gchar
*theme_path
)
279 PidginSmileyTheme
*theme
;
280 PidginSmileyThemePrivate
*priv
;
281 PidginSmileyThemeIndex
*index
;
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)
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
);
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
);
324 pidgin_smiley_theme_probe(void)
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
))
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
;
351 while ((theme_dir_name
= g_dir_read_name(dir
))) {
354 /* Ignore Pidgin 2.x.y "none" theme. */
355 if (g_strcmp0(theme_dir_name
, "none") == 0)
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
);
372 /*******************************************************************************
374 ******************************************************************************/
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
);
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
);
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
)
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
);
413 return priv
->icon_pixbuf
;
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
);
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();
438 acc
= purple_conversation_get_account(conv
);
440 proto_name
= purple_account_get_protocol_name(acc
);
442 return purple_smiley_theme_get_smileys(theme
, (gpointer
)proto_name
);
446 pidgin_smiley_theme_activate_impl(PurpleSmileyTheme
*theme
)
448 PidginSmileyThemePrivate
*priv
= PIDGIN_SMILEY_THEME_GET_PRIVATE(theme
);
449 PidginSmileyThemeIndex
*index
;
451 GList
*it
, *it2
, *it3
;
453 g_return_if_fail(priv
!= NULL
);
455 if (priv
->smiley_lists_map
)
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
;
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
);
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
);
515 smileys
= g_hash_table_lookup(priv
->smiley_lists_map
, ui_data
);
519 return g_hash_table_lookup(priv
->smiley_lists_map
, "default");
523 pidgin_smiley_theme_get_all(void)
525 pidgin_smiley_theme_probe();
527 return smiley_themes
;
531 _pidgin_smiley_theme_init(void)
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
))
560 purple_smiley_theme_set_current(PURPLE_SMILEY_THEME(theme
));
565 _pidgin_smiley_theme_uninit(void)
567 g_strfreev(probe_dirs
);
570 /*******************************************************************************
572 ******************************************************************************/
575 pidgin_smiley_theme_finalize(GObject
*obj
)
577 PidginSmileyThemePrivate
*priv
= PIDGIN_SMILEY_THEME_GET_PRIVATE(obj
);
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
);
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
;
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);