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