From 2ce0ccd78003fce557bb76883c87ca2cb101608d Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Mon, 5 Oct 2009 10:02:40 +0000 Subject: [PATCH] Added first support for CSS files. The default style.css is not added and can be changed to alter the visual appearance. More style options can be added in time. Changing styles from the GLK interface is still a mess though. git-svn-id: http://lassie.dyndns-server.com/svn/gargoyle-gtk@126 ddfedd41-794f-dd11-ae45-00112f111e67 --- libchimara/chimara-glk-private.h | 4 + libchimara/chimara-glk.c | 5 +- libchimara/style.c | 271 +++++++++++++++++++++++++++++-- libchimara/style.h | 8 + tests/chimara.menus | 1 + tests/style.css | 63 +++++++ 6 files changed, 340 insertions(+), 12 deletions(-) create mode 100644 tests/style.css diff --git a/libchimara/chimara-glk-private.h b/libchimara/chimara-glk-private.h index e1067b2..25417d3 100644 --- a/libchimara/chimara-glk-private.h +++ b/libchimara/chimara-glk-private.h @@ -27,6 +27,10 @@ struct _ChimaraGlkPrivate { PangoFontDescription *monospace_font_desc; /* Spacing between Glk windows */ guint spacing; + /* The CSS file to read style defaults from */ + gchar *css_file; + /* Hashtable containing the default styles */ + GHashTable *default_styles; /* *** Threading data *** */ /* Glk program loaded in widget */ diff --git a/libchimara/chimara-glk.c b/libchimara/chimara-glk.c index ae39f16..e0b6059 100644 --- a/libchimara/chimara-glk.c +++ b/libchimara/chimara-glk.c @@ -78,6 +78,8 @@ chimara_glk_init(ChimaraGlk *self) priv->protect = FALSE; priv->default_font_desc = pango_font_description_from_string("Sans"); priv->monospace_font_desc = pango_font_description_from_string("Monospace"); + priv->css_file = "style.css"; + priv->default_styles = g_hash_table_new(g_str_hash, g_str_equal); priv->program = NULL; priv->thread = NULL; priv->event_queue = NULL; @@ -188,6 +190,7 @@ chimara_glk_finalize(GObject *object) pango_font_description_free(priv->default_font_desc); pango_font_description_free(priv->monospace_font_desc); g_free(priv->current_dir); + g_hash_table_destroy(priv->default_styles); G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object); } @@ -688,7 +691,7 @@ chimara_glk_new(void) priv->abort_lock = g_mutex_new(); priv->arrange_lock = g_mutex_new(); priv->rearranged = g_cond_new(); - + return GTK_WIDGET(self); } diff --git a/libchimara/style.c b/libchimara/style.c index 3826ebf..3f86bad 100644 --- a/libchimara/style.c +++ b/libchimara/style.c @@ -1,6 +1,9 @@ #include "style.h" +#include +#include extern GPrivate *glk_data_key; +static gboolean chimara_style_initialized = FALSE; /** * glk_set_style: @@ -63,19 +66,265 @@ style_init_textbuffer(GtkTextBuffer *buffer) { g_return_if_fail(buffer != NULL); + if( G_UNLIKELY(!chimara_style_initialized) ) { + style_init(); + } + + ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); + g_hash_table_foreach(glk_data->default_styles, style_add_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer)); +} + +static void +style_add_tag_to_textbuffer(gpointer key, gpointer tag, gpointer tag_table) +{ + gtk_text_tag_table_add(tag_table, tag); +} + +void +style_init() +{ ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); + GtkTextTag *tag; + + /* Create the CSS file scanner */ + GScanner *scanner = g_scanner_new(NULL); + int f = open(glk_data->css_file, O_RDONLY); + g_return_if_fail(f != -1); + g_scanner_input_file(scanner, f); + scanner->input_name = glk_data->css_file; + scanner->config->cset_identifier_first = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#"; + scanner->config->cset_identifier_nth = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_0123456789"; + scanner->config->symbol_2_token = TRUE; + scanner->config->cpair_comment_single = NULL; + + /* Initialise the default styles */ + g_hash_table_insert(glk_data->default_styles, "normal", gtk_text_tag_new("normal")); + + tag = gtk_text_tag_new("emphasized"); + g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL); + g_hash_table_insert(glk_data->default_styles, "emphasized", tag); + + tag = gtk_text_tag_new("preformatted"); + g_object_set(tag, "font-desc", glk_data->monospace_font_desc, NULL); + g_hash_table_insert(glk_data->default_styles, "preformatted", tag); + + tag = gtk_text_tag_new("header"); + g_object_set(tag, "size-points", 18.0, "weight", PANGO_WEIGHT_BOLD, NULL); + g_hash_table_insert(glk_data->default_styles, "header", tag); + + tag = gtk_text_tag_new("subheader"); + g_object_set(tag, "size-points", 14.0, "weight", PANGO_WEIGHT_BOLD, NULL); + g_hash_table_insert(glk_data->default_styles, "subheader", tag); + + tag = gtk_text_tag_new("alert"); + g_object_set(tag, "foreground", "#aa0000", "weight", PANGO_WEIGHT_BOLD, NULL); + g_hash_table_insert(glk_data->default_styles, "alert", tag); + + tag = gtk_text_tag_new("note"); + g_object_set(tag, "foreground", "#aaaa00", "weight", PANGO_WEIGHT_BOLD, NULL); + g_hash_table_insert(glk_data->default_styles, "note", tag); + + tag = gtk_text_tag_new("block-quote"); + g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, NULL); + g_hash_table_insert(glk_data->default_styles, "block-quote", tag); + + g_hash_table_insert(glk_data->default_styles, "input", gtk_text_tag_new("input")); + g_hash_table_insert(glk_data->default_styles, "user1", gtk_text_tag_new("user1")); + g_hash_table_insert(glk_data->default_styles, "user2", gtk_text_tag_new("user2")); + + /* Run the scanner over the CSS file */ + while( g_scanner_peek_next_token(scanner) != G_TOKEN_EOF) { + if( !style_accept_style_identifier(scanner) ) + break; + } + + g_scanner_destroy(scanner); +} + +static gboolean +style_accept(GScanner *scanner, GTokenType token) +{ + if( g_scanner_get_next_token(scanner) != token ) { + g_scanner_unexp_token(scanner, token, NULL, NULL, NULL, "CSS Error", 1); + return FALSE; + } else { + return TRUE; + } +} + +static gboolean +style_accept_style_identifier(GScanner *scanner) +{ + ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key); + + GtkTextTag *current_tag; + GTokenType token = g_scanner_get_next_token(scanner); + GTokenValue value = g_scanner_cur_value(scanner); + + if(token != G_TOKEN_IDENTIFIER) { + g_scanner_error(scanner, "CSS Error: style identifier expected"); + return FALSE; + } + + printf("Identifier: %s\n", value.v_identifier); + current_tag = g_hash_table_lookup(glk_data->default_styles, value.v_identifier); + + if(current_tag == NULL) { + g_scanner_error(scanner, "CSS Error: invalid style identifier"); + return FALSE; + } + + if( !style_accept(scanner, '{') ) + return FALSE; + + while( g_scanner_peek_next_token(scanner) != '}') { + if( !style_accept_style_hint(scanner, current_tag) ) + return FALSE; + } + + if( !style_accept(scanner, '}') ) + return FALSE; + + return TRUE; +} + +static gboolean +style_accept_style_hint(GScanner *scanner, GtkTextTag *current_tag) +{ + GTokenType token = g_scanner_get_next_token(scanner); + GTokenValue value = g_scanner_cur_value(scanner); + gchar *hint; + + if(token != G_TOKEN_IDENTIFIER) { + g_scanner_error(scanner, "CSS Error: style hint expected"); + return FALSE; + } + + hint = g_strdup(value.v_identifier); + printf("Hint: %s\n", hint); + + if( !style_accept(scanner, ':') ) + return FALSE; + + token = g_scanner_get_next_token(scanner); + value = g_scanner_cur_value(scanner); + + if( !strcmp(hint, "font-family") ) { + if(token != G_TOKEN_STRING) { + g_scanner_error(scanner, "CSS Error: string expected"); + return FALSE; + } + g_object_set(current_tag, "family", value.v_string, "family-set", TRUE, NULL); + } + else if( !strcmp(hint, "font-weight") ) { + if(token != G_TOKEN_IDENTIFIER) { + g_scanner_error(scanner, "CSS Error: bold/normal expected"); + return FALSE; + } + + if( !strcmp(value.v_identifier, "bold") ) + g_object_set(current_tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL); + else if( !strcmp(value.v_identifier, "normal") ) + g_object_set(current_tag, "weight", PANGO_WEIGHT_NORMAL, "weight-set", TRUE, NULL); + else { + g_scanner_error(scanner, "CSS Error: bold/normal expected"); + return FALSE; + } + } + else if( !strcmp(hint, "font-style") ) { + if(token != G_TOKEN_IDENTIFIER) { + g_scanner_error(scanner, "CSS Error: italic/normal expected"); + return FALSE; + } + + if( !strcmp(value.v_identifier, "italic") ) + g_object_set(current_tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL); + else if( !strcmp(value.v_identifier, "normal") ) + g_object_set(current_tag, "style", PANGO_STYLE_NORMAL, "style-set", TRUE, NULL); + else { + g_scanner_error(scanner, "CSS Error: italic/normal expected"); + return FALSE; + } + } + else if( !strcmp(hint, "font-size") ) { + if(token == G_TOKEN_INT) + g_object_set(current_tag, "size-points", (float)value.v_int, "size-set", TRUE, NULL); + else if(token == G_TOKEN_FLOAT) + g_object_set(current_tag, "size-points", value.v_float, "size-set", TRUE, NULL); + else { + g_scanner_error(scanner, "CSS Error: integer or float expected"); + return FALSE; + } + } + else if( !strcmp(hint, "color") ) { + if(token != G_TOKEN_IDENTIFIER) { + g_scanner_error(scanner, "CSS Error: hex color expected"); + return FALSE; + } + g_object_set(current_tag, "foreground", value.v_identifier, "foreground-set", TRUE, NULL); + } + else if( !strcmp(hint, "background-color") ) { + if(token != G_TOKEN_IDENTIFIER) { + g_scanner_error(scanner, "CSS Error: hex color expected"); + return FALSE; + } + g_object_set(current_tag, "background", value.v_identifier, "background-set", TRUE, NULL); + } + else if( !strcmp(hint, "text-align") ) { + if(token != G_TOKEN_IDENTIFIER) { + g_scanner_error(scanner, "CSS Error: left/right/center expected"); + return FALSE; + } + + if( !strcmp(value.v_identifier, "left") ) + g_object_set(current_tag, "justification", GTK_JUSTIFY_LEFT, "justification-set", TRUE, NULL); + else if( !strcmp(value.v_identifier, "right") ) + g_object_set(current_tag, "justification", GTK_JUSTIFY_RIGHT, "justification-set", TRUE, NULL); + else if( !strcmp(value.v_identifier, "center") ) + g_object_set(current_tag, "justification", GTK_JUSTIFY_CENTER, "justification-set", TRUE, NULL); + else { + g_scanner_error(scanner, "CSS Error: left/right/center expected"); + return FALSE; + } + } + else if( !strcmp(hint, "margin-left") ) { + if(token != G_TOKEN_INT) { + g_scanner_error(scanner, "CSS Error: integer expected"); + return FALSE; + } + g_object_set(current_tag, "left-margin", value.v_int, "left-margin-set", TRUE, NULL); + } + else if( !strcmp(hint, "margin-right") ) { + if(token != G_TOKEN_INT) { + g_scanner_error(scanner, "CSS Error: integer expected"); + return FALSE; + } + g_object_set(current_tag, "right-margin", value.v_int, "right-margin-set", TRUE, NULL); + } + else if( !strcmp(hint, "margin-top") ) { + if(token != G_TOKEN_INT) { + g_scanner_error(scanner, "CSS Error: integer expected"); + return FALSE; + } + g_object_set(current_tag, "pixels-above-lines", value.v_int, "pixels-above-lines-set", TRUE, NULL); + } + else if( !strcmp(hint, "margin-bottom") ) { + if(token != G_TOKEN_INT) { + g_scanner_error(scanner, "CSS Error: integer expected"); + return FALSE; + } + g_object_set(current_tag, "pixels-below-lines", value.v_int, "pixels-below-lines-set", TRUE, NULL); + } + + else { + g_scanner_error(scanner, "CSS Error: invalid style hint %s", hint); + return FALSE; + } + + if( !style_accept(scanner, ';') ) + return FALSE; - gtk_text_buffer_create_tag(buffer, "normal", NULL); - gtk_text_buffer_create_tag(buffer, "emphasized", "style", PANGO_STYLE_ITALIC, NULL); - gtk_text_buffer_create_tag(buffer, "preformatted", "font-desc", glk_data->monospace_font_desc, NULL); - gtk_text_buffer_create_tag(buffer, "header", "size-points", 18.0, "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "subheader", "size-points", 14.0, "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "alert", "foreground", "#aa0000", "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "note", "foreground", "#aaaa00", "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "block-quote", "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, NULL); - gtk_text_buffer_create_tag(buffer, "input", NULL); - gtk_text_buffer_create_tag(buffer, "user1", NULL); - gtk_text_buffer_create_tag(buffer, "user2", NULL); + return TRUE; } static void diff --git a/libchimara/style.h b/libchimara/style.h index fcd5302..72af60a 100644 --- a/libchimara/style.h +++ b/libchimara/style.h @@ -2,11 +2,19 @@ #define STYLE_H #include +#include +#include +#include #include "glk.h" #include "magic.h" #include "chimara-glk-private.h" #include "stream.h" G_GNUC_INTERNAL void style_init_textbuffer(GtkTextBuffer *buffer); +G_GNUC_INTERNAL void style_init(); +static gboolean style_accept(GScanner *scanner, GTokenType token); +static gboolean style_accept_style_identifier(GScanner *scanner); +static gboolean style_accept_style_hint(GScanner *scanner, GtkTextTag *current_tag); +static void style_add_tag_to_textbuffer(gpointer key, gpointer tag, gpointer tag_table); #endif diff --git a/tests/chimara.menus b/tests/chimara.menus index 233ec4c..4e3e7d4 100644 --- a/tests/chimara.menus +++ b/tests/chimara.menus @@ -10,5 +10,6 @@ + diff --git a/tests/style.css b/tests/style.css new file mode 100644 index 0000000..0b97e76 --- /dev/null +++ b/tests/style.css @@ -0,0 +1,63 @@ +/* Possible selectors: + * normal + * emphasized + * preformatted + * header + * subheader + * alert + * note + * block-quote + * input + * user1 + * user2 + * + * Possible style hints: + * font-family (string) + * font-size (float) + * font-weight (normal/bold) + * font-style (normal/italic) + * color (#hex-value) + * background-color (#hex-value) + * text-align (left/right/center) + */ + + +normal { + font-family: 'times'; + font-size: 12; +} + +header { + font-size: 18; + font-weight: bold; + text-align: center; +} + +subheader { + font-size: 14; + font-weight: bold; +} + +alert { + color: #aa0000; + font-weight: bold; +} + +note { + color: #aaaa00; + font-weight: bold; +} + +block-quote { + text-align: center; + font-style: italic; +} + +input { +} + +user1 { +} + +user2 { +} -- 2.30.2