Misc Garglk extensions documenting
[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 /**
166  * garglk_set_zcolors_stream:
167  * @str: a stream.
168  * @fg: a 24-bit foreground color.
169  * @bg: a 24-bit background color.
170  *
171  * This function changes the foreground color of @str to @fg and the background
172  * color to @bg. @fg and @bg are encoded the same way as described in
173  * %stylehint_TextColor. See garglk_set_zcolors() for more information.
174  */
175 void
176 garglk_set_zcolors_stream(strid_t str, glui32 fg, glui32 bg)
177 {
178 #ifdef DEBUG_STYLES
179         g_printf("garglk_set_zcolors_stream(str->rock=%d, fg=%08X, bg=%08X)\n", str->rock, fg, bg);
180 #endif
181
182         VALID_STREAM(str, return);
183         g_return_if_fail(str->window != NULL);
184
185         winid_t window = str->window;
186
187         gdk_threads_enter();
188
189         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(window->widget) );
190         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
191         GdkColor fore, back;
192         GdkColor *fore_pointer = NULL;
193         GdkColor *back_pointer = NULL;
194         gchar *fore_name;
195         gchar *back_name;
196
197         switch(fg) {
198         case zcolor_Transparent:
199         case zcolor_Cursor:
200                 WARNING(_("zcolor_Transparent, zcolor_Cursor not implemented"));
201                 // Fallthrough to default
202         case zcolor_Default:
203                 fore_name = g_strdup("default");
204                 break;
205         case zcolor_Current:
206         {
207                 if(window->zcolor) {
208                         // Get the current foreground color
209                         GdkColor *current_color;
210                         g_object_get(window->zcolor, "foreground-gdk", &current_color, NULL);
211                         fore_name = gdkcolor_to_hex(current_color);
212
213                         // Copy the color and use it
214                         fore.red = current_color->red;
215                         fore.green = current_color->green;
216                         fore.blue = current_color->blue;
217                         fore_pointer = &fore;
218                 } else {
219                         fore_name = g_strdup("default");
220                 }
221                 break;
222         }
223         default:
224                 glkcolor_to_gdkcolor(fg, &fore);
225                 fore_pointer = &fore;
226                 fore_name = glkcolor_to_hex(fg);
227         }
228
229         switch(bg) {
230         case zcolor_Transparent:
231         case zcolor_Cursor:
232                 WARNING(_("zcolor_Transparent, zcolor_Cursor not implemented"));
233                 // Fallthrough to default
234         case zcolor_Default:
235                 back_name = g_strdup("default");
236                 break;
237         case zcolor_Current:
238         {
239                 if(window->zcolor) {
240                         // Get the current background color
241                         GdkColor *current_color;
242                         g_object_get(window->zcolor, "background-gdk", &current_color, NULL);
243                         back_name = gdkcolor_to_hex(current_color);
244
245                         // Copy the color and use it
246                         back.red = current_color->red;
247                         back.green = current_color->green;
248                         back.blue = current_color->blue;
249                         back_pointer = &back;
250                 } else {
251                         back_name = g_strdup("default");
252                 }
253                 break;
254         }
255         default:
256                 glkcolor_to_gdkcolor(bg, &back);
257                 back_pointer = &back;
258                 back_name = glkcolor_to_hex(bg);
259         }
260
261         if(fore_pointer == NULL && back_pointer == NULL) {
262                 // NULL value means to ignore the zcolor property altogether
263                 window->zcolor = NULL;
264         } else {
265                 char *name = g_strdup_printf("zcolor:#%s/#%s", fore_name, back_name);
266                 g_free(fore_name);
267                 g_free(back_name);
268
269                 // See if we have used this color combination before
270                 GtkTextTag *tag = gtk_text_tag_table_lookup(tags, name);
271
272                 if(tag == NULL) {
273                         // Create a new texttag with the specified colors
274                         tag = gtk_text_buffer_create_tag(
275                                 buffer,
276                                 name,
277                                 "foreground-gdk", fore_pointer,
278                                 "foreground-set", fore_pointer != NULL,
279                                 "background-gdk", back_pointer,
280                                 "background-set", back_pointer != NULL,
281                                 NULL
282                         );
283                 }
284
285                 // From now on, text will be drawn in the specified colors
286                 window->zcolor = tag;
287
288                 // Update the reversed version if necessary
289                 if(str->window->zcolor_reversed) {
290                         gint reversed = GPOINTER_TO_INT( g_object_get_data( G_OBJECT(str->window->zcolor_reversed), "reverse-color" ) );
291
292                         gdk_threads_leave();
293                         garglk_set_reversevideo_stream(str, reversed != 0);
294                         gdk_threads_enter();
295                 }
296         
297         }
298
299         gdk_threads_leave();
300 }
301
302 /**
303  * garglk_set_zcolors:
304  * @fg: a 24-bit foreground color.
305  * @bg: a 24-bit background color.
306  *
307  * Glk works with styles, not specific colors. This is not quite compatible with
308  * the Z-machine, so this Glk extension implements Z-machine style colors.
309  *
310  * This function changes the foreground color of the current stream to @fg and 
311  * the background color to @bg. @fg and @bg are encoded the same way as
312  * described in %stylehint_TextColor.
313  */
314 void 
315 garglk_set_zcolors(glui32 fg, glui32 bg)
316 {
317         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
318         g_return_if_fail(glk_data->current_stream != NULL);
319
320         garglk_set_zcolors_stream(glk_data->current_stream, fg, bg);
321 }
322
323 /**
324  * garglk_set_reversevideo_stream:
325  * @str: a stream.
326  * @reverse: nonzero for reverse colors, zero for normal colors.
327  *
328  * If @reverse is not zero, uses the foreground color of @str as its background
329  * and vice versa. If @reverse is zero, changes the colors of @str back to
330  * normal.
331  */
332 void
333 garglk_set_reversevideo_stream(strid_t str, glui32 reverse)
334 {
335 #ifdef DEBUG_STYLES
336         g_printf("garglk_set_reversevideo_stream(str->rock=%d, reverse=%d)\n", str->rock, reverse);
337 #endif
338
339         VALID_STREAM(str, return);
340         g_return_if_fail(str->window != NULL);
341         g_return_if_fail(str->window->type != wintype_TextBuffer || str->window->type != wintype_TextGrid);
342
343         // Determine the current colors
344         
345         // If all fails, use black/white
346         // FIXME: Use system theme here
347         GdkColor foreground, background;
348         gdk_color_parse("black", &foreground);
349         gdk_color_parse("white", &background);
350         GdkColor *current_foreground = &foreground;
351         GdkColor *current_background = &background;
352
353         gdk_threads_enter();
354
355         style_stream_colors(str, &current_foreground, &current_background);
356
357         if(reverse) {
358                 GdkColor *temp = current_foreground;
359                 current_foreground = current_background;
360                 current_background = temp;
361         }
362
363         // Name the color
364         gchar *name = g_strdup_printf(
365                 "zcolor:#%04X%04X%04X/#%04X%04X%04X",
366                 current_foreground->red,
367                 current_foreground->green,
368                 current_foreground->blue,
369                 current_background->red,
370                 current_background->green,
371                 current_background->blue
372         );
373
374         // Create a tag for the new colors if it doesn't exist yet
375         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(str->window->widget) ); 
376         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
377         GtkTextTag *tag = gtk_text_tag_table_lookup(tags, name);
378         if(tag == NULL) {
379                 tag = gtk_text_buffer_create_tag(
380                         buffer,
381                         name,
382                         "foreground-gdk", current_foreground,
383                         "foreground-set", TRUE,
384                         "background-gdk", current_background,
385                         "background-set", TRUE,
386                         NULL
387                 );
388                 g_object_set_data( G_OBJECT(tag), "reverse-color", GINT_TO_POINTER(reverse) );
389         }
390
391         // From now on, text will be drawn in the specified colors
392         str->window->zcolor_reversed = tag;
393
394         // Update the background of the gtktextview to correspond with the current background color
395         if(current_background != NULL) {
396                 gtk_widget_modify_base(str->window->widget, GTK_STATE_NORMAL, current_background);
397         }
398
399         gdk_threads_leave();
400 }
401
402 /**
403  * garglk_set_reversevideo:
404  * @reverse: nonzero for reverse colors, zero for normal colors.
405  *
406  * If @reverse is not zero, uses the foreground color of the current stream as
407  * its background and vice versa. If @reverse is zero, changes the colors of the
408  * current stream back to normal.
409  */
410 void 
411 garglk_set_reversevideo(glui32 reverse)
412 {
413         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
414         g_return_if_fail(glk_data->current_stream != NULL);
415         g_return_if_fail(glk_data->current_stream->window != NULL);
416
417         garglk_set_reversevideo_stream(glk_data->current_stream, reverse);
418 }