Merge branch 'master' of ssh://git.stderr.nl/projects/chimara/chimara
[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_story_title:
93  * @title: Title bar text for the currently running story.
94  *
95  * This function is a hint to the library to put @title in the title bar of the
96  * window that the Glk program is running in. It overrides
97  * garglk_set_program_name() and garglk_set_story_name(), if they were displayed
98  * in the title bar, although they may still be displayed somewhere else.
99  *
100  * <warning><para>This function is not currently implemented.</para></warning>
101  */
102 void
103 garglk_set_story_title(const char *title)
104 {
105         WARNING(_("Not implemented"));
106 }
107
108 /**
109  * garglk_unput_string:
110  * @str: a null-terminated string.
111  *
112  * Removes @str from the end of the current stream, if indeed it is there. The
113  * stream's write count is decreased accordingly, and the stream's echo stream
114  * is also modified, if it has one.
115  *
116  * <warning><para>This function is not currently implemented.</para></warning>
117  */
118 void 
119 garglk_unput_string(char *str)
120 {
121         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
122         g_return_if_fail(glk_data->current_stream != NULL);
123
124         WARNING(_("Not implemented"));
125 }
126
127 /**
128  * garglk_unput_string_uni:
129  * @str: a zero-terminated array of Unicode code points.
130  *
131  * Like garglk_unput_string(), but for Unicode streams.
132  *
133  * <warning><para>This function is not currently implemented.</para></warning>
134  */
135 void 
136 garglk_unput_string_uni(glui32 *str)
137 {
138         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
139         g_return_if_fail(glk_data->current_stream != NULL);
140         
141         WARNING(_("Not implemented"));
142 }
143
144 /**
145  * garglk_set_zcolors_stream:
146  * @str: a stream.
147  * @fg: a 24-bit foreground color.
148  * @bg: a 24-bit background color.
149  *
150  * This function changes the foreground color of @str to @fg and the background
151  * color to @bg. @fg and @bg are encoded the same way as described in
152  * %stylehint_TextColor. See garglk_set_zcolors() for more information.
153  */
154 void
155 garglk_set_zcolors_stream(strid_t str, glui32 fg, glui32 bg)
156 {
157 #ifdef DEBUG_STYLES
158         g_printf("garglk_set_zcolors_stream(str->rock=%d, fg=%08X, bg=%08X)\n", str->rock, fg, bg);
159 #endif
160
161         VALID_STREAM(str, return);
162         g_return_if_fail(str->window != NULL);
163
164         winid_t window = str->window;
165
166         gdk_threads_enter();
167
168         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(window->widget) );
169         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
170         GdkColor fore, back;
171         GdkColor *fore_pointer = NULL;
172         GdkColor *back_pointer = NULL;
173         gchar *fore_name;
174         gchar *back_name;
175
176         switch(fg) {
177         case zcolor_Transparent:
178         case zcolor_Cursor:
179                 WARNING(_("zcolor_Transparent, zcolor_Cursor not implemented"));
180                 // Fallthrough to default
181         case zcolor_Default:
182                 fore_name = g_strdup("default");
183                 break;
184         case zcolor_Current:
185         {
186                 if(window->zcolor) {
187                         // Get the current foreground color
188                         GdkColor *current_color;
189                         g_object_get(window->zcolor, "foreground-gdk", &current_color, NULL);
190                         fore_name = gdkcolor_to_hex(current_color);
191
192                         // Copy the color and use it
193                         fore.red = current_color->red;
194                         fore.green = current_color->green;
195                         fore.blue = current_color->blue;
196                         fore_pointer = &fore;
197                 } else {
198                         fore_name = g_strdup("default");
199                 }
200                 break;
201         }
202         default:
203                 glkcolor_to_gdkcolor(fg, &fore);
204                 fore_pointer = &fore;
205                 fore_name = glkcolor_to_hex(fg);
206         }
207
208         switch(bg) {
209         case zcolor_Transparent:
210         case zcolor_Cursor:
211                 WARNING(_("zcolor_Transparent, zcolor_Cursor not implemented"));
212                 // Fallthrough to default
213         case zcolor_Default:
214                 back_name = g_strdup("default");
215                 break;
216         case zcolor_Current:
217         {
218                 if(window->zcolor) {
219                         // Get the current background color
220                         GdkColor *current_color;
221                         g_object_get(window->zcolor, "background-gdk", &current_color, NULL);
222                         back_name = gdkcolor_to_hex(current_color);
223
224                         // Copy the color and use it
225                         back.red = current_color->red;
226                         back.green = current_color->green;
227                         back.blue = current_color->blue;
228                         back_pointer = &back;
229                 } else {
230                         back_name = g_strdup("default");
231                 }
232                 break;
233         }
234         default:
235                 glkcolor_to_gdkcolor(bg, &back);
236                 back_pointer = &back;
237                 back_name = glkcolor_to_hex(bg);
238         }
239
240         if(fore_pointer == NULL && back_pointer == NULL) {
241                 // NULL value means to ignore the zcolor property altogether
242                 window->zcolor = NULL;
243         } else {
244                 char *name = g_strdup_printf("zcolor:#%s/#%s", fore_name, back_name);
245                 g_free(fore_name);
246                 g_free(back_name);
247
248                 // See if we have used this color combination before
249                 GtkTextTag *tag = gtk_text_tag_table_lookup(tags, name);
250
251                 if(tag == NULL) {
252                         // Create a new texttag with the specified colors
253                         tag = gtk_text_buffer_create_tag(
254                                 buffer,
255                                 name,
256                                 "foreground-gdk", fore_pointer,
257                                 "foreground-set", fore_pointer != NULL,
258                                 "background-gdk", back_pointer,
259                                 "background-set", back_pointer != NULL,
260                                 NULL
261                         );
262                 }
263
264                 // From now on, text will be drawn in the specified colors
265                 window->zcolor = tag;
266
267                 // Update the reversed version if necessary
268                 if(str->window->zcolor_reversed) {
269                         gint reversed = GPOINTER_TO_INT( g_object_get_data( G_OBJECT(str->window->zcolor_reversed), "reverse-color" ) );
270
271                         gdk_threads_leave();
272                         garglk_set_reversevideo_stream(str, reversed != 0);
273                         gdk_threads_enter();
274                 }
275         
276         }
277
278         gdk_threads_leave();
279 }
280
281 /**
282  * garglk_set_zcolors:
283  * @fg: a 24-bit foreground color.
284  * @bg: a 24-bit background color.
285  *
286  * Glk works with styles, not specific colors. This is not quite compatible with
287  * the Z-machine, so this Glk extension implements Z-machine style colors.
288  *
289  * This function changes the foreground color of the current stream to @fg and 
290  * the background color to @bg. @fg and @bg are encoded the same way as
291  * described in %stylehint_TextColor.
292  */
293 void 
294 garglk_set_zcolors(glui32 fg, glui32 bg)
295 {
296         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
297         g_return_if_fail(glk_data->current_stream != NULL);
298
299         garglk_set_zcolors_stream(glk_data->current_stream, fg, bg);
300 }
301
302 /**
303  * garglk_set_reversevideo_stream:
304  * @str: a stream.
305  * @reverse: nonzero for reverse colors, zero for normal colors.
306  *
307  * If @reverse is not zero, uses the foreground color of @str as its background
308  * and vice versa. If @reverse is zero, changes the colors of @str back to
309  * normal.
310  */
311 void
312 garglk_set_reversevideo_stream(strid_t str, glui32 reverse)
313 {
314 #ifdef DEBUG_STYLES
315         g_printf("garglk_set_reversevideo_stream(str->rock=%d, reverse=%d)\n", str->rock, reverse);
316 #endif
317
318         VALID_STREAM(str, return);
319         g_return_if_fail(str->window != NULL);
320         g_return_if_fail(str->window->type != wintype_TextBuffer || str->window->type != wintype_TextGrid);
321
322         // Determine the current colors
323         
324         // If all fails, use black/white
325         // FIXME: Use system theme here
326         GdkColor foreground, background;
327         gdk_color_parse("black", &foreground);
328         gdk_color_parse("white", &background);
329         GdkColor *current_foreground = &foreground;
330         GdkColor *current_background = &background;
331
332         gdk_threads_enter();
333
334         style_stream_colors(str, &current_foreground, &current_background);
335
336         if(reverse) {
337                 GdkColor *temp = current_foreground;
338                 current_foreground = current_background;
339                 current_background = temp;
340         }
341
342         // Name the color
343         gchar *name = g_strdup_printf(
344                 "zcolor:#%04X%04X%04X/#%04X%04X%04X",
345                 current_foreground->red,
346                 current_foreground->green,
347                 current_foreground->blue,
348                 current_background->red,
349                 current_background->green,
350                 current_background->blue
351         );
352
353         // Create a tag for the new colors if it doesn't exist yet
354         GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(str->window->widget) ); 
355         GtkTextTagTable *tags = gtk_text_buffer_get_tag_table(buffer);
356         GtkTextTag *tag = gtk_text_tag_table_lookup(tags, name);
357         if(tag == NULL) {
358                 tag = gtk_text_buffer_create_tag(
359                         buffer,
360                         name,
361                         "foreground-gdk", current_foreground,
362                         "foreground-set", TRUE,
363                         "background-gdk", current_background,
364                         "background-set", TRUE,
365                         NULL
366                 );
367                 g_object_set_data( G_OBJECT(tag), "reverse-color", GINT_TO_POINTER(reverse) );
368         }
369
370         // From now on, text will be drawn in the specified colors
371         str->window->zcolor_reversed = tag;
372
373         // Update the background of the gtktextview to correspond with the current background color
374         if(current_background != NULL) {
375                 gtk_widget_modify_base(str->window->widget, GTK_STATE_NORMAL, current_background);
376         }
377
378         gdk_threads_leave();
379 }
380
381 /**
382  * garglk_set_reversevideo:
383  * @reverse: nonzero for reverse colors, zero for normal colors.
384  *
385  * If @reverse is not zero, uses the foreground color of the current stream as
386  * its background and vice versa. If @reverse is zero, changes the colors of the
387  * current stream back to normal.
388  */
389 void 
390 garglk_set_reversevideo(glui32 reverse)
391 {
392         ChimaraGlkPrivate *glk_data = g_private_get(glk_data_key);
393         g_return_if_fail(glk_data->current_stream != NULL);
394         g_return_if_fail(glk_data->current_stream->window != NULL);
395
396         garglk_set_reversevideo_stream(glk_data->current_stream, reverse);
397 }