+ ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
+
+ /* Place the default text tags in the textbuffer's tag table */
+ g_hash_table_foreach(glk_data->styles->text_buffer, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
+
+ /* Copy the override text tags to the textbuffers's tag table */
+ g_hash_table_foreach(glk_data->glk_styles->text_buffer, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
+
+ /* Assign the 'default' tag the lowest priority */
+ gtk_text_tag_set_priority( gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "default"), 0 );
+}
+
+
+/* Internal function: call this to initialize the default styles to a textgrid. */
+void
+style_init_textgrid(GtkTextBuffer *buffer)
+{
+ g_return_if_fail(buffer != NULL);
+
+ ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
+
+ /* Place the default text tags in the textbuffer's tag table */
+ g_hash_table_foreach(glk_data->styles->text_grid, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
+
+ /* Copy the current text tags to the textbuffers's tag table */
+ g_hash_table_foreach(glk_data->glk_styles->text_grid, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
+
+ /* Assign the 'default' tag the lowest priority */
+ gtk_text_tag_set_priority( gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "default"), 0 );
+}
+
+/* Internal function used to iterate over a style table, copying it */
+static void
+style_copy_tag_to_textbuffer(gpointer key, gpointer tag, gpointer target_table)
+{
+ g_return_if_fail(key != NULL);
+ g_return_if_fail(tag != NULL);
+ g_return_if_fail(target_table != NULL);
+
+ gtk_text_tag_table_add(target_table, gtk_text_tag_copy( GTK_TEXT_TAG(tag) ));
+}
+
+/* Internal function that copies a text tag */
+GtkTextTag *
+gtk_text_tag_copy(GtkTextTag *tag)
+{
+ GtkTextTag *copy;
+ char *tag_name;
+ GParamSpec **properties;
+ unsigned nprops, count;
+
+ g_return_val_if_fail(tag != NULL, NULL);
+
+ g_object_get(tag, "name", &tag_name, NULL);
+ copy = gtk_text_tag_new(tag_name);
+ g_free(tag_name);
+
+ /* Copy all the original tag's properties to the new tag */
+ properties = g_object_class_list_properties( G_OBJECT_GET_CLASS(tag), &nprops );
+ for(count = 0; count < nprops; count++) {
+
+ /* Only copy properties that are readable, writable, not construct-only,
+ and not deprecated */
+ GParamFlags flags = properties[count]->flags;
+ if(flags & G_PARAM_CONSTRUCT_ONLY
+ || flags & G_PARAM_DEPRECATED
+ || !(flags & G_PARAM_READABLE)
+ || !(flags & G_PARAM_WRITABLE))
+ continue;
+
+ const char *prop_name = g_param_spec_get_name(properties[count]);
+ GValue prop_value = G_VALUE_INIT;
+
+ g_value_init( &prop_value, G_PARAM_SPEC_VALUE_TYPE(properties[count]) );
+ g_object_get_property( G_OBJECT(tag), prop_name, &prop_value );
+ /* Don't copy the PangoTabArray if it is NULL, that prints a warning */
+ if(strcmp(prop_name, "tabs") == 0 && g_value_get_boxed(&prop_value) == NULL) {
+ g_value_unset(&prop_value);
+ continue;
+ }
+ g_object_set_property( G_OBJECT(copy), prop_name, &prop_value );
+ g_value_unset(&prop_value);
+ }
+ g_free(properties);
+
+ /* Copy the data that was added manually */
+ gpointer reverse_color = g_object_get_data( G_OBJECT(tag), "reverse-color" );
+
+ if(reverse_color) {
+ g_object_set_data( G_OBJECT(copy), "reverse-color", reverse_color );
+ }
+
+ return copy;
+}
+
+/* Internal function that constructs the default styles */
+void
+style_init(ChimaraGlk *glk)
+{
+ CHIMARA_GLK_USE_PRIVATE(glk, priv);
+
+ GHashTable *default_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GHashTable *default_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GHashTable *glk_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GHashTable *glk_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
+ GtkTextTag *tag;
+
+ /* Initialise the default styles for a text grid */
+ tag = gtk_text_tag_new("default");
+ g_object_set(tag, "family", "Monospace", "family-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "default", tag);
+
+ tag = gtk_text_tag_new("normal");
+ g_hash_table_insert(default_text_grid_styles, "normal", tag);
+
+ tag = gtk_text_tag_new("emphasized");
+ g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "emphasized", tag);
+
+ tag = gtk_text_tag_new("preformatted");
+ g_hash_table_insert(default_text_grid_styles, "preformatted", tag);
+
+ tag = gtk_text_tag_new("header");
+ g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "header", tag);
+
+ tag = gtk_text_tag_new("subheader");
+ g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "subheader", tag);
+
+ tag = gtk_text_tag_new("alert");
+ g_object_set(tag, "foreground", "#aa0000", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "alert", tag);
+
+ tag = gtk_text_tag_new("note");
+ g_object_set(tag, "foreground", "#aaaa00", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "note", tag);
+
+ tag = gtk_text_tag_new("block-quote");
+ g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "block-quote", tag);
+
+ tag = gtk_text_tag_new("input");
+ g_hash_table_insert(default_text_grid_styles, "input", tag);
+
+ tag = gtk_text_tag_new("user1");
+ g_hash_table_insert(default_text_grid_styles, "user1", tag);
+
+ tag = gtk_text_tag_new("user2");
+ g_hash_table_insert(default_text_grid_styles, "user2", tag);
+
+ tag = gtk_text_tag_new("hyperlink");
+ g_object_set(tag, "foreground", "#0000ff", "foreground-set", TRUE, "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
+ g_hash_table_insert(default_text_grid_styles, "hyperlink", tag);
+
+ /* Initialise the default styles for a text buffer */
+ tag = gtk_text_tag_new("default");
+ g_object_set(tag, "family", "Serif", "family-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "default", tag);
+
+ tag = gtk_text_tag_new("normal");
+ g_hash_table_insert(default_text_buffer_styles, "normal", tag);
+
+ tag = gtk_text_tag_new("emphasized");
+ g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "emphasized", tag);
+
+ tag = gtk_text_tag_new("preformatted");
+ g_object_set(tag, "family", "Monospace", "family-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "preformatted", tag);
+
+ tag = gtk_text_tag_new("header");
+ g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "header", tag);
+
+ tag = gtk_text_tag_new("subheader");
+ g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "subheader", tag);
+
+ tag = gtk_text_tag_new("alert");
+ g_object_set(tag, "foreground", "#aa0000", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "alert", tag);
+
+ tag = gtk_text_tag_new("note");
+ g_object_set(tag, "foreground", "#aaaa00", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "note", tag);
+
+ tag = gtk_text_tag_new("block-quote");
+ g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "justification-set", TRUE, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "block-quote", tag);
+
+ tag = gtk_text_tag_new("input");
+ g_hash_table_insert(default_text_buffer_styles, "input", tag);
+
+ tag = gtk_text_tag_new("user1");
+ g_hash_table_insert(default_text_buffer_styles, "user1", tag);
+
+ tag = gtk_text_tag_new("user2");
+ g_hash_table_insert(default_text_buffer_styles, "user2", tag);
+
+ tag = gtk_text_tag_new("hyperlink");
+ g_object_set(tag, "foreground", "#0000ff", "foreground-set", TRUE, "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
+ g_hash_table_insert(default_text_buffer_styles, "hyperlink", tag);
+
+ priv->styles->text_grid = default_text_grid_styles;
+ priv->styles->text_buffer = default_text_buffer_styles;
+
+ /* Initialize the GLK styles to empty tags */
+ int i;
+ for(i=0; i<style_NUMSTYLES; i++) {
+ tag = gtk_text_tag_new(GLK_TAG_NAMES[i]);
+ g_hash_table_insert(glk_text_grid_styles, (gchar*) GLK_TAG_NAMES[i], tag);
+ tag = gtk_text_tag_new(GLK_TAG_NAMES[i]);
+ g_hash_table_insert(glk_text_buffer_styles, (gchar*) GLK_TAG_NAMES[i], tag);
+ }
+
+ priv->glk_styles->text_grid = glk_text_grid_styles;
+ priv->glk_styles->text_buffer = glk_text_buffer_styles;
+
+}
+
+/* Reset style tables to the library's internal defaults */
+void
+reset_default_styles(ChimaraGlk *glk)
+{
+ /* TODO: write this function */
+}
+
+/* Create the CSS file scanner */
+GScanner *
+create_css_file_scanner(void)
+{
+ GScanner *scanner = g_scanner_new(NULL);
+ scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "#";
+ scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z "-_" G_CSET_DIGITS;
+ scanner->config->symbol_2_token = TRUE;
+ scanner->config->cpair_comment_single = NULL;
+ scanner->config->scan_float = FALSE;
+ return scanner;
+}
+
+/* Run the scanner over the CSS file, overriding the default styles */
+void
+scan_css_file(GScanner *scanner, ChimaraGlk *glk)
+{
+ while( g_scanner_peek_next_token(scanner) != G_TOKEN_EOF) {
+ if( !style_accept_style_selector(scanner, glk) )
+ break;
+ }
+
+ g_scanner_destroy(scanner);
+}
+
+/* Internal function: parses a token */
+static gboolean
+style_accept(GScanner *scanner, GTokenType token)
+{
+ GTokenType next = g_scanner_get_next_token(scanner);
+ if(next != token) {
+ g_scanner_unexp_token(scanner, token, NULL, NULL, NULL, "CSS Error", 1);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* Internal function: parses a style selector */
+static gboolean
+style_accept_style_selector(GScanner *scanner, ChimaraGlk *glk)
+{
+ CHIMARA_GLK_USE_PRIVATE(glk, priv);
+
+ GtkTextTag *current_tag;
+ gchar *field;
+ GTokenType token = g_scanner_get_next_token(scanner);
+ GTokenValue value = g_scanner_cur_value(scanner);
+
+ if(
+ token != G_TOKEN_IDENTIFIER ||
+ ( strcmp(value.v_identifier, "buffer") && strcmp(value.v_identifier, "grid") )
+ ) {
+ g_scanner_error(scanner, "CSS Error: buffer/grid expected");
+ return FALSE;
+ }
+
+ field = g_strdup(value.v_identifier);
+
+ /* Parse the tag name to change */
+ if( g_scanner_peek_next_token(scanner) == '{') {
+ style_accept(scanner, '{');
+ if( !strcmp(field, "buffer") )
+ current_tag = g_hash_table_lookup(priv->styles->text_buffer, "default");
+ else
+ current_tag = g_hash_table_lookup(priv->styles->text_grid, "default");
+ } else {
+ if( !style_accept(scanner, '.') )
+ return FALSE;
+
+ token = g_scanner_get_next_token(scanner);
+ value = g_scanner_cur_value(scanner);
+
+ if(token != G_TOKEN_IDENTIFIER) {
+ g_scanner_error(scanner, "CSS Error: style selector expected");
+ return FALSE;
+ }
+
+ if( !strcmp(field, "buffer") )
+ current_tag = g_hash_table_lookup(priv->styles->text_buffer, value.v_identifier);
+ else
+ current_tag = g_hash_table_lookup(priv->styles->text_grid, 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;
+}
+
+/* Internal function: parses a style hint */
+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);
+
+ 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;
+
+ return TRUE;