2 * Conversation Themes for Pidgin
4 * Pidgin is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "glibcompat.h"
26 #include "gtkconv-theme.h"
28 #include "conversation.h"
35 #include "gtkwebview.h"
40 #define PIDGIN_CONV_THEME_GET_PRIVATE(Gobject) \
41 (G_TYPE_INSTANCE_GET_PRIVATE((Gobject), PIDGIN_TYPE_CONV_THEME, PidginConvThemePrivate))
43 /******************************************************************************
45 *****************************************************************************/
48 /* current config options */
49 char *variant
; /* allowed to be NULL if there are no variants */
52 /* Info.plist keys/values */
62 char *incoming_content_html
;
63 char *outgoing_content_html
;
64 char *incoming_next_content_html
;
65 char *outgoing_next_content_html
;
66 char *incoming_context_html
;
67 char *outgoing_context_html
;
68 char *incoming_next_context_html
;
69 char *outgoing_next_context_html
;
73 } PidginConvThemePrivate
;
75 /******************************************************************************
77 *****************************************************************************/
86 /******************************************************************************
88 *****************************************************************************/
90 static GObjectClass
*parent_class
= NULL
;
91 static GParamSpec
*properties
[PROP_LAST
];
93 /******************************************************************************
95 *****************************************************************************/
98 get_key(PidginConvThemePrivate
*priv
, const char *key
, gboolean specific
)
102 /* Try variant-specific key */
103 if (specific
&& priv
->variant
) {
104 char *name
= g_strdup_printf("%s:%s", key
, priv
->variant
);
105 val
= g_hash_table_lookup(priv
->info
, name
);
109 /* Try generic key */
111 val
= g_hash_table_lookup(priv
->info
, key
);
117 /* The template path can either come from the theme, or can
118 * be stock Template.html that comes with Pidgin */
120 get_template_path(const char *dir
)
124 file
= g_build_filename(dir
, "Contents", "Resources", "Template.html", NULL
);
126 if (!g_file_test(file
, G_FILE_TEST_EXISTS
)) {
128 #if defined(_WIN32) && !defined(USE_WIN32_FHS)
129 file
= g_build_filename(PURPLE_DATADIR
,
130 "theme", "Template.html", NULL
);
132 file
= g_build_filename(PURPLE_DATADIR
,
133 "pidgin", "theme", "Template.html", NULL
);
141 get_template_html(PidginConvThemePrivate
*priv
, const char *dir
)
145 if (priv
->template_html
)
146 return priv
->template_html
;
148 file
= get_template_path(dir
);
150 if (!g_file_get_contents(file
, &priv
->template_html
, NULL
, NULL
)) {
151 purple_debug_error("webkit", "Could not locate a Template.html (%s)\n", file
);
152 priv
->template_html
= g_strdup("");
156 return priv
->template_html
;
160 get_basestyle_css(PidginConvThemePrivate
*priv
, const char *dir
)
164 if (priv
->basestyle_css
)
165 return priv
->basestyle_css
;
167 file
= g_build_filename(dir
, "Contents", "Resources", "main.css", NULL
);
168 if (!g_file_get_contents(file
, &priv
->basestyle_css
, NULL
, NULL
))
169 priv
->basestyle_css
= g_strdup("");
172 return priv
->basestyle_css
;
176 get_header_html(PidginConvThemePrivate
*priv
, const char *dir
)
180 if (priv
->header_html
)
181 return priv
->header_html
;
183 file
= g_build_filename(dir
, "Contents", "Resources", "Header.html", NULL
);
184 if (!g_file_get_contents(file
, &priv
->header_html
, NULL
, NULL
))
185 priv
->header_html
= g_strdup("");
188 return priv
->header_html
;
192 get_footer_html(PidginConvThemePrivate
*priv
, const char *dir
)
196 if (priv
->footer_html
)
197 return priv
->footer_html
;
199 file
= g_build_filename(dir
, "Contents", "Resources", "Footer.html", NULL
);
200 if (!g_file_get_contents(file
, &priv
->footer_html
, NULL
, NULL
))
201 priv
->footer_html
= g_strdup("");
204 return priv
->footer_html
;
208 get_topic_html(PidginConvThemePrivate
*priv
, const char *dir
)
212 if (priv
->topic_html
)
213 return priv
->topic_html
;
215 file
= g_build_filename(dir
, "Contents", "Resources", "Topic.html", NULL
);
216 if (!g_file_get_contents(file
, &priv
->topic_html
, NULL
, NULL
)) {
217 purple_debug_info("webkit", "%s could not find Resources/Topic.html\n", dir
);
218 priv
->topic_html
= g_strdup("");
222 return priv
->topic_html
;
226 get_content_html(PidginConvThemePrivate
*priv
, const char *dir
)
230 if (priv
->content_html
)
231 return priv
->content_html
;
233 file
= g_build_filename(dir
, "Contents", "Resources", "Content.html", NULL
);
234 if (!g_file_get_contents(file
, &priv
->content_html
, NULL
, NULL
)) {
235 purple_debug_info("webkit", "%s did not have a Content.html\n", dir
);
236 priv
->content_html
= g_strdup("");
240 return priv
->content_html
;
244 get_status_html(PidginConvThemePrivate
*priv
, const char *dir
)
248 if (priv
->status_html
)
249 return priv
->status_html
;
251 file
= g_build_filename(dir
, "Contents", "Resources", "Status.html", NULL
);
252 if (!g_file_get_contents(file
, &priv
->status_html
, NULL
, NULL
)) {
253 purple_debug_info("webkit", "%s could not find Resources/Status.html\n", dir
);
254 priv
->status_html
= g_strdup(get_content_html(priv
, dir
));
258 return priv
->status_html
;
262 get_incoming_content_html(PidginConvThemePrivate
*priv
, const char *dir
)
266 if (priv
->incoming_content_html
)
267 return priv
->incoming_content_html
;
269 file
= g_build_filename(dir
, "Contents", "Resources", "Incoming", "Content.html", NULL
);
270 if (!g_file_get_contents(file
, &priv
->incoming_content_html
, NULL
, NULL
)) {
271 purple_debug_info("webkit", "%s did not have a Incoming/Content.html\n", dir
);
272 priv
->incoming_content_html
= g_strdup(get_content_html(priv
, dir
));
276 return priv
->incoming_content_html
;
280 get_incoming_next_content_html(PidginConvThemePrivate
*priv
, const char *dir
)
284 if (priv
->incoming_next_content_html
)
285 return priv
->incoming_next_content_html
;
287 file
= g_build_filename(dir
, "Contents", "Resources", "Incoming", "NextContent.html", NULL
);
288 if (!g_file_get_contents(file
, &priv
->incoming_next_content_html
, NULL
, NULL
)) {
289 priv
->incoming_next_content_html
= g_strdup(get_incoming_content_html(priv
, dir
));
293 return priv
->incoming_next_content_html
;
297 get_incoming_context_html(PidginConvThemePrivate
*priv
, const char *dir
)
301 if (priv
->incoming_context_html
)
302 return priv
->incoming_context_html
;
304 file
= g_build_filename(dir
, "Contents", "Resources", "Incoming", "Context.html", NULL
);
305 if (!g_file_get_contents(file
, &priv
->incoming_context_html
, NULL
, NULL
)) {
306 purple_debug_info("webkit", "%s did not have a Incoming/Context.html\n", dir
);
307 priv
->incoming_context_html
= g_strdup(get_incoming_content_html(priv
, dir
));
311 return priv
->incoming_context_html
;
315 get_incoming_next_context_html(PidginConvThemePrivate
*priv
, const char *dir
)
319 if (priv
->incoming_next_context_html
)
320 return priv
->incoming_next_context_html
;
322 file
= g_build_filename(dir
, "Contents", "Resources", "Incoming", "NextContext.html", NULL
);
323 if (!g_file_get_contents(file
, &priv
->incoming_next_context_html
, NULL
, NULL
)) {
324 priv
->incoming_next_context_html
= g_strdup(get_incoming_context_html(priv
, dir
));
328 return priv
->incoming_next_context_html
;
332 get_outgoing_content_html(PidginConvThemePrivate
*priv
, const char *dir
)
336 if (priv
->outgoing_content_html
)
337 return priv
->outgoing_content_html
;
339 file
= g_build_filename(dir
, "Contents", "Resources", "Outgoing", "Content.html", NULL
);
340 if (!g_file_get_contents(file
, &priv
->outgoing_content_html
, NULL
, NULL
)) {
341 priv
->outgoing_content_html
= g_strdup(get_incoming_content_html(priv
, dir
));
345 return priv
->outgoing_content_html
;
349 get_outgoing_next_content_html(PidginConvThemePrivate
*priv
, const char *dir
)
353 if (priv
->outgoing_next_content_html
)
354 return priv
->outgoing_next_content_html
;
356 file
= g_build_filename(dir
, "Contents", "Resources", "Outgoing", "NextContent.html", NULL
);
357 if (!g_file_get_contents(file
, &priv
->outgoing_next_content_html
, NULL
, NULL
)) {
358 priv
->outgoing_next_content_html
= g_strdup(get_outgoing_content_html(priv
, dir
));
361 return priv
->outgoing_next_content_html
;
365 get_outgoing_context_html(PidginConvThemePrivate
*priv
, const char *dir
)
369 if (priv
->outgoing_context_html
)
370 return priv
->outgoing_context_html
;
372 file
= g_build_filename(dir
, "Contents", "Resources", "Outgoing", "Context.html", NULL
);
373 if (!g_file_get_contents(file
, &priv
->outgoing_context_html
, NULL
, NULL
)) {
374 priv
->outgoing_context_html
= g_strdup(get_incoming_context_html(priv
, dir
));
378 return priv
->outgoing_context_html
;
382 get_outgoing_next_context_html(PidginConvThemePrivate
*priv
, const char *dir
)
386 if (priv
->outgoing_next_context_html
)
387 return priv
->outgoing_next_context_html
;
389 file
= g_build_filename(dir
, "Contents", "Resources", "Outgoing", "NextContext.html", NULL
);
390 if (!g_file_get_contents(file
, &priv
->outgoing_next_context_html
, NULL
, NULL
)) {
391 priv
->outgoing_next_context_html
= g_strdup(get_outgoing_context_html(priv
, dir
));
395 return priv
->outgoing_next_context_html
;
398 /******************************************************************************
400 *****************************************************************************/
403 pidgin_conv_theme_get_property(GObject
*obj
, guint param_id
, GValue
*value
,
406 PidginConvTheme
*theme
= PIDGIN_CONV_THEME(obj
);
410 g_value_set_boxed(value
, (gpointer
)pidgin_conversation_theme_get_info(theme
));
414 g_value_set_string(value
, pidgin_conversation_theme_get_variant(theme
));
418 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj
, param_id
, psec
);
424 pidgin_conv_theme_set_property(GObject
*obj
, guint param_id
, const GValue
*value
,
427 PidginConvTheme
*theme
= PIDGIN_CONV_THEME(obj
);
431 pidgin_conversation_theme_set_info(theme
, g_value_get_boxed(value
));
435 pidgin_conversation_theme_set_variant(theme
, g_value_get_string(value
));
439 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj
, param_id
, psec
);
445 pidgin_conv_theme_init(GTypeInstance
*instance
,
449 PidginConvThemePrivate
*priv
;
451 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(instance
);
456 pidgin_conv_theme_finalize(GObject
*obj
)
458 PidginConvThemePrivate
*priv
;
461 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(obj
);
463 g_free(priv
->template_html
);
464 g_free(priv
->header_html
);
465 g_free(priv
->footer_html
);
466 g_free(priv
->topic_html
);
467 g_free(priv
->status_html
);
468 g_free(priv
->content_html
);
469 g_free(priv
->incoming_content_html
);
470 g_free(priv
->outgoing_content_html
);
471 g_free(priv
->incoming_next_content_html
);
472 g_free(priv
->outgoing_next_content_html
);
473 g_free(priv
->incoming_context_html
);
474 g_free(priv
->outgoing_context_html
);
475 g_free(priv
->incoming_next_context_html
);
476 g_free(priv
->outgoing_next_context_html
);
477 g_free(priv
->basestyle_css
);
480 g_hash_table_destroy(priv
->info
);
482 list
= priv
->variants
;
485 list
= g_list_delete_link(list
, list
);
487 g_free(priv
->variant
);
489 if (priv
->nick_colors
)
490 g_array_unref(priv
->nick_colors
);
492 parent_class
->finalize(obj
);
496 pidgin_conv_theme_class_init(PidginConvThemeClass
*klass
)
498 GObjectClass
*obj_class
= G_OBJECT_CLASS(klass
);
500 parent_class
= g_type_class_peek_parent(klass
);
502 g_type_class_add_private(klass
, sizeof(PidginConvThemePrivate
));
504 obj_class
->get_property
= pidgin_conv_theme_get_property
;
505 obj_class
->set_property
= pidgin_conv_theme_set_property
;
506 obj_class
->finalize
= pidgin_conv_theme_finalize
;
509 properties
[PROP_INFO
] = g_param_spec_boxed("info", "Info",
510 "The information about this theme",
512 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
| G_PARAM_STATIC_STRINGS
);
515 properties
[PROP_VARIANT
] = g_param_spec_string("variant", "Variant",
516 "The current variant for this theme",
517 NULL
, G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
);
519 g_object_class_install_properties(obj_class
, PROP_LAST
, properties
);
523 pidgin_conversation_theme_get_type(void)
525 static GType type
= 0;
527 static const GTypeInfo info
= {
528 sizeof(PidginConvThemeClass
),
529 NULL
, /* base_init */
530 NULL
, /* base_finalize */
531 (GClassInitFunc
)pidgin_conv_theme_class_init
, /* class_init */
532 NULL
, /* class_finalize */
533 NULL
, /* class_data */
534 sizeof(PidginConvTheme
),
536 pidgin_conv_theme_init
, /* instance_init */
537 NULL
, /* value table */
539 type
= g_type_register_static(PURPLE_TYPE_THEME
,
540 "PidginConvTheme", &info
, 0);
545 /*****************************************************************************
546 * Public API functions
547 *****************************************************************************/
550 pidgin_conversation_theme_get_info(const PidginConvTheme
*theme
)
552 PidginConvThemePrivate
*priv
;
554 g_return_val_if_fail(theme
!= NULL
, NULL
);
556 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
561 pidgin_conversation_theme_set_info(PidginConvTheme
*theme
, GHashTable
*info
)
563 PidginConvThemePrivate
*priv
;
565 g_return_if_fail(theme
!= NULL
);
567 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
570 g_hash_table_destroy(priv
->info
);
574 g_object_notify_by_pspec(G_OBJECT(theme
), properties
[PROP_INFO
]);
578 pidgin_conversation_theme_lookup(PidginConvTheme
*theme
, const char *key
, gboolean specific
)
580 PidginConvThemePrivate
*priv
;
582 g_return_val_if_fail(theme
!= NULL
, NULL
);
584 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
586 return get_key(priv
, key
, specific
);
590 pidgin_conversation_theme_get_template(PidginConvTheme
*theme
, PidginConvThemeTemplateType type
)
592 PidginConvThemePrivate
*priv
;
596 g_return_val_if_fail(theme
!= NULL
, NULL
);
598 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
599 dir
= purple_theme_get_dir(PURPLE_THEME(theme
));
602 case PIDGIN_CONVERSATION_THEME_TEMPLATE_MAIN
:
603 html
= get_template_html(priv
, dir
);
605 case PIDGIN_CONVERSATION_THEME_TEMPLATE_HEADER
:
606 html
= get_header_html(priv
, dir
);
608 case PIDGIN_CONVERSATION_THEME_TEMPLATE_FOOTER
:
609 html
= get_footer_html(priv
, dir
);
611 case PIDGIN_CONVERSATION_THEME_TEMPLATE_TOPIC
:
612 html
= get_topic_html(priv
, dir
);
614 case PIDGIN_CONVERSATION_THEME_TEMPLATE_STATUS
:
615 html
= get_status_html(priv
, dir
);
617 case PIDGIN_CONVERSATION_THEME_TEMPLATE_CONTENT
:
618 html
= get_content_html(priv
, dir
);
620 case PIDGIN_CONVERSATION_THEME_TEMPLATE_INCOMING_CONTENT
:
621 html
= get_incoming_content_html(priv
, dir
);
623 case PIDGIN_CONVERSATION_THEME_TEMPLATE_INCOMING_NEXT_CONTENT
:
624 html
= get_incoming_next_content_html(priv
, dir
);
626 case PIDGIN_CONVERSATION_THEME_TEMPLATE_INCOMING_CONTEXT
:
627 html
= get_incoming_context_html(priv
, dir
);
629 case PIDGIN_CONVERSATION_THEME_TEMPLATE_INCOMING_NEXT_CONTEXT
:
630 html
= get_incoming_next_context_html(priv
, dir
);
632 case PIDGIN_CONVERSATION_THEME_TEMPLATE_OUTGOING_CONTENT
:
633 html
= get_outgoing_content_html(priv
, dir
);
635 case PIDGIN_CONVERSATION_THEME_TEMPLATE_OUTGOING_NEXT_CONTENT
:
636 html
= get_outgoing_next_content_html(priv
, dir
);
638 case PIDGIN_CONVERSATION_THEME_TEMPLATE_OUTGOING_CONTEXT
:
639 html
= get_outgoing_context_html(priv
, dir
);
641 case PIDGIN_CONVERSATION_THEME_TEMPLATE_OUTGOING_NEXT_CONTEXT
:
642 html
= get_outgoing_next_context_html(priv
, dir
);
644 case PIDGIN_CONVERSATION_THEME_TEMPLATE_BASESTYLE_CSS
:
645 html
= get_basestyle_css(priv
, dir
);
648 purple_debug_error("gtkconv-theme",
649 "Requested invalid template type (%d) for theme %s.\n",
650 type
, purple_theme_get_name(PURPLE_THEME(theme
)));
658 pidgin_conversation_theme_add_variant(PidginConvTheme
*theme
, char *variant
)
660 PidginConvThemePrivate
*priv
;
662 g_return_if_fail(theme
!= NULL
);
663 g_return_if_fail(variant
!= NULL
);
665 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
667 priv
->variants
= g_list_prepend(priv
->variants
, variant
);
671 pidgin_conversation_theme_get_variant(PidginConvTheme
*theme
)
673 PidginConvThemePrivate
*priv
;
675 g_return_val_if_fail(theme
!= NULL
, NULL
);
677 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
679 return priv
->variant
;
683 pidgin_conversation_theme_set_variant(PidginConvTheme
*theme
, const char *variant
)
685 PidginConvThemePrivate
*priv
;
689 g_return_if_fail(theme
!= NULL
);
690 g_return_if_fail(variant
!= NULL
);
692 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
694 g_free(priv
->variant
);
695 priv
->variant
= g_strdup(variant
);
697 val
= get_key(priv
, "CFBundleIdentifier", FALSE
);
698 prefname
= g_strdup_printf(PIDGIN_PREFS_ROOT
"/conversations/themes/%s/variant",
699 g_value_get_string(val
));
700 purple_prefs_set_string(prefname
, variant
);
703 g_object_notify_by_pspec(G_OBJECT(theme
), properties
[PROP_VARIANT
]);
707 pidgin_conversation_theme_get_variants(PidginConvTheme
*theme
)
709 PidginConvThemePrivate
*priv
;
711 g_return_val_if_fail(theme
!= NULL
, NULL
);
713 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
715 return priv
->variants
;
719 pidgin_conversation_theme_get_template_path(PidginConvTheme
*theme
)
723 g_return_val_if_fail(theme
!= NULL
, NULL
);
725 dir
= purple_theme_get_dir(PURPLE_THEME(theme
));
727 return get_template_path(dir
);
731 pidgin_conversation_theme_get_css_path(PidginConvTheme
*theme
)
733 PidginConvThemePrivate
*priv
;
736 g_return_val_if_fail(theme
!= NULL
, NULL
);
738 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
740 dir
= purple_theme_get_dir(PURPLE_THEME(theme
));
741 if (!priv
->variant
) {
742 return g_build_filename(dir
, "Contents", "Resources", "main.css", NULL
);
744 char *file
= g_strdup_printf("%s.css", priv
->variant
);
745 char *ret
= g_build_filename(dir
, "Contents", "Resources", "Variants", file
, NULL
);
752 pidgin_conversation_theme_get_nick_colors(PidginConvTheme
*theme
)
754 PidginConvThemePrivate
*priv
;
757 g_return_val_if_fail(theme
!= NULL
, NULL
);
759 priv
= PIDGIN_CONV_THEME_GET_PRIVATE(theme
);
761 dir
= purple_theme_get_dir(PURPLE_THEME(theme
));
762 if (NULL
== priv
->nick_colors
)
764 char *file
= g_build_filename(dir
, "Contents", "Resources", "Incoming", "SenderColors.txt", NULL
);
766 priv
->nick_colors
= g_array_new(FALSE
, FALSE
, sizeof(GdkRGBA
));
767 if (g_file_get_contents(file
, &contents
, NULL
, NULL
)) {
769 gchar
** color_strings
= g_strsplit_set(contents
, "\r\n:", -1);
771 for(i
=0; color_strings
[i
]; i
++)
774 if (gdk_rgba_parse(&color
, color_strings
[i
])) {
775 g_array_append_val(priv
->nick_colors
, color
);
779 g_strfreev(color_strings
);
785 if(priv
->nick_colors
->len
)
786 return g_array_ref(priv
->nick_colors
);