Overhauled the whole reverse video thing.
[projects/chimara/chimara.git] / libchimara / garglk.c
1 #include <glib.h>
2 #include <glib/gi18n.h>
3 #include <glib/gprintf.h>
4 #include <libchimara/glk.h>
5 #include "chimara-glk-private.h"
6 #include "stream.h"
7 #include "fileref.h"
8 #include "style.h"
9 #include "garglk.h"
10
11 extern GPrivate *glk_data_key;
12
13 /**
14  * garglk_fileref_get_name:
15  * @fref: A file reference.
16  *
17  * Gets the actual disk filename that @fref refers to, in the platform's
18  * native filename encoding. The string is owned by @fref and must not be
19  * changed or freed.
20  *
21  * Returns: a string in filename encoding.
22  */
23 char * 
24 garglk_fileref_get_name(frefid_t fref)
25 {
26         VALID_FILEREF(fref, return NULL);
27         return fref->filename;
28 }
29
30 /**
31  * garglk_set_program_name:
32  * @name: Name of the Glk program that is running.
33  *
34  * This function is used to let the library know the name of the currently
35  * running Glk program, in case it wants to display this information somewhere
36  * &mdash; for example, in the title bar of a window. A typical use of this
37  * function would be:
38  * |[ garglk_set_program_name("SuperGlkFrotz 0.1"); ]|
39  */
40 void 
41 garglk_set_program_name(const char *name)
42 {
43         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
44         glk_data->program_name = g_strdup(name);
45         g_object_notify(G_OBJECT(glk_data->self), "program-name");
46 }
47
48 /**
49  * garglk_set_program_info:
50  * @info: Information about the Glk program that is running.
51  *
52  * This function is used to provide the library with additional information
53  * about the currently running Glk program, in case it wants to display this
54  * information somewhere &mdash; for example, in an About box. A typical use of
55  * this function would be:
56  * |[ 
57  * garglk_set_program_info("SuperGlkFrotz, version 0.1\n"
58  *     "Original Frotz by Stefan Jokisch\n"
59  *     "Unix port by Jim Dunleavy and David Griffith\n"
60  *     "Glk port by Tor Andersson\n"
61  *     "Animation, networking, and evil AI by Sven Metcalfe");
62  * ]|
63  */
64 void 
65 garglk_set_program_info(const char *info)
66 {
67         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
68         glk_data->program_info = g_strdup(info);
69         g_object_notify(G_OBJECT(glk_data->self), "program-info");
70 }
71
72 /**
73  * garglk_set_story_name:
74  * @name: Name of the story that the Glk program is currently interpreting.
75  *
76  * If the Glk program running is an interactive fiction interpreter, then this
77  * function can be used to let the library know the name of the story currently
78  * loaded in the interpreter, in case it wants to display this information
79  * anywhere &mdash; for example, in the title bar of a window. A typical use of
80  * this function would be:
81  * |[ garglk_set_story_name("Lighan Ses Lion, el Zarf"); ]|
82  */
83 void 
84 garglk_set_story_name(const char *name)
85 {
86         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
87         glk_data->story_name = g_strdup(name);
88         g_object_notify(G_OBJECT(glk_data->self), "story-name");
89 }
90
91 /**
92  * garglk_set_line_terminators:
93  * @win: A window.
94  * @keycodes: An array of <code>keycode_</code> constants.
95  * @numkeycodes: The length of @keycodes.
96  *
97  * Amends the current line input request of @win to include terminating key
98  * codes. Any of the specified key codes will terminate the line input request 
99  * (without printing a newline). 
100  *
101  * Usually, in the event structure returned from a line input request, @val2 is
102  * zero, but if garglk_set_line_terminators() has been called during that input
103  * request, @val2 will be filled in with the key code that terminated the input
104  * request.
105  *
106  * This function only applies to one input request; any subsequent line input
107  * requests on that window are treated normally.
108  *
109  * If @numkeycodes is zero, then any previous call to 
110  * garglk_set_line_terminators() is cancelled and the input request is treated
111  * normally.
112  *
113  * <warning><para>This function is not currently implemented.</para></warning>
114  */
115 void 
116 garglk_set_line_terminators(winid_t win, const glui32 *keycodes, glui32 numkeycodes)
117 {
118         VALID_WINDOW(win, return);
119         g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid);
120         
121         if(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE) {
122                 ILLEGAL(_("Tried to set the line terminators on a window without a line input request."));
123                 return;
124         }
125
126         WARNING(_("Not implemented"));
127 }
128
129 /**
130  * garglk_unput_string:
131  * @str: a null-terminated string.
132  *
133  * Removes @str from the end of the current stream, if indeed it is there. The
134  * stream's write count is decreased accordingly, and the stream's echo stream
135  * is also modified, if it has one.
136  *
137  * <warning><para>This function is not currently implemented.</para></warning>
138  */
139 void 
140 garglk_unput_string(char *str)
141 {
142         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
143         g_return_if_fail(glk_data->current_stream != NULL);
144
145         WARNING(_("Not implemented"));
146 }
147
148 /**
149  * garglk_unput_string_uni:
150  * @str: a zero-terminated array of Unicode code points.
151  *
152  * Like garglk_unput_string(), but for Unicode streams.
153  *
154  * <warning><para>This function is not currently implemented.</para></warning>
155  */
156 void 
157 garglk_unput_string_uni(glui32 *str)
158 {
159         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
160         g_return_if_fail(glk_data->current_stream != NULL);
161         
162         WARNING(_("Not implemented"));
163 }
164
165 /* TODO document */
166 void
167 garglk_set_zcolors_stream(strid_t str, glui32 fg, glui32 bg)
168 {
169 #ifdef DEBUG_STYLES
170         g_printf("garglk_set_zcolors_stream(str->rock=%d, fg=%08X, bg=%08X)\n", str->rock, fg, bg);
171 #endif
172
173         VALID_STREAM(str, return);
174         g_return_if_fail(str->window != NULL);
175
176         winid_t window = str->window;
177
178         gdk_threads_enter();
179
180         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(window->widget) );
181         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
182         GdkColor fore, back;
183         GdkColor *fore_pointer = NULL;
184         GdkColor *back_pointer = NULL;
185         gchar *fore_name;
186         gchar *back_name;
187
188         switch(fg) {
189         case zcolor_Transparent:
190         case zcolor_Cursor:
191                 WARNING(_("zcolor_Transparent, zcolor_Cursor not implemented"));
192                 // Fallthrough to default
193         case zcolor_Default:
194                 fore_name = g_strdup("default");
195                 break;
196         case zcolor_Current:
197         {
198                 if(window->zcolor) {
199                         // Get the current foreground color
200                         GdkColor *current_color;
201                         g_object_get(window->zcolor, "foreground-gdk", &current_color, NULL);
202                         fore_name = g_strdup_printf("%02X%02X%02X", current_color->red, current_color->green, current_color->blue);
203
204                         // Copy the color and use it
205                         fore.red = current_color->red;
206                         fore.green = current_color->green;
207                         fore.blue = current_color->blue;
208                         fore_pointer = &fore;
209                 } else {
210                         fore_name = g_strdup("default");
211                 }
212                 break;
213         }
214         default:
215                 glkcolor_to_gdkcolor(fg, &fore);
216                 fore_pointer = &fore;
217                 fore_name = g_strdup_printf("%02X%02X%02X",
218                         ((fg & 0xff0000) >> 16),
219                         ((fg & 0x00ff00) >> 8),
220                         (fg & 0x0000ff)
221                 );
222         }
223
224         switch(bg) {
225         case zcolor_Transparent:
226         case zcolor_Cursor:
227                 WARNING(_("zcolor_Transparent, zcolor_Cursor not implemented"));
228                 // Fallthrough to default
229         case zcolor_Default:
230                 back_name = g_strdup("default");
231                 break;
232         case zcolor_Current:
233         {
234                 if(window->zcolor) {
235                         // Get the current background color
236                         GdkColor *current_color;
237                         g_object_get(window->zcolor, "background-gdk", &current_color, NULL);
238                         back_name = g_strdup_printf("%02X%02X%02X", current_color->red, current_color->green, current_color->blue);
239
240                         // Copy the color and use it
241                         back.red = current_color->red;
242                         back.green = current_color->green;
243                         back.blue = current_color->blue;
244                         back_pointer = &back;
245                 } else {
246                         back_name = g_strdup("default");
247                 }
248                 break;
249         }
250         default:
251                 glkcolor_to_gdkcolor(bg, &back);
252                 back_pointer = &back;
253                 back_name = g_strdup_printf("%02X%02X%02X",
254                         ((bg & 0xff0000) >> 16),
255                         ((bg & 0x00ff00) >> 8),
256                         (bg & 0x0000ff)
257                 );
258         }
259
260         if(fore_pointer == NULL && back_pointer == NULL) {
261                 // NULL value means to ignore the zcolor property altogether
262                 window->zcolor = NULL;
263         } else {
264                 char *name = g_strdup_printf("zcolor:#%s/#%s", fore_name, back_name);
265                 g_free(fore_name);
266                 g_free(back_name);
267
268                 // See if we have used this color combination before
269                 GtkTextTag *tag = gtk_text_tag_table_lookup(tags, name);
270
271                 if(tag == NULL) {
272                         // Create a new texttag with the specified colors
273                         tag = gtk_text_buffer_create_tag(
274                                 buffer,
275                                 name,
276                                 "foreground-gdk", fore_pointer,
277                                 "foreground-set", fore_pointer != NULL,
278                                 "background-gdk", back_pointer,
279                                 "background-set", back_pointer != NULL,
280                                 NULL
281                         );
282                 }
283
284                 // From now on, text will be drawn in the specified colors
285                 window->zcolor = tag;
286         }
287
288         gdk_threads_leave();
289 }
290
291 /**
292  * garglk_set_zcolors:
293  * @fg: one of the <code>zcolor_</code> constants.
294  * @bg: one of the <code>zcolor_</code> constants.
295  *
296  * Glk works with styles, not specific colors. This is not quite compatible with
297  * the Z-machine, so this Glk extension implements Z-machine style colors.
298  *
299  * This function changes the foreground color of the current stream to @fg and 
300  * the background color to @bg.
301  *
302  * <warning><para>This function is not currently implemented.</para></warning>
303  */
304 void 
305 garglk_set_zcolors(glui32 fg, glui32 bg)
306 {
307         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
308         g_return_if_fail(glk_data->current_stream != NULL);
309
310         garglk_set_zcolors_stream(glk_data->current_stream, fg, bg);
311 }
312
313 /* TODO document */
314 void
315 garglk_set_reversevideo_stream(strid_t str, glui32 reverse)
316 {
317 #ifdef DEBUG_STYLES
318         g_printf("garglk_set_reversevideo_stream(str->rock=%d, reverse=%d)\n", str->rock, reverse);
319 #endif
320
321         VALID_STREAM(str, return);
322         g_return_if_fail(str->window != NULL);
323         g_return_if_fail(str->window->type != wintype_TextBuffer || str->window->type != wintype_TextGrid);
324
325         // Determine the current colors
326         
327         // If all fails, use black/white
328         // FIXME: Use system theme here
329         GdkColor foreground, background;
330         gdk_color_parse("black", &foreground);
331         gdk_color_parse("white", &background);
332         GdkColor *current_foreground = &foreground;
333         GdkColor *current_background = &background;
334
335         gdk_threads_enter();
336
337         style_stream_colors(str, &current_foreground, &current_background);
338
339         if(reverse) {
340                 GdkColor *temp = current_foreground;
341                 current_foreground = current_background;
342                 current_background = temp;
343         }
344
345         // Name the color
346         gchar *name = g_strdup_printf(
347                 "zcolor:#%04X%04X%04X/#%04X%04X%04X",
348                 current_foreground->red,
349                 current_foreground->green,
350                 current_foreground->blue,
351                 current_background->red,
352                 current_background->green,
353                 current_background->blue
354         );
355
356         // Create a tag for the new colors if it doesn't exist yet
357         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(str->window->widget) ); 
358         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
359         GtkTextTag *tag = gtk_text_tag_table_lookup(tags, name);
360         if(tag == NULL) {
361                 tag = gtk_text_buffer_create_tag(
362                         buffer,
363                         name,
364                         "foreground-gdk", current_foreground,
365                         "foreground-set", TRUE,
366                         "background-gdk", current_background,
367                         "background-set", TRUE,
368                         NULL
369                 );
370         }
371
372         // From now on, text will be drawn in the specified colors
373         str->window->zcolor_reversed = tag;
374         
375         gdk_threads_leave();
376 }
377
378 /**
379  * garglk_set_reversevideo:
380  * @reverse: nonzero for reverse colors, zero for normal colors.
381  *
382  * If @reverse is not zero, uses the foreground color of the current stream as
383  * its background and vice versa. If @reverse is zero, changes the colors of the
384  * current stream back to normal.
385  */
386 void 
387 garglk_set_reversevideo(glui32 reverse)
388 {
389         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
390         g_return_if_fail(glk_data->current_stream != NULL);
391         g_return_if_fail(glk_data->current_stream->window != NULL);
392
393         garglk_set_reversevideo_stream(glk_data->current_stream, reverse);
394 }