Added the dispatch layer headers. In order to get this to compile, I had to add stubs...
[rodin/chimara.git] / libchimara / style.c
1 #include "style.h"
2 #include <stdio.h>
3 #include <fcntl.h>
4
5 extern GPrivate *glk_data_key;
6 static gboolean chimara_style_initialized = FALSE;
7
8 /**
9  * glk_set_style:
10  * @styl: The style to apply
11  *
12  * Changes the style of the current output stream. @styl should be one of the
13  * <code>style_</code> constants listed above. However, any value is actually
14  * legal; if the interpreter does not recognize the style value, it will treat
15  * it as %style_Normal.
16  * <note><para>
17  *  This policy allows for the future definition of styles without breaking old
18  *  Glk libraries.
19  * </para></note>
20  */
21 void
22 glk_set_style(glui32 styl)
23 {
24         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
25         g_return_if_fail(glk_data->current_stream != NULL);
26         glk_set_style_stream(glk_data->current_stream, styl);
27 }
28
29 /* Internal function: mapping from style enum to tag name */
30 static gchar *
31 get_tag_name(glui32 style)
32 {
33         switch(style) {
34                 case style_Normal: return "normal";
35                 case style_Emphasized: return "emphasized";
36                 case style_Preformatted: return "preformatted";
37                 case style_Header: return "header";
38                 case style_Subheader: return "subheader";
39                 case style_Alert: return "alert";
40                 case style_Note: return "note";
41                 case style_BlockQuote: return "block-quote";
42                 case style_Input: return "input";
43                 case style_User1: return "user1";
44                 case style_User2: return "user2";
45         }
46
47         WARNING("Unsupported style");
48         return "normal";
49 }
50
51 /** 
52  * glk_set_style_stream:
53  * @str: Output stream to change the style of
54  * @styl: The style to apply
55  *
56  * This changes the style of the stream @str. See glk_set_style().
57  */
58 void
59 glk_set_style_stream(strid_t str, glui32 styl) {
60         str->style = get_tag_name(styl);
61 }
62
63 /* Internal function: call this to initialize the default styles to a textbuffer. */
64 void
65 style_init_textbuffer(GtkTextBuffer *buffer)
66 {
67         g_return_if_fail(buffer != NULL);
68
69         if( G_UNLIKELY(!chimara_style_initialized) ) {
70                 style_init();
71         }
72
73         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
74         g_hash_table_foreach(glk_data->default_styles, style_add_tag_to_textbuffer, gtk_text_buffer_get_tag_table(buffer));
75 }
76
77 static void
78 style_add_tag_to_textbuffer(gpointer key, gpointer tag, gpointer tag_table)
79 {
80         gtk_text_tag_table_add(tag_table, tag);
81 }
82
83 void
84 style_init()
85 {
86         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
87         GtkTextTag *tag;
88
89         /* Create the CSS file scanner */
90         GScanner *scanner = g_scanner_new(NULL);
91         int f = open(glk_data->css_file, O_RDONLY);
92         g_return_if_fail(f != -1);
93         g_scanner_input_file(scanner, f);
94         scanner->input_name = glk_data->css_file;
95         scanner->config->cset_identifier_first = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#";
96         scanner->config->cset_identifier_nth = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_0123456789";
97         scanner->config->symbol_2_token = TRUE;
98         scanner->config->cpair_comment_single = NULL;
99
100         /* Initialise the default styles */
101         g_hash_table_insert(glk_data->default_styles, "normal", gtk_text_tag_new("normal"));
102
103         tag = gtk_text_tag_new("emphasized");
104         g_object_set(tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
105         g_hash_table_insert(glk_data->default_styles, "emphasized", tag);
106
107         tag = gtk_text_tag_new("preformatted");
108         g_object_set(tag, "font-desc", glk_data->monospace_font_desc, NULL);
109         g_hash_table_insert(glk_data->default_styles, "preformatted", tag);
110
111         tag = gtk_text_tag_new("header");
112         g_object_set(tag, "size-points", 18.0, "weight", PANGO_WEIGHT_BOLD, NULL);
113         g_hash_table_insert(glk_data->default_styles, "header", tag);
114
115         tag = gtk_text_tag_new("subheader");
116         g_object_set(tag, "size-points", 14.0, "weight", PANGO_WEIGHT_BOLD, NULL);
117         g_hash_table_insert(glk_data->default_styles, "subheader", tag);
118
119         tag = gtk_text_tag_new("alert");
120         g_object_set(tag, "foreground", "#aa0000", "weight", PANGO_WEIGHT_BOLD, NULL);
121         g_hash_table_insert(glk_data->default_styles, "alert", tag);
122
123         tag = gtk_text_tag_new("note");
124         g_object_set(tag, "foreground", "#aaaa00", "weight", PANGO_WEIGHT_BOLD, NULL);
125         g_hash_table_insert(glk_data->default_styles, "note", tag);
126
127         tag = gtk_text_tag_new("block-quote");
128         g_object_set(tag, "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, NULL);
129         g_hash_table_insert(glk_data->default_styles, "block-quote", tag);
130
131         g_hash_table_insert(glk_data->default_styles, "input", gtk_text_tag_new("input"));
132         g_hash_table_insert(glk_data->default_styles, "user1", gtk_text_tag_new("user1"));
133         g_hash_table_insert(glk_data->default_styles, "user2", gtk_text_tag_new("user2"));
134
135         /* Run the scanner over the CSS file */
136         while( g_scanner_peek_next_token(scanner) != G_TOKEN_EOF) {
137                 if( !style_accept_style_identifier(scanner) )
138                         break;
139         }
140
141         g_scanner_destroy(scanner);
142 }
143
144 static gboolean
145 style_accept(GScanner *scanner, GTokenType token)
146 {
147         if( g_scanner_get_next_token(scanner) != token ) {
148                 g_scanner_unexp_token(scanner, token, NULL, NULL, NULL, "CSS Error", 1);
149                 return FALSE;
150         } else {
151                 return TRUE;
152         }
153 }
154
155 static gboolean
156 style_accept_style_identifier(GScanner *scanner)
157 {
158         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
159
160         GtkTextTag *current_tag;
161         GTokenType token = g_scanner_get_next_token(scanner);
162         GTokenValue value = g_scanner_cur_value(scanner);
163
164         if(token != G_TOKEN_IDENTIFIER) {
165                 g_scanner_error(scanner, "CSS Error: style identifier expected");
166                 return FALSE;
167         }
168
169         printf("Identifier: %s\n", value.v_identifier);
170         current_tag = g_hash_table_lookup(glk_data->default_styles, value.v_identifier);
171
172         if(current_tag == NULL) {
173                 g_scanner_error(scanner, "CSS Error: invalid style identifier");
174                 return FALSE;
175         }
176
177         if( !style_accept(scanner, '{') )
178                 return FALSE;
179
180         while( g_scanner_peek_next_token(scanner) != '}') {
181                 if( !style_accept_style_hint(scanner, current_tag) )
182                         return FALSE;
183         }
184                 
185         if( !style_accept(scanner, '}') )
186                 return FALSE;
187
188         return TRUE;
189 }
190
191 static gboolean
192 style_accept_style_hint(GScanner *scanner, GtkTextTag *current_tag)
193 {
194         GTokenType token = g_scanner_get_next_token(scanner);
195         GTokenValue value = g_scanner_cur_value(scanner);
196         gchar *hint;
197
198         if(token != G_TOKEN_IDENTIFIER) {
199                 g_scanner_error(scanner, "CSS Error: style hint expected");
200                 return FALSE;
201         }
202
203         hint = g_strdup(value.v_identifier);
204         printf("Hint: %s\n", hint);
205
206         if( !style_accept(scanner, ':') )
207                 return FALSE;
208
209         token = g_scanner_get_next_token(scanner);
210         value = g_scanner_cur_value(scanner);
211
212         if( !strcmp(hint, "font-family") ) {
213                 if(token != G_TOKEN_STRING) {
214                         g_scanner_error(scanner, "CSS Error: string expected");
215                         return FALSE;
216                 }
217                 g_object_set(current_tag, "family", value.v_string, "family-set", TRUE, NULL);
218         }
219         else if( !strcmp(hint, "font-weight") ) {
220                 if(token != G_TOKEN_IDENTIFIER) {
221                         g_scanner_error(scanner, "CSS Error: bold/normal expected");
222                         return FALSE;
223                 }
224
225                 if( !strcmp(value.v_identifier, "bold") )
226                         g_object_set(current_tag, "weight", PANGO_WEIGHT_BOLD, "weight-set", TRUE, NULL);
227                 else if( !strcmp(value.v_identifier, "normal") )
228                         g_object_set(current_tag, "weight", PANGO_WEIGHT_NORMAL, "weight-set", TRUE, NULL);
229                 else {
230                         g_scanner_error(scanner, "CSS Error: bold/normal expected");
231                         return FALSE;
232                 }
233         }
234         else if( !strcmp(hint, "font-style") ) {
235                 if(token != G_TOKEN_IDENTIFIER) {
236                         g_scanner_error(scanner, "CSS Error: italic/normal expected");
237                         return FALSE;
238                 }
239
240                 if( !strcmp(value.v_identifier, "italic") )
241                         g_object_set(current_tag, "style", PANGO_STYLE_ITALIC, "style-set", TRUE, NULL);
242                 else if( !strcmp(value.v_identifier, "normal") )
243                         g_object_set(current_tag, "style", PANGO_STYLE_NORMAL, "style-set", TRUE, NULL);
244                 else {
245                         g_scanner_error(scanner, "CSS Error: italic/normal expected");
246                         return FALSE;
247                 }
248         }
249         else if( !strcmp(hint, "font-size") ) {
250                 if(token == G_TOKEN_INT) 
251                         g_object_set(current_tag, "size-points", (float)value.v_int, "size-set", TRUE, NULL);
252                 else if(token == G_TOKEN_FLOAT)
253                         g_object_set(current_tag, "size-points", value.v_float, "size-set", TRUE, NULL);
254                 else {
255                         g_scanner_error(scanner, "CSS Error: integer or float expected");
256                         return FALSE;
257                 }
258         }
259         else if( !strcmp(hint, "color") ) {
260                 if(token != G_TOKEN_IDENTIFIER) {
261                         g_scanner_error(scanner, "CSS Error: hex color expected");
262                         return FALSE;
263                 }
264                 g_object_set(current_tag, "foreground", value.v_identifier, "foreground-set", TRUE, NULL);
265         }
266         else if( !strcmp(hint, "background-color") ) {
267                 if(token != G_TOKEN_IDENTIFIER) {
268                         g_scanner_error(scanner, "CSS Error: hex color expected");
269                         return FALSE;
270                 }
271                 g_object_set(current_tag, "background", value.v_identifier, "background-set", TRUE, NULL);
272         }
273         else if( !strcmp(hint, "text-align") ) {
274                 if(token != G_TOKEN_IDENTIFIER) {
275                         g_scanner_error(scanner, "CSS Error: left/right/center expected");
276                         return FALSE;
277                 }
278                 
279                 if( !strcmp(value.v_identifier, "left") )
280                         g_object_set(current_tag, "justification", GTK_JUSTIFY_LEFT, "justification-set", TRUE, NULL);
281                 else if( !strcmp(value.v_identifier, "right") )
282                         g_object_set(current_tag, "justification", GTK_JUSTIFY_RIGHT, "justification-set", TRUE, NULL);
283                 else if( !strcmp(value.v_identifier, "center") )
284                         g_object_set(current_tag, "justification", GTK_JUSTIFY_CENTER, "justification-set", TRUE, NULL);
285                 else {
286                         g_scanner_error(scanner, "CSS Error: left/right/center expected");
287                         return FALSE;
288                 }
289         }
290         else if( !strcmp(hint, "margin-left") ) {
291                 if(token != G_TOKEN_INT) {
292                         g_scanner_error(scanner, "CSS Error: integer expected");
293                         return FALSE;
294                 }
295                 g_object_set(current_tag, "left-margin", value.v_int, "left-margin-set", TRUE, NULL);
296         }
297         else if( !strcmp(hint, "margin-right") ) {
298                 if(token != G_TOKEN_INT) {
299                         g_scanner_error(scanner, "CSS Error: integer expected");
300                         return FALSE;
301                 }
302                 g_object_set(current_tag, "right-margin", value.v_int, "right-margin-set", TRUE, NULL);
303         }
304         else if( !strcmp(hint, "margin-top") ) {
305                 if(token != G_TOKEN_INT) {
306                         g_scanner_error(scanner, "CSS Error: integer expected");
307                         return FALSE;
308                 }
309                 g_object_set(current_tag, "pixels-above-lines", value.v_int, "pixels-above-lines-set", TRUE, NULL);
310         }
311         else if( !strcmp(hint, "margin-bottom") ) {
312                 if(token != G_TOKEN_INT) {
313                         g_scanner_error(scanner, "CSS Error: integer expected");
314                         return FALSE;
315                 }
316                 g_object_set(current_tag, "pixels-below-lines", value.v_int, "pixels-below-lines-set", TRUE, NULL);
317         }
318                 
319         else {
320                 g_scanner_error(scanner, "CSS Error: invalid style hint %s", hint);
321                 return FALSE;
322         }
323
324         if( !style_accept(scanner, ';') )
325                 return FALSE;
326
327         return TRUE;
328 }
329
330 static void
331 color_format(glui32 val, gchar *buffer)
332 {
333         sprintf(buffer, "#%02X%02X%02X",
334                 ((val & 0xff0000) >> 16),
335                 ((val & 0x00ff00) >> 8),
336                 (val & 0x0000ff)
337         );
338 }
339
340 /* Internal function: changes a GTK tag to correspond with the given style. */
341 static void
342 apply_stylehint_to_tag(GtkTextTag *tag, glui32 hint, glsi32 val)
343 {
344         g_return_if_fail(tag != NULL);
345
346         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
347         GObject *tag_object = G_OBJECT(tag);
348         gint reverse_color = 0;
349
350         /* FIXME where should we keep track of this?
351         g_object_get(tag, "reverse_color", &reverse_color, NULL);
352         */
353
354         int i = 0;
355         gchar color[20];
356         switch(hint) {
357         case stylehint_Indentation:
358                 g_object_set(tag_object, "left_margin", 5*val, NULL);
359                 g_object_set(tag_object, "right_margin", 5*val, NULL);
360                 break;
361         
362         case stylehint_ParaIndentation:
363                 g_object_set(tag_object, "indent", 5*val, NULL);
364                 break;
365
366         case stylehint_Justification:
367                 switch(val) {
368                         case stylehint_just_LeftFlush:  i = GTK_JUSTIFY_LEFT; break;
369                         case stylehint_just_LeftRight:  i = GTK_JUSTIFY_FILL; break;
370                         case stylehint_just_Centered:   i = GTK_JUSTIFY_CENTER; break;
371                         case stylehint_just_RightFlush: i = GTK_JUSTIFY_RIGHT; break;
372                         default: 
373                                 WARNING("Unknown justification");
374                                 i = GTK_JUSTIFY_LEFT;
375                 }
376                 g_object_set(tag_object, "justification", i, NULL);
377                 break;
378
379         case stylehint_Weight:
380                 switch(val) {
381                         case -1: i = PANGO_WEIGHT_LIGHT; break;
382                         case  0: i = PANGO_WEIGHT_NORMAL; break;
383                         case  1: i = PANGO_WEIGHT_BOLD; break;
384                         default: WARNING("Unknown font weight");
385                 }
386                 g_object_set(tag_object, "weight", i, NULL);
387                 break;
388
389         case stylehint_Size:
390                 g_object_set(tag_object, "size", 14+(2*val), NULL);
391                 break;
392
393         case stylehint_Oblique:
394                 g_object_set(tag_object, "style", val ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL, NULL);
395                 break;
396
397         case stylehint_Proportional:
398                 g_object_set(tag_object, "font-desc", val ? glk_data->default_font_desc : glk_data->monospace_font_desc, NULL);
399                 break;
400
401         case stylehint_TextColor:
402                 color_format(val, color);
403
404                 if(!reverse_color)
405                         g_object_set(tag_object, "foreground", color, NULL);
406                 else
407                         g_object_set(tag_object, "background", color, NULL);
408
409                 break;
410
411         case stylehint_BackColor:
412                 color_format(val, color);
413
414                 if(!reverse_color)
415                         g_object_set(tag_object, "background", color, NULL);
416                 else
417                         g_object_set(tag_object, "foreground", color, NULL);
418
419                 break;
420
421         case stylehint_ReverseColor:
422                 if(reverse_color != val) {
423                         /* Flip the fore- and background colors */
424                         gchar* foreground_color;
425                         gchar* background_color;
426                         g_object_get(tag_object, "foreground", &foreground_color, NULL);
427                         g_object_get(tag_object, "background", &background_color, NULL);
428                         g_object_set(tag_object, "foreground", background_color, NULL);
429                         g_object_set(tag_object, "background", foreground_color, NULL);
430                         g_free(foreground_color);
431                         g_free(background_color);
432                 }
433                 break;
434
435         default:
436                 WARNING("Unknown style hint");
437         }
438 }
439
440 /**
441  * glk_stylehint_set:
442  * @wintype: The window type to set a style hint on, or %wintype_AllTypes.
443  * @styl: The style to set a hint for.
444  * @hint: The type of style hint, one of the <code>stylehint_</code> constants.
445  * @val: The style hint. The meaning of this depends on @hint.
446  *
447  * Sets a hint about the appearance of one style for a particular type of 
448  * window. You can also set wintype to %wintype_AllTypes, which sets a hint for 
449  * all types of window.
450  * <note><para>
451  *  There is no equivalent constant to set a hint for all styles of a single 
452  *  window type.
453  * </para></note>
454  */
455 void
456 glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, glsi32 val)
457 {
458         gchar *tag_name = get_tag_name(styl);
459
460         /* Iterate over all the window and update their styles if nessecary */
461         winid_t win = glk_window_iterate(NULL, NULL);
462         while(win != NULL) {
463                 if(wintype != wintype_TextBuffer)
464                         continue; /* FIXME: add support for text grid windows */
465
466                 if(wintype == wintype_AllTypes || glk_window_get_type(win) == wintype) {
467                         GtkWidget *textview = win->widget;
468                         GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) );
469                         GtkTextTagTable *table = gtk_text_buffer_get_tag_table(textbuffer);
470                         GtkTextTag *to_change = gtk_text_tag_table_lookup(table, tag_name);
471
472                         apply_stylehint_to_tag(to_change, hint, val);
473                 }
474         }
475 }
476
477 void
478 glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint)
479 {
480 }
481
482 glui32
483 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2)
484 {
485         return styl1 != styl2;
486 }
487
488 glui32
489 glk_style_measure(winid_t win, glui32 styl, glui32 hint, glui32 *result)
490 {
491         return FALSE;
492 }