Reset Glk style hints at beginning of program
[projects/chimara/chimara.git] / libchimara / style.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <math.h>
4 #include "chimara-glk-private.h"
5 #include "glk.h"
6 #include "style.h"
7 #include "magic.h"
8 #include "stream.h"
9 #include "strio.h"
10
11 extern GPrivate glk_data_key;
12
13 static gboolean style_accept(GScanner *scanner, GTokenType token);
14 static gboolean style_accept_style_selector(GScanner *scanner, ChimaraGlk *glk);
15 static gboolean style_accept_style_hint(GScanner *scanner, GtkTextTag *current_tag);
16 static void style_copy_tag_to_textbuffer(gpointer key, gpointer tag, gpointer target_table);
17 static void text_tag_to_attr_list(GtkTextTag *tag, PangoAttrList *list);
18 GtkTextTag* gtk_text_tag_copy(GtkTextTag *tag);
19 static void style_cascade_colors(GtkTextTag *tag, GtkTextTag *glk_tag, GtkTextTag *default_tag, GdkColor **foreground, GdkColor **background);
20
21 /**
22  * glk_set_style:
23  * @styl: The style to apply
24  *
25  * Changes the style of the current output stream. @styl should be one of the
26  * <code>style_</code> constants. However, any value is actually legal; if the 
27  * interpreter does not recognize the style value, it will treat it as 
28  * %style_Normal.
29  * <note><para>
30  *  This policy allows for the future definition of styles without breaking old
31  *  Glk libraries.
32  * </para></note>
33  */
34 void
35 glk_set_style(glui32 styl)
36 {
37         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
38         g_return_if_fail(glk_data->current_stream != NULL);
39         glk_set_style_stream(glk_data->current_stream, styl);
40 }
41
42 /* The first 11 tag names must correspond to the first 11 glk tag names as defined below */
43 static const gchar* TAG_NAMES[] = {
44         "normal",
45         "emphasized",
46         "preformatted",
47         "header",
48         "subheader",
49         "alert",
50         "note",
51         "block-quote",
52         "input",
53         "user1",
54         "user2",
55         "hyperlink",
56         "pager",
57         "default"
58 };
59
60 /* The first 11 glk tag names must correspond to the first 11 tag names as defined above */
61 static const gchar* GLK_TAG_NAMES[] = {
62         "glk-normal",
63         "glk-emphasized",
64         "glk-preformatted",
65         "glk-header",
66         "glk-subheader",
67         "glk-alert",
68         "glk-note",
69         "glk-block-quote",
70         "glk-input",
71         "glk-user1",
72         "glk-user2"
73 };
74
75 const gchar**
76 style_get_tag_names()
77 {
78         return TAG_NAMES;
79 }
80
81 /* Internal function: mapping from style enum to tag name */
82 static const gchar*
83 get_tag_name(glui32 style)
84 {
85         if(style >= CHIMARA_NUM_STYLES) {
86                 WARNING("Unsupported style");
87                 return "normal";
88         } else {
89                 return (gchar*) TAG_NAMES[style];
90         }
91 }
92
93 /* Internal function: mapping from glk style enum to tag name */
94 static const gchar*
95 get_glk_tag_name(glui32 style)
96 {
97         if(style >= style_NUMSTYLES) {
98                 WARNING("Unsupported style");
99                 return "normal";
100         } else {
101                 return (gchar*) GLK_TAG_NAMES[style];
102         }
103 }
104
105 /** 
106  * glk_set_style_stream:
107  * @str: Output stream to change the style of
108  * @styl: The style to apply
109  *
110  * This changes the style of the stream @str. See glk_set_style().
111  */
112 void
113 glk_set_style_stream(strid_t str, glui32 styl) {
114 #ifdef DEBUG_STYLES
115         g_printf("glk_set_style(str->rock=%d, styl=%d)\n", str->rock, styl);
116 #endif
117
118         if(str->window == NULL)
119                 return;
120
121         flush_window_buffer(str->window);
122         str->style = (gchar*) get_tag_name(styl);
123         str->glk_style = (gchar*) get_glk_tag_name(styl);
124 }
125
126 /* Internal function: call this to initialize the layout of the 'more' prompt. */
127 void
128 style_init_more_prompt(winid_t win)
129 {
130         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
131
132         win->pager_layout = gtk_widget_create_pango_layout(win->widget, "More");
133         pango_layout_set_attributes(win->pager_layout, glk_data->pager_attr_list);
134 }
135
136 /* Internal function: call this to initialize the default styles to a textbuffer. */
137 void
138 style_init_textbuffer(GtkTextBuffer *buffer)
139 {
140         g_return_if_fail(buffer != NULL);
141
142         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
143
144         /* Place the default text tags in the textbuffer's tag table */
145         g_hash_table_foreach(glk_data->styles->text_buffer, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
146
147         /* Copy the override text tags to the textbuffers's tag table */
148         g_hash_table_foreach(glk_data->glk_styles->text_buffer, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
149
150         /* Assign the 'default' tag the lowest priority */
151         gtk_text_tag_set_priority( gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "default"), 0 );
152 }
153
154
155 /* Internal function: call this to initialize the default styles to a textgrid. */
156 void
157 style_init_textgrid(GtkTextBuffer *buffer)
158 {
159         g_return_if_fail(buffer != NULL);
160
161         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
162
163         /* Place the default text tags in the textbuffer's tag table */
164         g_hash_table_foreach(glk_data->styles->text_grid, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
165
166         /* Copy the current text tags to the textbuffers's tag table */
167         g_hash_table_foreach(glk_data->glk_styles->text_grid, style_copy_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
168
169         /* Assign the 'default' tag the lowest priority */
170         gtk_text_tag_set_priority( gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), "default"), 0 );
171 }
172
173 /* Internal function used to iterate over a style table, copying it */
174 static void
175 style_copy_tag_to_textbuffer(gpointer key, gpointer tag, gpointer target_table)
176 {
177         g_return_if_fail(key != NULL);
178         g_return_if_fail(tag != NULL);
179         g_return_if_fail(target_table != NULL);
180
181         gtk_text_tag_table_add(target_table, gtk_text_tag_copy( GTK_TEXT_TAG(tag) ));
182 }
183
184 /* Internal function that copies a text tag */
185 GtkTextTag *
186 gtk_text_tag_copy(GtkTextTag *tag)
187 {
188         GtkTextTag *copy;
189         char *tag_name;
190         GParamSpec **properties;
191         unsigned nprops, count;
192
193         g_return_val_if_fail(tag != NULL, NULL);
194
195         g_object_get(tag, "name", &tag_name, NULL);
196         copy = gtk_text_tag_new(tag_name);
197         g_free(tag_name);
198
199         /* Copy all the original tag's properties to the new tag */
200         properties = g_object_class_list_properties( G_OBJECT_GET_CLASS(tag), &nprops );
201         for(count = 0; count < nprops; count++) {
202
203                 /* Only copy properties that are readable, writable, not construct-only,
204                 and not deprecated */
205                 GParamFlags flags = properties[count]->flags;
206                 if(flags & G_PARAM_CONSTRUCT_ONLY
207                         || flags & G_PARAM_DEPRECATED
208                         || !(flags & G_PARAM_READABLE)
209                         || !(flags & G_PARAM_WRITABLE))
210                         continue;
211
212                 const char *prop_name = g_param_spec_get_name(properties[count]);
213                 GValue prop_value = G_VALUE_INIT;
214
215                 g_value_init( &prop_value, G_PARAM_SPEC_VALUE_TYPE(properties[count]) );
216                 g_object_get_property( G_OBJECT(tag), prop_name, &prop_value );
217                 /* Don't copy the PangoTabArray if it is NULL, that prints a warning */
218                 if(strcmp(prop_name, "tabs") == 0 && g_value_get_boxed(&prop_value) == NULL) {
219                         g_value_unset(&prop_value);
220                         continue;
221                 }
222                 g_object_set_property( G_OBJECT(copy), prop_name, &prop_value );
223                 g_value_unset(&prop_value);
224         }
225         g_free(properties);
226
227         /* Copy the data that was added manually */
228         gpointer reverse_color = g_object_get_data( G_OBJECT(tag), "reverse-color" );
229
230         if(reverse_color) {
231                 g_object_set_data( G_OBJECT(copy), "reverse-color", reverse_color );
232         }
233
234         return copy;
235 }
236
237 /* Internal function that constructs the default styles */
238 void
239 style_init(ChimaraGlk *glk)
240 {
241         CHIMARA_GLK_USE_PRIVATE(glk, priv);
242         
243         GHashTable *default_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
244         GHashTable *default_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
245         GtkTextTag *tag;
246
247         /* Initialise the default styles for a text grid */
248         tag = gtk_text_tag_new("default");
249         g_object_set(tag, "family", "Monospace", "family-set", TRUE, NULL);
250         g_hash_table_insert(default_text_grid_styles, "default", tag);
251
252         tag = gtk_text_tag_new("normal");
253         g_hash_table_insert(default_text_grid_styles, "normal", tag);
254
255         tag = gtk_text_tag_new("emphasized");
256         g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
257         g_hash_table_insert(default_text_grid_styles, "emphasized", tag);
258
259         tag = gtk_text_tag_new("preformatted");
260         g_hash_table_insert(default_text_grid_styles, "preformatted", tag);
261
262         tag = gtk_text_tag_new("header");
263         g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
264         g_hash_table_insert(default_text_grid_styles, "header", tag);
265
266         tag = gtk_text_tag_new("subheader");
267         g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
268         g_hash_table_insert(default_text_grid_styles, "subheader", tag);
269
270         tag = gtk_text_tag_new("alert");
271         g_object_set(tag, "foreground", "#aa0000", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
272         g_hash_table_insert(default_text_grid_styles, "alert", tag);
273
274         tag = gtk_text_tag_new("note");
275         g_object_set(tag, "foreground", "#aaaa00", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
276         g_hash_table_insert(default_text_grid_styles, "note", tag);
277
278         tag = gtk_text_tag_new("block-quote");
279         g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
280         g_hash_table_insert(default_text_grid_styles, "block-quote", tag);
281
282         tag = gtk_text_tag_new("input");
283         g_hash_table_insert(default_text_grid_styles, "input", tag);
284
285         tag = gtk_text_tag_new("user1");
286         g_hash_table_insert(default_text_grid_styles, "user1", tag);
287
288         tag = gtk_text_tag_new("user2");
289         g_hash_table_insert(default_text_grid_styles, "user2", tag);
290
291         tag = gtk_text_tag_new("hyperlink");
292         g_object_set(tag, "foreground", "#0000ff", "foreground-set", TRUE, "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
293         g_hash_table_insert(default_text_grid_styles, "hyperlink", tag);
294
295         /* Initialise the default styles for a text buffer */
296         tag = gtk_text_tag_new("default");
297         g_object_set(tag, "family", "Serif", "family-set", TRUE, NULL);
298         g_hash_table_insert(default_text_buffer_styles, "default", tag);
299
300         tag = gtk_text_tag_new("normal");
301         g_hash_table_insert(default_text_buffer_styles, "normal", tag);
302
303         tag = gtk_text_tag_new("emphasized");
304         g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
305         g_hash_table_insert(default_text_buffer_styles, "emphasized", tag);
306
307         tag = gtk_text_tag_new("preformatted");
308         g_object_set(tag, "family", "Monospace", "family-set", TRUE, NULL);
309         g_hash_table_insert(default_text_buffer_styles, "preformatted", tag);
310
311         tag = gtk_text_tag_new("header");
312         g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
313         g_hash_table_insert(default_text_buffer_styles, "header", tag);
314
315         tag = gtk_text_tag_new("subheader");
316         g_object_set(tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
317         g_hash_table_insert(default_text_buffer_styles, "subheader", tag);
318
319         tag = gtk_text_tag_new("alert");
320         g_object_set(tag, "foreground", "#aa0000", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
321         g_hash_table_insert(default_text_buffer_styles, "alert", tag);
322
323         tag = gtk_text_tag_new("note");
324         g_object_set(tag, "foreground", "#aaaa00", "foreground-set", TRUE, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
325         g_hash_table_insert(default_text_buffer_styles, "note", tag);
326
327         tag = gtk_text_tag_new("block-quote");
328         g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "justification-set", TRUE, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
329         g_hash_table_insert(default_text_buffer_styles, "block-quote", tag);
330
331         tag = gtk_text_tag_new("input");
332         g_hash_table_insert(default_text_buffer_styles, "input", tag);
333
334         tag = gtk_text_tag_new("user1");
335         g_hash_table_insert(default_text_buffer_styles, "user1", tag);
336
337         tag = gtk_text_tag_new("user2");
338         g_hash_table_insert(default_text_buffer_styles, "user2", tag);
339
340         tag = gtk_text_tag_new("hyperlink");
341         g_object_set(tag, "foreground", "#0000ff", "foreground-set", TRUE, "underline", PANGO_UNDERLINE_SINGLE, "underline-set", TRUE, NULL);
342         g_hash_table_insert(default_text_buffer_styles, "hyperlink", tag);
343
344         GtkTextTag *pager_tag = gtk_text_tag_new("pager");
345         g_object_set(pager_tag, "family", "Monospace", "family-set", TRUE, "foreground", "#ffffff", "foreground-set", TRUE, "background", "#000000", "background-set", TRUE, NULL);
346         g_hash_table_insert(default_text_buffer_styles, "pager", pager_tag);
347         text_tag_to_attr_list(pager_tag, priv->pager_attr_list);
348
349         priv->styles->text_grid = default_text_grid_styles;
350         priv->styles->text_buffer = default_text_buffer_styles;
351
352         style_reset_glk(glk);
353 }
354
355 /* Reset the style hints set from the Glk program to be blank. Call this when
356 starting a new game so that style hints from the previous game don't carry
357 over. */
358 void
359 style_reset_glk(ChimaraGlk *glk)
360 {
361         CHIMARA_GLK_USE_PRIVATE(glk, priv);
362
363         GHashTable *glk_text_grid_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
364         GHashTable *glk_text_buffer_styles = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
365         GtkTextTag *tag;
366
367         /* Initialize the GLK styles to empty tags */
368         int i;
369         for(i=0; i<style_NUMSTYLES; i++) {
370                 tag = gtk_text_tag_new(GLK_TAG_NAMES[i]);
371                 g_hash_table_insert(glk_text_grid_styles, (gchar*) GLK_TAG_NAMES[i], tag);
372                 tag = gtk_text_tag_new(GLK_TAG_NAMES[i]);
373                 g_hash_table_insert(glk_text_buffer_styles, (gchar*) GLK_TAG_NAMES[i], tag);
374         }
375
376         priv->glk_styles->text_grid = glk_text_grid_styles;
377         priv->glk_styles->text_buffer = glk_text_buffer_styles;
378
379 }
380
381 /* Reset style tables to the library's internal defaults */
382 void
383 reset_default_styles(ChimaraGlk *glk)
384 {
385         /* TODO: write this function */
386 }
387
388 /* Create the CSS file scanner */
389 GScanner *
390 create_css_file_scanner(void)
391 {
392         GScanner *scanner = g_scanner_new(NULL);
393         scanner->config->cset_identifier_first = G_CSET_a_2_z G_CSET_A_2_Z "#";
394         scanner->config->cset_identifier_nth = G_CSET_a_2_z G_CSET_A_2_Z "-_" G_CSET_DIGITS;
395         scanner->config->symbol_2_token = TRUE;
396         scanner->config->cpair_comment_single = NULL;
397         scanner->config->scan_float = FALSE;
398         return scanner;
399 }
400
401 /* Run the scanner over the CSS file, overriding the default styles */
402 void
403 scan_css_file(GScanner *scanner, ChimaraGlk *glk)
404 {
405         while( g_scanner_peek_next_token(scanner) != G_TOKEN_EOF) {
406                 if( !style_accept_style_selector(scanner, glk) )
407                         break;
408         }
409
410         g_scanner_destroy(scanner);
411
412         /* Update the pager prompt to the new style */
413         style_update(glk);
414 }
415
416 /* Internal function: parses a token */
417 static gboolean
418 style_accept(GScanner *scanner, GTokenType token)
419 {
420         GTokenType next = g_scanner_get_next_token(scanner);
421         if(next != token) {
422                 g_scanner_unexp_token(scanner, token, NULL, NULL, NULL, "CSS Error", 1);
423                 return FALSE;
424         }
425         return TRUE;
426 }
427
428 /* Internal function: parses a style selector */
429 static gboolean
430 style_accept_style_selector(GScanner *scanner, ChimaraGlk *glk)
431 {
432         CHIMARA_GLK_USE_PRIVATE(glk, priv);
433
434         GtkTextTag *current_tag;
435         gchar *field;
436         GTokenType token = g_scanner_get_next_token(scanner);
437         GTokenValue value = g_scanner_cur_value(scanner);
438
439         if(
440                 token != G_TOKEN_IDENTIFIER ||
441                 ( strcmp(value.v_identifier, "buffer") && strcmp(value.v_identifier, "grid") )
442         ) {
443                 g_scanner_error(scanner, "CSS Error: buffer/grid expected");
444                 return FALSE;
445         }
446
447         field = g_strdup(value.v_identifier);
448
449         /* Parse the tag name to change */
450         if( g_scanner_peek_next_token(scanner) == '{') {
451                 style_accept(scanner, '{');
452                 if( !strcmp(field, "buffer") )
453                         current_tag = g_hash_table_lookup(priv->styles->text_buffer, "default");
454                 else
455                         current_tag = g_hash_table_lookup(priv->styles->text_grid, "default");
456         } else {
457                 if( !style_accept(scanner, '.') )
458                         return FALSE;
459
460                 token = g_scanner_get_next_token(scanner);
461                 value = g_scanner_cur_value(scanner);
462
463                 if(token != G_TOKEN_IDENTIFIER) {
464                         g_scanner_error(scanner, "CSS Error: style selector expected");
465                         return FALSE;
466                 }
467
468                 if( !strcmp(field, "buffer") )
469                         current_tag = g_hash_table_lookup(priv->styles->text_buffer, value.v_identifier);
470                 else
471                         current_tag = g_hash_table_lookup(priv->styles->text_grid, value.v_identifier);
472
473                 if(current_tag == NULL) {
474                         g_scanner_error(scanner, "CSS Error: invalid style identifier");
475                         return FALSE;
476                 }
477
478                 if( !style_accept(scanner, '{') )
479                         return FALSE;
480         }
481
482         while( g_scanner_peek_next_token(scanner) != '}') {
483                 if( !style_accept_style_hint(scanner, current_tag) )
484                         return FALSE;
485         }
486                 
487         if( !style_accept(scanner, '}') )
488                 return FALSE;
489
490         return TRUE;
491 }
492
493 /* Internal function: parses a style hint */
494 static gboolean
495 style_accept_style_hint(GScanner *scanner, GtkTextTag *current_tag)
496 {
497         GTokenType token = g_scanner_get_next_token(scanner);
498         GTokenValue value = g_scanner_cur_value(scanner);
499         gchar *hint;
500
501         if(token != G_TOKEN_IDENTIFIER) {
502                 g_scanner_error(scanner, "CSS Error: style hint expected");
503                 return FALSE;
504         }
505
506         hint = g_strdup(value.v_identifier);
507
508         if( !style_accept(scanner, ':') )
509                 return FALSE;
510
511         token = g_scanner_get_next_token(scanner);
512         value = g_scanner_cur_value(scanner);
513
514         if( !strcmp(hint, "font-family") ) {
515                 if(token != G_TOKEN_STRING) {
516                         g_scanner_error(scanner, "CSS Error: string expected");
517                         return FALSE;
518                 }
519                 g_object_set(current_tag, "family", value.v_string, "family-set", TRUE, NULL);
520         }
521         else if( !strcmp(hint, "font-weight") ) {
522                 if(token != G_TOKEN_IDENTIFIER) {
523                         g_scanner_error(scanner, "CSS Error: bold/normal expected");
524                         return FALSE;
525                 }
526
527                 if( !strcmp(value.v_identifier, "bold") )
528                         g_object_set(current_tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
529                 else if( !strcmp(value.v_identifier, "normal") )
530                         g_object_set(current_tag, "weight", PANGO_WEIGHT_NORMAL, "weight-set", TRUE, NULL);
531                 else {
532                         g_scanner_error(scanner, "CSS Error: bold/normal expected");
533                         return FALSE;
534                 }
535         }
536         else if( !strcmp(hint, "font-style") ) {
537                 if(token != G_TOKEN_IDENTIFIER) {
538                         g_scanner_error(scanner, "CSS Error: italic/normal expected");
539                         return FALSE;
540                 }
541
542                 if( !strcmp(value.v_identifier, "italic") )
543                         g_object_set(current_tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
544                 else if( !strcmp(value.v_identifier, "normal") )
545                         g_object_set(current_tag, "style", PANGO_STYLE_NORMAL, "style-set", TRUE, NULL);
546                 else {
547                         g_scanner_error(scanner, "CSS Error: italic/normal expected");
548                         return FALSE;
549                 }
550         }
551         else if( !strcmp(hint, "font-size") ) {
552                 if(token == G_TOKEN_INT) 
553                         g_object_set(current_tag, "size-points", (float)value.v_int, "size-set", TRUE, NULL);
554                 else if(token == G_TOKEN_FLOAT)
555                         g_object_set(current_tag, "size-points", value.v_float, "size-set", TRUE, NULL);
556                 else {
557                         g_scanner_error(scanner, "CSS Error: integer or float expected");
558                         return FALSE;
559                 }
560         }
561         else if( !strcmp(hint, "color") ) {
562                 if(token != G_TOKEN_IDENTIFIER) {
563                         g_scanner_error(scanner, "CSS Error: hex color expected");
564                         return FALSE;
565                 }
566
567                 g_object_set(current_tag, "foreground", value.v_identifier, "foreground-set", TRUE, NULL);
568         }
569         else if( !strcmp(hint, "background-color") ) {
570                 if(token != G_TOKEN_IDENTIFIER) {
571                         g_scanner_error(scanner, "CSS Error: hex color expected");
572                         return FALSE;
573                 }
574                 g_object_set(current_tag, "background", value.v_identifier, "background-set", TRUE, NULL);
575         }
576         else if( !strcmp(hint, "text-align") ) {
577                 if(token != G_TOKEN_IDENTIFIER) {
578                         g_scanner_error(scanner, "CSS Error: left/right/center expected");
579                         return FALSE;
580                 }
581                 
582                 if( !strcmp(value.v_identifier, "left") )
583                         g_object_set(current_tag, "justification", GTK_JUSTIFY_LEFT, "justification-set", TRUE, NULL);
584                 else if( !strcmp(value.v_identifier, "right") )
585                         g_object_set(current_tag, "justification", GTK_JUSTIFY_RIGHT, "justification-set", TRUE, NULL);
586                 else if( !strcmp(value.v_identifier, "center") )
587                         g_object_set(current_tag, "justification", GTK_JUSTIFY_CENTER, "justification-set", TRUE, NULL);
588                 else {
589                         g_scanner_error(scanner, "CSS Error: left/right/center expected");
590                         return FALSE;
591                 }
592         }
593         else if( !strcmp(hint, "margin-left") ) {
594                 if(token != G_TOKEN_INT) {
595                         g_scanner_error(scanner, "CSS Error: integer expected");
596                         return FALSE;
597                 }
598                 g_object_set(current_tag, "left-margin", value.v_int, "left-margin-set", TRUE, NULL);
599         }
600         else if( !strcmp(hint, "margin-right") ) {
601                 if(token != G_TOKEN_INT) {
602                         g_scanner_error(scanner, "CSS Error: integer expected");
603                         return FALSE;
604                 }
605                 g_object_set(current_tag, "right-margin", value.v_int, "right-margin-set", TRUE, NULL);
606         }
607         else if( !strcmp(hint, "margin-top") ) {
608                 if(token != G_TOKEN_INT) {
609                         g_scanner_error(scanner, "CSS Error: integer expected");
610                         return FALSE;
611                 }
612                 g_object_set(current_tag, "pixels-above-lines", value.v_int, "pixels-above-lines-set", TRUE, NULL);
613         }
614         else if( !strcmp(hint, "margin-bottom") ) {
615                 if(token != G_TOKEN_INT) {
616                         g_scanner_error(scanner, "CSS Error: integer expected");
617                         return FALSE;
618                 }
619                 g_object_set(current_tag, "pixels-below-lines", value.v_int, "pixels-below-lines-set", TRUE, NULL);
620         }
621                 
622         else {
623                 g_scanner_error(scanner, "CSS Error: invalid style hint %s", hint);
624                 return FALSE;
625         }
626
627         if( !style_accept(scanner, ';') )
628                 return FALSE;
629
630         return TRUE;
631 }
632
633 /* Internal function: parses a glk color to a GdkColor */
634 void
635 glkcolor_to_gdkcolor(glui32 val, GdkColor *color)
636 {
637         color->red = 256 * ((val & 0xff0000) >> 16);
638         color->green = 256 * ((val & 0x00ff00) >> 8);
639         color->blue = 256 * (val & 0x0000ff);
640 }
641
642 /* Internal function: parses a glk color to a hex string */
643 gchar*
644 glkcolor_to_hex(glui32 val)
645 {
646         return g_strdup_printf("%04X%04X%04X",
647                 256 * ((val & 0xff0000) >> 16),
648                 256 * ((val & 0x00ff00) >> 8),
649                 256 * (val & 0x0000ff)
650         );
651 }
652
653 /* Internal function: parses a gdk color to a hex string */
654 gchar*
655 gdkcolor_to_hex(GdkColor *color)
656 {
657         return g_strdup_printf("%04X%04X%04X",
658                 color->red, color->green, color->blue
659         );
660 }
661
662 /* Internal function: parses a GdkColor to a glk color */
663 static glui32
664 gdkcolor_to_glkcolor(GdkColor *color)
665 {
666         g_return_val_if_fail(color != NULL, 0);
667         return (glui32) color->pixel;
668 }
669
670 /* Internal function: changes a GTK tag to correspond with the given style. */
671 static void
672 apply_stylehint_to_tag(GtkTextTag *tag, glui32 wintype, glui32 styl, glui32 hint, glsi32 val)
673 {
674         g_return_if_fail(tag != NULL);
675
676         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
677         GObject *tag_object = G_OBJECT(tag);
678
679         gint reverse_color = GPOINTER_TO_INT( g_object_get_data(tag_object, "reverse-color") );
680
681         int i = 0;
682         GdkColor color;
683         switch(hint) {
684         case stylehint_Indentation:
685                 g_object_set(tag_object, "left-margin", 5*val, "left-margin-set", TRUE, NULL);
686                 g_object_set(tag_object, "right-margin", 5*val, "right-margin-set", TRUE, NULL);
687                 break;
688         
689         case stylehint_ParaIndentation:
690                 g_object_set(tag_object, "indent", 5*val, "indent-set", TRUE, NULL);
691                 break;
692
693         case stylehint_Justification:
694                 switch(val) {
695                         case stylehint_just_LeftFlush:  i = GTK_JUSTIFY_LEFT; break;
696                         case stylehint_just_LeftRight:  i = GTK_JUSTIFY_FILL; break;
697                         case stylehint_just_Centered:   i = GTK_JUSTIFY_CENTER; break;
698                         case stylehint_just_RightFlush: i = GTK_JUSTIFY_RIGHT; break;
699                         default: 
700                                 WARNING("Unknown justification");
701                                 i = GTK_JUSTIFY_LEFT;
702                 }
703                 g_object_set(tag_object, "justification", i, "justification-set", TRUE, NULL);
704                 break;
705
706         case stylehint_Weight:
707                 switch(val) {
708                         case -1: i = PANGO_WEIGHT_LIGHT; break;
709                         case  0: i = PANGO_WEIGHT_NORMAL; break;
710                         case  1: i = PANGO_WEIGHT_BOLD; break;
711                         default: WARNING("Unknown font weight");
712                 }
713                 g_object_set(tag_object, "weight", i, "weight-set", TRUE, NULL);
714                 break;
715
716         case stylehint_Size:
717                 {
718                         gdouble scale = PANGO_SCALE_MEDIUM;
719                         switch(val) {
720                                 case -3: scale = PANGO_SCALE_XX_SMALL; break;
721                                 case -2: scale = PANGO_SCALE_X_SMALL; break;
722                                 case -1: scale = PANGO_SCALE_SMALL; break;
723                                 case  0: scale = PANGO_SCALE_MEDIUM; break;
724                                 case  1: scale = PANGO_SCALE_LARGE; break;
725                                 case  2: scale = PANGO_SCALE_X_LARGE; break;
726                                 case  3: scale = PANGO_SCALE_XX_LARGE; break;
727                                 default:
728                                         /* We follow Pango's convention of having each magnification
729                                         step be a scaling of 1.2 */
730                                         scale = pow(1.2, (double)val);
731                         }
732                         g_object_set(tag_object, "scale", scale, "scale-set", TRUE, NULL);
733                 }
734                 break;
735
736         case stylehint_Oblique:
737                 g_object_set(tag_object, "style", val ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL, "style-set", TRUE, NULL);
738                 break;
739
740         case stylehint_Proportional:
741         {
742                 gchar *font_family;
743                 gboolean family_set;
744
745                 if(wintype != wintype_TextBuffer) {
746                         if(val)
747                                 WARNING("Style hint 'propotional' only supported on text buffers.");
748
749                         break;
750                 }
751
752                 GtkTextTag *font_tag = g_hash_table_lookup(glk_data->styles->text_buffer, val? "default" : "preformatted");
753                 g_object_get(font_tag, "family", &font_family, "family-set", &family_set, NULL);
754                 g_object_set(tag_object, "family", font_family, "family-set", family_set, NULL);
755                 g_free(font_family);
756         }
757                 break;
758
759         case stylehint_TextColor:
760                 glkcolor_to_gdkcolor(val, &color);
761
762                 if(!reverse_color)
763                         g_object_set(tag_object, "foreground-gdk", &color, "foreground-set", TRUE, NULL);
764                 else
765                         g_object_set(tag_object, "background-gdk", &color, "background-set", TRUE, NULL);
766
767                 break;
768
769         case stylehint_BackColor:
770                 glkcolor_to_gdkcolor(val, &color);
771
772                 if(!reverse_color)
773                         g_object_set(tag_object, "background-gdk", &color, "background-set", TRUE, NULL);
774                 else
775                         g_object_set(tag_object, "foreground-gdk", &color, "background-set", TRUE, NULL);
776
777                 break;
778
779         case stylehint_ReverseColor:
780         {
781                 // Determine the current colors
782                 
783                 // If all fails, use black/white
784                 // FIXME: Use system theme here
785                 GdkColor foreground, background;
786                 gdk_color_parse("black", &foreground);
787                 gdk_color_parse("white", &background);
788                 GdkColor *current_foreground = &foreground;
789                 GdkColor *current_background = &background;
790
791                 if(wintype == wintype_TextBuffer) {
792                         GtkTextTag* default_tag = g_hash_table_lookup(glk_data->styles->text_buffer, "default");
793                         GtkTextTag* base_tag = g_hash_table_lookup(glk_data->styles->text_buffer, get_tag_name(styl));
794                         style_cascade_colors(base_tag, tag, default_tag, &current_foreground, &current_background);
795                 }
796                 else if(wintype == wintype_TextGrid) {
797                         GtkTextTag* default_tag = g_hash_table_lookup(glk_data->styles->text_grid, "default");
798                         GtkTextTag* base_tag = g_hash_table_lookup(glk_data->styles->text_grid, get_tag_name(styl));
799                         style_cascade_colors(base_tag, tag, default_tag, &current_foreground, &current_background);
800                 }
801
802                 if(val) {
803                         /* Flip the fore- and background colors */
804                         GdkColor *temp = current_foreground;
805                         current_foreground = current_background;
806                         current_background = temp;
807                 }
808
809                 g_object_set(tag,
810                         "foreground-gdk", current_foreground,
811                         "foreground-set", TRUE,
812                         "background-gdk", current_background,
813                         "background-set", TRUE,
814                         NULL
815                 );
816
817                 g_object_set_data( tag_object, "reverse-color", GINT_TO_POINTER(val != 0) );
818                 break;
819         }
820
821         default:
822                 WARNING("Unknown style hint");
823         }
824 }
825
826 /* Internal function: queries a text tag for the value of a given style hint */
827 static gint
828 query_tag(GtkTextTag *tag, glui32 wintype, glui32 hint)
829 {
830         gint intval;
831         gdouble doubleval;
832         GdkColor *colval;
833
834         g_return_val_if_fail(tag != NULL, 0);
835
836         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
837
838         switch(hint) {
839         case stylehint_Indentation:
840                 g_object_get(tag, "left_margin", &intval, NULL);
841                 return intval/5;
842         
843         case stylehint_ParaIndentation:
844                 g_object_get(tag, "indent", &intval, NULL);
845                 return intval/5;
846
847         case stylehint_Justification:
848                 g_object_get(tag, "justification", &intval, NULL);
849                 switch(intval) {
850                         case GTK_JUSTIFY_LEFT: return stylehint_just_LeftFlush;
851                         case GTK_JUSTIFY_FILL: return stylehint_just_LeftRight;
852                         case GTK_JUSTIFY_CENTER: return stylehint_just_Centered;
853                         case GTK_JUSTIFY_RIGHT: return stylehint_just_RightFlush;
854                         default: 
855                                 WARNING("Unknown justification");
856                                 return stylehint_just_LeftFlush;
857                 }
858
859         case stylehint_Weight:
860                 g_object_get(tag, "weight", &intval, NULL);
861                 switch(intval) {
862                         case PANGO_WEIGHT_LIGHT: return -1;
863                         case PANGO_WEIGHT_NORMAL: return 0;
864                         case PANGO_WEIGHT_BOLD: return 1;
865                         default: WARNING("Unknown font weight"); return 0;
866                 }
867
868         case stylehint_Size:
869                 g_object_get(tag, "scale", &doubleval, NULL);
870                 return (gint)round(log(doubleval) / log(1.2));
871
872         case stylehint_Oblique:
873                 g_object_get(tag, "style", &intval , NULL);
874                 return intval == PANGO_STYLE_ITALIC ? 1 : 0;
875
876         case stylehint_Proportional:
877                 /* Use pango_font_family_is_monospace()? */
878         {
879                 gchar *font_family, *query_font_family;
880                 GtkTextTag *font_tag = g_hash_table_lookup(
881                     wintype == wintype_TextBuffer? glk_data->styles->text_buffer : glk_data->styles->text_grid,
882                     "preformatted");
883                 g_object_get(font_tag, "family", &font_family, NULL);
884                 g_object_get(tag, "family", &query_font_family, NULL);
885                 gint retval = strcmp(font_family, query_font_family)? 0 : 1;
886                 g_free(font_family);
887                 g_free(query_font_family);
888                 return retval;
889         }
890
891         case stylehint_TextColor:
892                 g_object_get(tag, "foreground-gdk", &colval, NULL);
893                 return gdkcolor_to_glkcolor(colval);
894
895         case stylehint_BackColor:
896                 g_object_get(tag, "background-gdk", &colval, NULL);
897                 return gdkcolor_to_glkcolor(colval);
898
899         case stylehint_ReverseColor:
900                 return GPOINTER_TO_INT( g_object_get_data(G_OBJECT(tag), "reverse_color") );
901
902         default:
903                 WARNING("Unknown style hint");
904         }
905         
906         return 0;
907 }
908
909 /**
910  * glk_stylehint_set:
911  * @wintype: The window type to set a style hint on, or %wintype_AllTypes.
912  * @styl: The style to set a hint for.
913  * @hint: The type of style hint, one of the <code>stylehint_</code> constants.
914  * @val: The style hint. The meaning of this depends on @hint.
915  *
916  * Sets a hint about the appearance of one style for a particular type of 
917  * window. You can also set @wintype to %wintype_AllTypes, which sets a hint for 
918  * all types of window.
919  * <note><para>
920  *  There is no equivalent constant to set a hint for all styles of a single 
921  *  window type.
922  * </para></note>
923  */
924 void
925 glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, glsi32 val)
926 {
927 #ifdef DEBUG_STYLES
928         g_printf("glk_stylehint_set(wintype=%d, styl=%d, hint=%d, val=%d)\n", wintype, styl, hint, val);
929 #endif
930
931         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
932
933         GtkTextTag *to_change;
934         if(wintype == wintype_TextBuffer || wintype == wintype_AllTypes) {
935                 to_change = g_hash_table_lookup( glk_data->glk_styles->text_buffer, get_glk_tag_name(styl) );
936                 apply_stylehint_to_tag(to_change, wintype_TextBuffer, styl, hint, val);
937         }
938
939         if(wintype == wintype_TextGrid || wintype == wintype_AllTypes) {
940                 to_change = g_hash_table_lookup( glk_data->glk_styles->text_grid, get_glk_tag_name(styl) );
941                 apply_stylehint_to_tag(to_change, wintype_TextGrid, styl, hint, val);
942         }
943 }
944
945 /**
946  * glk_stylehint_clear:
947  * @wintype: The window type to set a style hint on, or %wintype_AllTypes.
948  * @styl: The style to set a hint for.
949  * @hint: The type of style hint, one of the <code>stylehint_</code> constants.
950  *
951  * Clears a hint about the appearance of one style for a particular type of 
952  * window to its default value. You can also set @wintype to %wintype_AllTypes, 
953  * which clears a hint for all types of window.
954  * <note><para>
955  *  There is no equivalent constant to reset a hint for all styles of a single 
956  *  window type.
957  * </para></note>
958  */
959 void
960 glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint)
961 {
962 #ifdef DEBUG_STYLES
963         g_printf("glk_stylehint_clear(wintype=%d, styl=%d, hint=%d)\n", wintype, styl, hint);
964 #endif
965
966         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
967         GtkTextTag *tag;
968
969         switch(wintype) {
970         case wintype_TextBuffer:
971                 tag = g_hash_table_lookup( glk_data->glk_styles->text_buffer, get_glk_tag_name(styl) );
972                 if(tag) {
973                         glk_stylehint_set( wintype, styl, hint, query_tag(tag, wintype, hint) );
974                 }
975                 break;
976         case wintype_TextGrid:
977                 tag = g_hash_table_lookup( glk_data->glk_styles->text_grid, get_glk_tag_name(styl) );
978                 if(tag) {
979                         glk_stylehint_set( wintype, styl, hint, query_tag(tag, wintype, hint) );
980                 }
981         default:
982                 return;
983         }
984 }
985
986 /**
987  * glk_style_distinguish:
988  * @win: The window in which the styles are to be distinguished.
989  * @styl1: The first style to be distinguished from the second style.
990  * @styl2: The second style to be distinguished from the first style.
991  * 
992  * Decides whether two styles are visually distinguishable in the given window.
993  * The exact meaning of this is left for the library to determine.
994  * <note><title>Chimara</title><para>
995  *   Currently, all styles of one window are assumed to be mutually
996  *   distinguishable.
997  * </para></note>
998  * 
999  * Returns: %TRUE (1) if the two styles are visually distinguishable. If they 
1000  * are not, it returns %FALSE (0).
1001  */
1002 glui32
1003 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2)
1004 {
1005 #ifdef DEBUG_STYLES
1006         g_printf("glk_style_distinguish(win->rock=%d, styl1=%d, styl2=%d)\n", win->rock, styl1, styl2);
1007 #endif
1008
1009         /* FIXME */
1010         return styl1 != styl2;
1011 }
1012
1013 /**
1014  * glk_style_measure:
1015  * @win: The window from which to take the style.
1016  * @styl: The style to perform the measurement on.
1017  * @hint: The stylehint to measure.
1018  * @result: Address to write the result to.
1019  * 
1020  * Tries to test an attribute of one style in the given window @win. The library
1021  * may not be able to determine the attribute; if not, this returns %FALSE (0).
1022  * If it can, it returns %TRUE (1) and stores the value in the location pointed
1023  * at by @result. 
1024  * <note><para>
1025  *   As usual, it is legal for @result to be %NULL, although fairly pointless.
1026  * </para></note>
1027  *
1028  * The meaning of the value depends on the hint which was tested:
1029  * <variablelist>
1030  * <varlistentry>
1031  *   <term>%stylehint_Indentation, %stylehint_ParaIndentation</term>
1032  *   <listitem><para>The indentation and paragraph indentation. These are in a
1033  *   metric which is platform-dependent.</para>
1034  *   <note><para>Most likely either characters or pixels.</para></note>
1035  *   </listitem>
1036  * </varlistentry>
1037  * <varlistentry>
1038  *   <term>%stylehint_Justification</term>
1039  *   <listitem><para>One of the constants %stylehint_just_LeftFlush,
1040  *   %stylehint_just_LeftRight, %stylehint_just_Centered, or
1041  *   %stylehint_just_RightFlush.</para></listitem>
1042  * </varlistentry>
1043  * <varlistentry>
1044  *   <term>%stylehint_Size</term>
1045  *   <listitem><para>The font size. Again, this is in a platform-dependent
1046  *   metric.</para>
1047  *   <note><para>Pixels, points, or simply 1 if the library does not support
1048  *   varying font sizes.</para></note>
1049  *   </listitem>
1050  * </varlistentry>
1051  * <varlistentry>
1052  *   <term>%stylehint_Weight</term>
1053  *   <listitem><para>1 for heavy-weight fonts (boldface), 0 for normal weight,
1054  *   and -1 for light-weight fonts.</para></listitem>
1055  * </varlistentry>
1056  * <varlistentry>
1057  *   <term>%stylehint_Oblique</term>
1058  *   <listitem><para>1 for oblique fonts (italic), or 0 for normal angle.</para>
1059  *   </listitem>
1060  * </varlistentry>
1061  * <varlistentry>
1062  *   <term>%stylehint_Proportional</term>
1063  *   <listitem><para>1 for proportional-width fonts, or 0 for fixed-width.
1064  *   </para></listitem>
1065  * </varlistentry>
1066  * <varlistentry>
1067  *   <term>%stylehint_TextColor, %stylehint_BackColor</term>
1068  *   <listitem><para>These are values from 0x00000000 to 0x00FFFFFF, encoded as
1069  *   described in <link 
1070  *   linkend="chimara-Suggesting-the-Appearance-of-Styles">Suggesting the
1071  *   Appearance of Styles</link>.</para></listitem>
1072  * </varlistentry>
1073  * <varlistentry>
1074  *   <term>%stylehint_ReverseColor</term>
1075  *   <listitem><para>0 for normal printing, 1 if the foreground and background
1076  *   colors are reversed.</para></listitem>
1077  * </varlistentry>
1078  * </variablelist>
1079  * Signed values, such as the %stylehint_Weight value, are cast to
1080  * <type>glui32</type>. They may be cast to <type>glsi32</type> to be dealt with
1081  * in a more natural context.
1082  * 
1083  * Returns: TRUE upon successul retrieval, otherwise FALSE.
1084  */
1085 glui32
1086 glk_style_measure(winid_t win, glui32 styl, glui32 hint, glui32 *result)
1087 {
1088 #ifdef DEBUG_STYLES
1089         g_printf("glk_style_measure(win->rock=%d, styl=%d, hint=%d, result=...)\n", win->rock, styl, hint);
1090 #endif
1091
1092         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
1093         GtkTextTag *tag;
1094
1095         switch(win->type) {
1096         case wintype_TextBuffer:
1097                 tag = g_hash_table_lookup( glk_data->glk_styles->text_buffer, get_glk_tag_name(styl) );
1098                 if(result)
1099                         *result = query_tag(tag, win->type, hint);
1100                 break;
1101         case wintype_TextGrid:
1102                 tag = g_hash_table_lookup( glk_data->glk_styles->text_grid, get_glk_tag_name(styl) );
1103                 if(result)
1104                         *result = query_tag(tag, win->type, hint);
1105         default:
1106                 return FALSE;
1107         }
1108
1109         return TRUE;
1110 }
1111
1112 /* Internal function returning the current default font for a window type
1113  * This can be used later for size calculations. Only wintype_TextGrid and wintype_TextBuffer are
1114  * supported for now */
1115 PangoFontDescription *
1116 get_current_font(guint32 wintype)
1117 {
1118         ChimaraGlkPrivate *glk_data = g_private_get(&glk_data_key);
1119         GHashTable *styles, *glk_styles;
1120         PangoFontDescription *font;
1121
1122         switch(wintype) {
1123         case wintype_TextGrid:
1124                 styles = glk_data->styles->text_grid;
1125                 glk_styles = glk_data->glk_styles->text_grid;
1126                 font = pango_font_description_from_string("Monospace");
1127                 break;
1128         case wintype_TextBuffer:
1129                 styles = glk_data->styles->text_buffer;
1130                 glk_styles = glk_data->glk_styles->text_buffer;
1131                 font = pango_font_description_from_string("Serif");
1132                 break;
1133         default:
1134                 return NULL;
1135         }
1136
1137         PangoAttrList *list = pango_attr_list_new();
1138
1139         text_tag_to_attr_list( g_hash_table_lookup(styles, "default"), list );
1140         PangoAttrIterator *it = pango_attr_list_get_iterator(list);
1141         pango_attr_iterator_get_font(it, font, NULL, NULL);
1142         pango_attr_iterator_destroy(it);
1143
1144         text_tag_to_attr_list( g_hash_table_lookup(styles, "normal"), list );
1145         it = pango_attr_list_get_iterator(list);
1146         pango_attr_iterator_get_font(it, font, NULL, NULL);
1147         pango_attr_iterator_destroy(it);
1148
1149         text_tag_to_attr_list( g_hash_table_lookup(glk_styles, "glk-normal"), list );
1150         it = pango_attr_list_get_iterator(list);
1151         pango_attr_iterator_get_font(it, font, NULL, NULL);
1152         pango_attr_iterator_destroy(it);
1153
1154         /* Make a copy of the family, preventing it's destruction at the end of this function. */
1155         pango_font_description_set_family( font, pango_font_description_get_family(font) );
1156
1157         pango_attr_list_unref(list);
1158
1159         return font;
1160 }
1161
1162 /* Internal function copying the attributes of a text tag to a pango attribute list */
1163 static void
1164 text_tag_to_attr_list(GtkTextTag *tag, PangoAttrList *list)
1165 {
1166         gboolean set;
1167         GdkColor *foreground, *background;
1168         gchar *string;
1169         PangoFontDescription *font_desc;
1170         gboolean strikethrough;
1171         PangoUnderline underline;
1172
1173         g_object_get(tag, "foreground-set", &set, "foreground-gdk", &foreground, NULL);
1174         if(set) {
1175                 pango_attr_list_insert(
1176                         list,
1177                         pango_attr_foreground_new(foreground->red, foreground->green, foreground->blue)
1178                 );
1179         }
1180         g_object_get(tag, "background-set", &set, "background-gdk", &background, NULL);
1181         if(set) {
1182                 pango_attr_list_insert(
1183                         list,
1184                         pango_attr_background_new(background->red, background->green, background->blue)
1185                 );
1186         }
1187         g_object_get(tag, "language-set", &set, "language", &string, NULL);
1188         if(set) {
1189                 pango_attr_list_insert(
1190                         list,
1191                         pango_attr_language_new( pango_language_from_string(string) )
1192                 );
1193         }
1194
1195         /* Font description updates the following properties simultaniously:
1196          * family, style, weight, variant, stretch, size.
1197          * FIXME: Except it doesn't really.
1198          */
1199         g_object_get(tag, "font-desc", &font_desc, NULL);
1200         pango_attr_list_insert(
1201                 list,
1202                 pango_attr_font_desc_new(font_desc)
1203         );
1204
1205         g_object_get(tag, "strikethrough-set", &set, "strikethrough", &strikethrough, NULL);
1206         if(set) {
1207                 pango_attr_list_insert(
1208                         list,
1209                         pango_attr_strikethrough_new(strikethrough)
1210                 );
1211         }
1212         g_object_get(tag, "underline-set", &set, "underline", &underline, NULL);
1213         if(set) {
1214                 pango_attr_list_insert(
1215                         list,
1216                         pango_attr_underline_new(underline)
1217                 );
1218         }
1219 }
1220
1221 /* Update pager tag */
1222 void
1223 style_update(ChimaraGlk *glk)
1224 {
1225         CHIMARA_GLK_USE_PRIVATE(glk, priv);
1226
1227         GtkTextTag *pager_tag = GTK_TEXT_TAG( g_hash_table_lookup(priv->styles->text_buffer, "pager") );
1228         text_tag_to_attr_list(pager_tag, priv->pager_attr_list);
1229 }
1230
1231 /* Determine the current colors used to render the text for a given stream. 
1232  * This can be set in a number of places */
1233 static void
1234 style_cascade_colors(GtkTextTag *tag, GtkTextTag *glk_tag, GtkTextTag *default_tag, GdkColor **foreground, GdkColor **background)
1235 {
1236         gboolean foreground_set = FALSE;
1237         gboolean background_set = FALSE;
1238         gint reverse_color;
1239
1240         // Default color
1241         reverse_color = GPOINTER_TO_INT( g_object_get_data(G_OBJECT(default_tag), "reverse-color") );
1242         g_object_get(default_tag, "foreground-set", &foreground_set, "background-set", &background_set, NULL);
1243         if(foreground_set)
1244                 g_object_get(default_tag, "foreground-gdk", reverse_color ? background:foreground, NULL);
1245         if(background_set)
1246                 g_object_get(default_tag, "background-gdk", reverse_color ? foreground:background, NULL);
1247
1248         // Player defined color
1249         reverse_color = GPOINTER_TO_INT( g_object_get_data(G_OBJECT(tag), "reverse-color") );
1250         g_object_get(tag, "foreground-set", &foreground_set, "background-set", &background_set, NULL);
1251         if(foreground_set)
1252                 g_object_get(tag, "foreground-gdk", reverse_color ? background:foreground, NULL);
1253         if(background_set)
1254                 g_object_get(tag, "background-gdk", reverse_color ? foreground:background, NULL);
1255
1256         // GLK defined color
1257         reverse_color = GPOINTER_TO_INT( g_object_get_data(G_OBJECT(glk_tag), "reverse-color") );
1258         g_object_get(glk_tag, "foreground-set", &foreground_set, "background-set", &background_set, NULL);
1259         if(foreground_set)
1260                 g_object_get(glk_tag, "foreground-gdk", reverse_color ? background:foreground, NULL);
1261         if(background_set)
1262                 g_object_get(glk_tag, "background-gdk", reverse_color ? foreground:background, NULL);
1263
1264 }
1265
1266 /* Determine the current colors used to render the text for a given stream. 
1267  * This can be set in a number of places */
1268 void
1269 style_stream_colors(strid_t str, GdkColor **foreground, GdkColor **background)
1270 {
1271         VALID_STREAM(str, return);
1272         g_return_if_fail(str->window != NULL);
1273         g_return_if_fail(str->window->type != wintype_TextBuffer || str->window->type != wintype_TextGrid);
1274         
1275         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(str->window->widget) ); 
1276         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
1277         GtkTextTag* default_tag = gtk_text_tag_table_lookup(tags, "default");
1278         GtkTextTag* tag = gtk_text_tag_table_lookup(tags, str->style);
1279         GtkTextTag* glk_tag = gtk_text_tag_table_lookup(tags, str->glk_style);
1280
1281         style_cascade_colors(tag, glk_tag, default_tag, foreground, background);
1282
1283         gboolean foreground_set, background_set;
1284
1285         // Windows can have zcolors defined
1286         if(str->window->zcolor) {
1287                 g_object_get(str->window->zcolor, "foreground-set", &foreground_set, "background-set", &background_set, NULL);
1288                 if(foreground_set)
1289                         g_object_get(str->window->zcolor, "foreground-gdk", foreground, NULL);
1290                 if(background_set)
1291                         g_object_get(str->window->zcolor, "background-gdk", background, NULL);
1292         }
1293 }
1294
1295 /* Apply styles to a segment of text in a GtkTextBuffer, combining multiple
1296  * GtkTextTags.
1297  */
1298 void
1299 style_apply(winid_t win, GtkTextIter *start, GtkTextIter *end)
1300 {
1301         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
1302         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
1303
1304         GtkTextTag *default_tag = gtk_text_tag_table_lookup(tags, "default");
1305         GtkTextTag *style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->style);
1306         GtkTextTag *glk_style_tag = gtk_text_tag_table_lookup(tags, win->window_stream->glk_style);
1307
1308         // Player's style overrides
1309         gtk_text_buffer_apply_tag(buffer, style_tag, start, end);
1310
1311         // GLK Program's style overrides
1312         gtk_text_buffer_apply_tag(buffer, glk_style_tag, start, end);
1313
1314         // Default style
1315         gtk_text_buffer_apply_tag(buffer, default_tag, start, end);
1316
1317         // Link style overrides
1318         if(win->window_stream->hyperlink_mode) {
1319                 GtkTextTag *link_style_tag = gtk_text_tag_table_lookup(tags, "hyperlink");
1320                 GtkTextTag *link_tag = win->current_hyperlink->tag;
1321                 gtk_text_buffer_apply_tag(buffer, link_style_tag, start, end);
1322                 gtk_text_buffer_apply_tag(buffer, link_tag, start, end);
1323         }
1324
1325         // GLK Program's style overrides using garglk_set_zcolors()
1326         if(win->zcolor != NULL) {
1327                 gtk_text_buffer_apply_tag(buffer, win->zcolor, start, end);
1328         }
1329
1330         // GLK Program's style overrides using garglk_set_reversevideo()
1331         if(win->zcolor_reversed != NULL) {
1332                 gtk_text_buffer_apply_tag(buffer, win->zcolor_reversed, start, end);
1333         }
1334 }
1335