From: fliep Date: Sat, 23 May 2009 00:06:50 +0000 (+0000) Subject: Separated library source code from testing code, fixing #6 X-Git-Tag: v0.9~390 X-Git-Url: https://git.stderr.nl/gitweb?a=commitdiff_plain;h=78652af29a2f39e626febd5f4213da57d3a13901;p=projects%2Fchimara%2Fchimara.git Separated library source code from testing code, fixing #6 --- diff --git a/Makefile.am b/Makefile.am index 76d43cd..a379a24 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ ## Process this file with automake to produce Makefile.in ## Created by Anjuta -SUBDIRS = src interpreters docs po +SUBDIRS = libchimara interpreters tests docs po chimaradocdir = ${prefix}/doc/chimara dist_chimaradoc_DATA = \ diff --git a/configure.ac b/configure.ac index 6f2c579..383809f 100644 --- a/configure.ac +++ b/configure.ac @@ -7,7 +7,7 @@ # tarballname) AC_INIT([chimara], [0.1]) # Sanity check to make sure we are running Autoconf from the right directory -AC_CONFIG_SRCDIR(src/chimara-glk.c) +AC_CONFIG_SRCDIR(libchimara/chimara-glk.c) # Initialize Automake AM_INIT_AUTOMAKE([-Wall]) @@ -75,10 +75,11 @@ AC_CONFIG_FILES([ Makefile chimara.pc chimara-plugin.pc -src/Makefile +libchimara/Makefile interpreters/Makefile interpreters/frotz/Makefile interpreters/nitfol/Makefile +tests/Makefile docs/Makefile docs/reference/Makefile docs/reference/version.xml diff --git a/interpreters/frotz/Makefile.am b/interpreters/frotz/Makefile.am index cd54897..34bcfa4 100644 --- a/interpreters/frotz/Makefile.am +++ b/interpreters/frotz/Makefile.am @@ -6,9 +6,6 @@ frotz_la_SOURCES = buffer.c err.c fastmem.c files.c input.c main.c math.c \ text.c variable.c glkscreen.c glkmisc.c frotz.h glkfrotz.h glkio.h setup.h frotz_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) -# TODO: Remove this later, see issue #6 -frotz_la_CFLAGS = -I../../src - frotzdocdir = $(datadir)/doc/$(PACKAGE)/frotz dist_frotzdoc_DATA = AUTHORS COPYING README TODO diff --git a/interpreters/frotz/fastmem.c b/interpreters/frotz/fastmem.c index 113c50c..fa4394b 100644 --- a/interpreters/frotz/fastmem.c +++ b/interpreters/frotz/fastmem.c @@ -28,10 +28,10 @@ #include "frotz.h" -#include "glk.h" +#include #include "glkio.h" -#include "glkstart.h" -#include "gi_blorb.h" +#include +#include extern void seed_random (int); extern void restart_screen (void); diff --git a/interpreters/frotz/files.c b/interpreters/frotz/files.c index 6ed8c33..fd22631 100644 --- a/interpreters/frotz/files.c +++ b/interpreters/frotz/files.c @@ -19,7 +19,7 @@ */ #include "frotz.h" -#include "glk.h" +#include #include "glkio.h" extern void set_more_prompts (bool); diff --git a/interpreters/frotz/glkfrotz.h b/interpreters/frotz/glkfrotz.h index 0f8ff4b..471953e 100644 --- a/interpreters/frotz/glkfrotz.h +++ b/interpreters/frotz/glkfrotz.h @@ -12,7 +12,7 @@ #include #include -#include "glk.h" +#include extern int curr_status_ht; extern int mach_status_ht; diff --git a/interpreters/frotz/main.c b/interpreters/frotz/main.c index a403c13..6ac7aa0 100644 --- a/interpreters/frotz/main.c +++ b/interpreters/frotz/main.c @@ -163,8 +163,8 @@ void z_piracy (void) * */ -#include "glk.h" -#include "glkstart.h" +#include +#include static int myargc; static char **myargv; diff --git a/interpreters/frotz/quetzal.c b/interpreters/frotz/quetzal.c index e41a57b..afc2e0e 100644 --- a/interpreters/frotz/quetzal.c +++ b/interpreters/frotz/quetzal.c @@ -19,7 +19,7 @@ */ #include "frotz.h" -#include "glk.h" +#include #include "glkio.h" #define get_c fgetc diff --git a/interpreters/nitfol/Makefile.am b/interpreters/nitfol/Makefile.am index e52ddf8..e15903e 100644 --- a/interpreters/nitfol/Makefile.am +++ b/interpreters/nitfol/Makefile.am @@ -19,8 +19,5 @@ nitfol_la_SOURCES = automap.c solve.c infix.c debug.c inform.c quetzal.c \ $(SOUND) nitfol_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) -# TODO: Remove this later, see issue #6 -nitfol_la_CFLAGS = -I../../src - nitfoldocdir = $(datadir)/doc/$(PACKAGE)/nitfol dist_nitfoldoc_DATA = ChangeLog COPYING INSTALL README diff --git a/interpreters/nitfol/README b/interpreters/nitfol/README new file mode 100644 index 0000000..e467265 --- /dev/null +++ b/interpreters/nitfol/README @@ -0,0 +1,18 @@ +This is release 0.5 of Nitfol, a z-machine interpreter using Glk for +its input and output. This release should be considered beta quality. + +Nitfol was written from scratch, with portability in mind. There's +probably some stuff in there that isn't portable, so if you find some, +let me know. It supports z1 through z8. z6 sort of works, but +doesn't display everything correctly. + +Play. Let me know if you can crash it or get it to do things an +interpreter shouldn't. + +See INSTALL for compilation/installation instructions. + +See nitfol.html or nitfol.info for full documentation. + +Evin Robertson, the author, can be reached at nitfol@my-deja.com + + diff --git a/interpreters/nitfol/blorb.c b/interpreters/nitfol/blorb.c index aedb96f..a83068b 100644 --- a/interpreters/nitfol/blorb.c +++ b/interpreters/nitfol/blorb.c @@ -1,5 +1,5 @@ #include "nitfol.h" -#include "gi_blorb.h" +#include /* Link this in only if your glk supports blorb */ diff --git a/interpreters/nitfol/gi_blorb.h b/interpreters/nitfol/gi_blorb.h deleted file mode 100644 index c71b100..0000000 --- a/interpreters/nitfol/gi_blorb.h +++ /dev/null @@ -1,96 +0,0 @@ -#ifndef _GI_BLORB_H -#define _GI_BLORB_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* gi_blorb.h: Blorb library layer for Glk API. - gi_blorb version 1.1. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - - This file is copyright 1998-1999 by Andrew Plotkin. You may copy, - distribute, and incorporate it into your own programs, by any means - and under any conditions, as long as you do not modify it. You may - also modify this file, incorporate it into your own programs, - and distribute the modified version, as long as you retain a notice - in your program or documentation which mentions my name and the URL - shown above. -*/ - -/* Error type and error codes */ -typedef glui32 giblorb_err_t; -#define giblorb_err_None (0) -#define giblorb_err_CompileTime (1) -#define giblorb_err_Alloc (2) -#define giblorb_err_Read (3) -#define giblorb_err_NotAMap (4) -#define giblorb_err_Format (5) -#define giblorb_err_NotFound (6) - -/* Methods for loading a chunk */ -#define giblorb_method_DontLoad (0) -#define giblorb_method_Memory (1) -#define giblorb_method_FilePos (2) - -/* Four-byte constants */ - -#define giblorb_make_id(c1, c2, c3, c4) \ - (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4)) - -#define giblorb_ID_Snd (giblorb_make_id('S', 'n', 'd', ' ')) -#define giblorb_ID_Exec (giblorb_make_id('E', 'x', 'e', 'c')) -#define giblorb_ID_Pict (giblorb_make_id('P', 'i', 'c', 't')) -#define giblorb_ID_Copyright (giblorb_make_id('(', 'c', ')', ' ')) -#define giblorb_ID_AUTH (giblorb_make_id('A', 'U', 'T', 'H')) -#define giblorb_ID_ANNO (giblorb_make_id('A', 'N', 'N', 'O')) - -/* giblorb_map_t: Holds the complete description of an open Blorb - file. This type is opaque for normal interpreter use. */ -typedef struct giblorb_map_struct giblorb_map_t; - -/* giblorb_result_t: Result when you try to load a chunk. */ -typedef struct giblorb_result_struct { - glui32 chunknum; /* The chunk number (for use in - giblorb_unload_chunk(), etc.) */ - union { - void *ptr; /* A pointer to the data (if you used - giblorb_method_Memory) */ - glui32 startpos; /* The position in the file (if you - used giblorb_method_FilePos) */ - } data; - glui32 length; /* The length of the data */ - glui32 chunktype; /* The type of the chunk. */ -} giblorb_result_t; - -extern giblorb_err_t giblorb_create_map(strid_t file, - giblorb_map_t **newmap); -extern giblorb_err_t giblorb_destroy_map(giblorb_map_t *map); - -extern giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 chunktype, - glui32 count); -extern giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 chunknum); -extern giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, - glui32 chunknum); - -extern giblorb_err_t giblorb_load_resource(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 usage, - glui32 resnum); -extern giblorb_err_t giblorb_count_resources(giblorb_map_t *map, - glui32 usage, glui32 *num, glui32 *min, glui32 *max); - -/* The following functions are part of the Glk library itself, not - the Blorb layer (whose code is in gi_blorb.c). These functions - are necessarily implemented in platform-dependent code. -*/ -extern giblorb_err_t giblorb_set_resource_map(strid_t file); - -#ifdef __cplusplus -} -#endif - -#endif /* _GI_BLORB_H */ diff --git a/interpreters/nitfol/glk.h b/interpreters/nitfol/glk.h deleted file mode 100644 index 2c5d58d..0000000 --- a/interpreters/nitfol/glk.h +++ /dev/null @@ -1,426 +0,0 @@ -#ifndef GLK_H -#define GLK_H - -/* glk.h: Header file for Glk API, version 0.7.0. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - - This file is copyright 1998-2004 by Andrew Plotkin. You may copy, - distribute, and incorporate it into your own programs, by any means - and under any conditions, as long as you do not modify it. You may - also modify this file, incorporate it into your own programs, - and distribute the modified version, as long as you retain a notice - in your program or documentation which mentions my name and the URL - shown above. -*/ - -/* You may have to edit the definition of glui32 to make sure it's really a - 32-bit unsigned integer type, and glsi32 to make sure it's really a - 32-bit signed integer type. If they're not, horrible things will happen. */ -#include -#if (USHORT_MAX == 4294967295) -typedef unsigned short glui32; -typedef signed short glsi32; -#elif (UINT_MAX == 4294967295) -typedef unsigned int glui32; -typedef signed int glsi32; -#elif (ULONG_MAX) == 4294967295) -typedef unsigned long glui32; -typedef signed long glsi32; -#else -#error No 32-bit integer type found. -#endif - - -/* These are the compile-time conditionals that reveal various Glk optional - modules. */ -#define GLK_MODULE_UNICODE -#define GLK_MODULE_IMAGE -#define GLK_MODULE_SOUND -#define GLK_MODULE_HYPERLINKS - -/* These types are opaque object identifiers. They're pointers to opaque - C structures, which are defined differently by each library. */ -typedef struct glk_window_struct *winid_t; -typedef struct glk_stream_struct *strid_t; -typedef struct glk_fileref_struct *frefid_t; -typedef struct glk_schannel_struct *schanid_t; - -#define gestalt_Version (0) -#define gestalt_CharInput (1) -#define gestalt_LineInput (2) -#define gestalt_CharOutput (3) -#define gestalt_CharOutput_CannotPrint (0) -#define gestalt_CharOutput_ApproxPrint (1) -#define gestalt_CharOutput_ExactPrint (2) -#define gestalt_MouseInput (4) -#define gestalt_Timer (5) -#define gestalt_Graphics (6) -#define gestalt_DrawImage (7) -#define gestalt_Sound (8) -#define gestalt_SoundVolume (9) -#define gestalt_SoundNotify (10) -#define gestalt_Hyperlinks (11) -#define gestalt_HyperlinkInput (12) -#define gestalt_SoundMusic (13) -#define gestalt_GraphicsTransparency (14) -#define gestalt_Unicode (15) - -#define evtype_None (0) -#define evtype_Timer (1) -#define evtype_CharInput (2) -#define evtype_LineInput (3) -#define evtype_MouseInput (4) -#define evtype_Arrange (5) -#define evtype_Redraw (6) -#define evtype_SoundNotify (7) -#define evtype_Hyperlink (8) - -typedef struct event_struct { - glui32 type; - winid_t win; - glui32 val1, val2; -} event_t; - -#define keycode_Unknown (0xffffffff) -#define keycode_Left (0xfffffffe) -#define keycode_Right (0xfffffffd) -#define keycode_Up (0xfffffffc) -#define keycode_Down (0xfffffffb) -#define keycode_Return (0xfffffffa) -#define keycode_Delete (0xfffffff9) -#define keycode_Escape (0xfffffff8) -#define keycode_Tab (0xfffffff7) -#define keycode_PageUp (0xfffffff6) -#define keycode_PageDown (0xfffffff5) -#define keycode_Home (0xfffffff4) -#define keycode_End (0xfffffff3) -#define keycode_Func1 (0xffffffef) -#define keycode_Func2 (0xffffffee) -#define keycode_Func3 (0xffffffed) -#define keycode_Func4 (0xffffffec) -#define keycode_Func5 (0xffffffeb) -#define keycode_Func6 (0xffffffea) -#define keycode_Func7 (0xffffffe9) -#define keycode_Func8 (0xffffffe8) -#define keycode_Func9 (0xffffffe7) -#define keycode_Func10 (0xffffffe6) -#define keycode_Func11 (0xffffffe5) -#define keycode_Func12 (0xffffffe4) -/* The last keycode is always (0x100000000 - keycode_MAXVAL) */ -#define keycode_MAXVAL (28) - -#define style_Normal (0) -#define style_Emphasized (1) -#define style_Preformatted (2) -#define style_Header (3) -#define style_Subheader (4) -#define style_Alert (5) -#define style_Note (6) -#define style_BlockQuote (7) -#define style_Input (8) -#define style_User1 (9) -#define style_User2 (10) -#define style_NUMSTYLES (11) - -typedef struct stream_result_struct { - glui32 readcount; - glui32 writecount; -} stream_result_t; - -#define wintype_AllTypes (0) -#define wintype_Pair (1) -#define wintype_Blank (2) -#define wintype_TextBuffer (3) -#define wintype_TextGrid (4) -#define wintype_Graphics (5) - -#define winmethod_Left (0x00) -#define winmethod_Right (0x01) -#define winmethod_Above (0x02) -#define winmethod_Below (0x03) -#define winmethod_DirMask (0x0f) - -#define winmethod_Fixed (0x10) -#define winmethod_Proportional (0x20) -#define winmethod_DivisionMask (0xf0) - -#define fileusage_Data (0x00) -#define fileusage_SavedGame (0x01) -#define fileusage_Transcript (0x02) -#define fileusage_InputRecord (0x03) -#define fileusage_TypeMask (0x0f) - -#define fileusage_TextMode (0x100) -#define fileusage_BinaryMode (0x000) - -#define filemode_Write (0x01) -#define filemode_Read (0x02) -#define filemode_ReadWrite (0x03) -#define filemode_WriteAppend (0x05) - -#define seekmode_Start (0) -#define seekmode_Current (1) -#define seekmode_End (2) - -#define stylehint_Indentation (0) -#define stylehint_ParaIndentation (1) -#define stylehint_Justification (2) -#define stylehint_Size (3) -#define stylehint_Weight (4) -#define stylehint_Oblique (5) -#define stylehint_Proportional (6) -#define stylehint_TextColor (7) -#define stylehint_BackColor (8) -#define stylehint_ReverseColor (9) -#define stylehint_NUMHINTS (10) - -#define stylehint_just_LeftFlush (0) -#define stylehint_just_LeftRight (1) -#define stylehint_just_Centered (2) -#define stylehint_just_RightFlush (3) - -/* glk_main() is the top-level function which you define. The Glk library - calls it. */ -extern void glk_main(void); - -extern void glk_exit(void); -extern void glk_set_interrupt_handler(void (*func)(void)); -extern void glk_tick(void); - -extern glui32 glk_gestalt(glui32 sel, glui32 val); -extern glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, - glui32 arrlen); - -extern unsigned char glk_char_to_lower(unsigned char ch); -extern unsigned char glk_char_to_upper(unsigned char ch); - -extern winid_t glk_window_get_root(void); -extern winid_t glk_window_open(winid_t split, glui32 method, glui32 size, - glui32 wintype, glui32 rock); -extern void glk_window_close(winid_t win, stream_result_t *result); -extern void glk_window_get_size(winid_t win, glui32 *widthptr, - glui32 *heightptr); -extern void glk_window_set_arrangement(winid_t win, glui32 method, - glui32 size, winid_t keywin); -extern void glk_window_get_arrangement(winid_t win, glui32 *methodptr, - glui32 *sizeptr, winid_t *keywinptr); -extern winid_t glk_window_iterate(winid_t win, glui32 *rockptr); -extern glui32 glk_window_get_rock(winid_t win); -extern glui32 glk_window_get_type(winid_t win); -extern winid_t glk_window_get_parent(winid_t win); -extern winid_t glk_window_get_sibling(winid_t win); -extern void glk_window_clear(winid_t win); -extern void glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos); - -extern strid_t glk_window_get_stream(winid_t win); -extern void glk_window_set_echo_stream(winid_t win, strid_t str); -extern strid_t glk_window_get_echo_stream(winid_t win); -extern void glk_set_window(winid_t win); - -extern strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode, - glui32 rock); -extern strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, - glui32 rock); -extern void glk_stream_close(strid_t str, stream_result_t *result); -extern strid_t glk_stream_iterate(strid_t str, glui32 *rockptr); -extern glui32 glk_stream_get_rock(strid_t str); -extern void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode); -extern glui32 glk_stream_get_position(strid_t str); -extern void glk_stream_set_current(strid_t str); -extern strid_t glk_stream_get_current(void); - -extern void glk_put_char(unsigned char ch); -extern void glk_put_char_stream(strid_t str, unsigned char ch); -extern void glk_put_string(char *s); -extern void glk_put_string_stream(strid_t str, char *s); -extern void glk_put_buffer(char *buf, glui32 len); -extern void glk_put_buffer_stream(strid_t str, char *buf, glui32 len); -extern void glk_set_style(glui32 styl); -extern void glk_set_style_stream(strid_t str, glui32 styl); - -extern glsi32 glk_get_char_stream(strid_t str); -extern glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len); -extern glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len); - -extern void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, - glsi32 val); -extern void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint); -extern glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2); -extern glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint, - glui32 *result); - -extern frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock); -extern frefid_t glk_fileref_create_by_name(glui32 usage, char *name, - glui32 rock); -extern frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, - glui32 rock); -extern frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, - glui32 rock); -extern void glk_fileref_destroy(frefid_t fref); -extern frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr); -extern glui32 glk_fileref_get_rock(frefid_t fref); -extern void glk_fileref_delete_file(frefid_t fref); -extern glui32 glk_fileref_does_file_exist(frefid_t fref); - -extern void glk_select(event_t *event); -extern void glk_select_poll(event_t *event); - -extern void glk_request_timer_events(glui32 millisecs); - -extern void glk_request_line_event(winid_t win, char *buf, glui32 maxlen, - glui32 initlen); -extern void glk_request_char_event(winid_t win); -extern void glk_request_mouse_event(winid_t win); - -extern void glk_cancel_line_event(winid_t win, event_t *event); -extern void glk_cancel_char_event(winid_t win); -extern void glk_cancel_mouse_event(winid_t win); - -#ifdef GLK_MODULE_UNICODE - -extern glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, - glui32 numchars); -extern glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, - glui32 numchars); -extern glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, - glui32 numchars, glui32 lowerrest); - -extern void glk_put_char_uni(glui32 ch); -extern void glk_put_string_uni(glui32 *s); -extern void glk_put_buffer_uni(glui32 *buf, glui32 len); -extern void glk_put_char_stream_uni(strid_t str, glui32 ch); -extern void glk_put_string_stream_uni(strid_t str, glui32 *s); -extern void glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); - -extern glsi32 glk_get_char_stream_uni(strid_t str); -extern glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); -extern glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len); - -extern strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, - glui32 rock); -extern strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, - glui32 fmode, glui32 rock); - -extern void glk_request_char_event_uni(winid_t win); -extern void glk_request_line_event_uni(winid_t win, glui32 *buf, - glui32 maxlen, glui32 initlen); - -#endif /* GLK_MODULE_UNICODE */ - -#ifdef GLK_MODULE_IMAGE - -#define imagealign_InlineUp (0x01) -#define imagealign_InlineDown (0x02) -#define imagealign_InlineCenter (0x03) -#define imagealign_MarginLeft (0x04) -#define imagealign_MarginRight (0x05) - -extern glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2); -extern glui32 glk_image_draw_scaled(winid_t win, glui32 image, - glsi32 val1, glsi32 val2, glui32 width, glui32 height); -extern glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height); - -extern void glk_window_flow_break(winid_t win); - -extern void glk_window_erase_rect(winid_t win, - glsi32 left, glsi32 top, glui32 width, glui32 height); -extern void glk_window_fill_rect(winid_t win, glui32 color, - glsi32 left, glsi32 top, glui32 width, glui32 height); -extern void glk_window_set_background_color(winid_t win, glui32 color); - -#endif /* GLK_MODULE_IMAGE */ - -#ifdef GLK_MODULE_SOUND - -extern schanid_t glk_schannel_create(glui32 rock); -extern void glk_schannel_destroy(schanid_t chan); -extern schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr); -extern glui32 glk_schannel_get_rock(schanid_t chan); - -extern glui32 glk_schannel_play(schanid_t chan, glui32 snd); -extern glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, - glui32 notify); -extern void glk_schannel_stop(schanid_t chan); -extern void glk_schannel_set_volume(schanid_t chan, glui32 vol); - -extern void glk_sound_load_hint(glui32 snd, glui32 flag); - -#endif /* GLK_MODULE_SOUND */ - -#ifdef GLK_MODULE_HYPERLINKS - -extern void glk_set_hyperlink(glui32 linkval); -extern void glk_set_hyperlink_stream(strid_t str, glui32 linkval); -extern void glk_request_hyperlink_event(winid_t win); -extern void glk_cancel_hyperlink_event(winid_t win); - -#endif /* GLK_MODULE_HYPERLINKS */ - -/* XXX non-official Glk functions that may or may not exist */ - -#define GARGLK 1 - -extern char* garglk_fileref_get_name(frefid_t fref); - -extern void garglk_set_program_name(const char *name); -extern void garglk_set_program_info(const char *info); -extern void garglk_set_story_name(const char *name); -extern void garglk_set_config(const char *name); - -/* not implemented */ - -#define garglk_font_Roman (0) -#define garglk_font_Italic (1) -#define garglk_font_Bold (2) -#define garglk_font_BoldItalic (3) -#define garglk_font_MonoRoman (4) -#define garglk_font_MonoItalic (5) -#define garglk_font_MonoBold (6) -#define garglk_font_MonoBoldItalic (7) - -#define garglk_color_White (0) -#define garglk_color_Red (1) -#define garglk_color_Green (2) -#define garglk_color_Blue (3) -#define garglk_color_Cyan (4) -#define garglk_color_Magenta (5) -#define garglk_color_Yellow (6) -#define garglk_color_Black (7) - -extern void garglk_set_style_font(glui32 font); -extern void garglk_set_style_stream_font(strid_t str, glui32 font); -extern void garglk_set_style_color(glui32 bg, glui32 fg); -extern void garglk_set_style_stream_color(strid_t str, glui32 bg, glui32 fg); - -/* JM: functions added to support Z-machine features that aren't in the Glk standard */ - -/* garglk_set_line_terminators - amends the current line input request to include terminating - * key codes. any of the specified key codes will terminate input (without printing a newline), - * and the key code will be returned in the event record as val2. */ -extern void garglk_set_line_terminators(winid_t win, const glui32 *keycodes, glui32 numkeycodes); - -/* garglk_unput_string - removes the specified string from the end of the output buffer, if - * indeed it is there. */ -extern void garglk_unput_string(char *str); -extern void garglk_unput_string_uni(glui32 *str); - -#define zcolor_Current (0) -#define zcolor_Default (1) -#define zcolor_Black (2) -#define zcolor_Red (3) -#define zcolor_Green (4) -#define zcolor_Yellow (5) -#define zcolor_Blue (6) -#define zcolor_Magenta (7) -#define zcolor_Cyan (8) -#define zcolor_White (9) -#define zcolor_LightGrey (10) -#define zcolor_MediumGrey (11) -#define zcolor_DarkGrey (12) -#define zcolor_NUMCOLORS (13) - -extern void garglk_set_zcolors(glui32 fg, glui32 bg); -extern void garglk_set_reversevideo(glui32 reverse); - -#endif /* GLK_H */ diff --git a/interpreters/nitfol/glkstart.h b/interpreters/nitfol/glkstart.h deleted file mode 100644 index 8835064..0000000 --- a/interpreters/nitfol/glkstart.h +++ /dev/null @@ -1,70 +0,0 @@ -/* glkstart.h: Unix-specific header file for GlkTerm, CheapGlk, and XGlk - (Unix implementations of the Glk API). - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html -*/ - -/* This header defines an interface that must be used by program linked - with the various Unix Glk libraries -- at least, the three I wrote. - (I encourage anyone writing a Unix Glk library to use this interface, - but it's not part of the Glk spec.) - - Because Glk is *almost* perfectly portable, this interface *almost* - doesn't have to exist. In practice, it's small. -*/ - -#ifndef GT_START_H -#define GT_START_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* We define our own TRUE and FALSE and NULL, because ANSI - is a strange world. */ -#ifndef TRUE -#define TRUE 1 -#endif -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef NULL -#define NULL 0 -#endif - -#define glkunix_arg_End (0) -#define glkunix_arg_ValueFollows (1) -#define glkunix_arg_NoValue (2) -#define glkunix_arg_ValueCanFollow (3) -#define glkunix_arg_NumberValue (4) - -typedef struct glkunix_argumentlist_struct { - char *name; - int argtype; - char *desc; -} glkunix_argumentlist_t; - -typedef struct glkunix_startup_struct { - int argc; - char **argv; -} glkunix_startup_t; - -/* The list of command-line arguments; this should be defined in your code. */ -extern glkunix_argumentlist_t glkunix_arguments[]; - -/* The external function; this should be defined in your code. */ -extern int glkunix_startup_code(glkunix_startup_t *data); - -/* Some helpful utility functions which the library makes available - to your code. Obviously, this is nonportable; so you should - only call it from glkunix_startup_code(). -*/ -extern strid_t glkunix_stream_open_pathname(char *pathname, glui32 textmode, - glui32 rock); - -#ifdef __cplusplus -} -#endif - -#endif /* GT_START_H */ - diff --git a/interpreters/nitfol/main.c b/interpreters/nitfol/main.c index ce0ceac..cbd8cde 100644 --- a/interpreters/nitfol/main.c +++ b/interpreters/nitfol/main.c @@ -19,7 +19,7 @@ */ #include "nitfol.h" -#include "gi_blorb.h" +#include static void set_zfile(strid_t file) diff --git a/interpreters/nitfol/nitfol.h b/interpreters/nitfol/nitfol.h index 319a536..7aa50ed 100644 --- a/interpreters/nitfol/nitfol.h +++ b/interpreters/nitfol/nitfol.h @@ -24,7 +24,7 @@ #include /* For time() */ #include /* for isspace, isgraph, etc. */ #include -#include "glk.h" +#include #define GLK_EOF ((glsi32) -1) #define NITFOL_MAJOR 0 @@ -328,7 +328,7 @@ typedef enum { OBJ_GET_INFO, OBJ_RECEIVE, OBJ_MOVE } watchinfo; #include "z_io.h" #include "no_snd.h" -#include "gi_blorb.h" +#include #include "no_graph.h" #include "no_blorb.h" diff --git a/interpreters/nitfol/no_blorb.c b/interpreters/nitfol/no_blorb.c index 0798545..4f1e122 100644 --- a/interpreters/nitfol/no_blorb.c +++ b/interpreters/nitfol/no_blorb.c @@ -1,5 +1,5 @@ #include "nitfol.h" -#include "gi_blorb.h" +#include /* Link this in only if your glk doesn't support blorb */ diff --git a/interpreters/nitfol/startdos.c b/interpreters/nitfol/startdos.c index 4455078..5f2372d 100644 --- a/interpreters/nitfol/startdos.c +++ b/interpreters/nitfol/startdos.c @@ -10,7 +10,7 @@ #include #include #include "nitfol.h" -#include "glkstart.h" +#include static char *game_filename = NULL; diff --git a/interpreters/nitfol/startunix.c b/interpreters/nitfol/startunix.c index 945ac3e..ba9e3fe 100644 --- a/interpreters/nitfol/startunix.c +++ b/interpreters/nitfol/startunix.c @@ -1,6 +1,6 @@ #line 228 "opt2glkc.pl" #include "nitfol.h" -#include "glkstart.h" +#include #ifdef DEBUGGING #include #endif diff --git a/libchimara/.svnignore b/libchimara/.svnignore new file mode 100644 index 0000000..af9133f --- /dev/null +++ b/libchimara/.svnignore @@ -0,0 +1,6 @@ +.deps +.libs +Makefile +Makefile.in +test-chimara +chimara.ui diff --git a/libchimara/Makefile.am b/libchimara/Makefile.am new file mode 100644 index 0000000..f173bca --- /dev/null +++ b/libchimara/Makefile.am @@ -0,0 +1,34 @@ +## Process this file with automake to produce Makefile.in + +AM_CFLAGS = -Wall + +lib_LTLIBRARIES = libchimara.la + +libchimara_la_SOURCES = \ + abort.c abort.h \ + case.c \ + charset.c charset.h \ + chimara-glk.c chimara-glk.h chimara-glk-private.h \ + event.c event.h \ + fileref.c fileref.h \ + gestalt.c \ + glk.c glk.h \ + magic.c magic.h \ + input.c input.h \ + stream.c stream.h \ + strio.c \ + timer.c timer.h \ + window.c window.h \ + gi_blorb.c gi_blorb.h \ + resource.c resource.h \ + style.c style.h \ + glkstart.h +libchimara_la_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"Chimara\" +libchimara_la_CFLAGS = @CHIMARA_CFLAGS@ $(AM_CFLAGS) +libchimara_la_LIBADD = @CHIMARA_LIBS@ +libchimara_la_LDFLAGS = -no-undefined -export-symbols-regex "^(chimara_)?glk_" +libchimara_includedir = $(includedir)/chimara/chimara +libchimara_include_HEADERS = chimara-glk.h + +EXTRA_DIST = doc.c diff --git a/libchimara/abort.c b/libchimara/abort.c new file mode 100644 index 0000000..61234a0 --- /dev/null +++ b/libchimara/abort.c @@ -0,0 +1,71 @@ +#include "event.h" +#include +#include + +#include "chimara-glk-private.h" + +extern ChimaraGlkPrivate *glk_data; + +/** + * glk_set_interrupt_handler: + * @func: A pointer to an interrupt handler function. + * + * Sets @func to be the interrupt handler. @func should be a pointer to a + * function which takes no argument and returns no result. If Glk receives an + * interrupt, and you have set an interrupt handler, your handler will be + * called, before the process is shut down. + * + * Initially there is no interrupt handler. You can reset to not having any by + * calling #glk_set_interrupt_handler(%NULL). + * + * If you call glk_set_interrupt_handler() with a new handler function while an + * older one is set, the new one replaces the old one. Glk does not try to queue + * interrupt handlers. + * + * You should not try to interact with the player in your interrupt handler. Do + * not call glk_select() or glk_select_poll(). Anything you print to a window + * may not be visible to the player. + */ +void +glk_set_interrupt_handler(void (*func)(void)) +{ + glk_data->interrupt_handler = func; +} + +/* Internal function: abort this Glk program, freeing resources and calling the +user's interrupt handler. */ +static void +abort_glk() +{ + if(glk_data->interrupt_handler) + (*(glk_data->interrupt_handler))(); + g_signal_emit_by_name(glk_data->self, "stopped"); + g_thread_exit(NULL); +} + +/* Internal function: Signal this Glk thread to abort. Does nothing if the abort +mutex has already been freed. (That means the thread already ended.) */ +void +signal_abort() +{ + if(glk_data && glk_data->abort_lock) { + g_mutex_lock(glk_data->abort_lock); + glk_data->abort_signalled = TRUE; + g_mutex_unlock(glk_data->abort_lock); + /* Stop blocking on the event queue condition */ + event_throw(evtype_Abort, NULL, 0, 0); + } +} + +/* Internal function: check if the Glk program has been interrupted. */ +void +check_for_abort() +{ + g_mutex_lock(glk_data->abort_lock); + if(glk_data->abort_signalled) + { + g_mutex_unlock(glk_data->abort_lock); + abort_glk(); + } + g_mutex_unlock(glk_data->abort_lock); +} diff --git a/libchimara/abort.h b/libchimara/abort.h new file mode 100644 index 0000000..1ba2bcc --- /dev/null +++ b/libchimara/abort.h @@ -0,0 +1,8 @@ +#ifndef ABORT_H +#define ABORT_H + +G_GNUC_INTERNAL void check_for_abort(); +G_GNUC_INTERNAL void signal_abort(); + +#endif + diff --git a/libchimara/case.c b/libchimara/case.c new file mode 100644 index 0000000..47086e1 --- /dev/null +++ b/libchimara/case.c @@ -0,0 +1,172 @@ +#include +#include "glk.h" + +/** + * glk_char_to_lower: + * @ch: A Latin-1 character. + * + * You can convert Latin-1 characters between upper and lower case with two Glk + * utility functions, glk_char_to_lower() and glk_char_to_upper(). These have a + * few advantages over the standard ANSI tolower() and + * toupper() macros. They work for the entire Latin-1 + * character set, including accented letters; they behave consistently on all + * platforms, since they're part of the Glk library; and they are safe for all + * characters. That is, if you call glk_char_to_lower() on a lower-case + * character, or a character which is not a letter, you'll get the argument + * back unchanged. + * + * The case-sensitive characters in Latin-1 are the ranges 0x41..0x5A, + * 0xC0..0xD6, 0xD8..0xDE (upper case) and the ranges 0x61..0x7A, 0xE0..0xF6, + * 0xF8..0xFE (lower case). These are arranged in parallel; so + * glk_char_to_lower() will add 0x20 to values in the upper-case ranges, and + * glk_char_to_upper() will subtract 0x20 from values in the lower-case ranges. + * + * Returns: A lowercase or non-letter Latin-1 character. + */ +unsigned char +glk_char_to_lower(unsigned char ch) +{ + if( (ch >= 0x41 && ch <= 0x5A) || (ch >= 0xC0 && ch <= 0xD6) || (ch >= 0xD8 && ch <= 0xDE) ) + return ch + 0x20; + return ch; +} + +/** + * glk_char_to_upper: + * @ch: A Latin-1 character. + * + * If @ch is a lowercase character in the Latin-1 character set, converts it to + * uppercase. Otherwise, leaves it unchanged. See glk_char_to_lower(). + * + * Returns: An uppercase or non-letter Latin-1 character. + */ +unsigned char +glk_char_to_upper(unsigned char ch) +{ + if( (ch >= 0x61 && ch <= 0x7A) || (ch >= 0xE0 && ch <= 0xF6) || (ch >= 0xF8 && ch <= 0xFE) ) + return ch - 0x20; + return ch; +} + +/** + * glk_buffer_to_lower_case_uni: + * @buf: A character array in UCS-4. + * @len: Available length of @buf. + * @numchars: Number of characters in @buf. + * + * Unicode character conversion is trickier, and must be applied to character + * arrays, not single characters. These functions + * (glk_buffer_to_lower_case_uni(), glk_buffer_to_upper_case_uni(), and + * glk_buffer_to_title_case_uni()) provide two length arguments because a + * string of Unicode characters may expand when its case changes. The @len + * argument is the available length of the buffer; @numchars is the number of + * characters in the buffer initially. (So @numchars must be less than or equal + * to @len. The contents of the buffer after @numchars do not affect the + * operation.) + * + * The functions return the number of characters after conversion. If this is + * greater than @len, the characters in the array will be safely truncated at + * @len, but the true count will be returned. (The contents of the buffer after + * the returned count are undefined.) + * + * The lower_case and upper_case functions do what + * you'd expect: they convert every character in the buffer (the first @numchars + * of them) to its upper or lower-case equivalent, if there is such a thing. + * + * See the Unicode spec (chapter 3.13, chapter 4.2, etc) for the exact + * definitions of upper, lower, and title-case mapping. + * + * + * Unicode has some strange case cases. For example, a combined character + * that looks like ss might properly be upper-cased into + * two characters S. Title-casing is even + * stranger; ss (at the beginning of a word) might be + * title-cased into a different combined character that looks like + * Ss. The glk_buffer_to_title_case_uni() function is actually + * title-casing the first character of the buffer. If it makes a difference. + * + * + * Returns: The number of characters after conversion. + */ +glui32 +glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, glui32 numchars) +{ + g_return_val_if_fail(buf != NULL && (len > 0 || numchars > 0), 0); + g_return_val_if_fail(numchars <= len, 0); + + /* GLib has a function that converts _one_ UCS-4 character to _one_ + lowercase UCS-4 character; so apparently we don't have to worry about the + string length changing... */ + glui32 *ptr; + for(ptr = buf; ptr < buf + numchars; ptr++) + *ptr = g_unichar_tolower(*ptr); + + return numchars; +} + +/** + * glk_buffer_to_upper_case_uni: + * @buf: A character array in UCS-4. + * @len: Available length of @buf. + * @numchars: Number of characters in @buf. + * + * Converts the first @numchars characters of @buf to their uppercase + * equivalents, if there is such a thing. See glk_buffer_to_lower_case_uni(). + * + * Returns: The number of characters after conversion. + */ +glui32 +glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, glui32 numchars) +{ + g_return_val_if_fail(buf != NULL && (len > 0 || numchars > 0), 0); + g_return_val_if_fail(numchars <= len, 0); + + /* GLib has a function that converts _one_ UCS-4 character to _one_ + uppercase UCS-4 character; so apparently we don't have to worry about the + string length changing... */ + glui32 *ptr; + for(ptr = buf; ptr < buf + numchars; ptr++) + *ptr = g_unichar_toupper(*ptr); + + return numchars; +} + +/** + * glk_buffer_to_title_case_uni: + * @buf: A character array in UCS-4. + * @len: Available length of @buf. + * @numchars: Number of characters in @buf. + * @lowerrest: %TRUE if the rest of @buf should be lowercased, %FALSE + * otherwise. + * + * See glk_buffer_to_lower_case_uni(). The title_case function has + * an additional (boolean) flag. Its basic function is to change the first + * character of the buffer to upper-case, and leave the rest of the buffer + * unchanged. If @lowerrest is true, it changes all the non-first characters to + * lower-case (instead of leaving them alone.) + * + * + * Earlier drafts of this spec had a separate function which title-cased the + * first character of every word in the buffer. I took + * this out after reading Unicode Standard Annex #29, which explains how + * to divide a string into words. If you want it, feel free to implement it. + * + * + * Returns: The number of characters after conversion. + */ +glui32 +glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, glui32 numchars, glui32 lowerrest) +{ + g_return_val_if_fail(buf != NULL && (len > 0 || numchars > 0), 0); + g_return_val_if_fail(numchars <= len, 0); + + /* GLib has a function that converts _one_ UCS-4 character to _one_ + titlecase UCS-4 character; so apparently we don't have to worry about the + string length changing... */ + *buf = g_unichar_totitle(*buf); + /* Call lowercase on the rest of the string */ + if(lowerrest) + return glk_buffer_to_lower_case_uni(buf + 1, len - 1, numchars - 1) + 1; + return numchars; +} + diff --git a/libchimara/charset.c b/libchimara/charset.c new file mode 100644 index 0000000..3f047cc --- /dev/null +++ b/libchimara/charset.c @@ -0,0 +1,129 @@ +#include "charset.h" +#include "magic.h" +#include + +/* Internal function: change illegal (control) characters in a string to a +placeholder character. Must free returned string afterwards. */ +static gchar * +remove_latin1_control_characters(const unsigned char *s, const gsize len) +{ + /* If len == 0, then return an empty string, not NULL */ + if(len == 0) + return g_strdup(""); + + gchar *retval = g_new0(gchar, len); + int i; + for(i = 0; i < len; i++) + if( (s[i] < 32 && s[i] != 10) || (s[i] >= 127 && s[i] <= 159) ) + retval[i] = PLACEHOLDER; + else + retval[i] = s[i]; + return retval; +} + +/* Internal function: convert a Latin-1 string to a UTF-8 string, replacing +Latin-1 control characters by a placeholder first. The UTF-8 string must be +freed afterwards. Returns NULL on error. */ +gchar * +convert_latin1_to_utf8(const gchar *s, const gsize len) +{ + GError *error = NULL; + gchar *canonical = remove_latin1_control_characters( (unsigned char *)s, + len); + gchar *retval = g_convert(canonical, len, "UTF-8", "ISO-8859-1", NULL, NULL, &error); + g_free(canonical); + + if(retval == NULL) + IO_WARNING("Error during latin1->utf8 conversion of string", s, error->message); + + return retval; +} + +/* Internal function: convert a Latin-1 string to a four-byte-per-character +big-endian string of gchars. The string must be freed afterwards. */ +gchar * +convert_latin1_to_ucs4be_string(const gchar *s, const gsize len) +{ + /* "UCS-4BE" is also a conversion type in g_convert()... but this may be more efficient */ + gchar *retval = g_new0(gchar, len * 4); + int i; + for(i = 0; i < len; i++) + retval[i * 4 + 3] = s[i]; + return retval; +} + +/* Internal function: convert a null-terminated UTF-8 string to a +null-terminated Latin-1 string, replacing characters that cannot be represented +in Latin-1 by a placeholder. If bytes_written is not NULL it will be filled with +the number of bytes returned, not counting the NULL terminator. The returned +string must be freed afterwards. Returns NULL on error. */ +gchar * +convert_utf8_to_latin1(const gchar *s, gsize *bytes_written) +{ + GError *error = NULL; + gchar *retval = g_convert_with_fallback(s, -1, "ISO-8859-1", "UTF-8", PLACEHOLDER_STRING, NULL, bytes_written, &error); + + if(retval == NULL) + IO_WARNING("Error during utf8->latin1 conversion of string", s, error->message); + + return retval; +} + +/* Internal function: convert a null-terminated UTF-8 string to a +null-terminated UCS4 string. If items_written is not NULL it will be filled with +the number of code points returned, not counting the terminator. The returned +string must be freed afterwards. Returns NULL on error. */ +gunichar * +convert_utf8_to_ucs4(const gchar *s, glong *items_written) +{ + gunichar *retval = g_utf8_to_ucs4_fast(s, -1, items_written); + + if(retval == NULL) + WARNING_S("Error during utf8->unicode conversion of string", s); + + return retval; +} + +/* Internal function: Convert a Unicode buffer to a null-terminated UTF-8 +string. The returned string must be freed afterwards. Returns NULL on error. */ +gchar * +convert_ucs4_to_utf8(const gunichar *buf, const glong len) +{ + GError *error = NULL; + gchar *retval = g_ucs4_to_utf8(buf, len, NULL, NULL, &error); + + if(retval == NULL) + WARNING_S("Error during unicode->utf8 conversion", error->message); + + return retval; +} + +/* Internal function: Convert a Unicode buffer to a Latin-1 string. Do not do +any character processing, just return values > 255 as the placeholder character. +The returned string must be freed afterwards.*/ +gchar * +convert_ucs4_to_latin1_binary(const gunichar *buf, const glong len) +{ + gchar *retval = g_new0(gchar, len); + int foo; + for(foo = 0; foo < len; foo++) + retval[foo] = (buf[foo] > 255)? PLACEHOLDER : buf[foo]; + return retval; +} + +/* Internal function: convert a Unicode buffer to a four-byte-per-character +big-endian string of gchars. The string must be freed afterwards. */ +gchar * +convert_ucs4_to_ucs4be_string(const gunichar *buf, const glong len) +{ + gchar *retval = g_new0(gchar, len * 4); + int i; + for(i = 0; i < len; i++) + { + retval[i * 4] = buf[i] >> 24 ; + retval[i * 4 + 1] = buf[i] >> 16 & 0xFF; + retval[i * 4 + 2] = buf[i] >> 8 & 0xFF; + retval[i * 4 + 3] = buf[i] & 0xFF; + } + return retval; +} diff --git a/libchimara/charset.h b/libchimara/charset.h new file mode 100644 index 0000000..c170e93 --- /dev/null +++ b/libchimara/charset.h @@ -0,0 +1,18 @@ +#ifndef CHARSET_H +#define CHARSET_H + +#include + +#define PLACEHOLDER '?' +#define PLACEHOLDER_STRING "?" +/* Our placeholder character is '?'; other options are possible, like printing "0x7F" or something */ + +G_GNUC_INTERNAL gchar *convert_latin1_to_utf8(const gchar *s, const gsize len); +G_GNUC_INTERNAL gchar *convert_latin1_to_ucs4be_string(const gchar *s, const gsize len); +G_GNUC_INTERNAL gchar *convert_utf8_to_latin1(const gchar *s, gsize *bytes_written); +G_GNUC_INTERNAL gunichar *convert_utf8_to_ucs4(const gchar *s, glong *items_written); +G_GNUC_INTERNAL gchar *convert_ucs4_to_utf8(const gunichar *buf, const glong len); +G_GNUC_INTERNAL gchar *convert_ucs4_to_latin1_binary(const gunichar *buf, const glong len); +G_GNUC_INTERNAL gchar *convert_ucs4_to_ucs4be_string(const gunichar *buf, const glong len); + +#endif /* CHARSET_H */ diff --git a/libchimara/chimara-glk-private.h b/libchimara/chimara-glk-private.h new file mode 100644 index 0000000..ba638a0 --- /dev/null +++ b/libchimara/chimara-glk-private.h @@ -0,0 +1,63 @@ +#ifndef __CHIMARA_GLK_PRIVATE_H__ +#define __CHIMARA_GLK_PRIVATE_H__ + +#include +#include +#include +#include "glk.h" +#include "gi_blorb.h" +#include "chimara-glk.h" + +G_BEGIN_DECLS + +typedef struct _ChimaraGlkPrivate ChimaraGlkPrivate; + +struct _ChimaraGlkPrivate { + /* Pointer back to the widget itself for use in thread */ + ChimaraGlk *self; + /* Whether user input is expected */ + gboolean interactive; + /* Whether file operations are allowed */ + gboolean protect; + /* Font description of proportional font */ + PangoFontDescription *default_font_desc; + /* Font description of monospace font */ + PangoFontDescription *monospace_font_desc; + /* Spacing between Glk windows */ + guint spacing; + /* Glk program loaded in widget */ + GModule *program; + /* Thread in which Glk program is run */ + GThread *thread; + /* Event queue and threading stuff */ + GQueue *event_queue; + GMutex *event_lock; + GCond *event_queue_not_empty; + GCond *event_queue_not_full; + /* Abort mechanism */ + GMutex *abort_lock; + gboolean abort_signalled; + /* User-defined interrupt handler */ + void (*interrupt_handler)(void); + /* Global tree of all windows */ + GNode *root_window; + /* List of filerefs currently in existence */ + GList *fileref_list; + /* Current stream */ + strid_t current_stream; + /* List of streams currently in existence */ + GList *stream_list; + /* Current timer */ + guint timer_id; + /* Current resource blorb map */ + giblorb_map_t *resource_map; + /* File stream pointing to the blorb used as current resource map */ + strid_t resource_file; +}; + +#define CHIMARA_GLK_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), CHIMARA_TYPE_GLK, ChimaraGlkPrivate)) + +G_END_DECLS + +#endif /* __CHIMARA_GLK_PRIVATE_H__ */ diff --git a/libchimara/chimara-glk.c b/libchimara/chimara-glk.c new file mode 100644 index 0000000..16e6749 --- /dev/null +++ b/libchimara/chimara-glk.c @@ -0,0 +1,963 @@ +/* licensing and copyright information here */ + +#include +#include +#include +#include +#include +#include "chimara-glk.h" +#include "chimara-glk-private.h" +#include "glk.h" +#include "abort.h" +#include "window.h" + +#define CHIMARA_GLK_MIN_WIDTH 0 +#define CHIMARA_GLK_MIN_HEIGHT 0 + +/** + * SECTION:chimara-glk + * @short_description: Widget which executes a Glk program + * @stability: Unstable + * @include: chimara/chimara-glk.h + * + * The ChimaraGlk widget opens and runs a Glk program. The program must be + * compiled as a plugin module, with a function glk_main() + * that the Glk library can hook into. + * + * On Linux systems, this is a file with a name like + * plugin.so. For portability, you can use libtool and + * automake: + * |[ + * pkglib_LTLIBRARIES = plugin.la + * plugin_la_SOURCES = plugin.c foo.c bar.c + * plugin_la_LDFLAGS = -module -shared -avoid-version -export-symbols-regex "^glk_main$$" + * ]| + * This will produce plugin.la which is a text file + * containing the correct plugin file to open (see the relevant section of the + * + * Libtool manual). + */ + +typedef void (* glk_main_t) (void); + +enum { + PROP_0, + PROP_INTERACTIVE, + PROP_PROTECT, + PROP_DEFAULT_FONT_DESCRIPTION, + PROP_MONOSPACE_FONT_DESCRIPTION, + PROP_SPACING +}; + +enum { + STOPPED, + STARTED, + + LAST_SIGNAL +}; + +static guint chimara_glk_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER); + +static void +chimara_glk_init(ChimaraGlk *self) +{ + GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); + + priv->self = self; + priv->interactive = TRUE; + priv->protect = FALSE; + priv->default_font_desc = pango_font_description_from_string("Sans"); + priv->monospace_font_desc = pango_font_description_from_string("Monospace"); + priv->program = NULL; + priv->thread = NULL; + priv->event_queue = NULL; + priv->event_lock = NULL; + priv->event_queue_not_empty = NULL; + priv->event_queue_not_full = NULL; + priv->abort_lock = NULL; + priv->abort_signalled = FALSE; + priv->interrupt_handler = NULL; + priv->root_window = NULL; + priv->fileref_list = NULL; + priv->current_stream = NULL; + priv->stream_list = NULL; + priv->timer_id = 0; +} + +static void +chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + ChimaraGlk *glk = CHIMARA_GLK(object); + + switch(prop_id) + { + case PROP_INTERACTIVE: + chimara_glk_set_interactive( glk, g_value_get_boolean(value) ); + break; + case PROP_PROTECT: + chimara_glk_set_protect( glk, g_value_get_boolean(value) ); + break; + case PROP_DEFAULT_FONT_DESCRIPTION: + chimara_glk_set_default_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) ); + break; + case PROP_MONOSPACE_FONT_DESCRIPTION: + chimara_glk_set_monospace_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) ); + break; + case PROP_SPACING: + chimara_glk_set_spacing( glk, g_value_get_uint(value) ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + +static void +chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object); + + switch(prop_id) + { + case PROP_INTERACTIVE: + g_value_set_boolean(value, priv->interactive); + break; + case PROP_PROTECT: + g_value_set_boolean(value, priv->protect); + break; + case PROP_DEFAULT_FONT_DESCRIPTION: + g_value_set_pointer(value, priv->default_font_desc); + break; + case PROP_MONOSPACE_FONT_DESCRIPTION: + g_value_set_pointer(value, priv->monospace_font_desc); + break; + case PROP_SPACING: + g_value_set_uint(value, priv->spacing); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + +static void +chimara_glk_finalize(GObject *object) +{ + ChimaraGlk *self = CHIMARA_GLK(object); + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); + + /* Free the event queue */ + g_mutex_lock(priv->event_lock); + g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL); + g_queue_free(priv->event_queue); + g_cond_free(priv->event_queue_not_empty); + g_cond_free(priv->event_queue_not_full); + priv->event_queue = NULL; + g_mutex_unlock(priv->event_lock); + g_mutex_free(priv->event_lock); + + /* Free the abort signalling mechanism */ + g_mutex_lock(priv->abort_lock); + /* Make sure no other thread is busy with this */ + g_mutex_unlock(priv->abort_lock); + g_mutex_free(priv->abort_lock); + priv->abort_lock = NULL; + + /* Free private data */ + pango_font_description_free(priv->default_font_desc); + pango_font_description_free(priv->monospace_font_desc); + + G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object); +} + +/* Internal function: Recursively get the Glk window tree's size request */ +static void +request_recurse(winid_t win, GtkRequisition *requisition, guint spacing) +{ + if(win->type == wintype_Pair) + { + /* Get children's size requests */ + GtkRequisition child1, child2; + request_recurse(win->window_node->children->data, &child1, spacing); + request_recurse(win->window_node->children->next->data, &child2, spacing); + + /* If the split is fixed, get the size of the fixed child */ + if((win->split_method & winmethod_DivisionMask) == winmethod_Fixed) + { + switch(win->split_method & winmethod_DirMask) + { + case winmethod_Left: + child1.width = win->key_window? + win->constraint_size * win->key_window->unit_width + : 0; + break; + case winmethod_Right: + child2.width = win->key_window? + win->constraint_size * win->key_window->unit_width + : 0; + break; + case winmethod_Above: + child1.height = win->key_window? + win->constraint_size * win->key_window->unit_height + : 0; + break; + case winmethod_Below: + child2.height = win->key_window? + win->constraint_size * win->key_window->unit_height + : 0; + break; + } + } + + /* Add the children's requests */ + switch(win->split_method & winmethod_DirMask) + { + case winmethod_Left: + case winmethod_Right: + requisition->width = child1.width + child2.width + spacing; + requisition->height = MAX(child1.height, child2.height); + break; + case winmethod_Above: + case winmethod_Below: + requisition->width = MAX(child1.width, child2.width); + requisition->height = child1.height + child2.height + spacing; + break; + } + } + + /* For non-pair windows, just use the size that GTK requests */ + else + gtk_widget_size_request(win->frame, requisition); +} + +/* Overrides gtk_widget_size_request */ +static void +chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition) +{ + g_return_if_fail(widget); + g_return_if_fail(requisition); + g_return_if_fail(CHIMARA_IS_GLK(widget)); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); + + /* For now, just pass the size request on to the root Glk window */ + if(priv->root_window) + { + request_recurse(priv->root_window->data, requisition, priv->spacing); + requisition->width += 2 * GTK_CONTAINER(widget)->border_width; + requisition->height += 2 * GTK_CONTAINER(widget)->border_width; + } + else + { + requisition->width = CHIMARA_GLK_MIN_WIDTH + 2 * GTK_CONTAINER(widget)->border_width; + requisition->height = CHIMARA_GLK_MIN_HEIGHT + 2 * GTK_CONTAINER(widget)->border_width; + } +} + +/* Recursively give the Glk windows their allocated space */ +static void +allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing) +{ + if(win->type == wintype_Pair) + { + GtkAllocation child1, child2; + child1.x = allocation->x; + child1.y = allocation->y; + + if((win->split_method & winmethod_DivisionMask) == winmethod_Fixed) + { + /* If the key window has been closed, then default to 0; otherwise + use the key window to determine the size */ + switch(win->split_method & winmethod_DirMask) + { + case winmethod_Left: + child1.width = win->key_window? + CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing) + : 0; + break; + case winmethod_Right: + child2.width = win->key_window? + CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing) + : 0; + break; + case winmethod_Above: + child1.height = win->key_window? + CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing) + : 0; + break; + case winmethod_Below: + child2.height = win->key_window? + CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing) + : 0; + break; + } + } + else /* proportional */ + { + gdouble fraction = win->constraint_size / 100.0; + switch(win->split_method & winmethod_DirMask) + { + case winmethod_Left: + child1.width = (glui32) ceil( fraction * (allocation->width - spacing) ); + break; + case winmethod_Right: + child2.width = (glui32) ceil( fraction * (allocation->width - spacing) ); + break; + case winmethod_Above: + child1.height = (glui32) ceil( fraction * (allocation->height - spacing) ); + break; + case winmethod_Below: + child2.height = (glui32) ceil( fraction * (allocation->height - spacing) ); + break; + } + } + + /* Fill in the rest of the size requisitions according to the child specified above */ + switch(win->split_method & winmethod_DirMask) + { + case winmethod_Left: + child2.width = allocation->width - spacing - child1.width; + child2.x = child1.x + child1.width + spacing; + child2.y = child1.y; + child1.height = child2.height = allocation->height; + break; + case winmethod_Right: + child1.width = allocation->width - spacing - child2.width; + child2.x = child1.x + child1.width + spacing; + child2.y = child1.y; + child1.height = child2.height = allocation->height; + break; + case winmethod_Above: + child2.height = allocation->height - spacing - child1.height; + child2.x = child1.x; + child2.y = child1.y + child1.height + spacing; + child1.width = child2.width = allocation->width; + break; + case winmethod_Below: + child1.height = allocation->height - spacing - child2.height; + child2.x = child1.x; + child2.y = child1.y + child1.height + spacing; + child1.width = child2.width = allocation->width; + break; + } + + /* Recurse */ + allocate_recurse(win->window_node->children->data, &child1, spacing); + allocate_recurse(win->window_node->children->next->data, &child2, spacing); + } + + else if(win->type == wintype_TextGrid) + { + /* Pass the size allocation on to the framing widget */ + gtk_widget_size_allocate(win->frame, allocation); + /* It says in the spec that when a text grid window is resized smaller, + the bottom or right area is thrown away; when it is resized larger, the + bottom or right area is filled with blanks. */ + glui32 newwidth = (glui32)(win->widget->allocation.width / win->unit_width); + glui32 newheight = (glui32)(win->widget->allocation.height / win->unit_height); + gint line; + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextIter start, end; + + for(line = 0; line < win->height; line++) + { + gtk_text_buffer_get_iter_at_line(textbuffer, &start, line); + /* If this line is going to fall off the bottom, delete it */ + if(line >= newheight) + { + end = start; + gtk_text_iter_forward_to_line_end(&end); + gtk_text_iter_forward_char(&end); + gtk_text_buffer_delete(textbuffer, &start, &end); + break; + } + /* If this line is not long enough, add spaces on the end */ + if(newwidth > win->width) + { + gchar *spaces = g_strnfill(newwidth - win->width, ' '); + gtk_text_iter_forward_to_line_end(&start); + gtk_text_buffer_insert(textbuffer, &start, spaces, -1); + g_free(spaces); + } + /* But if it's too long, delete characters from the end */ + else if(newwidth < win->width) + { + end = start; + gtk_text_iter_forward_chars(&start, newwidth); + gtk_text_iter_forward_to_line_end(&end); + gtk_text_buffer_delete(textbuffer, &start, &end); + } + /* Note: if the widths are equal, do nothing */ + } + /* Add blank lines if there aren't enough lines to fit the new size */ + if(newheight > win->height) + { + gchar *blanks = g_strnfill(win->width, ' '); + gchar **blanklines = g_new0(gchar *, (newheight - win->height) + 1); + int count; + for(count = 0; count < newheight - win->height; count++) + blanklines[count] = blanks; + blanklines[newheight - win->height] = NULL; + gchar *text = g_strjoinv("\n", blanklines); + g_free(blanklines); /* not g_strfreev() */ + g_free(blanks); + + gtk_text_buffer_get_end_iter(textbuffer, &start); + gtk_text_buffer_insert(textbuffer, &start, "\n", -1); + gtk_text_buffer_insert(textbuffer, &start, text, -1); + g_free(text); + } + + win->width = newwidth; + win->height = newheight; + } + + /* For non-pair, non-text-grid windows, just give them the size */ + else + gtk_widget_size_allocate(win->frame, allocation); +} + +/* Overrides gtk_widget_size_allocate */ +static void +chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation) +{ + g_return_if_fail(widget); + g_return_if_fail(allocation); + g_return_if_fail(CHIMARA_IS_GLK(widget)); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); + + widget->allocation = *allocation; + + if(priv->root_window) { + GtkAllocation child; + child.x = allocation->x + GTK_CONTAINER(widget)->border_width; + child.y = allocation->y + GTK_CONTAINER(widget)->border_width; + child.width = CLAMP(allocation->width - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->width); + child.height = CLAMP(allocation->height - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->height); + allocate_recurse(priv->root_window->data, &child, priv->spacing); + } +} + +/* Recursively invoke callback() on the GtkWidget of each non-pair window in the tree */ +static void +forall_recurse(winid_t win, GtkCallback callback, gpointer callback_data) +{ + if(win->type == wintype_Pair) + { + forall_recurse(win->window_node->children->data, callback, callback_data); + forall_recurse(win->window_node->children->next->data, callback, callback_data); + } + else + (*callback)(win->frame, callback_data); +} + +/* Overrides gtk_container_forall */ +static void +chimara_glk_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) +{ + g_return_if_fail(container); + g_return_if_fail(CHIMARA_IS_GLK(container)); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container); + + /* All the children are "internal" */ + if(!include_internals) + return; + + if(priv->root_window) + forall_recurse(priv->root_window->data, callback, callback_data); +} + +static void +chimara_glk_stopped(ChimaraGlk *self) +{ + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); + + /* Free the plugin */ + if( priv->program && !g_module_close(priv->program) ) + g_warning( "Error closing module: %s", g_module_error() ); +} + +static void +chimara_glk_started(ChimaraGlk *self) +{ + /* TODO: Add default signal handler implementation here */ +} + +/* G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */ +#ifndef G_PARAM_STATIC_STRINGS +#define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB) +#endif + +static void +chimara_glk_class_init(ChimaraGlkClass *klass) +{ + /* Override methods of parent classes */ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->set_property = chimara_glk_set_property; + object_class->get_property = chimara_glk_get_property; + object_class->finalize = chimara_glk_finalize; + + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + widget_class->size_request = chimara_glk_size_request; + widget_class->size_allocate = chimara_glk_size_allocate; + + GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); + container_class->forall = chimara_glk_forall; + + /* Signals */ + klass->stopped = chimara_glk_stopped; + klass->started = chimara_glk_started; + /** + * ChimaraGlk::stopped: + * @glk: The widget that received the signal + * + * The ::stopped signal is emitted when the a Glk program finishes + * executing in the widget, whether it ended normally, or was interrupted. + */ + chimara_glk_signals[STOPPED] = g_signal_new("stopped", + G_OBJECT_CLASS_TYPE(klass), 0, + G_STRUCT_OFFSET(ChimaraGlkClass, stopped), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * ChimaraGlk::started: + * @glk: The widget that received the signal + * + * The ::started signal is emitted when a Glk program starts executing in + * the widget. + */ + chimara_glk_signals[STARTED] = g_signal_new ("started", + G_OBJECT_CLASS_TYPE (klass), 0, + G_STRUCT_OFFSET(ChimaraGlkClass, started), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /* Properties */ + /** + * ChimaraGlk:interactive: + * + * Sets whether the widget is interactive. A Glk widget is normally + * interactive, but in non-interactive mode, keyboard and mouse input are + * ignored and the Glk program is controlled by chimara_glk_feed_text(). + * More prompts when a lot of text is printed to a text + * buffer are also disabled. This is typically used when you wish to control + * an interpreter program by feeding it a predefined list of commands. + */ + g_object_class_install_property( object_class, PROP_INTERACTIVE, + g_param_spec_boolean("interactive", _("Interactive"), + _("Whether user input is expected in the Glk program"), + TRUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); + + /** + * ChimaraGlk:protect: + * + * Sets whether the Glk program is allowed to do file operations. In protect + * mode, all file operations will fail. + */ + g_object_class_install_property(object_class, PROP_PROTECT, + g_param_spec_boolean("protect", _("Protected"), + _("Whether the Glk program is barred from doing file operations"), + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); + + /* We can't use G_PARAM_CONSTRUCT on these because then the constructor will + initialize them with NULL */ + /** + * ChimaraGlk:default-font-description: + * + * Pointer to a #PangoFontDescription describing the default proportional + * font, to be used in text buffer windows for example. + * + * Default value: font description created from the string + * Sans + */ + g_object_class_install_property(object_class, PROP_DEFAULT_FONT_DESCRIPTION, + g_param_spec_pointer("default-font-description", _("Default Font"), + _("Font description of the default proportional font"), + G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); + + /** + * ChimaraGlk:monospace-font-description: + * + * Pointer to a #PangoFontDescription describing the default monospace font, + * to be used in text grid windows and #style_Preformatted, for example. + * + * Default value: font description created from the string + * Monospace + */ + g_object_class_install_property(object_class, PROP_MONOSPACE_FONT_DESCRIPTION, + g_param_spec_pointer("monospace-font-description", _("Monospace Font"), + _("Font description of the default monospace font"), + G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); + + /** + * ChimaraGlk:spacing: + * + * The amount of space between the Glk windows. + */ + g_object_class_install_property(object_class, PROP_SPACING, + g_param_spec_uint("spacing", _("Spacing"), + _("The amount of space between Glk windows"), + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); + + /* Private data */ + g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate)); +} + +/* PUBLIC FUNCTIONS */ + +/** + * chimara_glk_new: + * + * Creates and initializes a new #ChimaraGlk widget. + * + * Return value: a #ChimaraGlk widget, with a floating reference. + */ +GtkWidget * +chimara_glk_new(void) +{ + ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL)); + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); + + priv->event_queue = g_queue_new(); + priv->event_lock = g_mutex_new(); + priv->event_queue_not_empty = g_cond_new(); + priv->event_queue_not_full = g_cond_new(); + priv->abort_lock = g_mutex_new(); + + return GTK_WIDGET(self); +} + +/** + * chimara_glk_set_interactive: + * @glk: a #ChimaraGlk widget + * @interactive: whether the widget should expect user input + * + * Sets the #ChimaraGlk:interactive property of @glk. + */ +void +chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + priv->interactive = interactive; +} + +/** + * chimara_glk_get_interactive: + * @glk: a #ChimaraGlk widget + * + * Returns whether @glk is interactive (expecting user input). See + * #ChimaraGlk:interactive. + * + * Return value: %TRUE if @glk is interactive. + */ +gboolean +chimara_glk_get_interactive(ChimaraGlk *glk) +{ + g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + return priv->interactive; +} + +/** + * chimara_glk_set_protect: + * @glk: a #ChimaraGlk widget + * @protect: whether the widget should allow the Glk program to do file + * operations + * + * Sets the #ChimaraGlk:protect property of @glk. In protect mode, the Glk + * program is not allowed to do file operations. + */ +void +chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + priv->protect = protect; +} + +/** + * chimara_glk_get_protect: + * @glk: a #ChimaraGlk widget + * + * Returns whether @glk is in protect mode (banned from doing file operations). + * See #ChimaraGlk:protect. + * + * Return value: %TRUE if @glk is in protect mode. + */ +gboolean +chimara_glk_get_protect(ChimaraGlk *glk) +{ + g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + return priv->protect; +} + +/** + * chimara_glk_set_default_font_description: + * @glk: a #ChimaraGlk widget + * @font: a #PangoFontDescription + * + * Sets @glk's default proportional font. See + * #ChimaraGlk:default-font-description. + */ +void +chimara_glk_set_default_font_description(ChimaraGlk *glk, PangoFontDescription *font) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + g_return_if_fail(font); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + pango_font_description_free(priv->default_font_desc); + priv->default_font_desc = pango_font_description_copy(font); + + /* TODO: Apply the font description to all the windows and recalculate the sizes */ +} + +/** + * chimara_glk_set_default_font_string: + * @glk: a #ChimaraGlk widget + * @font: string representation of a font description + * + * Sets @glk's default proportional font according to the string @font, which + * must be a string in the form FAMILY-LIST + * [STYLE-OPTIONS] + * [SIZE], such as Charter,Utopia + * Italic 12 or Sans. See + * #ChimaraGlk:default-font-description. + */ +void +chimara_glk_set_default_font_string(ChimaraGlk *glk, const gchar *font) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + g_return_if_fail(font || *font); + + PangoFontDescription *fontdesc = pango_font_description_from_string(font); + g_return_if_fail(fontdesc); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + pango_font_description_free(priv->default_font_desc); + priv->default_font_desc = fontdesc; + + /* TODO: Apply the font description to all the windows and recalculate the sizes */ +} + +/** + * chimara_glk_get_default_font_description: + * @glk: a #ChimaraGlk widget + * + * Returns @glk's default proportional font. + * + * Return value: a newly-allocated #PangoFontDescription which must be freed + * using pango_font_description_free(), or %NULL on error. + */ +PangoFontDescription * +chimara_glk_get_default_font_description(ChimaraGlk *glk) +{ + g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + return pango_font_description_copy(priv->default_font_desc); +} + +/** + * chimara_glk_set_monospace_font_description: + * @glk: a #ChimaraGlk widget + * @font: a #PangoFontDescription + * + * Sets @glk's default monospace font. See + * #ChimaraGlk:monospace-font-description. + */ +void +chimara_glk_set_monospace_font_description(ChimaraGlk *glk, PangoFontDescription *font) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + g_return_if_fail(font); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + pango_font_description_free(priv->monospace_font_desc); + priv->monospace_font_desc = pango_font_description_copy(font); + + /* TODO: Apply the font description to all the windows and recalculate the sizes */ +} + +/** + * chimara_glk_set_monospace_font_string: + * @glk: a #ChimaraGlk widget + * @font: string representation of a font description + * + * Sets @glk's default monospace font according to the string @font, which must + * be a string in the form FAMILY-LIST + * [STYLE-OPTIONS] + * [SIZE], such as Courier + * Bold 12 or Monospace. See + * #ChimaraGlk:monospace-font-description. + */ +void +chimara_glk_set_monospace_font_string(ChimaraGlk *glk, const gchar *font) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + g_return_if_fail(font || *font); + + PangoFontDescription *fontdesc = pango_font_description_from_string(font); + g_return_if_fail(fontdesc); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + pango_font_description_free(priv->monospace_font_desc); + priv->monospace_font_desc = fontdesc; + + /* TODO: Apply the font description to all the windows and recalculate the sizes */ +} + +/** + * chimara_glk_get_monospace_font_description: + * @glk: a #ChimaraGlk widget + * + * Returns @glk's default monospace font. + * + * Return value: a newly-allocated #PangoFontDescription which must be freed + * using pango_font_description_free(), or %NULL on error. + */ +PangoFontDescription * +chimara_glk_get_monospace_font_description(ChimaraGlk *glk) +{ + g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + return pango_font_description_copy(priv->monospace_font_desc); +} + +/** + * chimara_glk_set_spacing: + * @glk: a #ChimaraGlk widget + * @spacing: the number of pixels to put between Glk windows + * + * Sets the #ChimaraGlk:spacing property of @glk, which is the border width in + * pixels between Glk windows. + */ +void +chimara_glk_set_spacing(ChimaraGlk *glk, guint spacing) +{ + g_return_if_fail( glk || CHIMARA_IS_GLK(glk) ); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + priv->spacing = spacing; +} + +/** + * chimara_glk_get_spacing: + * @glk: a #ChimaraGlk widget + * + * Gets the value set by chimara_glk_set_spacing(). + * + * Return value: pixels of spacing between Glk windows + */ +guint +chimara_glk_get_spacing(ChimaraGlk *glk) +{ + g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), 0); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + return priv->spacing; +} + +/* glk_enter() is the actual function called in the new thread in which glk_main() runs. */ +static gpointer +glk_enter(gpointer glk_main) +{ + extern ChimaraGlkPrivate *glk_data; + g_signal_emit_by_name(glk_data->self, "started"); + ((glk_main_t)glk_main)(); + g_signal_emit_by_name(glk_data->self, "stopped"); + return NULL; +} + +/** + * chimara_glk_run: + * @glk: a #ChimaraGlk widget + * @plugin: path to a plugin module compiled with glk.h + * @error: location to store a GError, or + * %NULL + * + * Opens a Glk program compiled as a plugin and runs its glk_main() function in + * a separate thread. On failure, returns %FALSE and sets @error. + * + * Return value: %TRUE if the Glk program was started successfully. + */ +gboolean +chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error) +{ + g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE); + g_return_val_if_fail(plugin, FALSE); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + + /* Open the module to run */ + glk_main_t glk_main; + g_assert( g_module_supported() ); + priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY); + + if(!priv->program) + { + g_warning( "Error opening module: %s", g_module_error() ); + return FALSE; + } + if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &glk_main) ) + { + g_warning( "Error finding glk_main(): %s", g_module_error() ); + return FALSE; + } + + extern ChimaraGlkPrivate *glk_data; + /* Set the thread's private data */ + /* TODO: Do this with a GPrivate */ + glk_data = priv; + + /* Run in a separate thread */ + priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error); + + return !(priv->thread == NULL); +} + +/** + * chimara_glk_stop: + * @glk: a #ChimaraGlk widget + * + * Signals the Glk program running in @glk to abort. Note that if the program is + * caught in an infinite loop in which glk_tick() is not called, this may not + * work. + */ +void +chimara_glk_stop(ChimaraGlk *glk) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + /* TODO: check if glk is actually running a program */ + signal_abort(); +} + +/** + * chimara_glk_wait: + * @glk: a #ChimaraGlk widget + * + * Holds up the main thread and waits for the Glk program running in @glk to + * finish. + */ +void +chimara_glk_wait(ChimaraGlk *glk) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + + ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); + g_thread_join(priv->thread); +} diff --git a/libchimara/chimara-glk.h b/libchimara/chimara-glk.h new file mode 100644 index 0000000..ef121a2 --- /dev/null +++ b/libchimara/chimara-glk.h @@ -0,0 +1,62 @@ +/* Copyright / licensing information here. */ + +#ifndef __CHIMARA_GLK_H__ +#define __CHIMARA_GLK_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define CHIMARA_TYPE_GLK (chimara_glk_get_type()) +#define CHIMARA_GLK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + CHIMARA_TYPE_GLK, ChimaraGlk)) +#define CHIMARA_GLK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + CHIMARA_TYPE_GLK, ChimaraGlkClass)) +#define CHIMARA_IS_GLK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ + CHIMARA_TYPE_GLK)) +#define CHIMARA_IS_GLK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ + CHIMARA_TYPE_GLK)) +#define CHIMARA_GLK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + CHIMARA_TYPE_GLK, ChimaraGlkClass)) + +/** + * ChimaraGlk: + * + * This structure contains no public members. + */ +typedef struct _ChimaraGlk { + GtkContainer parent_instance; + + /*< public >*/ +} ChimaraGlk; + +typedef struct _ChimaraGlkClass { + GtkContainerClass parent_class; + /* Signals */ + void(* stopped) (ChimaraGlk *self); + void(* started) (ChimaraGlk *self); +} ChimaraGlkClass; + +GType chimara_glk_get_type(void) G_GNUC_CONST; +GtkWidget *chimara_glk_new(void); +void chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive); +gboolean chimara_glk_get_interactive(ChimaraGlk *glk); +void chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect); +gboolean chimara_glk_get_protect(ChimaraGlk *glk); +void chimara_glk_set_default_font_description(ChimaraGlk *glk, PangoFontDescription *font); +void chimara_glk_set_default_font_string(ChimaraGlk *glk, const gchar *font); +PangoFontDescription *chimara_glk_get_default_font_description(ChimaraGlk *glk); +void chimara_glk_set_monospace_font_description(ChimaraGlk *glk, PangoFontDescription *font); +void chimara_glk_set_monospace_font_string(ChimaraGlk *glk, const gchar *font); +PangoFontDescription *chimara_glk_get_monospace_font_description(ChimaraGlk *glk); +void chimara_glk_set_spacing(ChimaraGlk *glk, guint spacing); +guint chimara_glk_get_spacing(ChimaraGlk *glk); +gboolean chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error); +void chimara_glk_stop(ChimaraGlk *glk); +void chimara_glk_wait(ChimaraGlk *glk); + +G_END_DECLS + +#endif /* __CHIMARA_GLK_H__ */ diff --git a/libchimara/doc.c b/libchimara/doc.c new file mode 100644 index 0000000..2b60373 --- /dev/null +++ b/libchimara/doc.c @@ -0,0 +1,1733 @@ +/* + * doc.c - Contains the short and long descriptions of all the documentation + * sections in the Glk spec, as well as the GtkDoc comments for symbols + * defined only in glk.h. + */ + +/** + * SECTION:glk-exiting + * @short_description: How to terminate a Glk program cleanly + * @include: glk.h + * + * A Glk program usually ends when the end of the glk_main() function is + * reached. You can also terminate it earlier. + */ + +/** + * SECTION:glk-interrupt + * @short_description: Specifying an interrupt handler for cleaning up critical + * resources + * @include: glk.h + * + * Most platforms have some provision for interrupting a program — + * command + * period on the Macintosh, controlC + * in Unix, possibly a window manager item, or other possibilities. + * This can happen at any time, including while execution is nested inside one + * of your own functions, or inside a Glk library function. + * + * If you need to clean up critical resources, you can specify an interrupt + * handler function. + */ + +/** + * SECTION:glk-tick + * @short_description: Yielding time to the operating system + * @include: glk.h + * + * Many platforms have some annoying thing that has to be done every so often, + * or the gnurrs come from the voodvork out and eat your computer. + * + * Well, not really. But you should call glk_tick() every so often, just in + * case. It may be necessary to yield time to other applications in a + * cooperative-multitasking OS, or to check for player interrupts in an infinite + * loop. + */ + +/** + * SECTION:glk-types + * @short_description: Basic types used in Glk + * @include: glk.h + * + * For simplicity, all the arguments used in Glk calls are of a very few types. + * + * + * 32-bit unsigned integer + * Unsigned integers are used wherever possible, which is + * nearly everywhere. This type is called #glui32. + * + * + * 32-bit signed integer + * This type is called #glsi32. Rarely used. + * + * + * + * References to library objects + * These are pointers to opaque C structures; each library + * will use different structures, so you can not and should not try to + * manipulate their contents. See Opaque Objects. + * + * + * Pointer to one of the above types + * Pointer to a structure which consists entirely of the + * above types. + * + * + * unsigned char + * This is used only for Latin-1 text characters; see + * Character Encoding. + * + * + * + * Pointer to char + * Sometimes this means a null-terminated string; sometimes + * an unterminated buffer, with length as a separate #glui32 argument. The + * documentation says which. + * + * + * Pointer to void + * When nothing else will do. + * + * + */ + +/** + * SECTION:glk-opaque-objects + * @short_description: Complex objects in Glk + * @include: glk.h + * + * Glk keeps track of a few classes of special objects. These are opaque to your + * program; you always refer to them using pointers to opaque C structures. + * + * Currently, these classes are: + * + * + * Windows + * Screen panels, used to input or output information. + * + * + * + * Streams + * Data streams, to which you can input or output text. + * + * There are file streams and window streams, since you can + * output data to windows or files. + * + * + * + * File references + * Pointers to files in permanent storage. + * In Unix a file reference is a pathname; on the Mac, an + * FSSpec. Actually there's a little more information included, + * such as file type and whether it is a text or binary file. + * + * + * + * Sound channels + * Audio output channels. + * Not all Glk libraries support sound. + * + * + * + * + * + * Note that there may be more object classes in future versions of the Glk API. + * + * + * When you create one of these objects, it is always possible that the creation + * will fail (due to lack of memory, or some other OS error.) When this happens, + * the allocation function will return %NULL (0) instead of a valid pointer. You + * should always test for this possibility. + * + * %NULL is never the identifier of any object (window, stream, file reference, + * or sound channel). The value %NULL is often used to indicate no + * object or nothing, but it is not a valid reference. If + * a Glk function takes an object reference as an argument, it is illegal to + * pass in %NULL unless the function definition says otherwise. + * + * The glk.h file defines types + * #winid_t, #strid_t, #frefid_t, #schanid_t to store references. These are + * pointers to struct #glk_window_struct, #glk_stream_struct, + * #glk_fileref_struct, and #glk_schannel_struct respectively. It is, of course, + * illegal to pass one kind of pointer to a function which expects another. + * + * + * This is how you deal with opaque objects from a C program. If you are using + * Glk through a virtual machine, matters will probably be different. Opaque + * objects may be represented as integers, or as VM objects of some sort. + * + * + * Rocks + * + * Every one of these objects (window, stream, file reference, or sound channel) + * has a rock value. This is simply a 32-bit integer value which + * you provide, for your own purposes, when you create the object. + * + * The library — so to speak — stuffs this value under a + * rock for safe-keeping, and gives it back to you when you ask for it. + * + * If you don't know what to use the rocks for, provide 0 and forget + * about it. + * + * + * Iteration Through Opaque Objects + * + * For each class of opaque objects, there is an iterate function, which you can + * use to obtain a list of all existing objects of that class. It takes the form + * |[ + * CLASSid_t glk_CLASS_iterate(CLASSid_t obj, #glui32 *rockptr); + * ]| + * ...where CLASS represents one of the + * opaque object classes. + * + * + * So, at the current time, these are the functions glk_window_iterate(), + * glk_stream_iterate(), glk_fileref_iterate(), and glk_schannel_iterate(). + * There may be more classes in future versions of the spec; they all behave + * the same. + * + * + * Calling glk_CLASS_iterate(%NULL, r) + * returns the first object; calling + * glk_CLASS_iterate(obj, r) returns + * the next object, until there aren't any more, at which time it returns %NULL. + * + * + * The @rockptr argument is a pointer to a location; whenever + * glk_CLASS_iterate() returns an + * object, the object's rock is stored in the location (*@rockptr). + * If you don't want the rocks to be returned, you may set @rockptr to %NULL. + * + * + * You usually use this as follows: + * |[ + * obj = glk_CLASS_iterate(NULL, NULL); + * while (obj) { + * /* ...do something with obj... */ + * obj = glk_CLASS_iterate(obj, NULL); + * } + * ]| + * + * + * If you create or destroy objects inside this loop, obviously, the results are + * unpredictable. However it is always legal to call + * glk_CLASS_iterate(obj, r) as long as + * @obj is a valid object id, or %NULL. + * + * + * The order in which objects are returned is entirely arbitrary. The library + * may even rearrange the order every time you create or destroy an object of + * the given class. As long as you do not create or destroy any object, the rule + * is that glk_CLASS_iterate(obj, r) has + * a fixed result, and iterating through the results as above will list every + * object exactly once. + * + * + */ + +/** + * SECTION:glk-gestalt + * @short_description: Testing Glk's capabilities + * @include: glk.h + * + * The gestalt mechanism (cheerfully stolen from the Mac OS) is a + * system by which the Glk API can be upgraded without making your life + * impossible. New capabilities (graphics, sound, or so on) can be added without + * changing the basic specification. The system also allows for + * optional capabilities — those which not all Glk library + * implementations will support — and allows you to check for their + * presence without trying to infer them from a version number. + * + * The basic idea is that you can request information about the capabilities of + * the API, by calling the gestalt functions. + */ + +/** + * SECTION:glk-character-input + * @short_description: Waiting for a single keystroke + * @include: glk.h + * + * You can request that the player hit a single key. See Character Input Events. + * + * If you use the basic text API, the character code which is returned can be + * any value from 0 to 255. The printable character codes have already been + * described. The remaining codes are typically control codes: control + * A to controlZ and a few + * others. + * + * There are also a number of special codes, representing special keyboard + * keys, which can be returned from a char-input event. These are represented + * as 32-bit integers, starting with 4294967295 (0xFFFFFFFF) and working down. + * The special key codes are defined in the glk.h file. They include one code for return or enter, + * one for delete or backspace, twelve function keys, and one code + * for any key which has no Latin-1 or special code. The full list of key codes + * is included below. + * + * Various implementations of Glk will vary widely in which characters the + * player can enter. The most obvious limitation is that some characters are + * mapped to others. For example, most keyboards return a controlI + * code when the tab key is + * pressed. The Glk library, if it can recognize this at all, will generate a + * #keycode_Tab event (value 0xFFFFFFF7) when this occurs. + * Therefore, for these keyboards, no keyboard key will generate a controlI + * event (value 9.) The Glk library will probably map many of the + * control codes to the other special keycodes. + * + * + * On the other hand, the library may be very clever and discriminate between + * tab and controlI. This is + * legal. The idea is, however, that if your program asks the player to + * press the tab + * key, you should check for a + * #keycode_Tab event as opposed to a control + * I event. + * + * + * Some characters may not be enterable simply because they do not exist. + * + * + * Not all keyboards have a home or end key. A pen-based platform may not recognize + * any control characters at all. + * + * + * Some characters may not be enterable because they are reserved for the + * purposes of the interface. For example, the Mac Glk library reserves the + * tab key for switching between different Glk + * windows. Therefore, on the Mac, the library will never generate a + * #keycode_Tab event or a + * controlI + * event. + * + * + * Note that the linefeed or controlJ + * character, which is the only printable control character, is probably not + * typable. This is because, in most libraries, it will be converted to + * #keycode_Return. Again, you should check for + * #keycode_Return if your program asks the player to + * press the return + * key. + * + * + * + * The delete and backspace keys are merged into a single + * keycode because they have such an astonishing history of being confused in + * the first place... this spec formally waives any desire to define the + * difference. Of course, a library is free to distinguish delete and backspace during line input. This is when it + * matters most; conflating the two during character input should not be a + * large problem. + * + * + * You can test for this by using the #gestalt_CharInput selector. + * + * + * Glk porters take note: it is not a goal to be able to generate every + * single possible key event. If the library says that it can generate a + * particular keycode, then game programmers will assume that it is + * available, and ask players to use it. If a #keycode_Home + * event can only be generated by typing escapecontrolA + * , and the player does not know this, the player will be lost + * when the game says Press the home key to see the next + * hint. It is better for the library to say that it + * cannot generate a #keycode_Home event; that way the game + * can detect the situation and ask the user to type H + * instead. + * + * + * Of course, it is better not to rely on obscure keys in any case. The arrow + * keys and return are nearly certain to be + * available; the others are of gradually decreasing reliability, and you + * (the game programmer) should not depend on them. You must be certain to + * check for the ones you want to use, including the arrow keys and return, and be prepared to use different keys in + * your interface if #gestalt_CharInput says they are not available. + * + */ + +/** + * SECTION:glk-case + * @short_description: Changing the case of strings + * @include: glk.h + * + * Glk has functions to manipulate the case of both Latin-1 and Unicode strings. + * One Latin-1 lowercase character corresponds to one uppercase character, and + * vice versa, so the Latin-1 functions act on single characters. The Unicode + * functions act on whole strings, since the length of the string may change. + */ + +/** + * SECTION:glk-window-opening + * @short_description: Creating new windows and closing them + * @include: glk.h + * + * You can open a new window using glk_window_open() and close it again using + * glk_window_close(). + */ + +/** + * SECTION:glk-window-constraints + * @short_description: Manipulating the size of a window + * @include: glk.h + * + * There are library functions to change and to measure the size of a window. + */ + +/** + * SECTION:glk-window-types + * @short_description: Blank, pair, text grid, text buffer, and graphics windows + * @include: glk.h + * + * A technical description of all the window types, and exactly how they behave. + */ + +/** + * SECTION:glk-echo-streams + * @short_description: Creating a copy of a window's output + * @include: glk.h + * + * Every window has an associated window stream; you print to the window by + * printing to this stream. However, it is possible to attach a second stream to + * a window. Any text printed to the window is also echoed to this second + * stream, which is called the window's echo stream. + * + * Effectively, any call to glk_put_char() (or the other output commands) which + * is directed to the window's window stream, is replicated to the window's echo + * stream. This also goes for the style commands such as glk_set_style(). + * + * Note that the echoing is one-way. You can still print text directly to the + * echo stream, and it will go wherever the stream is bound, but it does not + * back up and appear in the window. + * + * An echo stream can be of any type, even another window's window stream. + * + * + * This would be somewhat silly, since it would mean that any text printed to + * the window would be duplicated in another window. More commonly, you would + * set a window's echo stream to be a file stream, in order to create a + * transcript file from that window. + * + * + * A window can only have one echo stream. But a single stream can be the echo + * stream of any number of windows, sequentially or simultaneously. + * + * If a window is closed, its echo stream remains open; it is not automatically + * closed. + * + * + * Do not confuse the window's window stream with its echo stream. The window + * stream is owned by the window, and dies with it. The echo + * stream is merely temporarily associated with the window. + * + * + * If a stream is closed, and it is the echo stream of one or more windows, + * those windows are reset to not echo anymore. (So then calling + * glk_window_get_echo_stream() on them will return %NULL.) + */ + +/** + * SECTION:glk-window-other + * @short_description: Miscellaneous functions for windows + * @include: glk.h + * + * This section contains functions for windows that don't fit anywhere else. + */ + +/** + * SECTION:glk-events + * @short_description: Waiting for events + * @include: glk.h + * + * As described in Your + * Program's Main Function, all player input is handed to your program by + * the glk_select() call, in the form of events. You should write at least one + * event loop to retrieve these events. + */ + +/** + * SECTION:glk-character-input-events + * @short_description: Events representing a single keystroke + * @include: glk.h + * + * You can request character input from text buffer and text grid windows. See + * #evtype_CharInput. There are separate functions for requesting Latin-1 input + * and Unicode input; see #gestalt_Unicode. + */ + +/** + * SECTION:glk-line-input-events + * @short_description: Events representing a line of user input + * @include: glk.h + * + * You can request line input from text buffer and text grid windows. See + * #evtype_LineInput. There are separate functions for requesting Latin-1 input + * and Unicode input; see #gestalt_Unicode. + */ + +/** + * SECTION:glk-streams + * @short_description: Input and output abstractions + * @include: glk.h + * + * All character output in Glk is done through streams. Every window has an + * output stream associated with it. You can also write to files on disk; every + * open file is represented by an output stream as well. + * + * There are also input streams; these are used for reading from files on disk. + * It is possible for a stream to be both an input and an output stream. + * + * + * Player input is done through line and character input events, not streams. + * This is a small inelegance in theory. In practice, player input is slow and + * things can interrupt it, whereas file input is immediate. If a network + * extension to Glk were proposed, it would probably use events and not + * streams, since network communication is not immediate. + * + * + * It is also possible to create a stream that reads or writes to a buffer in + * memory. + * + * Finally, there may be platform-specific types of streams, which are created + * before your program starts running. + * + * + * For example, a program running under Unix may have access to standard input + * as a stream, even though there is no Glk call to explicitly open standard + * input. On the Mac, data in a Mac resource may be available through a + * resource-reading stream. + * + * + * You do not need to worry about the origin of such streams; just read or write + * them as usual. For information about how platform-specific streams come to + * be, see Startup Options. + * + * A stream is opened with a particular file mode, see the + * filemode_ constants below. + * + * For information on opening streams, see the discussion of each specific type + * of stream in The Types of + * Streams. Remember that it is always possible that opening a stream + * will fail, in which case the creation function will return %NULL. + * + * Each stream remembers two character counts, the number of characters printed + * to and read from that stream. The write-count is exactly one per + * glk_put_char() call; it is figured before any platform-dependent character + * cookery. + * + * + * For example, if a newline character is converted to + * linefeed-plus-carriage-return, the stream's count still only goes up by + * one; similarly if an accented character is displayed as two characters. + * + * + * The read-count is exactly one per glk_get_char_stream() call, as long as the + * call returns an actual character (as opposed to an end-of-file token.) + * + * Glk has a notion of the current (output) stream. If you print + * text without specifying a stream, it goes to the current output stream. The + * current output stream may be %NULL, meaning that there isn't one. It is + * illegal to print text to stream %NULL, or to print to the current stream when + * there isn't one. + * + * If the stream which is the current stream is closed, the current stream + * becomes %NULL. + */ + +/** + * SECTION:glk-print + * @short_description: Printing to streams + * @include: glk.h + * + * You can print Latin-1 and Unicode characters, null-terminated strings, or + * buffers to any stream. The characters will be converted into the appropriate + * format for that stream. + */ + +/** + * SECTION:glk-read + * @short_description: Reading from streams + * @include: glk.h + * + * You can read Latin-1 or Unicode characters, buffers, or whole lines from any + * stream. The characters will be converted into the form in which you request + * them. + */ + +/** + * SECTION:glk-closing-streams + * @short_description: Closing streams and retrieving their character counts + * @include: glk.h + * + * When you close a Glk stream, you have the opportunity to examine the + * character counts — the number of characters written to or read from the + * stream. + */ + +/** + * SECTION:glk-stream-positions + * @short_description: Moving the read/write mark + * @include: glk.h + * + * You can set the position of the read/write mark in a stream. + * + * + * Which makes one wonder why they're called streams in the + * first place. Oh well. + * + */ + +/** + * SECTION:glk-stream-types + * @short_description: Window, memory, and file streams + * @include: glk.h + * + * Window Streams + * + * Every window has an output stream associated with it. This is created + * automatically, with #filemode_Write, when you open the window. You get it + * with glk_window_get_stream(). + * + * A window stream cannot be closed with glk_stream_close(). It is closed + * automatically when you close its window with glk_window_close(). + * + * Only printable characters (including newline) may be printed to a window + * stream. See Character + * Encoding. + * + * + * Memory Streams + * + * You can open a stream which reads from or writes to a space in memory. See + * glk_stream_open_memory() and glk_stream_open_memory_uni(). When opening a + * memory stream, you specify a buffer to which the stream's output will be + * written, and its length @buflen. + * + * When outputting, if more than @buflen characters are written to the stream, + * all of them beyond the buffer length will be thrown away, so as not to + * overwrite the buffer. (The character count of the stream will still be + * maintained correctly. That is, it will count the number of characters written + * into the stream, not the number that fit into the buffer.) + * + * If the buffer is %NULL, or for that matter if @buflen is zero, then + * everything written to the stream is thrown away. This + * may be useful if you are interested in the character count. + * + * When inputting, if more than @buflen characters are read from the stream, the + * stream will start returning -1 (signalling end-of-file.) If the buffer is + * %NULL, the stream will always return end-of-file. + * + * The data is written to the buffer exactly as it was passed to the printing + * functions (glk_put_char(), etc.); input functions will read the data exactly + * as it exists in memory. No platform-dependent cookery will be done on it. + * + * + * You can write a disk file in text mode, but a memory stream is effectively + * always in binary mode. + * + * + * Whether reading or writing, the contents of the buffer are undefined until + * the stream is closed. The library may store the data there as it is written, + * or deposit it all in a lump when the stream is closed. It is illegal to + * change the contents of the buffer while the stream is open. + * + * + * File Streams + * + * You can open a stream which reads from or writes to a disk file. See + * glk_stream_open_file() and glk_stream_open_file_uni(). + * + * The file may be written in text or binary mode; this is determined by the + * file reference you open the stream with. Similarly, platform-dependent + * attributes such as file type are determined by the file reference. See File References. + * + * + */ + +/** + * SECTION:glk-stream-other + * @short_description: Miscellaneous functions for streams + * @include: glk.h + * + * This section includes functions for streams that don't fit anywhere else. + */ + +/** + * SECTION:glk-fileref + * @short_description: A platform-independent way to refer to disk files + * @include: glk.h + * + * You deal with disk files using file references. Each fileref is an opaque C + * structure pointer; see Opaque + * Objects. + * + * A file reference contains platform-specific information about the name and + * location of the file, and possibly its type, if the platform has a notion of + * file type. It also includes a flag indication whether the file is a text file + * or binary file. + * + * + * Note that this is different from the standard C I/O library, in which you + * specify text or binary mode when the file is opened. + * + * + * A fileref does not have to refer to a file which actually exists. You can + * create a fileref for a nonexistent file, and then open it in write mode to + * create a new file. + * + * You always provide a usage argument when you create a fileref. The usage is a + * mask of constants (see below) to indicate the file type and the mode (text or + * binary.) These values are used when you create a new file, and also to filter + * file lists when the player is selecting a file to load. + * + * In general, you should use text mode if the player expects to read the file + * with a platform-native text editor; you should use binary mode if the file is + * to be read back by your program, or if the data must be stored exactly. Text + * mode is appropriate for #fileusage_Transcript; binary mode is appropriate for + * #fileusage_SavedGame and probably for #fileusage_InputRecord. #fileusage_Data + * files may be text or binary, depending on what you use them for. + */ + +/** + * SECTION:glk-fileref-types + * @short_description: Four different ways to create a file reference + * @include: glk.h + * + * There are four different functions for creating a fileref, depending on how + * you wish to specify it. Remember that it is always possible that a fileref + * creation will fail and return %NULL. + */ + +/** + * SECTION:glk-fileref-other + * @short_description: Miscellaneous functions for file references + * @include: glk.h + * + * This section includes functions for file references that don't fit anywhere + * else. + */ + +/*---------------- TYPES AND CONSTANTS FROM GLK.H ----------------------------*/ + +/** + * glui32: + * + * A 32-bit unsigned integer type, used wherever possible in Glk. + */ + +/** + * glsi32: + * + * A 32-bit signed integer type, rarely used. + */ + +/** + * GLK_MODULE_UNICODE: + * + * If this preprocessor symbol is defined, so are all the Unicode functions and + * constants (see #gestalt_Unicode). If not, not. + */ + +/** + * winid_t: + * + * Opaque structure representing a Glk window. It has no user-accessible + * members. + */ + +/** + * strid_t: + * + * Opaque structure representing an input or output stream. It has no + * user-accessible members. + */ + +/** + * frefid_t: + * + * Opaque structure representing a file reference. It has no user-accessible + * members. + */ + +/** + * gestalt_Version: + * + * For an example of the gestalt mechanism, consider the selector + * #gestalt_Version. If you do + * |[ + * #glui32 res; + * res = #glk_gestalt(#gestalt_Version, 0); + * ]| + * res will be set to a 32-bit number which encodes the version of + * the Glk spec which the library implements. The upper 16 bits stores the major + * version number; the next 8 bits stores the minor version number; the low 8 + * bits stores an even more minor version number, if any. + * + * + * So the version number 78.2.11 would be encoded as 0x004E020B. + * + * + * The current Glk specification version is 0.7.0, so this selector will return + * 0x00000700. + * + * |[ + * #glui32 res; + * res = #glk_gestalt_ext(#gestalt_Version, 0, NULL, 0); + * ]| + * does exactly the same thing. Note that, in either case, the second argument + * is not used; so you should always pass 0 to avoid future surprises. + */ + +/** + * gestalt_CharInput: + * + * If you set ch to a character code, or a special code (from + * 0xFFFFFFFF down), and call + * |[ + * #glui32 res; + * res = #glk_gestalt(#gestalt_CharInput, ch); + * ]| + * then res will be %TRUE (1) if that character can be typed by + * the player in character input, and %FALSE (0) if not. See Character Input. + */ + +/** + * gestalt_LineInput: + * + * If you set ch to a character code, and call + * |[ + * #glui32 res; + * res = #glk_gestalt(#gestalt_LineInput, ch); + * ]| + * then res will be %TRUE (1) if that character can be typed by the + * player in line input, and %FALSE (0) if not. Note that if ch is + * a nonprintable Latin-1 character (0 to 31, 127 to 159), then this is + * guaranteed to return %FALSE. See Line + * Input. + */ + +/** + * gestalt_CharOutput: + * + * If you set ch to a character code (Latin-1 or higher), and call + * |[ + * #glui32 res, len; + * res = #glk_gestalt_ext(#gestalt_CharOutput, ch, &len, 1); + * ]| + * then res will be one of #gestalt_CharOutput_CannotPrint, + * #gestalt_CharOutput_ExactPrint, or #gestalt_CharOutput_ApproxPrint (see + * below.) + * + * In all cases, len (the #glui32 value pointed at by the third + * argument) will be the number of actual glyphs which will be used to represent + * the character. In the case of #gestalt_CharOutput_ExactPrint, this will + * always be 1; for #gestalt_CharOutput_CannotPrint, it may be 0 (nothing + * printed) or higher; for #gestalt_CharOutput_ApproxPrint, it may be 1 or + * higher. This information may be useful when printing text in a fixed-width + * font. + * + * + * As described in Other API + * Conventions, you may skip this information by passing %NULL as the + * third argument in glk_gestalt_ext(), or by calling glk_gestalt() instead. + * + * + * This selector will always return #gestalt_CharOutput_CannotPrint if + * ch is an unprintable eight-bit character (0 to 9, 11 to 31, 127 + * to 159.) + * + * + * Make sure you do not get confused by signed byte values. If you set a + * char variable ch to 0xFE, the + * small-thorn character (þ), and then call + * |[ res = #glk_gestalt(#gestalt_CharOutput, ch); ]| + * then (by the definition of C/C++) ch will be sign-extended to + * 0xFFFFFFFE, which is not a legitimate character, even in Unicode. You + * should write + * |[ res = #glk_gestalt(#gestalt_CharOutput, (unsigned char)ch); ]| + * instead. + * + * + * Unicode includes the concept of non-spacing or combining characters, which + * do not represent glyphs; and double-width characters, whose glyphs take up + * two spaces in a fixed-width font. Future versions of this spec may + * recognize these concepts by returning a len of 0 or 2 when + * #gestalt_CharOutput_ExactPrint is used. For the moment, we are adhering to + * a policy of simple stuff first. + * + */ + +/** + * gestalt_CharOutput_CannotPrint: + * + * When the #gestalt_CharOutput selector returns this for a character, the + * character cannot be meaningfully printed. If you try, the player may see + * nothing, or may see a placeholder. + */ + +/** + * gestalt_CharOutput_ApproxPrint: + * + * When the #gestalt_CharOutput selector returns this for a character, the + * library will print some approximation of the character. It will be more or + * less right, but it may not be precise, and it may not be distinguishable from + * other, similar characters. (Examples: + * ae for the one-character + * æ ligature, + * e for + * è, | + * for a broken vertical bar (¦).) + */ + +/** + * gestalt_CharOutput_ExactPrint: + * + * When the #gestalt_CharOutput selector returns this for a character, the + * character will be printed exactly as defined. + */ + +/** + * gestalt_Unicode: + * + * The basic text functions will be available in every Glk library. The Unicode + * functions may or may not be available. Before calling them, you should use + * the following gestalt selector: + * |[ + * glui32 res; + * res = #glk_gestalt(#gestalt_Unicode, 0); + * ]| + * + * This returns 1 if the Unicode functions are available. If it returns 0, you + * should not try to call them. They may print nothing, print gibberish, or + * cause a run-time error. The Unicode functions include + * glk_buffer_to_lower_case_uni(), glk_buffer_to_upper_case_uni(), + * glk_buffer_to_title_case_uni(), glk_put_char_uni(), glk_put_string_uni(), + * glk_put_buffer_uni(), glk_put_char_stream_uni(), glk_put_string_stream_uni(), + * glk_put_buffer_stream_uni(), glk_get_char_stream_uni(), + * glk_get_buffer_stream_uni(), glk_get_line_stream_uni(), + * glk_request_char_event_uni(), glk_request_line_event_uni(), + * glk_stream_open_file_uni(), glk_stream_open_memory_uni(). + * + * If you are writing a C program, there is an additional complication. A + * library which does not support Unicode may not implement the Unicode + * functions at all. Even if you put gestalt tests around your Unicode calls, + * you may get link-time errors. If the + * glk.h file is so old that it does not + * declare the Unicode functions and constants, you may even get compile-time + * errors. + * + * To avoid this, you can perform a preprocessor test for the existence of + * #GLK_MODULE_UNICODE. + */ + +/** + * evtype_None: + * + * No event. This is a placeholder, and glk_select() never returns it. + */ + +/** + * evtype_Timer: + * + * An event that repeats at fixed intervals. See Timer Events. + */ + +/** + * evtype_CharInput: + * + * A keystroke event in a window. See Character Input Events. + * + * If a window has a pending request for character input, and the player hits a + * key in that window, glk_select() will return an event whose type is + * #evtype_CharInput. Once this happens, the request is complete; it is no + * longer pending. You must call glk_request_char_event() or + * glk_request_char_event_uni() if you want another character from that window. + * + * In the event structure, @win tells what window the event came from. @val1 + * tells what character was entered; this will be a character code, or a special + * keycode. (See Character + * Input.) If you called glk_request_char_event(), @val1 will be in + * 0..255, or else a special keycode. In any case, @val2 will be 0. + */ + +/** + * evtype_LineInput: + * + * A full line of input completed in a window. See Line Input Events. + * + * If a window has a pending request for line input, and the player hits + * enter in that window (or whatever action is appropriate to + * enter his input), glk_select() will return an event whose type is + * #evtype_LineInput. Once this happens, the request is complete; it is no + * longer pending. You must call glk_request_line_event() if you want another + * line of text from that window. + * + * In the event structure, @win tells what window the event came from. @val1 + * tells how many characters were entered. @val2 will be 0. The characters + * themselves are stored in the buffer specified in the original + * glk_request_line_event() or glk_request_line_event_uni() call. + * + * There is no null terminator stored in the buffer. + * + * It is illegal to print anything to a window which has line input pending. + * + * + * This is because the window may be displaying and editing the player's + * input, and printing anything would make life unnecessarily complicated for + * the library. + * + */ + +/** + * evtype_MouseInput: + * + * A mouse click in a window. See Mouse Input Events. + */ + +/** + * evtype_Arrange: + * + * An event signalling that the sizes of some windows have changed. + * + * Some platforms allow the player to resize the Glk window during play. This + * will naturally change the sizes of your windows. If this occurs, then + * immediately after all the rearrangement, glk_select() will return an event + * whose type is #evtype_Arrange. You can use this notification to redisplay the + * contents of a graphics or text grid window whose size has changed. + * + * + * The display of a text buffer window is entirely up to the library, so you + * don't need to worry about those. + * + * + * In the event structure, @win will be %NULL if all windows are affected. If + * only some windows are affected, @win will refer to a window which contains + * all the affected windows. @val1 and @val2 will be 0. + * + * + * You can always play it safe, ignore @win, and redraw every graphics and + * text grid window. + * + * + * An arrangement event is guaranteed to occur whenever the player causes any + * window to change size, as measured by its own metric. + * + * + * Size changes caused by you — for example, if you open, close, or + * resize a window — do not trigger arrangement events. You must be + * aware of the effects of your window management, and redraw the windows that + * you affect. + * + * + * + * It is possible that several different player actions can cause windows to + * change size. For example, if the player changes the screen resolution, an + * arrangement event might be triggered. This might also happen if the player + * changes his display font to a different size; the windows would then be + * different sizes in the metric of rows and columns, which is + * the important metric and the only one you have access to. + * + * + * Arrangement events, like timer events, can be returned by glk_select_poll(). + * But this will not occur on all platforms. You must be ready to receive an + * arrangement event when you call glk_select_poll(), but it is possible that it + * will not arrive until the next time you call glk_select(). + * + * + * This is because on some platforms, window resizing is handled as part of + * player input; on others, it can be triggered by an external process such as + * a window manager. + * + */ + +/** + * evtype_Redraw: + * + * An event signalling that graphics windows must be redrawn. + * + * On platforms that support graphics, it is possible that the contents of a + * graphics window will be lost, and have to be redrawn from scratch. If this + * occurs, then glk_select() will return an event whose type is #evtype_Redraw. + * + * In the event structure, @win will be %NULL if all windows are affected. If + * only some windows are affected, @win will refer to a window which contains + * all the affected windows. @val1 and @val2 will be 0. + * + * + * You can always play it safe, ignore @win, and redraw every graphics window. + * + * + * Affected windows are already cleared to their background color when you + * receive the redraw event. + * + * Redraw events can be returned by glk_select_poll(). But, like arrangement + * events, this is platform-dependent. See #evtype_Arrange. + * + * For more about redraw events and how they affect graphics windows, see Graphics Windows. + */ + +/** + * evtype_SoundNotify: + * + * On platforms that support sound, you can request to receive an + * #evtype_SoundNotify event when a sound finishes playing. See Playing Sounds. + */ + +/** + * evtype_Hyperlink: + * + * On platforms that support hyperlinks, you can request to receive an + * #evtype_Hyperlink event when the player selects a link. See Accepting Hyperlink + * Events. + */ + +/** + * event_t: + * @type: the event type + * @win: the window that spawned the event, or %NULL + * @val1: information, the meaning of which depends on the type of event + * @val2: more information, the meaning of which depends on the type of event + * + * The event structure is self-explanatory. @type is the event type. The window + * that spawned the event, if relevant, is in @win. The remaining fields contain + * more information specific to the event. + * + * The event types are described below. Note that #evtype_None is zero, and the + * other values are positive. Negative event types (0x80000000 to 0xFFFFFFFF) + * are reserved for implementation-defined events. + */ + +/** + * keycode_Unknown: + * + * Represents any key that has no Latin-1 or special code. + */ + +/** + * keycode_Left: + * + * Represents the left arrow key. + */ + +/** + * keycode_Right: + * + * Represents the right arrow key. + */ + +/** + * keycode_Up: + * + * Represents the up arrow key. + */ + +/** + * keycode_Down: + * + * Represents the down arrow key. + */ + +/** + * keycode_Return: + * + * Represents the return or enter keys. + */ + +/** + * keycode_Delete: + * + * Represents the delete or backspace keys. + */ + +/** + * keycode_Escape: + * + * Represents the escape key. + */ + +/** + * keycode_Tab: + * + * Represents the tab key. + */ + +/** + * keycode_PageUp: + * + * Represents the page up key. + */ + +/** + * keycode_PageDown: + * + * Represents the page down key. + */ + +/** + * keycode_Home: + * + * Represents the home key. + */ + +/** + * keycode_End: + * + * Represents the end key. + */ + +/** + * keycode_Func1: + * + * Represents the F1 key. + */ + +/** + * keycode_Func2: + * + * Represents the F2 key. + */ + +/** + * keycode_Func3: + * + * Represents the F3 key. + */ + +/** + * keycode_Func4: + * + * Represents the F4 key. + */ + +/** + * keycode_Func5: + * + * Represents the F5 key. + */ + +/** + * keycode_Func6: + * + * Represents the F6 key. + */ + +/** + * keycode_Func7: + * + * Represents the F7 key. + */ + +/** + * keycode_Func8: + * + * Represents the F8 key. + */ + +/** + * keycode_Func9: + * + * Represents the F9 key. + */ + +/** + * keycode_Func10: + * + * Represents the F10 key. + */ + +/** + * keycode_Func11: + * + * Represents the F11 key. + */ + +/** + * keycode_Func12: + * + * Represents the F12 key. + */ + +/** + * keycode_MAXVAL: + * + * This value is equal to the number of special keycodes. The last keycode is + * The last keycode is always + * + * (0x100000000 - keycode_MAXVAL) + * (0x100000000 - keycode_MAXVAL) + * + * . + */ + +/** + * stream_result_t: + * @readcount: Number of characters read from the stream. + * @writecount: Number of characters printed to the stream, including ones that + * were thrown away. + * + * If you are interested in the character counts of a stream (see Streams), then you can pass a pointer to + * #stream_result_t as an argument of glk_stream_close() or glk_window_close(). + * The structure will be filled with the stream's final character counts. + */ + +/** + * wintype_AllTypes: + * + * A constant representing all window types, which may be used as the @wintype + * argument in glk_stylehint_set(). + */ + +/** + * wintype_Pair: + * + * A pair window is completely filled by the two windows it contains. It + * supports no input and no output, and it has no size. + * + * You cannot directly create a pair window; one is automatically created + * every time you split a window with glk_window_open(). Pair windows are + * always created with a rock value of 0. + * + * You can close a pair window with glk_window_close(); this also closes every + * window contained within the pair window. + * + * It is legal to split a pair window when you call glk_window_open(). + */ + +/** + * wintype_Blank: + * + * A blank window is always blank. It supports no input and no output. (You + * can call glk_window_get_stream() on it, as you can with any window, but + * printing to the resulting stream has no effect.) A blank window has no + * size; glk_window_get_size() will return (0,0), and it is illegal to set a + * window split with a fixed size in the measurement system of a blank window. + * + * + * A blank window is not the same as there being no windows. When Glk starts + * up, there are no windows at all, not even a window of the blank type. + * + */ + +/** + * wintype_TextBuffer: + * + * A text buffer window contains a linear stream of text. It supports output; + * when you print to it, the new text is added to the end. There is no way for + * you to affect text which has already been printed. There are no guarantees + * about how much text the window keeps; old text may be stored forever, so + * that the user can scroll back to it, or it may be thrown away as soon as it + * scrolls out of the window. + * + * + * Therefore, there may or may not be a player-controllable scroll bar or + * other scrolling widget. + * + * + * The display of the text in a text buffer is up to the library. Lines will + * probably not be broken in the middles of words — but if they are, the + * library is not doing anything illegal, only ugly. Text selection and copying + * to a clipboard, if available, are handled however is best on the player's + * machine. Paragraphs (as defined by newline characters in the output) may be + * indented. + * + * + * You should not, in general, fake this by printing spaces before each + * paragraph of prose text. Let the library and player preferences handle + * that. Special cases (like indented lists) are of course up to you. + * + * + * When a text buffer is cleared (with glk_window_clear()), the library will do + * something appropriate; the details may vary. It may clear the window, with + * later text appearing at the top — or the bottom. It may simply print + * enough blank lines to scroll the current text out of the window. It may + * display a distinctive page-break symbol or divider. + * + * The size of a text buffer window is necessarily imprecise. Calling + * glk_window_get_size() will return the number of rows and columns that would + * be available if the window was filled with + * 0 (zero) characters in the normal font. + * However, the window may use a non-fixed-width font, so that number of + * characters in a line could vary. The window might even support + * variable-height text (say, if the player is using large text for emphasis); + * that would make the number of lines in the window vary as well. + * + * Similarly, when you set a fixed-size split in the measurement system of a + * text buffer, you are setting a window which can handle a fixed number of rows + * (or columns) of 0 characters. The number of rows (or + * characters) that will actually be displayed depends on font variances. + * + * A text buffer window supports both character and line input, but not mouse + * input. + * + * In character input, there will be some visible signal that the window is + * waiting for a keystroke. (Typically, a cursor at the end of the text.) When + * the player hits a key in that window, an event is generated, but the key is + * not printed in the window. + * + * In line input, again, there will be some visible signal. It is most common + * for the player to compose input in the window itself, at the end of the text. + * (This is how IF story input usually looks.) But it's not strictly required. + * An alternative approach is the way MUD clients usually work: there is a + * dedicated one-line input window, outside of Glk's window space, and the user + * composes input there. + * + * + * If this approach is used, there will still be some way to handle input from + * two windows at once. It is the library's responsibility to make this + * available to the player. You only need request line input and wait for the + * result. + * + * + * When the player finishes his line of input, the library will display the + * input text at the end of the buffer text (if it wasn't there already.) It + * will be followed by a newline, so that the next text you print will start a + * new line (paragraph) after the input. + * + * If you call glk_cancel_line_event(), the same thing happens; whatever text + * the user was composing is visible at the end of the buffer text, followed by + * a newline. + */ + +/** + * wintype_TextGrid: + * + * A text grid contains a rectangular array of characters, in a fixed-width + * font. Its size is the number of columns and rows of the array. + * + * A text grid window supports output. It maintains knowledge of an output + * cursor position. When the window is opened, it is filled with blanks (space + * characters), and the output cursor starts in the top left corner — + * character (0,0). If the window is cleared with glk_window_clear(), the window + * is filled with blanks again, and the cursor returns to the top left corner. + * + * When you print, the characters of the output are laid into the array in + * order, left to right and top to bottom. When the cursor reaches the end of a + * line, it goes to the beginning of the next line. The library makes no attempt + * to wrap lines at word breaks. + * + * + * Note that printing fancy characters may cause the cursor to advance more + * than one position per character. (For example, the æ + * ligature may print as two characters.) See Output, for how to test this situation. + * + * + * You can set the cursor position with glk_window_move_cursor(). + * + * When a text grid window is resized smaller, the bottom or right area is + * thrown away, but the remaining area stays unchanged. When it is resized + * larger, the new bottom or right area is filled with blanks. + * + * + * You may wish to watch for #evtype_Arrange events, and clear-and-redraw your + * text grid windows when you see them change size. + * + * + * Text grid window support character and line input, as well as mouse input (if + * a mouse is available.) + * + * Mouse input returns the position of the character that was touched, from + * (0,0) to + * + * (width-1,height-1) + * (width - 1, height - 1) + * + * . + * + * Character input is as described in the previous section. + * + * Line input is slightly different; it is guaranteed to take place in the + * window, at the output cursor position. The player can compose input only to + * the right edge of the window; therefore, the maximum input length is + * + * (windowwidth - 1 - cursorposition) + * (windowwidth - 1 - cursorposition) + * + * . If the maxlen argument of glk_request_line_event() is smaller than this, + * the library will not allow the input cursor to go more than maxlen characters + * past its start point. + * + * + * This allows you to enter text in a fixed-width field, without the player + * being able to overwrite other parts of the window. + * + * + * When the player finishes his line of input, it will remain visible in the + * window, and the output cursor will be positioned at the beginning of the + * next row. Again, if you glk_cancel_line_event(), the + * same thing happens. + */ + +/** + * wintype_Graphics: + * + * A graphics window contains a rectangular array of pixels. Its size is the + * number of columns and rows of the array. + * + * Each graphics window has a background color, which is initially white. You + * can change this; see Graphics in Graphics + * Windows. + * + * When a text grid window is resized smaller, the bottom or right area is + * thrown away, but the remaining area stays unchanged. When it is resized + * larger, the new bottom or right area is filled with the background color. + * + * + * You may wish to watch for #evtype_Arrange events, and clear-and-redraw your + * graphics windows when you see them change size. + * + * + * In some libraries, you can receive a graphics-redraw event (#evtype_Redraw) + * at any time. This signifies that the window in question has been cleared to + * its background color, and must be redrawn. If you create any graphics + * windows, you must handle these events. + * + * + * Redraw events can be triggered when a Glk window is uncovered or made + * visible by the platform's window manager. On the other hand, some Glk + * libraries handle these problem automatically — for example, with a + * backing store — and do not send you redraw events. On the third hand, + * the backing store may be discarded if memory is low, or for other reasons + * — perhaps the screen's color depth has changed. So redraw events are + * always a possibility, even in clever libraries. This is why you must be + * prepared to handle them. + * + * However, you will not receive a redraw event when you create a graphics + * window. It is assumed that you will do the initial drawing of your own + * accord. You also do not get redraw events when a graphics window is + * enlarged. If you ordered the enlargement, you already know about it; if the + * player is responsible, you receive a window-arrangement event, which covers + * the situation. + * + * + * For a description of the drawing functions that apply to graphics windows, + * see Graphics in Graphics + * Windows. + * + * Graphics windows support no text input or output. + * + * Not all libraries support graphics windows. You can test whether Glk graphics + * are available using the gestalt system. In a C program, you can also test + * whether the graphics functions are defined at compile-time. See Testing for Graphics + * Capabilities. + * + * + * As with all windows, you should also test for %NULL when you create a + * graphics window. + * + */ + +/** + * winmethod_Left: + * + * When calling glk_window_open() with this @method, the new window will be + * to the left of the old one which was split. + */ + +/** + * winmethod_Right: + * + * When calling glk_window_open() with this @method, the new window will be + * to the right of the old one which was split. + */ + +/** + * winmethod_Above: + * + * When calling glk_window_open() with this @method, the new window will be + * above the old one which was split. + */ + +/** + * winmethod_Below: + * + * When calling glk_window_open() with this @method, the new window will be + * below the old one which was split. + */ + +/** + * winmethod_DirMask: + * + * Bitwise AND this value with a window splitting method argument to find + * whether the split is #winmethod_Left, #winmethod_Right, #winmethod_Above, or + * #winmethod_Below. + */ + +/** + * winmethod_Fixed: + * + * When calling glk_window_open() with this @method, the new window will be + * a fixed size. (See glk_window_open()). + */ + +/** + * winmethod_Proportional: + * + * When calling glk_window_open() with this @method, the new window will be + * a given proportion of the old window's size. (See glk_window_open()). + */ + +/** + * winmethod_DivisionMask: + * + * Bitwise AND this value with a window splitting method argument to find + * whether the new window has #winmethod_Fixed or #winmethod_Proportional. + */ + +/** + * fileusage_Data: + * + * Any other kind of file (preferences, statistics, arbitrary data.) + */ + +/** + * fileusage_SavedGame: + * + * A file which stores game state. + */ + +/** + * fileusage_Transcript: + * + * A file which contains a stream of text from the game (often an echo stream + * from a window.) + */ + +/** + * fileusage_InputRecord: + * + * A file which records player input. + */ + +/** + * fileusage_TextMode: + * + * The file contents will be transformed to a platform-native text file as they + * are written out. Newlines may be converted to linefeeds or + * linefeed-plus-carriage-return combinations; Latin-1 characters may be + * converted to native character codes. When reading a file in text mode, native + * line breaks will be converted back to newline (0x0A) characters, and native + * character codes may be converted to Latin-1. + * + * + * Line breaks will always be converted; other conversions are more + * questionable. If you write out a file in text mode, and then read it back + * in text mode, high-bit characters (128 to 255) may be transformed or lost. + * + * Chimara + * + * Text mode files in Chimara are in UTF-8, which is GTK+'s native file + * encoding. + * + */ + +/** + * fileusage_BinaryMode: + * + * The file contents will be stored exactly as they are written, and read back + * in the same way. The resulting file may not be viewable on platform-native + * text file viewers. + */ + +/** + * fileusage_TypeMask: + * + * Bitwise AND this value with a file usage argument to find whether the file + * type is #fileusage_SavedGame, #fileusage_Transcript, #fileusage_InputRecord, + * or #fileusage_Data. + */ + +/** + * filemode_Write: + * + * An output stream. + * + * + * Corresponds to mode "w" in the stdio library, using fopen(). + * + */ + +/** + * filemode_Read: + * + * An input stream. + * + * + * Corresponds to mode "r" in the stdio library, using fopen(). + * + */ + +/** + * filemode_ReadWrite: + * + * Both an input and an output stream. + * + * + * Corresponds to mode "r+" in the stdio library, using fopen(). + * + */ + +/** + * filemode_WriteAppend: + * + * An output stream, but the data will added to the end of whatever already + * existed in the destination, instead of replacing it. + * + * + * Corresponds to mode "a" in the stdio library, using fopen(). + * + */ + +/** + * seekmode_Start: + * + * In glk_stream_set_position(), signifies that @pos is counted in characters + * after the beginning of the file. + */ + +/** + * seekmode_Current: + * + * In glk_stream_set_position(), signifies that @pos is counted in characters + * after the current position (moving backwards if @pos is negative.) + */ + +/** + * seekmode_End: + * + * In glk_stream_set_position(), signifies that @pos is counted in characters + * after the end of the file. (@pos should always be zero or negative, so that + * this will move backwards to a position within the file. + */ + diff --git a/libchimara/event.c b/libchimara/event.c new file mode 100644 index 0000000..d7cb659 --- /dev/null +++ b/libchimara/event.c @@ -0,0 +1,94 @@ +#include "event.h" +#include "magic.h" +#include "glk.h" +#include + +#include "chimara-glk-private.h" + +extern ChimaraGlkPrivate *glk_data; + +#define EVENT_TIMEOUT_MICROSECONDS (3000000) + +/* Internal function: push an event onto the event queue. If the event queue is +full, wait for max three seconds and then drop the event. If the event queue is +NULL, i.e. freed, then fail silently. */ +void +event_throw(glui32 type, winid_t win, glui32 val1, glui32 val2) +{ + if(!glk_data->event_queue) + return; + + GTimeVal timeout; + g_get_current_time(&timeout); + g_time_val_add(&timeout, EVENT_TIMEOUT_MICROSECONDS); + + g_mutex_lock(glk_data->event_lock); + + /* Wait for room in the event queue */ + while( g_queue_get_length(glk_data->event_queue) >= EVENT_QUEUE_MAX_LENGTH ) + if( !g_cond_timed_wait(glk_data->event_queue_not_full, glk_data->event_lock, &timeout) ) + { + /* Drop the event after 3 seconds */ + g_mutex_unlock(glk_data->event_lock); + return; + } + + event_t *event = g_new0(event_t, 1); + event->type = type; + event->win = win; + event->val1 = val1; + event->val2 = val2; + g_queue_push_head(glk_data->event_queue, event); + + /* Signal that there is an event */ + g_cond_signal(glk_data->event_queue_not_empty); + + g_mutex_unlock(glk_data->event_lock); +} + +/** + * glk_select: + * @event: Pointer to an #event_t. + * + * Causes the program to wait for an event, and then store it in the structure + * pointed to by @event. Unlike most Glk functions that take pointers, the + * argument of glk_select() may not be %NULL. + * + * Most of the time, you only get the events that you request. However, there + * are some events which can arrive at any time. This is why you must always + * call glk_select() in a loop, and continue the loop until you get the event + * you really want. + */ +void +glk_select(event_t *event) +{ + g_return_if_fail(event != NULL); + + g_mutex_lock(glk_data->event_lock); + + /* Wait for an event */ + while( g_queue_is_empty(glk_data->event_queue) ) + g_cond_wait(glk_data->event_queue_not_empty, glk_data->event_lock); + + event_t *retrieved_event = g_queue_pop_tail(glk_data->event_queue); + if(retrieved_event == NULL) + { + g_mutex_unlock(glk_data->event_lock); + WARNING("Retrieved NULL event from non-empty event queue"); + return; + } + memcpy(event, retrieved_event, sizeof(event_t)); + g_free(retrieved_event); + + /* Signal that the event queue is no longer full */ + g_cond_signal(glk_data->event_queue_not_full); + + g_mutex_unlock(glk_data->event_lock); + + /* Check for interrupt */ + glk_tick(); + + /* If an abort event was generated, the thread should have exited by now */ + g_assert(event->type != evtype_Abort); +} + diff --git a/libchimara/event.h b/libchimara/event.h new file mode 100644 index 0000000..7cf80bd --- /dev/null +++ b/libchimara/event.h @@ -0,0 +1,12 @@ +#ifndef EVENT_H +#define EVENT_H + +#include +#include "glk.h" + +#define EVENT_QUEUE_MAX_LENGTH (100) +#define evtype_Abort (-1) + +G_GNUC_INTERNAL void event_throw(glui32 type, winid_t win, glui32 val1, glui32 val2); + +#endif diff --git a/libchimara/fileref.c b/libchimara/fileref.c new file mode 100644 index 0000000..2b5cf53 --- /dev/null +++ b/libchimara/fileref.c @@ -0,0 +1,406 @@ +#include +#include +#include +#include +#include +#include "fileref.h" +#include "magic.h" +#include "chimara-glk-private.h" + +extern ChimaraGlkPrivate *glk_data; + +/** + * glk_fileref_iterate: + * @fref: A file reference, or %NULL. + * @rockptr: Return location for the next fileref's rock, or %NULL. + * + * Iterates through all the existing filerefs. See Iterating Through Opaque + * Objects. + * + * Returns: the next file reference, or %NULL if there are no more. + */ +frefid_t +glk_fileref_iterate(frefid_t fref, glui32 *rockptr) +{ + VALID_FILEREF_OR_NULL(fref, return NULL); + + GList *retnode; + + if(fref == NULL) + retnode = glk_data->fileref_list; + else + retnode = fref->fileref_list->next; + frefid_t retval = retnode? (frefid_t)retnode->data : NULL; + + /* Store the fileref's rock in rockptr */ + if(retval && rockptr) + *rockptr = glk_fileref_get_rock(retval); + + return retval; +} + +/** + * glk_fileref_get_rock: + * @fref: A file reference. + * + * Retrieves the file reference @fref's rock value. See Rocks. + * + * Returns: A rock value. + */ +glui32 +glk_fileref_get_rock(frefid_t fref) +{ + VALID_FILEREF(fref, return 0); + return fref->rock; +} + +/* Internal function: create a fileref using the given parameters. */ +static frefid_t +fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode) +{ + g_return_val_if_fail(filename != NULL, NULL); + + frefid_t f = g_new0(struct glk_fileref_struct, 1); + f->magic = MAGIC_FILEREF; + f->rock = rock; + f->filename = g_strdup(filename); + f->usage = usage; + f->orig_filemode = orig_filemode; + + /* Add it to the global fileref list */ + glk_data->fileref_list = g_list_prepend(glk_data->fileref_list, f); + f->fileref_list = glk_data->fileref_list; + + return f; +} + +/** + * glk_fileref_create_temp: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @rock: The new fileref's rock value. + * + * Creates a reference to a temporary file. It is always a new file (one which + * does not yet exist). The file (once created) will be somewhere out of the + * player's way. + * + * + * This is why no name is specified; the player will never need to know it. + * + * + * A temporary file should never be used for long-term storage. It may be + * deleted automatically when the program exits, or at some later time, say + * when the machine is turned off or rebooted. You do not have to worry about + * deleting it yourself. + * + * Returns: A new fileref, or #NULL if the fileref creation failed. + */ +frefid_t +glk_fileref_create_temp(glui32 usage, glui32 rock) +{ + /* Get a temp file */ + GError *error = NULL; + gchar *filename = NULL; + gint handle = g_file_open_tmp("glkXXXXXX", &filename, &error); + if(handle == -1) + { + WARNING_S("Error creating temporary file", error->message); + if(filename) + g_free(filename); + return NULL; + } + if(close(handle) == -1) /* There is no g_close() */ + { + IO_WARNING( "Error closing temporary file", filename, g_strerror(errno) ); + if(filename) + g_free(filename); + return NULL; + } + + frefid_t f = fileref_new(filename, rock, usage, filemode_Write); + g_free(filename); + return f; +} + +/** + * glk_fileref_create_by_prompt: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @fmode: File mode, contolling the dialog's behavior. + * @rock: The new fileref's rock value. + * + * Creates a reference to a file by asking the player to locate it. The library + * may simply prompt the player to type a name, or may use a platform-native + * file navigation tool. (The prompt, if any, is inferred from the usage + * argument.) + * + * Chimara + * + * Chimara uses a GtkFileChooserDialog. + * + * + * @fmode must be one of these values: + * + * + * #filemode_Read + * The file must already exist; and the player will be asked + * to select from existing files which match the usage. + * + * + * #filemode_Write + * The file should not exist; if the player selects an + * existing file, he will be warned that it will be replaced. + * + * + * + * #filemode_ReadWrite + * The file may or may not exist; if it already exists, the + * player will be warned that it will be modified. + * + * + * #filemode_WriteAppend + * Same behavior as #filemode_ReadWrite. + * + * + * + * The @fmode argument should generally match the @fmode which will be used to + * open the file. + * + * + * It is possible that the prompt or file tool will have a + * cancel option. If the player chooses this, + * glk_fileref_create_by_prompt() will return %NULL. This is a major reason + * why you should make sure the return value is valid before you use it. + * + * + * Returns: A new fileref, or #NULL if the fileref creation failed or the + * dialog was canceled. + */ +frefid_t +glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock) +{ + /* TODO: Remember current working directory and last used filename + for each usage */ + GtkWidget *chooser; + + gdk_threads_enter(); + + switch(fmode) + { + case filemode_Read: + chooser = gtk_file_chooser_dialog_new("Select a file to open", NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), + GTK_FILE_CHOOSER_ACTION_OPEN); + break; + case filemode_Write: + chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), + GTK_FILE_CHOOSER_ACTION_SAVE); + gtk_file_chooser_set_do_overwrite_confirmation( + GTK_FILE_CHOOSER(chooser), TRUE); + break; + case filemode_ReadWrite: + case filemode_WriteAppend: + chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), + GTK_FILE_CHOOSER_ACTION_SAVE); + break; + default: + ILLEGAL_PARAM("Unknown file mode: %u", fmode); + gdk_threads_leave(); + return NULL; + } + + if(gtk_dialog_run( GTK_DIALOG(chooser) ) != GTK_RESPONSE_ACCEPT) + { + gtk_widget_destroy(chooser); + gdk_threads_leave(); + return NULL; + } + gchar *filename = + gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) ); + frefid_t f = fileref_new(filename, rock, usage, fmode); + g_free(filename); + gtk_widget_destroy(chooser); + + gdk_threads_leave(); + return f; +} + +/** + * glk_fileref_create_by_name: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @name: A filename. + * @rock: The new fileref's rock value. + * + * This creates a reference to a file with a specific name. The file will be + * in a fixed location relevant to your program, and visible to the player. + * + * + * This usually means in the same directory as your program. + * + * Chimara + * + * In Chimara, the file is created in the current working directory. + * + * + * Since filenames are highly platform-specific, you should use + * glk_fileref_create_by_name() with care. It is legal to pass any string in the + * name argument. However, the library may have to mangle, transform, or + * truncate the string to make it a legal native filename. + * + * + * For example, if you create two filerefs with the names File + * and FILE, they may wind up pointing to the same file; the + * platform may not support case distinctions in file names. Another example: + * on a platform where file type is specified by filename suffix, the library + * will add an appropriate suffix based on the usage; any suffix in the string + * will be overwritten or added to. For that matter, remember that the period + * is not a legal character in Acorn filenames... + * + * + * The most conservative approach is to pass a string of no more than 8 + * characters, consisting entirely of upper-case letters and numbers, starting + * with a letter. You can then be reasonably sure that the resulting filename + * will display all the characters you specify — in some form. + * + * Returns: A new fileref, or %NULL if the fileref creation failed. + */ +frefid_t +glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock) +{ + g_return_val_if_fail(name != NULL && strlen(name) > 0, NULL); + + /* Find out what encoding filenames are in */ + const gchar **charsets; /* Do not free */ + g_get_filename_charsets(&charsets); + + /* Convert name to that encoding */ + GError *error = NULL; + gchar *osname = g_convert(name, -1, charsets[0], "ISO-8859-1", NULL, NULL, + &error); + if(osname == NULL) + { + WARNING_S("Error during latin1->filename conversion", error->message); + return NULL; + } + + /* Do any string-munging here to remove illegal characters from filename. + On ext3, the only illegal characters are '/' and '\0'. TODO: Should this + function be allowed to reference files in other directories, or should we + disallow '/'? */ + + frefid_t f = fileref_new(osname, rock, usage, filemode_ReadWrite); + g_free(osname); + return f; +} + +/** + * glk_fileref_create_from_fileref: + * @usage: Bitfield with one or more of the fileusage_ constants. + * @fref: Fileref to copy. + * @rock: The new fileref's rock value. + * + * This copies an existing file reference @fref, but changes the usage. (The + * original fileref is not modified.) + * + * The use of this function can be tricky. If you change the type of the fileref + * (#fileusage_Data, #fileusage_SavedGame, etc), the new reference may or may + * not point to the same actual disk file. + * + * + * This generally depends on whether the platform uses suffixes to indicate + * file type. + * + * + * If you do this, and open both file references for writing, the results are + * unpredictable. It is safest to change the type of a fileref only if it refers + * to a nonexistent file. + * + * If you change the mode of a fileref (#fileusage_TextMode, + * #fileusage_BinaryMode), but leave the rest of the type unchanged, the new + * fileref will definitely point to the same disk file as the old one. + * + * Obviously, if you write to a file in text mode and then read from it in + * binary mode, the results are platform-dependent. + * + * Returns: A new fileref, or %NULL if the fileref creation failed. + */ +frefid_t +glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock) +{ + VALID_FILEREF(fref, return NULL); + return fileref_new(fref->filename, rock, usage, fref->orig_filemode); +} + +/** + * glk_fileref_destroy: + * @fref: Fileref to destroy. + * + * Destroys a fileref which you have created. This does not + * affect the disk file; it just reclaims the resources allocated by the + * glk_fileref_create... function. + * + * It is legal to destroy a fileref after opening a file with it (while the + * file is still open.) The fileref is only used for the opening operation, + * not for accessing the file stream. + */ +void +glk_fileref_destroy(frefid_t fref) +{ + VALID_FILEREF(fref, return); + + glk_data->fileref_list = g_list_delete_link(glk_data->fileref_list, fref->fileref_list); + if(fref->filename) + g_free(fref->filename); + + fref->magic = MAGIC_FREE; + g_free(fref); +} + +/** + * glk_fileref_delete_file: + * @fref: A refrence to the file to delete. + * + * Deletes the file referred to by @fref. It does not destroy @fref itself. + */ +void +glk_fileref_delete_file(frefid_t fref) +{ + VALID_FILEREF(fref, return); + if( glk_fileref_does_file_exist(fref) ) + if(g_unlink(fref->filename) == -1) + IO_WARNING( "Error deleting file", fref->filename, g_strerror(errno) ); +} + +/** + * glk_fileref_does_file_exist: + * @fref: A fileref to check. + * + * Checks whether the file referred to by @fref exists. + * + * Returns: %TRUE (1) if @fref refers to an existing file, and %FALSE (0) if + * not. + */ +glui32 +glk_fileref_does_file_exist(frefid_t fref) +{ + VALID_FILEREF(fref, return 0); + if( g_file_test(fref->filename, G_FILE_TEST_EXISTS) ) + return 1; + return 0; +} + diff --git a/libchimara/fileref.h b/libchimara/fileref.h new file mode 100644 index 0000000..cfbdcca --- /dev/null +++ b/libchimara/fileref.h @@ -0,0 +1,28 @@ +#ifndef FILEREF_H +#define FILEREF_H + +#include +#include "glk.h" + +/** + * glk_fileref_struct: + * + * This is an opaque structure (see + * Opaque Structures and should not be accessed directly. + */ +struct glk_fileref_struct +{ + /*< private >*/ + glui32 magic, rock; + /* Pointer to the list node in the global fileref list that contains this + fileref */ + GList* fileref_list; + /* Fileref parameters */ + gchar *filename; /* Always stored in the default filename encoding, not + UTF8 or Latin-1 */ + glui32 orig_filemode; /* Used to check if the user gets a fileref in read + mode and then tries to open it in write mode */ + glui32 usage; +}; + +#endif diff --git a/libchimara/gestalt.c b/libchimara/gestalt.c new file mode 100644 index 0000000..398aede --- /dev/null +++ b/libchimara/gestalt.c @@ -0,0 +1,123 @@ +#include /* Surprisingly, the only symbol needed is NULL */ +#include "glk.h" + +/* Version of the Glk specification implemented by this library */ +#define MAJOR_VERSION 0 +#define MINOR_VERSION 7 +#define SUB_VERSION 0 + +/** + * glk_gestalt: + * @sel: A selector, representing which capability to request information + * about. + * @val: Extra information, depending on the value of @sel. + * + * Calls the gestalt system to request information about selector @sel, without + * passing an array to store extra information in (see glk_gestalt_ext()). + * + * Returns: an integer, depending on what selector was called. + */ +glui32 +glk_gestalt(glui32 sel, glui32 val) +{ + return glk_gestalt_ext(sel, val, NULL, 0); +} + +/** + * glk_gestalt_ext: + * @sel: A selector, representing which capability to request information + * about. + * @val: Extra information, depending on the value of @sel. + * @arr: Location of an array to store extra information in, or %NULL. + * @arrlen: Length of @arr, or 0 if @arr is %NULL. + * + * Calls the gestalt system to request information about the capabilities of the + * API. The selector @sel tells which capability you are requesting information + * about; the other three arguments are additional information, which may or may + * not be meaningful. The @arr and @arrlen arguments of glk_gestalt_ext() are + * always optional; you may always pass %NULL and 0, if you do not want whatever + * information they represent. glk_gestalt() is simply a shortcut for this; + * #glk_gestalt(x, y) is exactly the same as + * #glk_gestalt_ext(x, y, %NULL, 0). + * + * The critical point is that if the Glk library has never heard of the selector + * @sel, it will return 0. It is always safe to call + * #glk_gestalt(x, y) (or #glk_gestalt_ext(x, y, %NULL, + * 0)). Even if you are using an old library, which was compiled before + * the given capability was imagined, you can test for the capability by calling + * glk_gestalt(); the library will correctly indicate that it does not support + * it, by returning 0. + * + * (It is also safe to call #glk_gestalt_ext(x, y, z, zlen) for an + * unknown selector x, where z is not %NULL, as long + * as z points at an array of at least zlen elements. + * The selector will be careful not to write beyond that point in the array, if + * it writes to the array at all.) + * + * (If a selector does not use the second argument, you should always pass 0; do + * not assume that the second argument is simply ignored. This is because the + * selector may be extended in the future. You will continue to get the current + * behavior if you pass 0 as the second argument, but other values may produce + * other behavior.) + * + * Returns: an integer, depending on what selector was called. + */ +glui32 +glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen) +{ + switch(sel) + { + /* Version number */ + case gestalt_Version: + return (MAJOR_VERSION << 16) + (MINOR_VERSION << 8) + SUB_VERSION; + + /* Which characters can the player type in line input? */ + case gestalt_LineInput: + /* Does not accept control chars */ + if( val < 32 || (val >= 127 && val <= 159) ) + return 0; + return 1; + + /* Which characters can the player type in char input? */ + case gestalt_CharInput: + /* Does not accept control chars or unknown */ + if( val < 32 || (val >= 127 && val <= 159) || val == keycode_Unknown ) + return 0; + return 1; + + /* Which characters can we print? */ + case gestalt_CharOutput: + /* All characters are printed as one character, in any case */ + if(arr && arrlen > 0) + *arr = 1; + /* Cannot print control chars except \n, or chars > 255 */ + if( (val < 32 && val != 10) || (val >= 127 && val <= 159) || (val > 255) ) + return gestalt_CharOutput_CannotPrint; + /* Can print all other Latin-1 characters */ + return gestalt_CharOutput_ExactPrint; + + /* Unicode capabilities present */ + case gestalt_Unicode: + return 1; + + /* Timer capabilities present */ + case gestalt_Timer: + return 1; + + /* Unsupported capabilities */ + case gestalt_MouseInput: + case gestalt_Graphics: + case gestalt_DrawImage: + case gestalt_Sound: + case gestalt_SoundVolume: + case gestalt_SoundNotify: + case gestalt_Hyperlinks: + case gestalt_HyperlinkInput: + case gestalt_SoundMusic: + case gestalt_GraphicsTransparency: + /* Selector not supported */ + default: + return 0; + } +} + diff --git a/libchimara/gi_blorb.c b/libchimara/gi_blorb.c new file mode 100644 index 0000000..329280d --- /dev/null +++ b/libchimara/gi_blorb.c @@ -0,0 +1,603 @@ +/* gi_blorb.c: Blorb library layer for Glk API. + gi_blorb version 1.4. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + + This file is copyright 1998-2000 by Andrew Plotkin. You may copy, + distribute, and incorporate it into your own programs, by any means + and under any conditions, as long as you do not modify it. You may + also modify this file, incorporate it into your own programs, + and distribute the modified version, as long as you retain a notice + in your program or documentation which mentions my name and the URL + shown above. +*/ + +#include "glk.h" +#include "gi_blorb.h" + +#ifndef NULL +#define NULL 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/* The magic macro of endian conversion. */ + +#define giblorb_native4(v) \ + ( (((glui32)((v)[3]) ) & 0x000000ff) \ + | (((glui32)((v)[2]) << 8) & 0x0000ff00) \ + | (((glui32)((v)[1]) << 16) & 0x00ff0000) \ + | (((glui32)((v)[0]) << 24) & 0xff000000)) + +/* More four-byte constants. */ + +#define giblorb_ID_FORM (giblorb_make_id('F', 'O', 'R', 'M')) +#define giblorb_ID_IFRS (giblorb_make_id('I', 'F', 'R', 'S')) +#define giblorb_ID_RIdx (giblorb_make_id('R', 'I', 'd', 'x')) + +/* giblorb_chunkdesc_t: Describes one chunk of the Blorb file. */ +typedef struct giblorb_chunkdesc_struct { + glui32 type; + glui32 len; + glui32 startpos; /* start of chunk header */ + glui32 datpos; /* start of data (either startpos or startpos+8) */ + + void *ptr; /* pointer to malloc'd data, if loaded */ + int auxdatnum; /* entry in the auxsound/auxpict array; -1 if none. + This only applies to chunks that represent resources; */ + +} giblorb_chunkdesc_t; + +/* giblorb_resdesc_t: Describes one resource in the Blorb file. */ +typedef struct giblorb_resdesc_struct { + glui32 usage; + glui32 resnum; + glui32 chunknum; +} giblorb_resdesc_t; + +/* giblorb_map_t: Holds the complete description of an open Blorb file. */ +struct giblorb_map_struct { + glui32 inited; /* holds giblorb_Inited_Magic if the map structure is + valid */ + strid_t file; + + int numchunks; + giblorb_chunkdesc_t *chunks; /* list of chunk descriptors */ + + int numresources; + giblorb_resdesc_t *resources; /* list of resource descriptors */ + giblorb_resdesc_t **ressorted; /* list of pointers to descriptors + in map->resources -- sorted by usage and resource number. */ +}; + +#define giblorb_Inited_Magic (0xB7012BED) + +/* Static variables. */ + +static int lib_inited = FALSE; + +static giblorb_err_t giblorb_initialize(void); +static giblorb_err_t giblorb_initialize_map(giblorb_map_t *map); +static void giblorb_qsort(giblorb_resdesc_t **list, int len); +static giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample, + giblorb_resdesc_t **list, int len); +static void *giblorb_malloc(glui32 len); +static void *giblorb_realloc(void *ptr, glui32 len); +static void giblorb_free(void *ptr); + +static giblorb_err_t giblorb_initialize() +{ + return giblorb_err_None; +} + +giblorb_err_t giblorb_create_map(strid_t file, giblorb_map_t **newmap) +{ + giblorb_err_t err; + giblorb_map_t *map; + glui32 readlen; + glui32 nextpos, totallength; + giblorb_chunkdesc_t *chunks; + int chunks_size, numchunks; + char buffer[16]; + + *newmap = NULL; + + if (!lib_inited) { + err = giblorb_initialize(); + if (err) + return err; + lib_inited = TRUE; + } + + /* First, chew through the file and index the chunks. */ + + glk_stream_set_position(file, 0, seekmode_Start); + + readlen = glk_get_buffer_stream(file, buffer, 12); + if (readlen != 12) + return giblorb_err_Read; + + if (giblorb_native4(buffer+0) != giblorb_ID_FORM) + return giblorb_err_Format; + if (giblorb_native4(buffer+8) != giblorb_ID_IFRS) + return giblorb_err_Format; + + totallength = giblorb_native4(buffer+4) + 8; + nextpos = 12; + + chunks_size = 8; + numchunks = 0; + chunks = (giblorb_chunkdesc_t *)giblorb_malloc(sizeof(giblorb_chunkdesc_t) + * chunks_size); + + while (nextpos < totallength) { + glui32 type, len; + int chunum; + giblorb_chunkdesc_t *chu; + + glk_stream_set_position(file, nextpos, seekmode_Start); + + readlen = glk_get_buffer_stream(file, buffer, 8); + if (readlen != 8) + return giblorb_err_Read; + + type = giblorb_native4(buffer+0); + len = giblorb_native4(buffer+4); + + if (numchunks >= chunks_size) { + chunks_size *= 2; + chunks = (giblorb_chunkdesc_t *)giblorb_realloc(chunks, + sizeof(giblorb_chunkdesc_t) * chunks_size); + } + + chunum = numchunks; + chu = &(chunks[chunum]); + numchunks++; + + chu->type = type; + chu->startpos = nextpos; + if (type == giblorb_ID_FORM) { + chu->datpos = nextpos; + chu->len = len+8; + } + else { + chu->datpos = nextpos+8; + chu->len = len; + } + chu->ptr = NULL; + chu->auxdatnum = -1; + + nextpos = nextpos + len + 8; + if (nextpos & 1) + nextpos++; + + if (nextpos > totallength) + return giblorb_err_Format; + } + + /* The basic IFF structure seems to be ok, and we have a list of + chunks. Now we allocate the map structure itself. */ + + map = (giblorb_map_t *)giblorb_malloc(sizeof(giblorb_map_t)); + if (!map) { + giblorb_free(chunks); + return giblorb_err_Alloc; + } + + map->inited = giblorb_Inited_Magic; + map->file = file; + map->chunks = chunks; + map->numchunks = numchunks; + map->resources = NULL; + map->ressorted = NULL; + map->numresources = 0; + /*map->releasenum = 0; + map->zheader = NULL; + map->resolution = NULL; + map->palettechunk = -1; + map->palette = NULL; + map->auxsound = NULL; + map->auxpict = NULL;*/ + + /* Now we do everything else involved in loading the Blorb file, + such as building resource lists. */ + + err = giblorb_initialize_map(map); + if (err) { + giblorb_destroy_map(map); + return err; + } + + *newmap = map; + return giblorb_err_None; +} + +static giblorb_err_t giblorb_initialize_map(giblorb_map_t *map) +{ + /* It is important that the map structure be kept valid during this + function. If this returns an error, giblorb_destroy_map() will + be called. */ + + int ix, jx; + giblorb_result_t chunkres; + giblorb_err_t err; + char *ptr; + glui32 len; + glui32 val; + glui32 numres; + int gotindex = FALSE; + + for (ix=0; ixnumchunks; ix++) { + giblorb_chunkdesc_t *chu = &map->chunks[ix]; + + switch (chu->type) { + + case giblorb_ID_RIdx: + /* Resource index chunk: build the resource list and + sort it. */ + + if (gotindex) + return giblorb_err_Format; /* duplicate index chunk */ + err = giblorb_load_chunk_by_number(map, giblorb_method_Memory, + &chunkres, ix); + if (err) + return err; + + ptr = chunkres.data.ptr; + len = chunkres.length; + numres = giblorb_native4(ptr+0); + + if (numres) { + int ix2; + giblorb_resdesc_t *resources; + giblorb_resdesc_t **ressorted; + + if (len != numres*12+4) + return giblorb_err_Format; /* bad length field */ + + resources = (giblorb_resdesc_t *)giblorb_malloc(numres + * sizeof(giblorb_resdesc_t)); + ressorted = (giblorb_resdesc_t **)giblorb_malloc(numres + * sizeof(giblorb_resdesc_t *)); + if (!ressorted || !resources) + return giblorb_err_Alloc; + + ix2 = 0; + for (jx=0; jxusage = giblorb_native4(ptr+jx*12+4); + res->resnum = giblorb_native4(ptr+jx*12+8); + respos = giblorb_native4(ptr+jx*12+12); + + while (ix2 < map->numchunks + && map->chunks[ix2].startpos < respos) + ix2++; + + if (ix2 >= map->numchunks + || map->chunks[ix2].startpos != respos) + return giblorb_err_Format; /* start pos does + not match a real chunk */ + + res->chunknum = ix2; + + ressorted[jx] = res; + } + + /* Sort a resource list (actually a list of pointers to + structures in map->resources.) This makes it easy + to find resources by usage and resource number. */ + giblorb_qsort(ressorted, numres); + + map->numresources = numres; + map->resources = resources; + map->ressorted = ressorted; + } + + giblorb_unload_chunk(map, ix); + gotindex = TRUE; + break; + + } + } + + return giblorb_err_None; +} + +giblorb_err_t giblorb_destroy_map(giblorb_map_t *map) +{ + int ix; + + if (!map || !map->chunks || map->inited != giblorb_Inited_Magic) + return giblorb_err_NotAMap; + + for (ix=0; ixnumchunks; ix++) { + giblorb_chunkdesc_t *chu = &(map->chunks[ix]); + if (chu->ptr) { + giblorb_free(chu->ptr); + chu->ptr = NULL; + } + } + + if (map->chunks) { + giblorb_free(map->chunks); + map->chunks = NULL; + } + + map->numchunks = 0; + + if (map->resources) { + giblorb_free(map->resources); + map->resources = NULL; + } + + if (map->ressorted) { + giblorb_free(map->ressorted); + map->ressorted = NULL; + } + + map->numresources = 0; + + map->file = NULL; + map->inited = 0; + + giblorb_free(map); + + return giblorb_err_None; +} + +/* Chunk-handling functions. */ + +giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 type, + glui32 count) +{ + int ix; + + for (ix=0; ix < map->numchunks; ix++) { + if (map->chunks[ix].type == type) { + if (count == 0) + break; + count--; + } + } + + if (ix >= map->numchunks) { + return giblorb_err_NotFound; + } + + return giblorb_load_chunk_by_number(map, method, res, ix); +} + +giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 chunknum) +{ + giblorb_chunkdesc_t *chu; + + if (chunknum < 0 || chunknum >= map->numchunks) + return giblorb_err_NotFound; + + chu = &(map->chunks[chunknum]); + + switch (method) { + + case giblorb_method_DontLoad: + /* do nothing */ + break; + + case giblorb_method_FilePos: + res->data.startpos = chu->datpos; + break; + + case giblorb_method_Memory: + if (!chu->ptr) { + giblorb_err_t err; + glui32 readlen; + void *dat = giblorb_malloc(chu->len); + + if (!dat) + return giblorb_err_Alloc; + + glk_stream_set_position(map->file, chu->datpos, + seekmode_Start); + + readlen = glk_get_buffer_stream(map->file, dat, + chu->len); + if (readlen != chu->len) + return giblorb_err_Read; + + chu->ptr = dat; + } + res->data.ptr = chu->ptr; + break; + } + + res->chunknum = chunknum; + res->length = chu->len; + res->chunktype = chu->type; + + return giblorb_err_None; +} + +giblorb_err_t giblorb_load_resource(giblorb_map_t *map, glui32 method, + giblorb_result_t *res, glui32 usage, glui32 resnum) +{ + giblorb_resdesc_t sample; + giblorb_resdesc_t *found; + + sample.usage = usage; + sample.resnum = resnum; + + found = giblorb_bsearch(&sample, map->ressorted, map->numresources); + + if (!found) + return giblorb_err_NotFound; + + return giblorb_load_chunk_by_number(map, method, res, found->chunknum); +} + +giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, glui32 chunknum) +{ + giblorb_chunkdesc_t *chu; + + if (chunknum < 0 || chunknum >= map->numchunks) + return giblorb_err_NotFound; + + chu = &(map->chunks[chunknum]); + + if (chu->ptr) { + giblorb_free(chu->ptr); + chu->ptr = NULL; + } + + return giblorb_err_None; +} + +giblorb_err_t giblorb_count_resources(giblorb_map_t *map, glui32 usage, + glui32 *num, glui32 *min, glui32 *max) +{ + int ix; + int count; + glui32 val; + glui32 minval, maxval; + + count = 0; + minval = 0; + maxval = 0; + + for (ix=0; ixnumresources; ix++) { + if (map->resources[ix].usage == usage) { + val = map->resources[ix].resnum; + if (count == 0) { + count++; + minval = val; + maxval = val; + } + else { + count++; + if (val < minval) + minval = val; + if (val > maxval) + maxval = val; + } + } + } + + if (num) + *num = count; + if (min) + *min = minval; + if (max) + *max = maxval; + + return giblorb_err_None; +} + +/* Sorting and searching. */ + +static int sortsplot(giblorb_resdesc_t *v1, giblorb_resdesc_t *v2) +{ + if (v1->usage < v2->usage) + return -1; + if (v1->usage > v2->usage) + return 1; + if (v1->resnum < v2->resnum) + return -1; + if (v1->resnum > v2->resnum) + return 1; + return 0; +} + +static void giblorb_qsort(giblorb_resdesc_t **list, int len) +{ + int ix, jx, res, val; + giblorb_resdesc_t *tmpptr, *pivot; + + if (len < 6) { + /* The list is short enough for a bubble-sort. */ + for (jx=len-1; jx>0; jx--) { + for (ix=0; ix 0) { + tmpptr = list[ix]; + list[ix] = list[ix+1]; + list[ix+1] = tmpptr; + } + } + } + } + else { + /* Split the list. */ + pivot = list[len/2]; + ix=0; + jx=len; + while (1) { + while (ix < jx-1 && sortsplot(list[ix], pivot) < 0) + ix++; + while (ix < jx-1 && sortsplot(list[jx-1], pivot) > 0) + jx--; + if (ix >= jx-1) + break; + tmpptr = list[ix]; + list[ix] = list[jx-1]; + list[jx-1] = tmpptr; + } + ix++; + /* Sort the halves. */ + giblorb_qsort(list+0, ix); + giblorb_qsort(list+ix, len-ix); + } +} + +giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample, + giblorb_resdesc_t **list, int len) +{ + int top, bot, val, res; + + bot = 0; + top = len; + + while (bot < top) { + val = (top+bot) / 2; + res = sortsplot(list[val], sample); + if (res == 0) + return list[val]; + if (res < 0) { + bot = val+1; + } + else { + top = val; + } + } + + return NULL; +} + + +/* Boring utility functions. If your platform doesn't support ANSI + malloc(), feel free to edit these however you like. */ + +#include /* The OS-native header file -- you can edit + this too. */ + +static void *giblorb_malloc(glui32 len) +{ + return malloc(len); +} + +static void *giblorb_realloc(void *ptr, glui32 len) +{ + return realloc(ptr, len); +} + +static void giblorb_free(void *ptr) +{ + free(ptr); +} + + diff --git a/libchimara/gi_blorb.h b/libchimara/gi_blorb.h new file mode 100644 index 0000000..ddec1bd --- /dev/null +++ b/libchimara/gi_blorb.h @@ -0,0 +1,88 @@ +#ifndef _GI_BLORB_H +#define _GI_BLORB_H + +/* gi_blorb.h: Blorb library layer for Glk API. + gi_blorb version 1.4. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + + This file is copyright 1998-2000 by Andrew Plotkin. You may copy, + distribute, and incorporate it into your own programs, by any means + and under any conditions, as long as you do not modify it. You may + also modify this file, incorporate it into your own programs, + and distribute the modified version, as long as you retain a notice + in your program or documentation which mentions my name and the URL + shown above. +*/ + +/* Error type and error codes */ +typedef glui32 giblorb_err_t; +#define giblorb_err_None (0) +#define giblorb_err_CompileTime (1) +#define giblorb_err_Alloc (2) +#define giblorb_err_Read (3) +#define giblorb_err_NotAMap (4) +#define giblorb_err_Format (5) +#define giblorb_err_NotFound (6) + +/* Methods for loading a chunk */ +#define giblorb_method_DontLoad (0) +#define giblorb_method_Memory (1) +#define giblorb_method_FilePos (2) + +/* Four-byte constants */ + +#define giblorb_make_id(c1, c2, c3, c4) \ + (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4)) + +#define giblorb_ID_Snd (giblorb_make_id('S', 'n', 'd', ' ')) +#define giblorb_ID_Exec (giblorb_make_id('E', 'x', 'e', 'c')) +#define giblorb_ID_Pict (giblorb_make_id('P', 'i', 'c', 't')) +#define giblorb_ID_Copyright (giblorb_make_id('(', 'c', ')', ' ')) +#define giblorb_ID_AUTH (giblorb_make_id('A', 'U', 'T', 'H')) +#define giblorb_ID_ANNO (giblorb_make_id('A', 'N', 'N', 'O')) + +/* giblorb_map_t: Holds the complete description of an open Blorb + file. This type is opaque for normal interpreter use. */ +typedef struct giblorb_map_struct giblorb_map_t; + +/* giblorb_result_t: Result when you try to load a chunk. */ +typedef struct giblorb_result_struct { + glui32 chunknum; /* The chunk number (for use in + giblorb_unload_chunk(), etc.) */ + union { + void *ptr; /* A pointer to the data (if you used + giblorb_method_Memory) */ + glui32 startpos; /* The position in the file (if you + used giblorb_method_FilePos) */ + } data; + glui32 length; /* The length of the data */ + glui32 chunktype; /* The type of the chunk. */ +} giblorb_result_t; + +extern giblorb_err_t giblorb_create_map(strid_t file, + giblorb_map_t **newmap); +extern giblorb_err_t giblorb_destroy_map(giblorb_map_t *map); + +extern giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 chunktype, + glui32 count); +extern giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 chunknum); +extern giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, + glui32 chunknum); + +extern giblorb_err_t giblorb_load_resource(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 usage, + glui32 resnum); +extern giblorb_err_t giblorb_count_resources(giblorb_map_t *map, + glui32 usage, glui32 *num, glui32 *min, glui32 *max); + +/* The following functions are part of the Glk library itself, not + the Blorb layer (whose code is in gi_blorb.c). These functions + are necessarily implemented in platform-dependent code. +*/ +extern giblorb_err_t giblorb_set_resource_map(strid_t file); +extern giblorb_map_t *giblorb_get_resource_map(void); + +#endif /* _GI_BLORB_H */ diff --git a/libchimara/glk.c b/libchimara/glk.c new file mode 100644 index 0000000..c4f3954 --- /dev/null +++ b/libchimara/glk.c @@ -0,0 +1,108 @@ +#include + +#include "glk.h" +#include "abort.h" +#include "chimara-glk.h" +#include "chimara-glk-private.h" +#include "gi_blorb.h" + +ChimaraGlkPrivate *glk_data = NULL; + +/** + * glk_exit: + * + * If you want to shut down your program in the middle of your + * glk_main() function, you can call glk_exit(). + * + * This function does not return. + * + * If you print some text to a window and then shut down your program, you can + * assume that the player will be able to read it. Most likely the Glk library + * will give a Hit any key to + * exit prompt. (There are other possiblities, however. + * A terminal-window version of Glk might simply exit and leave the last screen + * state visible in the terminal window.) + * + * + * You should only shut down your program with glk_exit() or by returning from + * your glk_main() function. If you call the ANSI + * exit() function, bad things may happen. Some versions of + * the Glk library may be designed for multiple sessions, for example, and you + * would be cutting off all the sessions instead of just yours. You would + * probably also prevent final text from being visible to the player. + * + * Chimara + * + * If there are any windows open at the time glk_exit() is called, then Chimara + * will leave them open. This way, the final text remains visible. Note that bad + * things most definitely will happen if you use the ANSI + * exit(). + * + */ +void +glk_exit(void) +{ + g_signal_emit_by_name(glk_data->self, "stopped"); + + /* Stop any timers */ + glk_request_timer_events(0); + + /* Close any open resource files */ + if(glk_data->resource_map != NULL) { + giblorb_destroy_map(glk_data->resource_map); + glk_stream_close(glk_data->resource_file, NULL); + } + + glk_data = NULL; + g_thread_exit(NULL); +} + +/** + * glk_tick: + * + * Carries out platform-dependent actions such as yielding time to the operating + * system and checking for interrupts. glk_tick() should be called every so + * often when there is a long interval between calls of glk_select() or + * glk_select_poll(). This call is fast; in fact, on average, it does nothing at + * all. So you can call it often. + * + * + * In a virtual machine interpreter, once per opcode is appropriate. In a + * program with lots of computation, pick a comparable rate. + * + * + * glk_tick() does not try to update the screen, or check for player input, or + * any other interface task. For that, you should call glk_select() or + * glk_select_poll(). See Events. + * + * + * Captious critics have pointed out that in the sample program + * model.c, I do not call glk_tick() at all. This is + * because model.c has no heavy loops. It does a bit of + * work for each command, and then cycles back to the top of the event loop. + * The glk_select() call, of course, blocks waiting for input, so it does all + * the yielding and interrupt-checking one could imagine. + * + * Basically, you must ensure there's some fixed upper bound on the + * amount of computation that can occur before a glk_tick() (or glk_select()) + * occurs. In a VM interpreter, where the VM code might contain an infinite + * loop, this is critical. In a C program, you can often eyeball it. + * + * But the next version of model.c will have a + * glk_tick() in the ornate printing loop of verb_yada(). + * Just to make the point. + * + * + */ +void +glk_tick() +{ + check_for_abort(); + + /* Do one iteration of the main loop if there are any events */ + gdk_threads_enter(); + if(gtk_events_pending()) + gtk_main_iteration(); + gdk_threads_leave(); +} + diff --git a/libchimara/glk.h b/libchimara/glk.h new file mode 100644 index 0000000..6b64f88 --- /dev/null +++ b/libchimara/glk.h @@ -0,0 +1,349 @@ +#ifndef GLK_H +#define GLK_H + +/* glk.h: Header file for Glk API, version 0.7.0. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + + This file is copyright 1998-2004 by Andrew Plotkin. You may copy, + distribute, and incorporate it into your own programs, by any means + and under any conditions, as long as you do not modify it. You may + also modify this file, incorporate it into your own programs, + and distribute the modified version, as long as you retain a notice + in your program or documentation which mentions my name and the URL + shown above. +*/ + +/* You may have to edit the definition of glui32 to make sure it's really a + 32-bit unsigned integer type, and glsi32 to make sure it's really a + 32-bit signed integer type. If they're not, horrible things will happen. */ +#include +typedef uint32_t glui32; +typedef int32_t glsi32; + +/* These are the compile-time conditionals that reveal various Glk optional + modules. */ +#define GLK_MODULE_UNICODE +#define GLK_MODULE_IMAGE +#define GLK_MODULE_SOUND +#define GLK_MODULE_HYPERLINKS + +/* These types are opaque object identifiers. They're pointers to opaque + C structures, which are defined differently by each library. */ +typedef struct glk_window_struct *winid_t; +typedef struct glk_stream_struct *strid_t; +typedef struct glk_fileref_struct *frefid_t; +typedef struct glk_schannel_struct *schanid_t; + +#define gestalt_Version (0) +#define gestalt_CharInput (1) +#define gestalt_LineInput (2) +#define gestalt_CharOutput (3) +#define gestalt_CharOutput_CannotPrint (0) +#define gestalt_CharOutput_ApproxPrint (1) +#define gestalt_CharOutput_ExactPrint (2) +#define gestalt_MouseInput (4) +#define gestalt_Timer (5) +#define gestalt_Graphics (6) +#define gestalt_DrawImage (7) +#define gestalt_Sound (8) +#define gestalt_SoundVolume (9) +#define gestalt_SoundNotify (10) +#define gestalt_Hyperlinks (11) +#define gestalt_HyperlinkInput (12) +#define gestalt_SoundMusic (13) +#define gestalt_GraphicsTransparency (14) +#define gestalt_Unicode (15) + +#define evtype_None (0) +#define evtype_Timer (1) +#define evtype_CharInput (2) +#define evtype_LineInput (3) +#define evtype_MouseInput (4) +#define evtype_Arrange (5) +#define evtype_Redraw (6) +#define evtype_SoundNotify (7) +#define evtype_Hyperlink (8) + +typedef struct event_struct { + glui32 type; + winid_t win; + glui32 val1, val2; +} event_t; + +#define keycode_Unknown (0xffffffff) +#define keycode_Left (0xfffffffe) +#define keycode_Right (0xfffffffd) +#define keycode_Up (0xfffffffc) +#define keycode_Down (0xfffffffb) +#define keycode_Return (0xfffffffa) +#define keycode_Delete (0xfffffff9) +#define keycode_Escape (0xfffffff8) +#define keycode_Tab (0xfffffff7) +#define keycode_PageUp (0xfffffff6) +#define keycode_PageDown (0xfffffff5) +#define keycode_Home (0xfffffff4) +#define keycode_End (0xfffffff3) +#define keycode_Func1 (0xffffffef) +#define keycode_Func2 (0xffffffee) +#define keycode_Func3 (0xffffffed) +#define keycode_Func4 (0xffffffec) +#define keycode_Func5 (0xffffffeb) +#define keycode_Func6 (0xffffffea) +#define keycode_Func7 (0xffffffe9) +#define keycode_Func8 (0xffffffe8) +#define keycode_Func9 (0xffffffe7) +#define keycode_Func10 (0xffffffe6) +#define keycode_Func11 (0xffffffe5) +#define keycode_Func12 (0xffffffe4) +/* The last keycode is always (0x100000000 - keycode_MAXVAL) */ +#define keycode_MAXVAL (28) + +#define style_Normal (0) +#define style_Emphasized (1) +#define style_Preformatted (2) +#define style_Header (3) +#define style_Subheader (4) +#define style_Alert (5) +#define style_Note (6) +#define style_BlockQuote (7) +#define style_Input (8) +#define style_User1 (9) +#define style_User2 (10) +#define style_NUMSTYLES (11) + +typedef struct stream_result_struct { + glui32 readcount; + glui32 writecount; +} stream_result_t; + +#define wintype_AllTypes (0) +#define wintype_Pair (1) +#define wintype_Blank (2) +#define wintype_TextBuffer (3) +#define wintype_TextGrid (4) +#define wintype_Graphics (5) + +#define winmethod_Left (0x00) +#define winmethod_Right (0x01) +#define winmethod_Above (0x02) +#define winmethod_Below (0x03) +#define winmethod_DirMask (0x0f) + +#define winmethod_Fixed (0x10) +#define winmethod_Proportional (0x20) +#define winmethod_DivisionMask (0xf0) + +#define fileusage_Data (0x00) +#define fileusage_SavedGame (0x01) +#define fileusage_Transcript (0x02) +#define fileusage_InputRecord (0x03) +#define fileusage_TypeMask (0x0f) + +#define fileusage_TextMode (0x100) +#define fileusage_BinaryMode (0x000) + +#define filemode_Write (0x01) +#define filemode_Read (0x02) +#define filemode_ReadWrite (0x03) +#define filemode_WriteAppend (0x05) + +#define seekmode_Start (0) +#define seekmode_Current (1) +#define seekmode_End (2) + +#define stylehint_Indentation (0) +#define stylehint_ParaIndentation (1) +#define stylehint_Justification (2) +#define stylehint_Size (3) +#define stylehint_Weight (4) +#define stylehint_Oblique (5) +#define stylehint_Proportional (6) +#define stylehint_TextColor (7) +#define stylehint_BackColor (8) +#define stylehint_ReverseColor (9) +#define stylehint_NUMHINTS (10) + +#define stylehint_just_LeftFlush (0) +#define stylehint_just_LeftRight (1) +#define stylehint_just_Centered (2) +#define stylehint_just_RightFlush (3) + +/* glk_main() is the top-level function which you define. The Glk library + calls it. */ +extern void glk_main(void); + +extern void glk_exit(void); +extern void glk_set_interrupt_handler(void (*func)(void)); +extern void glk_tick(void); + +extern glui32 glk_gestalt(glui32 sel, glui32 val); +extern glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, + glui32 arrlen); + +extern unsigned char glk_char_to_lower(unsigned char ch); +extern unsigned char glk_char_to_upper(unsigned char ch); + +extern winid_t glk_window_get_root(void); +extern winid_t glk_window_open(winid_t split, glui32 method, glui32 size, + glui32 wintype, glui32 rock); +extern void glk_window_close(winid_t win, stream_result_t *result); +extern void glk_window_get_size(winid_t win, glui32 *widthptr, + glui32 *heightptr); +extern void glk_window_set_arrangement(winid_t win, glui32 method, + glui32 size, winid_t keywin); +extern void glk_window_get_arrangement(winid_t win, glui32 *methodptr, + glui32 *sizeptr, winid_t *keywinptr); +extern winid_t glk_window_iterate(winid_t win, glui32 *rockptr); +extern glui32 glk_window_get_rock(winid_t win); +extern glui32 glk_window_get_type(winid_t win); +extern winid_t glk_window_get_parent(winid_t win); +extern winid_t glk_window_get_sibling(winid_t win); +extern void glk_window_clear(winid_t win); +extern void glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos); + +extern strid_t glk_window_get_stream(winid_t win); +extern void glk_window_set_echo_stream(winid_t win, strid_t str); +extern strid_t glk_window_get_echo_stream(winid_t win); +extern void glk_set_window(winid_t win); + +extern strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode, + glui32 rock); +extern strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, + glui32 rock); +extern void glk_stream_close(strid_t str, stream_result_t *result); +extern strid_t glk_stream_iterate(strid_t str, glui32 *rockptr); +extern glui32 glk_stream_get_rock(strid_t str); +extern void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode); +extern glui32 glk_stream_get_position(strid_t str); +extern void glk_stream_set_current(strid_t str); +extern strid_t glk_stream_get_current(void); + +extern void glk_put_char(unsigned char ch); +extern void glk_put_char_stream(strid_t str, unsigned char ch); +extern void glk_put_string(char *s); +extern void glk_put_string_stream(strid_t str, char *s); +extern void glk_put_buffer(char *buf, glui32 len); +extern void glk_put_buffer_stream(strid_t str, char *buf, glui32 len); +extern void glk_set_style(glui32 styl); +extern void glk_set_style_stream(strid_t str, glui32 styl); + +extern glsi32 glk_get_char_stream(strid_t str); +extern glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len); +extern glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len); + +extern void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, + glsi32 val); +extern void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint); +extern glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2); +extern glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint, + glui32 *result); + +extern frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock); +extern frefid_t glk_fileref_create_by_name(glui32 usage, char *name, + glui32 rock); +extern frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, + glui32 rock); +extern frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, + glui32 rock); +extern void glk_fileref_destroy(frefid_t fref); +extern frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr); +extern glui32 glk_fileref_get_rock(frefid_t fref); +extern void glk_fileref_delete_file(frefid_t fref); +extern glui32 glk_fileref_does_file_exist(frefid_t fref); + +extern void glk_select(event_t *event); +extern void glk_select_poll(event_t *event); + +extern void glk_request_timer_events(glui32 millisecs); + +extern void glk_request_line_event(winid_t win, char *buf, glui32 maxlen, + glui32 initlen); +extern void glk_request_char_event(winid_t win); +extern void glk_request_mouse_event(winid_t win); + +extern void glk_cancel_line_event(winid_t win, event_t *event); +extern void glk_cancel_char_event(winid_t win); +extern void glk_cancel_mouse_event(winid_t win); + +#ifdef GLK_MODULE_UNICODE + +extern glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, + glui32 numchars); +extern glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, + glui32 numchars); +extern glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, + glui32 numchars, glui32 lowerrest); + +extern void glk_put_char_uni(glui32 ch); +extern void glk_put_string_uni(glui32 *s); +extern void glk_put_buffer_uni(glui32 *buf, glui32 len); +extern void glk_put_char_stream_uni(strid_t str, glui32 ch); +extern void glk_put_string_stream_uni(strid_t str, glui32 *s); +extern void glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); + +extern glsi32 glk_get_char_stream_uni(strid_t str); +extern glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); +extern glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len); + +extern strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, + glui32 rock); +extern strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, + glui32 fmode, glui32 rock); + +extern void glk_request_char_event_uni(winid_t win); +extern void glk_request_line_event_uni(winid_t win, glui32 *buf, + glui32 maxlen, glui32 initlen); + +#endif /* GLK_MODULE_UNICODE */ + +#ifdef GLK_MODULE_IMAGE + +#define imagealign_InlineUp (0x01) +#define imagealign_InlineDown (0x02) +#define imagealign_InlineCenter (0x03) +#define imagealign_MarginLeft (0x04) +#define imagealign_MarginRight (0x05) + +extern glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2); +extern glui32 glk_image_draw_scaled(winid_t win, glui32 image, + glsi32 val1, glsi32 val2, glui32 width, glui32 height); +extern glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height); + +extern void glk_window_flow_break(winid_t win); + +extern void glk_window_erase_rect(winid_t win, + glsi32 left, glsi32 top, glui32 width, glui32 height); +extern void glk_window_fill_rect(winid_t win, glui32 color, + glsi32 left, glsi32 top, glui32 width, glui32 height); +extern void glk_window_set_background_color(winid_t win, glui32 color); + +#endif /* GLK_MODULE_IMAGE */ + +#ifdef GLK_MODULE_SOUND + +extern schanid_t glk_schannel_create(glui32 rock); +extern void glk_schannel_destroy(schanid_t chan); +extern schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr); +extern glui32 glk_schannel_get_rock(schanid_t chan); + +extern glui32 glk_schannel_play(schanid_t chan, glui32 snd); +extern glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, + glui32 notify); +extern void glk_schannel_stop(schanid_t chan); +extern void glk_schannel_set_volume(schanid_t chan, glui32 vol); + +extern void glk_sound_load_hint(glui32 snd, glui32 flag); + +#endif /* GLK_MODULE_SOUND */ + +#ifdef GLK_MODULE_HYPERLINKS + +extern void glk_set_hyperlink(glui32 linkval); +extern void glk_set_hyperlink_stream(strid_t str, glui32 linkval); +extern void glk_request_hyperlink_event(winid_t win); +extern void glk_cancel_hyperlink_event(winid_t win); + +#endif /* GLK_MODULE_HYPERLINKS */ + +#endif /* GLK_H */ diff --git a/libchimara/glkstart.c b/libchimara/glkstart.c new file mode 100644 index 0000000..e9baf36 --- /dev/null +++ b/libchimara/glkstart.c @@ -0,0 +1,24 @@ +/* glkstart.c: Unix-specific startup code -- sample file. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + + This is Unix startup code for the simplest possible kind of Glk + program -- no command-line arguments; no startup files; no nothing. + + Remember, this is a sample file. You should copy it into the Glk + program you are compiling, and modify it to your needs. This should + *not* be compiled into the Glk library itself. +*/ + +#include "glk.h" +#include "glkstart.h" + +glkunix_argumentlist_t glkunix_arguments[] = { + { NULL, glkunix_arg_End, NULL } +}; + +int glkunix_startup_code(glkunix_startup_t *data) +{ + return TRUE; +} + diff --git a/libchimara/glkstart.h b/libchimara/glkstart.h new file mode 100644 index 0000000..e6b9010 --- /dev/null +++ b/libchimara/glkstart.h @@ -0,0 +1,57 @@ +/* glkstart.h: Unix-specific header file for GlkTerm, CheapGlk, and XGlk + (Unix implementations of the Glk API). + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html +*/ + +/* This header defines an interface that must be used by program linked + with the various Unix Glk libraries -- at least, the three I wrote. + (I encourage anyone writing a Unix Glk library to use this interface, + but it's not part of the Glk spec.) + + Because Glk is *almost* perfectly portable, this interface *almost* + doesn't have to exist. In practice, it's small. +*/ + +#ifndef GT_START_H +#define GT_START_H + +/* We define our own TRUE and FALSE and NULL, because ANSI + is a strange world. */ +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef NULL +#define NULL 0 +#endif + +#define glkunix_arg_End (0) +#define glkunix_arg_ValueFollows (1) +#define glkunix_arg_NoValue (2) +#define glkunix_arg_ValueCanFollow (3) +#define glkunix_arg_NumberValue (4) + +typedef struct glkunix_argumentlist_struct { + char *name; + int argtype; + char *desc; +} glkunix_argumentlist_t; + +typedef struct glkunix_startup_struct { + int argc; + char **argv; +} glkunix_startup_t; + +extern glkunix_argumentlist_t glkunix_arguments[]; + +extern int glkunix_startup_code(glkunix_startup_t *data); + +extern void glkunix_set_base_file(char *filename); +extern strid_t glkunix_stream_open_pathname(char *pathname, glui32 textmode, + glui32 rock); + +#endif /* GT_START_H */ + diff --git a/libchimara/input.c b/libchimara/input.c new file mode 100644 index 0000000..18a7ecb --- /dev/null +++ b/libchimara/input.c @@ -0,0 +1,551 @@ +#include "charset.h" +#include "magic.h" +#include "input.h" + +/* Forward declarations */ +static int flush_text_buffer(winid_t win); +static int flush_text_grid(winid_t win); + +/** + * glk_request_char_event: + * @win: A window to request char events from. + * + * Request input of a Latin-1 character or special key. A window cannot have + * requests for both character and line input at the same time. Nor can it have + * requests for character input of both types (Latin-1 and Unicode). It is + * illegal to call glk_request_char_event() if the window already has a pending + * request for either character or line input. + */ +void +glk_request_char_event(winid_t win) +{ + VALID_WINDOW(win, return); + g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); + g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); + + win->input_request_type = INPUT_REQUEST_CHARACTER; + g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler ); +} + +/** + * glk_request_char_event_uni: + * @win: A window to request char events from. + * + * Request input of a Unicode character or special key. See + * glk_request_char_event(). + */ +void +glk_request_char_event_uni(winid_t win) +{ + VALID_WINDOW(win, return); + g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); + g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); + + win->input_request_type = INPUT_REQUEST_CHARACTER_UNICODE; + g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler ); +} + +/** + * glk_cancel_char_event: + * @win: A window to cancel the latest char event request on. + * + * This cancels a pending request for character input. (Either Latin-1 or + * Unicode.) For convenience, it is legal to call glk_cancel_char_event() even + * if there is no charcter input request on that window. Glk will ignore the + * call in this case. + */ +void +glk_cancel_char_event(winid_t win) +{ + VALID_WINDOW(win, return); + g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); + + if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE) + { + win->input_request_type = INPUT_REQUEST_NONE; + g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); + } +} + +/* Internal function: Request either latin-1 or unicode line input, in a text grid window. */ +static void +text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext) +{ + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + + GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position"); + GtkTextIter start_iter, end_iter; + gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor); + + /* Determine the maximum length of the line input */ + gint cursorpos = gtk_text_iter_get_line_offset(&start_iter); + /* Odd; the Glk spec says the maximum input length is + windowwidth - 1 - cursorposition. I say no, because if cursorposition is + zero, then the input should fill the whole line. FIXME??? */ + win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len); + end_iter = start_iter; + gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length); + + /* If the buffer currently has a selection with one bound in the middle of + the input field, then deselect it. Otherwise the input field gets trashed */ + GtkTextIter start_sel, end_sel; + if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) ) + { + if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) ) + gtk_text_buffer_place_cursor(buffer, &end_sel); + if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) ) + gtk_text_buffer_place_cursor(buffer, &start_sel); + } + + /* Erase the text currently in the input field and replace it with a GtkEntry */ + gtk_text_buffer_delete(buffer, &start_iter, &end_iter); + win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter); + win->input_entry = gtk_entry_new(); + /* Set the entry's font to match that of the window */ + GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */ + gtk_widget_modify_font(win->input_entry, style->font_desc); + /* Make the entry as small as possible to fit with the text */ + gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE); + GtkBorder border = { 0, 0, 0, 0 }; + gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border); + gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length); + gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length); + + /* Insert pre-entered text if needed */ + if(insert) + gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext); + + /* Set background color of entry (TODO: implement as property) */ + GdkColor background; + gdk_color_parse("grey", &background); + gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background); + + g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win); + + gtk_widget_show(win->input_entry); + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor); + + g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler ); +} + +/* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */ +static void +text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext) +{ + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + + /* Move the input_position mark to the end of the window_buffer */ + GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position"); + GtkTextIter end_iter; + gtk_text_buffer_get_end_iter(buffer, &end_iter); + gtk_text_buffer_move_mark(buffer, input_position, &end_iter); + + /* Set the entire contents of the window_buffer as uneditable + * (so input can only be entered at the end) */ + GtkTextIter start_iter; + gtk_text_buffer_get_start_iter(buffer, &start_iter); + gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter); + gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter); + + /* Insert pre-entered text if needed */ + if(insert) + gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1); + + /* Scroll to input point */ + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position); + + gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE); + g_signal_handler_unblock(buffer, win->insert_text_handler); +} + +/** + * glk_request_line_event: + * @win: A text buffer or text grid window to request line input on. + * @buf: A buffer of at least @maxlen bytes. + * @maxlen: Length of the buffer. + * @initlen: The number of characters in @buf to pre-enter. + * + * Requests input of a line of Latin-1 characters. A window cannot have requests + * for both character and line input at the same time. Nor can it have requests + * for line input of both types (Latin-1 and Unicode). It is illegal to call + * glk_request_line_event() if the window already has a pending request for + * either character or line input. + * + * The @buf argument is a pointer to space where the line input will be stored. + * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the + * library will not accept more characters than this. If @initlen is nonzero, + * then the first @initlen bytes of @buf will be entered as pre-existing input + * — just as if the player had typed them himself. (The player can continue + * composing after this pre-entered input, or delete it or edit as usual.) + * + * The contents of the buffer are undefined until the input is completed (either + * by a line input event, or glk_cancel_line_event(). The library may or may not + * fill in the buffer as the player composes, while the input is still pending; + * it is illegal to change the contents of the buffer yourself. + */ +void +glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen) +{ + VALID_WINDOW(win, return); + g_return_if_fail(buf); + g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); + g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); + g_return_if_fail(initlen <= maxlen); + + win->input_request_type = INPUT_REQUEST_LINE; + win->line_input_buffer = buf; + win->line_input_buffer_max_len = maxlen; + + gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup(""); + switch(win->type) + { + case wintype_TextBuffer: + text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext); + break; + case wintype_TextGrid: + text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext); + break; + } + g_free(inserttext); +} + +/** + * glk_request_line_event_uni: + * @win: A text buffer or text grid window to request line input on. + * @buf: A buffer of at least @maxlen characters. + * @maxlen: Length of the buffer. + * @initlen: The number of characters in @buf to pre-enter. + * + * Request input of a line of Unicode characters. This works the same as + * glk_request_line_event(), except the result is stored in an array of + * glui32 values instead of an array of characters, and the values + * may be any valid Unicode code points. + * + * The result will be in Unicode Normalization Form C. This basically means that + * composite characters will be single characters where possible, instead of + * sequences of base and combining marks. See + * Unicode Standard Annex #15 + * for the details. + */ +void +glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen) +{ + VALID_WINDOW(win, return); + g_return_if_fail(buf); + g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); + g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); + g_return_if_fail(initlen <= maxlen); + + win->input_request_type = INPUT_REQUEST_LINE_UNICODE; + win->line_input_buffer_unicode = buf; + win->line_input_buffer_max_len = maxlen; + + gchar *utf8; + if(initlen > 0) { + utf8 = convert_ucs4_to_utf8(buf, initlen); + if(utf8 == NULL) + return; + } + else + utf8 = g_strdup(""); + + switch(win->type) + { + case wintype_TextBuffer: + text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8); + break; + case wintype_TextGrid: + text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8); + break; + } + g_free(utf8); +} + +/** + * glk_cancel_line_event: + * @win: A text buffer or text grid window to cancel line input on. + * @event: Will be filled in if the user had already input something. + * + * This cancels a pending request for line input. (Either Latin-1 or Unicode.) + * + * The event pointed to by the event argument will be filled in as if the + * player had hit enter, and the input composed so far will be stored in the + * buffer; see below. If you do not care about this information, pass %NULL as + * the @event argument. (The buffer will still be filled.) + * + * For convenience, it is legal to call glk_cancel_line_event() even if there + * is no line input request on that window. The event type will be set to + * #evtype_None in this case. + */ +void +glk_cancel_line_event(winid_t win, event_t *event) +{ + VALID_WINDOW(win, return); + g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); + + if(event != NULL) { + event->type = evtype_None; + event->win = win; + event->val1 = 0; + event->val2 = 0; + } + + if(win->input_request_type == INPUT_REQUEST_NONE) + return; + + g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); + + int chars_written = 0; + + if(win->type == wintype_TextGrid) { + g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); + chars_written = flush_text_grid(win); + } else if(win->type == wintype_TextBuffer) { + GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + g_signal_handler_block(window_buffer, win->insert_text_handler); + chars_written = flush_text_buffer(win); + } + + if(event != NULL && chars_written > 0) { + event->type = evtype_LineInput; + event->val1 = chars_written; + } +} + +/* Internal function: General callback for signal key-press-event on a text buffer or text grid window. Used in character input on both text buffers and grids, and also in line input on grids, to redirect keystrokes to the line input field. Blocked when not in use. */ +gboolean +on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win) +{ + /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */ + if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) ) + { + if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up + || event->keyval == GDK_Down || event->keyval == GDK_KP_Down + || event->keyval == GDK_Left || event->keyval == GDK_KP_Left + || event->keyval == GDK_Right || event->keyval == GDK_KP_Right + || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab + || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up + || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down + || event->keyval == GDK_Home || event->keyval == GDK_KP_Home + || event->keyval == GDK_End || event->keyval == GDK_KP_End) + return FALSE; /* Don't redirect these keys */ + gtk_widget_grab_focus(win->input_entry); + gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1); + gboolean retval = TRUE; + g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval); + return retval; /* Block this key event if the entry handled it */ + } + if(win->input_request_type != INPUT_REQUEST_CHARACTER && + win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE) + return FALSE; + + int keycode; + + switch(event->keyval) { + case GDK_Up: + case GDK_KP_Up: keycode = keycode_Up; break; + case GDK_Down: + case GDK_KP_Down: keycode = keycode_Down; break; + case GDK_Left: + case GDK_KP_Left: keycode = keycode_Left; break; + case GDK_Right: + case GDK_KP_Right: keycode = keycode_Right; break; + case GDK_Linefeed: + case GDK_Return: + case GDK_KP_Enter: keycode = keycode_Return; break; + case GDK_Delete: + case GDK_BackSpace: + case GDK_KP_Delete: keycode = keycode_Delete; break; + case GDK_Escape: keycode = keycode_Escape; break; + case GDK_Tab: + case GDK_KP_Tab: keycode = keycode_Tab; break; + case GDK_Page_Up: + case GDK_KP_Page_Up: keycode = keycode_PageUp; break; + case GDK_Page_Down: + case GDK_KP_Page_Down: keycode = keycode_PageDown; break; + case GDK_Home: + case GDK_KP_Home: keycode = keycode_Home; break; + case GDK_End: + case GDK_KP_End: keycode = keycode_End; break; + case GDK_F1: + case GDK_KP_F1: keycode = keycode_Func1; break; + case GDK_F2: + case GDK_KP_F2: keycode = keycode_Func2; break; + case GDK_F3: + case GDK_KP_F3: keycode = keycode_Func3; break; + case GDK_F4: + case GDK_KP_F4: keycode = keycode_Func4; break; + case GDK_F5: keycode = keycode_Func5; break; + case GDK_F6: keycode = keycode_Func6; break; + case GDK_F7: keycode = keycode_Func7; break; + case GDK_F8: keycode = keycode_Func8; break; + case GDK_F9: keycode = keycode_Func9; break; + case GDK_F10: keycode = keycode_Func10; break; + case GDK_F11: keycode = keycode_Func11; break; + case GDK_F12: keycode = keycode_Func12; break; + default: + keycode = gdk_keyval_to_unicode(event->keyval); + /* If keycode is 0, then keyval was not recognized; also return + unknown if Latin-1 input was requested and the character is not in + Latin-1 */ + if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255)) + keycode = keycode_Unknown; + } + + event_throw(evtype_CharInput, win, keycode, 0); + + /* Only one keypress will be handled */ + win->input_request_type = INPUT_REQUEST_NONE; + g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); + + return TRUE; +} + +/* Internal function: finish handling a line input request, for both text grid and text buffer windows. */ +static int +write_to_window_buffer(winid_t win, const gchar *inserted_text) +{ + int copycount = 0; + + /* Convert the string from UTF-8 to Latin-1 or Unicode */ + if(win->input_request_type == INPUT_REQUEST_LINE) + { + gsize bytes_written; + gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written); + + if(latin1 == NULL) + return 0; + + /* Place input in the echo stream */ + if(win->echo_stream != NULL) + glk_put_string_stream(win->echo_stream, latin1); + + /* Copy the string (bytes_written does not include the NULL at the end) */ + copycount = MIN(win->line_input_buffer_max_len, bytes_written); + memcpy(win->line_input_buffer, latin1, copycount); + g_free(latin1); + } + else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE) + { + glong items_written; + gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written); + + if(unicode == NULL) + return 0; + + /* Place input in the echo stream */ + if(win->echo_stream != NULL) + glk_put_string_stream_uni(win->echo_stream, unicode); + + /* Copy the string (but not the NULL at the end) */ + copycount = MIN(win->line_input_buffer_max_len, items_written); + memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar)); + g_free(unicode); + } + else + WARNING("Wrong input request type"); + + win->input_request_type = INPUT_REQUEST_NONE; + return copycount; +} + +/* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer. + * Returns the number of characters written, suitable for inclusion in a line input event. */ +static int +flush_text_buffer(winid_t win) +{ + VALID_WINDOW(win, return 0); + g_return_val_if_fail(win->type == wintype_TextBuffer, 0); + + GtkTextIter start_iter, end_iter, last_character; + + GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position"); + gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position); + gtk_text_buffer_get_end_iter(window_buffer, &end_iter); + gtk_text_buffer_get_end_iter(window_buffer, &last_character); + gtk_text_iter_backward_cursor_position(&last_character); + + gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE); + + if( strchr(last_char, '\n') != NULL ) + gtk_text_iter_backward_cursor_position(&end_iter); + + gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE); + + int chars_written = write_to_window_buffer(win, inserted_text); + g_free(inserted_text); + + return chars_written; +} + +/* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer. + * Returns the number of characters written, suitable for inclusion in a line input event. */ +static int +flush_text_grid(winid_t win) +{ + VALID_WINDOW(win, return 0); + g_return_val_if_fail(win->type == wintype_TextBuffer, 0); + + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + + gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) ); + /* Move the focus back into the text view */ + gtk_widget_grab_focus(win->widget); + /* Remove entry widget from text view */ + /* Should be ok even though this is the widget's own signal handler */ + gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) ); + win->input_entry = NULL; + /* Delete the child anchor */ + GtkTextIter start, end; + gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor); + end = start; + gtk_text_iter_forward_char(&end); /* Point after the child anchor */ + gtk_text_buffer_delete(buffer, &start, &end); + win->input_anchor = NULL; + + gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' '); + gchar *text_to_insert = g_strconcat(text, spaces, NULL); + g_free(spaces); + gtk_text_buffer_insert(buffer, &start, text_to_insert, -1); + g_free(text_to_insert); + + int chars_written = write_to_window_buffer(win, text); + g_free(text); + + return chars_written; +} + +/* Internal function: Callback for signal insert-text on a text buffer window. +Runs after the default handler has already inserted the text. +FIXME: This function assumes that newline was the last character typed into the +window. That assumption is wrong if, for example, text containing a newline was +pasted into the window. */ +void +after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) +{ + if( strchr(text, '\n') != NULL ) + { + /* Remove signal handlers */ + GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + g_signal_handler_block(window_buffer, win->insert_text_handler); + + /* Make the window uneditable again and retrieve the text that was input */ + gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE); + + int chars_written = flush_text_buffer(win); + event_throw(evtype_LineInput, win, chars_written, 0); + } +} + +/* Internal function: Callback for signal activate on the line input GtkEntry +in a text grid window. */ +void +on_input_entry_activate(GtkEntry *input_entry, winid_t win) +{ + g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); + + int chars_written = flush_text_grid(win); + event_throw(evtype_LineInput, win, chars_written, 0); +} + diff --git a/libchimara/input.h b/libchimara/input.h new file mode 100644 index 0000000..e4080fd --- /dev/null +++ b/libchimara/input.h @@ -0,0 +1,16 @@ +#ifndef INPUT_H +#define INPUT_H + +#include +#include +#include +#include + +#include "window.h" +#include "event.h" + +G_GNUC_INTERNAL gboolean on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win); +G_GNUC_INTERNAL void after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win); +G_GNUC_INTERNAL void on_input_entry_activate(GtkEntry *input_entry, winid_t win); + +#endif diff --git a/libchimara/magic.c b/libchimara/magic.c new file mode 100644 index 0000000..a385b96 --- /dev/null +++ b/libchimara/magic.c @@ -0,0 +1,56 @@ +#include +#include "glk.h" +#include "magic.h" + +/* The "magic" mechanism was stolen from Evin Robertson's GtkGlk. */ + +static gchar * +magic_to_string(glui32 magic) +{ + switch(magic) + { + case MAGIC_WINDOW: + return "winid_t"; + case MAGIC_STREAM: + return "strid_t"; + case MAGIC_FILEREF: + return "frefid_t"; + case MAGIC_SCHANNEL: + return "schanid_t"; + default: + g_return_val_if_reached("unknown"); + } +} + +/* Internal function: check the object's magic number to make sure it is the + right type, and not freed. */ +gboolean +magic_is_valid_or_null(const glui32 goodmagic, const glui32 realmagic, const gchar *function) +{ + if(realmagic != MAGIC_NULL) + { + if(realmagic != goodmagic) + { + if(realmagic == MAGIC_FREE) + g_critical("%s: Using a freed object", function); + else + g_critical( "%s: %s object not a %s", function, magic_to_string(realmagic), magic_to_string(goodmagic) ); + return FALSE; + } + } + return TRUE; +} + + +/* Internal function: check the object's magic number to make sure it is + not NULL, the right type, and not freed. */ +gboolean +magic_is_valid(const void *obj, const glui32 goodmagic, const glui32 realmagic, const gchar *function) +{ + if(obj == NULL) + { + g_critical( "%s: NULL %s pointer", function, magic_to_string(goodmagic) ); + return FALSE; + } + return magic_is_valid_or_null(goodmagic, realmagic, function); +} diff --git a/libchimara/magic.h b/libchimara/magic.h new file mode 100644 index 0000000..406b570 --- /dev/null +++ b/libchimara/magic.h @@ -0,0 +1,41 @@ +#ifndef __MAGIC_H__ +#define __MAGIC_H__ + +#include +#include "glk.h" + +#define MAGIC_FREE 0x46524545 /* "FREE" */ +#define MAGIC_NULL 0x4E554C4C /* "NULL" */ +#define MAGIC_WINDOW 0x57494E44 /* "WIND" */ +#define MAGIC_STREAM 0x53545245 /* "STRE" */ +#define MAGIC_FILEREF 0x46494C45 /* "FILE" */ +#define MAGIC_SCHANNEL 0x53434841 /* "SCHA" */ + +G_GNUC_INTERNAL gboolean magic_is_valid_or_null(const glui32 goodmagic, const glui32 realmagic, const gchar *function); +G_GNUC_INTERNAL gboolean magic_is_valid(const void *obj, const glui32 goodmagic, const glui32 realmagic, const gchar *function); + +#define VALID_MAGIC(obj, goodmagic, die) \ + if( !magic_is_valid(obj, goodmagic, obj->magic, G_STRFUNC) ) die +#define VALID_MAGIC_OR_NULL(obj, goodmagic, die) \ + if( !magic_is_valid_or_null(goodmagic, obj? obj->magic : MAGIC_NULL, G_STRFUNC) ) die + +#define VALID_WINDOW(o, d) VALID_MAGIC(o, MAGIC_WINDOW, d) +#define VALID_WINDOW_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_WINDOW, d) +#define VALID_STREAM(o, d) VALID_MAGIC(o, MAGIC_STREAM, d) +#define VALID_STREAM_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_STREAM, d) +#define VALID_FILEREF(o, d) VALID_MAGIC(o, MAGIC_FILEREF, d) +#define VALID_FILEREF_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_FILEREF, d) +#define VALID_SCHANNEL(o, d) VALID_MAGIC(o, MAGIC_SCHANNEL, d) +#define VALID_SCHANNEL_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_SCHANNEL, d) + +/* This works with string variables as well as literal strings */ +#define ILLEGAL(str) g_critical("%s: %s", G_STRFUNC, str) +/* This only works with literal strings */ +#define ILLEGAL_PARAM(str, param) g_critical("%s: " str, G_STRFUNC, param) + +#define WARNING(msg) g_warning("%s: %s", G_STRFUNC, msg) +#define WARNING_S(msg, str) g_warning("%s: %s: %s", G_STRFUNC, msg, str) +#define IO_WARNING(msg, str, errmsg) \ + g_warning("%s: %s \"%s\": %s", G_STRFUNC, msg, str, errmsg) + +#endif /* __MAGIC_H__ */ diff --git a/libchimara/resource.c b/libchimara/resource.c new file mode 100644 index 0000000..c6b4d34 --- /dev/null +++ b/libchimara/resource.c @@ -0,0 +1,54 @@ +#include "resource.h" + +extern ChimaraGlkPrivate *glk_data; + +/** + * giblorb_set_resource_map: + * @file The file stream to read the resource map from + * + * This function tells the library that the file is indeed the Blorby source + * of all resource goodness. Whenever your program calls an image or sound + * function, such as glk_image_draw(), the library will search this file for + * the resource you request. + * + * Do not close the stream after calling this function. The library is + * responsible for closing the stream at shutdown time. + */ +giblorb_err_t +giblorb_set_resource_map(strid_t file) +{ + giblorb_map_t *newmap; /* create map allocates memory */ + giblorb_err_t error = giblorb_create_map(file, &newmap); + + if(error != giblorb_err_None) { + g_free(newmap); + return error; + } + + /* Check if there was already an existing resource map */ + if(glk_data->resource_map != NULL) { + WARNING("Overwriting existing resource map.\n"); + giblorb_destroy_map(glk_data->resource_map); + glk_stream_close(glk_data->resource_file, NULL); + } + + glk_data->resource_map = newmap; + glk_data->resource_file = file; + return giblorb_err_None; +} + +/** + * giblorb_get_resource_map: + * + * This function returns the current resource map being used. Returns NULL + * if #giblorb_set_resource_map() has not been called yet. + */ +giblorb_map_t* +giblorb_get_resource_map() +{ + if(glk_data->resource_map == NULL) { + WARNING("Resource map not set yet.\n"); + } + + return glk_data->resource_map; +} diff --git a/libchimara/resource.h b/libchimara/resource.h new file mode 100644 index 0000000..7db741e --- /dev/null +++ b/libchimara/resource.h @@ -0,0 +1,10 @@ +#ifndef RESOURCE_H +#define RESOURCE_H + +#include +#include "glk.h" +#include "gi_blorb.h" +#include "chimara-glk-private.h" +#include "magic.h" + +#endif diff --git a/libchimara/stream.c b/libchimara/stream.c new file mode 100644 index 0000000..8043992 --- /dev/null +++ b/libchimara/stream.c @@ -0,0 +1,478 @@ +#include "stream.h" +#include "fileref.h" +#include "magic.h" +#include +#include +#include +#include + +#include "chimara-glk-private.h" +extern ChimaraGlkPrivate *glk_data; + +/* Internal function: create a stream with a specified rock value */ +static strid_t +stream_new_common(glui32 rock, glui32 fmode, enum StreamType type) +{ + strid_t str = g_new0(struct glk_stream_struct, 1); + str->magic = MAGIC_STREAM; + str->rock = rock; + str->file_mode = fmode; + str->type = type; + + /* Add it to the global stream list */ + glk_data->stream_list = g_list_prepend(glk_data->stream_list, str); + str->stream_list = glk_data->stream_list; + + return str; +} + +/* Internal function: create a window stream to go with window. */ +strid_t +window_stream_new(winid_t window) +{ + /* Create stream and connect it to window */ + strid_t str = stream_new_common(0, filemode_Write, STREAM_TYPE_WINDOW); + str->window = window; + str->style = "normal"; + return str; +} + +/** + * glk_stream_iterate: + * @str: A stream, or %NULL. + * @rockptr: Return location for the next window's rock, or %NULL. + * + * Iterates through all the existing streams. See Iterating Through Opaque + * Objects. + * + * Returns: the next stream, or %NULL if there are no more. + */ +strid_t +glk_stream_iterate(strid_t str, glui32 *rockptr) +{ + VALID_STREAM_OR_NULL(str, return NULL); + + GList *retnode; + + if(str == NULL) + retnode = glk_data->stream_list; + else + retnode = str->stream_list->next; + strid_t retval = retnode? (strid_t)retnode->data : NULL; + + /* Store the stream's rock in rockptr */ + if(retval && rockptr) + *rockptr = glk_stream_get_rock(retval); + + return retval; +} + +/** + * glk_stream_get_rock: + * @str: A stream. + * + * Retrieves the stream @str's rock value. See Rocks. + * + * Returns: A rock value. + */ +glui32 +glk_stream_get_rock(strid_t str) +{ + VALID_STREAM(str, return 0); + return str->rock; +} + +/** + * glk_stream_set_current: + * @str: An output stream, or %NULL. + * + * Sets the current stream to @str, which must be an output stream. You may set + * the current stream to %NULL, which means the current stream is not set to + * anything. + */ +void +glk_stream_set_current(strid_t str) +{ + VALID_STREAM_OR_NULL(str, return); + + if(str != NULL && str->file_mode == filemode_Read) + { + ILLEGAL("Cannot set current stream to non output stream"); + return; + } + + glk_data->current_stream = str; +} + +/** + * glk_stream_get_current: + * + * Returns the current stream, or %NULL if there is none. + * + * Returns: A stream, or %NULL. + */ +strid_t +glk_stream_get_current() +{ + return glk_data->current_stream; +} + +/** + * glk_put_char: + * @ch: A character in Latin-1 encoding. + * + * Prints one character to the current stream. As with all basic functions, the + * character is assumed to be in the Latin-1 character encoding. See Character Encoding. + */ +void +glk_put_char(unsigned char ch) +{ + VALID_STREAM(glk_data->current_stream, return); + glk_put_char_stream(glk_data->current_stream, ch); +} + +/** + * glk_put_char_uni: + * @ch: A Unicode code point. + * + * Prints one character to the current stream. The character is assumed to be a + * Unicode code point. See Character + * Encoding. + */ +void +glk_put_char_uni(glui32 ch) +{ + VALID_STREAM(glk_data->current_stream, return); + glk_put_char_stream_uni(glk_data->current_stream, ch); +} + +/** + * glk_put_string: + * @s: A null-terminated string in Latin-1 encoding. + * + * Prints a null-terminated string to the current stream. It is exactly + * equivalent to + * |[ + * for (ptr = @s; *ptr; ptr++) + * #glk_put_char(*ptr); + * ]| + * However, it may be more efficient. + */ +void +glk_put_string(char *s) +{ + VALID_STREAM(glk_data->current_stream, return); + glk_put_string_stream(glk_data->current_stream, s); +} + +/** + * glk_put_string_uni: + * @s: A zero-terminated string of Unicode code points. + * + * Prints a string of Unicode characters to the current stream. It is equivalent + * to a series of glk_put_char_uni() calls. A string ends on a #glui32 whose + * value is 0. + */ +void +glk_put_string_uni(glui32 *s) +{ + VALID_STREAM(glk_data->current_stream, return); + glk_put_string_stream_uni(glk_data->current_stream, s); +} + +/** + * glk_put_buffer: + * @buf: An array of characters in Latin-1 encoding. + * @len: Length of @buf. + * + * Prints a block of characters to the current stream. It is exactly equivalent + * to: + * |[ + * for (i = 0; i < @len; i++) + * #glk_put_char(@buf[i]); + * ]| + * However, it may be more efficient. + */ +void +glk_put_buffer(char *buf, glui32 len) +{ + VALID_STREAM(glk_data->current_stream, return); + glk_put_buffer_stream(glk_data->current_stream, buf, len); +} + +/** + * glk_put_buffer_uni: + * @buf: An array of Unicode code points. + * @len: Length of @buf. + * + * Prints a block of Unicode characters to the current stream. It is equivalent + * to a series of glk_put_char_uni() calls. + */ +void +glk_put_buffer_uni(glui32 *buf, glui32 len) +{ + VALID_STREAM(glk_data->current_stream, return); + glk_put_buffer_stream_uni(glk_data->current_stream, buf, len); +} + +/** + * glk_stream_open_memory: + * @buf: An allocated buffer, or %NULL. + * @buflen: Length of @buf. + * @fmode: Mode in which the buffer will be opened. Must be one of + * #filemode_Read, #filemode_Write, or #filemode_ReadWrite. + * @rock: The new stream's rock value. + * + * Opens a stream which reads from or writes to a space in memory. @buf points + * to the buffer where output will be read from or written to. @buflen is the + * length of the buffer. + * + * Unicode values (characters greater than 255) cannot be written to the buffer. + * If you try, they will be stored as 0x3F ("?") characters. + * + * Returns: the new stream, or %NULL on error. + */ +strid_t +glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock) +{ + g_return_val_if_fail(fmode != filemode_WriteAppend, NULL); + + strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_MEMORY); + str->buffer = buf; + str->mark = 0; + str->buflen = buflen; + str->unicode = FALSE; + return str; +} + +/** + * glk_stream_open_memory_uni: + * @buf: An allocated buffer, or %NULL. + * @buflen: Length of @buf. + * @fmode: Mode in which the buffer will be opened. Must be one of + * #filemode_Read, #filemode_Write, or #filemode_ReadWrite. + * @rock: The new stream's rock value. + * + * Works just like glk_stream_open_memory(), except that the buffer is an array + * of 32-bit words, instead of bytes. This allows you to write and read any + * Unicode character. The @buflen is the number of words, not the number of + * bytes. + * + * Returns: the new stream, or %NULL on error. + */ +strid_t +glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock) +{ + g_return_val_if_fail(fmode != filemode_WriteAppend, NULL); + + strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_MEMORY); + str->ubuffer = buf; + str->mark = 0; + str->buflen = buflen; + str->unicode = TRUE; + return str; +} + +/* Internal function: create a stream using the given parameters. */ +static strid_t +file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode) +{ + VALID_FILEREF(fileref, return NULL); + + gchar *modestr; + /* Binary mode is 0x000, text mode 0x100 */ + gboolean binary = !(fileref->usage & fileusage_TextMode); + switch(fmode) + { + case filemode_Read: + if(!g_file_test(fileref->filename, G_FILE_TEST_EXISTS)) { + ILLEGAL_PARAM("Tried to open a nonexistent file, '%s', in read mode", fileref->filename); + return NULL; + } + modestr = g_strdup(binary? "rb" : "r"); + break; + case filemode_Write: + modestr = g_strdup(binary? "wb" : "w"); + break; + case filemode_WriteAppend: + modestr = g_strdup(binary? "ab" : "a"); + break; + case filemode_ReadWrite: + if( g_file_test(fileref->filename, G_FILE_TEST_EXISTS) ) { + modestr = g_strdup(binary? "r+b" : "r+"); + } else { + modestr = g_strdup(binary? "w+b" : "w+"); + } + break; + default: + ILLEGAL_PARAM("Invalid file mode: %u", fmode); + return NULL; + } + + FILE *fp = g_fopen(fileref->filename, modestr); + g_free(modestr); + if(fp == NULL) { + IO_WARNING( "Error opening file", fileref->filename, g_strerror(errno) ); + return NULL; + } + + /* If they opened a file in write mode but didn't specifically get + permission to do so, complain if the file already exists */ + if(fileref->orig_filemode == filemode_Read && fmode != filemode_Read) { + gdk_threads_enter(); + + GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, + "File '%s' already exists. Overwrite?", fileref->filename); + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + gdk_threads_leave(); + + if(response != GTK_RESPONSE_YES) { + if(fclose(fp) != 0) + IO_WARNING( "Error closing file", fileref->filename, g_strerror(errno) ); + return NULL; + } + } + + strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_FILE); + str->file_pointer = fp; + str->binary = binary; + str->unicode = unicode; + str->filename = g_filename_to_utf8(fileref->filename, -1, NULL, NULL, NULL); + if(str->filename == NULL) + str->filename = g_strdup("Unknown file name"); /* fail silently */ + + return str; +} + +/** + * glk_stream_open_file: + * @fileref: Indicates the file which will be opened. + * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read, + * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite. + * @rock: The new stream's rock value. + * + * Opens a stream which reads to or writes from a disk file. If @fmode is + * #filemode_Read, the file must already exist; for the other modes, an empty + * file is created if none exists. If @fmode is #filemode_Write, and the file + * already exists, it is truncated down to zero length (an empty file). If + * @fmode is #filemode_WriteAppend, the file mark is set to the end of the + * file. + * + * When writing in binary mode, Unicode values (characters greater than 255) + * cannot be written to the file. If you try, they will be stored as 0x3F + * ("?") characters. In text mode, Unicode values may be stored + * exactly, approximated, or abbreviated, depending on what the platform's text + * files support. + * + * Returns: A new stream, or %NULL if the file operation failed. + */ +strid_t +glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock) +{ + return file_stream_new(fileref, fmode, rock, FALSE); +} + +/** + * glk_stream_open_file_uni: + * @fileref: Indicates the file which will be opened. + * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read, + * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite. + * @rock: The new stream's rock value. + * + * This works just like glk_stream_open_file(), except that in binary mode, + * characters are written and read as four-byte (big-endian) values. This + * allows you to write any Unicode character. + * + * In text mode, the file is written and read in a platform-dependent way, which + * may or may not handle all Unicode characters. A text-mode file created with + * glk_stream_open_file_uni() may have the same format as a text-mode file + * created with glk_stream_open_file(); or it may use a more Unicode-friendly + * format. + * + * Returns: A new stream, or %NULL if the file operation failed. + */ +strid_t +glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock) +{ + return file_stream_new(fileref, fmode, rock, TRUE); +} + +/** + * glk_stream_close: + * @str: Stream to close. + * @result: Pointer to a #stream_result_t, or %NULL. + * + * Closes the stream @str. The @result argument points to a structure which is + * filled in with the final character counts of the stream. If you do not care + * about these, you may pass %NULL as the @result argument. + * + * If @str is the current output stream, the current output stream is set to + * %NULL. + * + * You cannot close window streams; use glk_window_close() instead. See Window Opening, + * Closing, and Constraints. + */ +void +glk_stream_close(strid_t str, stream_result_t *result) +{ + VALID_STREAM(str, return); + + /* Free resources associated with one specific type of stream */ + switch(str->type) + { + case STREAM_TYPE_WINDOW: + ILLEGAL("Attempted to close a window stream. Use glk_window_close() instead."); + return; + + case STREAM_TYPE_MEMORY: + /* Do nothing */ + break; + + case STREAM_TYPE_FILE: + if(fclose(str->file_pointer) != 0) + IO_WARNING( "Failed to close file", str->filename, g_strerror(errno) ); + g_free(str->filename); + break; + default: + ILLEGAL_PARAM("Unknown stream type: %u", str->type); + return; + } + + stream_close_common(str, result); +} + +/* Internal function: Stuff to do upon closing any type of stream. */ +void +stream_close_common(strid_t str, stream_result_t *result) +{ + /* Remove the stream from the global stream list */ + glk_data->stream_list = g_list_delete_link(glk_data->stream_list, str->stream_list); + + /* If it was the current output stream, set that to NULL */ + if(glk_data->current_stream == str) + glk_data->current_stream = NULL; + + /* If it was one or more windows' echo streams, set those to NULL */ + winid_t win; + for(win = glk_window_iterate(NULL, NULL); win; + win = glk_window_iterate(win, NULL)) + if(win->echo_stream == str) + win->echo_stream = NULL; + + /* Return the character counts */ + if(result) + { + result->readcount = str->read_count; + result->writecount = str->write_count; + } + + str->magic = MAGIC_FREE; + g_free(str); +} diff --git a/libchimara/stream.h b/libchimara/stream.h new file mode 100644 index 0000000..0ea398e --- /dev/null +++ b/libchimara/stream.h @@ -0,0 +1,52 @@ +#ifndef STREAM_H +#define STREAM_H + +#include +#include "glk.h" +#include "window.h" + +enum StreamType +{ + STREAM_TYPE_WINDOW, + STREAM_TYPE_MEMORY, + STREAM_TYPE_FILE +}; + +/** + * glk_stream_struct: + * + * This is an opaque structure (see + * Opaque Structures and should not be accessed directly. + */ +struct glk_stream_struct +{ + /*< private >*/ + glui32 magic, rock; + /* Pointer to the list node in the global stream list that contains this + stream */ + GList* stream_list; + /* Stream parameters */ + glui32 file_mode; + glui32 read_count; + glui32 write_count; + enum StreamType type; + /* Specific to window stream: the window this stream is connected to */ + winid_t window; + /* For memory and file streams */ + gboolean unicode; + /* Specific to memory streams */ + gchar *buffer; + glui32 *ubuffer; + glui32 mark; + glui32 buflen; + /* Specific to file streams */ + FILE *file_pointer; + gboolean binary; + gchar *filename; /* Displayable filename in UTF-8 for error handling */ + + gchar *style; /* Name of the current style */ +}; + +G_GNUC_INTERNAL strid_t window_stream_new(winid_t window); +G_GNUC_INTERNAL void stream_close_common(strid_t str, stream_result_t *result); +#endif diff --git a/libchimara/strio.c b/libchimara/strio.c new file mode 100644 index 0000000..921b75c --- /dev/null +++ b/libchimara/strio.c @@ -0,0 +1,1175 @@ +#include "charset.h" +#include "magic.h" +#include "stream.h" +#include +#include +#include +#include +#include + +/* + * + **************** WRITING FUNCTIONS ******************************************** + * + */ + +/* Internal function: write a UTF-8 string to a text grid window's text buffer. */ +static void +write_utf8_to_grid(winid_t win, gchar *s) +{ + if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) + { + ILLEGAL("Tried to print to a text grid window with line input pending."); + return; + } + + /* Number of characters to insert */ + glong length = g_utf8_strlen(s, -1); + glong chars_left = length; + + gdk_threads_enter(); + + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position"); + + /* Get cursor position */ + GtkTextIter start; + gtk_text_buffer_get_iter_at_mark(buffer, &start, cursor); + /* Spaces available on this line */ + gint available_space = win->width - gtk_text_iter_get_line_offset(&start); + + while(chars_left > available_space && !gtk_text_iter_is_end(&start)) + { + GtkTextIter end = start; + gtk_text_iter_forward_to_line_end(&end); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), available_space); + chars_left -= available_space; + gtk_text_iter_forward_line(&start); + available_space = win->width; + } + if(!gtk_text_iter_is_end(&start)) + { + GtkTextIter end = start; + gtk_text_iter_forward_chars(&end, chars_left); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), -1); + } + + gtk_text_buffer_move_mark(buffer, cursor, &start); + + gdk_threads_leave(); +} + +/* Internal function: write a UTF-8 string to a text buffer window's text buffer. */ +static void +write_utf8_to_window(winid_t win, gchar *s) +{ + if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) + { + ILLEGAL("Tried to print to a text buffer window with line input pending."); + return; + } + + gdk_threads_enter(); + + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + + GtkTextIter iter; + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, s, -1, win->window_stream->style, NULL); + + gdk_threads_leave(); +} + +/* Internal function: write a Latin-1 buffer with length to a stream. */ +static void +write_buffer_to_stream(strid_t str, gchar *buf, glui32 len) +{ + switch(str->type) + { + case STREAM_TYPE_WINDOW: + /* Each window type has a different way of printing to it */ + switch(str->window->type) + { + /* Printing to these windows' streams does nothing */ + case wintype_Blank: + case wintype_Pair: + case wintype_Graphics: + str->write_count += len; + break; + + /* Text grid window */ + case wintype_TextGrid: + { + gchar *utf8 = convert_latin1_to_utf8(buf, len); + if(utf8 != NULL) + { + /* FIXME: What to do if string contains \n? Split the input string at newlines and write each string separately? */ + write_utf8_to_grid(str->window, utf8); + g_free(utf8); + } + } + str->write_count += len; + break; + + /* Text buffer window */ + case wintype_TextBuffer: + { + gchar *utf8 = convert_latin1_to_utf8(buf, len); + if(utf8 != NULL) + { + write_utf8_to_window(str->window, utf8); + g_free(utf8); + } + } + str->write_count += len; + break; + default: + ILLEGAL_PARAM("Unknown window type: %u", str->window->type); + } + + /* Now write the same buffer to the window's echo stream */ + if(str->window->echo_stream != NULL) + write_buffer_to_stream(str->window->echo_stream, buf, len); + + break; + + case STREAM_TYPE_MEMORY: + if(str->unicode && str->ubuffer) + { + int foo = 0; + while(str->mark < str->buflen && foo < len) + str->ubuffer[str->mark++] = (unsigned char)buf[foo++]; + } + if(!str->unicode && str->buffer) + { + int copycount = MIN(len, str->buflen - str->mark); + memmove(str->buffer + str->mark, buf, copycount); + str->mark += copycount; + } + + str->write_count += len; + break; + + case STREAM_TYPE_FILE: + if(str->binary) + { + if(str->unicode) + { + gchar *writebuffer = convert_latin1_to_ucs4be_string(buf, len); + fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer); + g_free(writebuffer); + } + else /* Regular file */ + { + fwrite(buf, sizeof(gchar), len, str->file_pointer); + } + } + else /* Text mode is the same for Unicode and regular files */ + { + gchar *utf8 = convert_latin1_to_utf8(buf, len); + if(utf8 != NULL) + { + g_fprintf(str->file_pointer, "%s", utf8); + g_free(utf8); + } + } + + str->write_count += len; + break; + default: + ILLEGAL_PARAM("Unknown stream type: %u", str->type); + } +} + +/* Internal function: write a Unicode buffer with length to a stream. */ +static void +write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len) +{ + switch(str->type) + { + case STREAM_TYPE_WINDOW: + /* Each window type has a different way of printing to it */ + switch(str->window->type) + { + /* Printing to these windows' streams does nothing */ + case wintype_Blank: + case wintype_Pair: + case wintype_Graphics: + str->write_count += len; + break; + + /* Text grid window */ + case wintype_TextGrid: + { + gchar *utf8 = convert_ucs4_to_utf8(buf, len); + if(utf8 != NULL) + { + /* FIXME: What to do if string contains \n? Split the input string at newlines and write each string separately? */ + write_utf8_to_grid(str->window, utf8); + g_free(utf8); + } + } + str->write_count += len; + break; + + /* Text buffer window */ + case wintype_TextBuffer: + { + gchar *utf8 = convert_ucs4_to_utf8(buf, len); + if(utf8 != NULL) + { + write_utf8_to_window(str->window, utf8); + g_free(utf8); + } + } + str->write_count += len; + break; + default: + ILLEGAL_PARAM("Unknown window type: %u", str->window->type); + } + + /* Now write the same buffer to the window's echo stream */ + if(str->window->echo_stream != NULL) + write_buffer_to_stream_uni(str->window->echo_stream, buf, len); + + break; + + case STREAM_TYPE_MEMORY: + if(str->unicode && str->ubuffer) + { + int copycount = MIN(len, str->buflen - str->mark); + memmove(str->ubuffer + str->mark, buf, copycount * sizeof(glui32)); + str->mark += copycount; + } + if(!str->unicode && str->buffer) + { + gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len); + int copycount = MIN(len, str->buflen - str->mark); + memmove(str->buffer + str->mark, latin1, copycount); + g_free(latin1); + str->mark += copycount; + } + + str->write_count += len; + break; + + case STREAM_TYPE_FILE: + if(str->binary) + { + if(str->unicode) + { + gchar *writebuffer = convert_ucs4_to_ucs4be_string(buf, len); + fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer); + g_free(writebuffer); + } + else /* Regular file */ + { + gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len); + fwrite(latin1, sizeof(gchar), len, str->file_pointer); + g_free(latin1); + } + } + else /* Text mode is the same for Unicode and regular files */ + { + gchar *utf8 = convert_ucs4_to_utf8(buf, len); + if(utf8 != NULL) + { + g_fprintf(str->file_pointer, "%s", utf8); + g_free(utf8); + } + } + + str->write_count += len; + break; + default: + ILLEGAL_PARAM("Unknown stream type: %u", str->type); + } +} + +/** + * glk_put_char_stream: + * @str: An output stream. + * @ch: A character in Latin-1 encoding. + * + * The same as glk_put_char(), except that you specify a stream @str to print + * to, instead of using the current stream. It is illegal for @str to be %NULL, + * or an input-only stream. + */ +void +glk_put_char_stream(strid_t str, unsigned char ch) +{ + VALID_STREAM(str, return); + g_return_if_fail(str->file_mode != filemode_Read); + + write_buffer_to_stream(str, (gchar *)&ch, 1); +} + +/** + * glk_put_char_stream_uni: + * @str: An output stream. + * @ch: A Unicode code point. + * + * The same as glk_put_char_uni(), except that you specify a stream @str to + * print to, instead of using the current stream. It is illegal for @str to be + * %NULL, or an input-only stream. + */ +void +glk_put_char_stream_uni(strid_t str, glui32 ch) +{ + VALID_STREAM(str, return); + g_return_if_fail(str->file_mode != filemode_Read); + + write_buffer_to_stream_uni(str, &ch, 1); +} + +/** + * glk_put_string_stream: + * @str: An output stream. + * @s: A null-terminated string in Latin-1 encoding. + * + * The same as glk_put_string(), except that you specify a stream @str to print + * to, instead of using the current stream. It is illegal for @str to be %NULL, + * or an input-only stream. + */ +void +glk_put_string_stream(strid_t str, char *s) +{ + VALID_STREAM(str, return); + g_return_if_fail(str->file_mode != filemode_Read); + + write_buffer_to_stream(str, s, strlen(s)); +} + +/** + * glk_put_string_stream_uni: + * @str: An output stream. + * @s: A null-terminated array of Unicode code points. + * + * The same as glk_put_string_uni(), except that you specify a stream @str to + * print to, instead of using the current stream. It is illegal for @str to be + * %NULL, or an input-only stream. + */ +void +glk_put_string_stream_uni(strid_t str, glui32 *s) +{ + VALID_STREAM(str, return); + g_return_if_fail(str->file_mode != filemode_Read); + + /* An impromptu strlen() for glui32 arrays */ + glong len = 0; + glui32 *ptr = s; + while(*ptr++) + len++; + write_buffer_to_stream_uni(str, s, len); +} + +/** + * glk_put_buffer_stream: + * @str: An output stream. + * @buf: An array of characters in Latin-1 encoding. + * @len: Length of @buf. + * + * The same as glk_put_buffer(), except that you specify a stream @str to print + * to, instead of using the current stream. It is illegal for @str to be %NULL, + * or an input-only stream. + */ +void +glk_put_buffer_stream(strid_t str, char *buf, glui32 len) +{ + VALID_STREAM(str, return); + g_return_if_fail(str->file_mode != filemode_Read); + + write_buffer_to_stream(str, buf, len); +} + +/** + * glk_put_buffer_stream_uni: + * @str: An output stream. + * @buf: An array of Unicode code points. + * @len: Length of @buf. + * + * The same as glk_put_buffer_uni(), except that you specify a stream @str to + * print to, instead of using the current stream. It is illegal for @str to be + * %NULL, or an input-only stream. + */ +void +glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) +{ + VALID_STREAM(str, return); + g_return_if_fail(str->file_mode != filemode_Read); + + write_buffer_to_stream_uni(str, buf, len); +} + +/* + * + **************** READING FUNCTIONS ******************************************** + * + */ + +/* Internal function: Read one big-endian four-byte character from file fp and +return it as a Unicode code point, or -1 on EOF */ +static glsi32 +read_ucs4be_char_from_file(FILE *fp) +{ + unsigned char readbuffer[4]; + if(fread(readbuffer, sizeof(unsigned char), 4, fp) < 4) + return -1; /* EOF */ + return + readbuffer[0] << 24 | + readbuffer[1] << 16 | + readbuffer[2] << 8 | + readbuffer[3]; +} + +/* Internal function: Read one UTF-8 character, which may be more than one byte, +from file fp and return it as a Unicode code point, or -1 on EOF */ +static glsi32 +read_utf8_char_from_file(FILE *fp) +{ + gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */ + int foo; + gunichar charresult = (gunichar)-2; + for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) + { + int ch = fgetc(fp); + if(ch == EOF) + return -1; + readbuffer[foo] = (gchar)ch; + charresult = g_utf8_get_char_validated(readbuffer, foo + 1); + /* charresult is -1 if invalid, -2 if incomplete, and the unicode code + point otherwise */ + } + /* Silently return unknown characters as 0xFFFD, Replacement Character */ + if(charresult == (gunichar)-1 || charresult == (gunichar)-2) + return 0xFFFD; + return charresult; +} + +/* Internal function: Tell whether this code point is a Unicode newline. The +file pointer and eight-bit flag are included in case the newline is a CR +(U+000D). If the next character is LF (U+000A) then it also belongs to the +newline. */ +static gboolean +is_unicode_newline(glsi32 ch, FILE *fp, gboolean utf8) +{ + if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) + return TRUE; + if(ch == 0x0D) { + glsi32 ch2 = utf8? read_utf8_char_from_file(fp) : + read_ucs4be_char_from_file(fp); + if(ch2 != 0x0A) + if(fseek(fp, utf8? -1 : -4, SEEK_CUR) == -1); + WARNING_S("Seek failed on stream", g_strerror(errno) ); + return TRUE; + } + return FALSE; +} + +/* Internal function: Read one character from a stream. Returns a value which + can be returned unchanged by glk_get_char_stream_uni(), but + glk_get_char_stream() must replace high values by the placeholder character. */ +static glsi32 +get_char_stream_common(strid_t str) +{ + switch(str->type) + { + case STREAM_TYPE_MEMORY: + if(str->unicode) + { + if(!str->ubuffer || str->mark >= str->buflen) + return -1; + glui32 ch = str->ubuffer[str->mark++]; + str->read_count++; + return ch; + } + else + { + if(!str->buffer || str->mark >= str->buflen) + return -1; + unsigned char ch = str->buffer[str->mark++]; + str->read_count++; + return ch; + } + break; + + case STREAM_TYPE_FILE: + if(str->binary) + { + if(str->unicode) + { + glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); + if(ch == -1) + return -1; + str->read_count++; + return ch; + } + else /* Regular file */ + { + int ch = fgetc(str->file_pointer); + if(ch == EOF) + return -1; + + str->read_count++; + return ch; + } + } + else /* Text mode is the same for Unicode and regular files */ + { + glsi32 ch = read_utf8_char_from_file(str->file_pointer); + if(ch == -1) + return -1; + + str->read_count++; + return ch; + } + default: + ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); + return -1; + } +} + +/** + * glk_get_char_stream: + * @str: An input stream. + * + * Reads one character from the stream @str. (There is no notion of a + * current input stream.) It is illegal for @str to be %NULL, or + * an output-only stream. + * + * The result will be between 0 and 255. As with all basic text functions, Glk + * assumes the Latin-1 encoding. See Character Encoding. If the end + * of the stream has been reached, the result will be -1. + * + * + * Note that high-bit characters (128..255) are not + * returned as negative numbers. + * + * + * If the stream contains Unicode data — for example, if it was created + * with glk_stream_open_file_uni() or glk_stream_open_memory_uni() — then + * characters beyond 255 will be returned as 0x3F ("?"). + * + * It is usually more efficient to read several characters at once with + * glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling + * glk_get_char_stream() several times. + * + * Returns: A character value between 0 and 255, or -1 on end of stream. + */ +glsi32 +glk_get_char_stream(strid_t str) +{ + VALID_STREAM(str, return -1); + g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1); + + glsi32 ch = get_char_stream_common(str); + return (ch > 0xFF)? PLACEHOLDER : ch; +} + +/** + * glk_get_char_stream_uni: + * @str: An input stream. + * + * Reads one character from the stream @str. The result will be between 0 and + * 0x7FFFFFFF. If the end of the stream has been reached, the result will be -1. + * + * Returns: A value between 0 and 0x7FFFFFFF, or -1 on end of stream. + */ +glsi32 +glk_get_char_stream_uni(strid_t str) +{ + VALID_STREAM(str, return -1); + g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1); + + return get_char_stream_common(str); +} + +/** + * glk_get_buffer_stream: + * @str: An input stream. + * @buf: A buffer with space for at least @len characters. + * @len: The number of characters to read. + * + * Reads @len characters from @str, unless the end of stream is reached first. + * No terminal null is placed in the buffer. + * + * Returns: The number of characters actually read. + */ +glui32 +glk_get_buffer_stream(strid_t str, char *buf, glui32 len) +{ + VALID_STREAM(str, return 0); + g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); + g_return_val_if_fail(buf != NULL, 0); + + switch(str->type) + { + case STREAM_TYPE_MEMORY: + { + int copycount = 0; + if(str->unicode) + { + while(copycount < len && str->ubuffer && str->mark < str->buflen) + { + glui32 ch = str->ubuffer[str->mark++]; + buf[copycount++] = (ch > 0xFF)? '?' : (char)ch; + } + } + else + { + if(str->buffer) /* if not, copycount stays 0 */ + copycount = MIN(len, str->buflen - str->mark); + memmove(buf, str->buffer + str->mark, copycount); + str->mark += copycount; + } + + str->read_count += copycount; + return copycount; + } + case STREAM_TYPE_FILE: + if(str->binary) + { + if(str->unicode) /* Binary file with 4-byte characters */ + { + /* Read len characters of 4 bytes each */ + unsigned char *readbuffer = g_new0(unsigned char, 4 * len); + size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer); + /* If there was an incomplete character */ + if(count % 4 != 0) + { + count -= count % 4; + WARNING("Incomplete character in binary Unicode file"); + } + + int foo; + for(foo = 0; foo < count; foo += 4) + { + glsi32 ch = readbuffer[foo] << 24 + | readbuffer[foo + 1] << 16 + | readbuffer[foo + 2] << 8 + | readbuffer[foo + 3]; + buf[foo / 4] = (ch > 255)? 0x3F : (char)ch; + } + g_free(readbuffer); + str->read_count += count / 4; + return count / 4; + } + else /* Regular binary file */ + { + size_t count = fread(buf, sizeof(char), len, str->file_pointer); + str->read_count += count; + return count; + } + } + else /* Text mode is the same for Unicode and regular files */ + { + /* Do it character-by-character */ + int foo; + for(foo = 0; foo < len; foo++) + { + glsi32 ch = read_utf8_char_from_file(str->file_pointer); + if(ch == -1) + break; + str->read_count++; + buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch; + } + return foo; + } + default: + ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); + return 0; + } +} + +/** + * glk_get_buffer_stream_uni: + * @str: An input stream. + * @buf: A buffer with space for at least @len Unicode code points. + * @len: The number of characters to read. + * + * Reads @len Unicode characters from @str, unless the end of stream is reached + * first. No terminal null is placed in the buffer. + * + * Returns: The number of Unicode characters actually read. + */ +glui32 +glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) +{ + VALID_STREAM(str, return 0); + g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); + g_return_val_if_fail(buf != NULL, 0); + + switch(str->type) + { + case STREAM_TYPE_MEMORY: + { + int copycount = 0; + if(str->unicode) + { + if(str->ubuffer) /* if not, copycount stays 0 */ + copycount = MIN(len, str->buflen - str->mark); + memmove(buf, str->ubuffer + str->mark, copycount * 4); + str->mark += copycount; + } + else + { + while(copycount < len && str->buffer && str->mark < str->buflen) + { + unsigned char ch = str->buffer[str->mark++]; + buf[copycount++] = ch; + } + } + + str->read_count += copycount; + return copycount; + } + case STREAM_TYPE_FILE: + if(str->binary) + { + if(str->unicode) /* Binary file with 4-byte characters */ + { + /* Read len characters of 4 bytes each */ + unsigned char *readbuffer = g_new0(unsigned char, 4 * len); + size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer); + /* If there was an incomplete character */ + if(count % 4 != 0) + { + count -= count % 4; + WARNING("Incomplete character in binary Unicode file"); + } + + int foo; + for(foo = 0; foo < count; foo += 4) + buf[foo / 4] = readbuffer[foo] << 24 + | readbuffer[foo + 1] << 16 + | readbuffer[foo + 2] << 8 + | readbuffer[foo + 3]; + g_free(readbuffer); + str->read_count += count / 4; + return count / 4; + } + else /* Regular binary file */ + { + unsigned char *readbuffer = g_new0(unsigned char, len); + size_t count = fread(readbuffer, sizeof(unsigned char), len, str->file_pointer); + int foo; + for(foo = 0; foo < count; foo++) + buf[foo] = readbuffer[foo]; + g_free(readbuffer); + str->read_count += count; + return count; + } + } + else /* Text mode is the same for Unicode and regular files */ + { + /* Do it character-by-character */ + int foo; + for(foo = 0; foo < len; foo++) + { + glsi32 ch = read_utf8_char_from_file(str->file_pointer); + if(ch == -1) + break; + str->read_count++; + buf[foo] = ch; + } + return foo; + } + default: + ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); + return 0; + } +} + +/** + * glk_get_line_stream: + * @str: An input stream. + * @buf: A buffer with space for at least @len characters. + * @len: The number of characters to read, plus one. + * + * Reads characters from @str, until either + * + * @len - 1 + * @len - 1 + * + * characters have been read or a newline has been read. It then puts a + * terminal null ('\0') aracter on + * the end. It returns the number of characters actually read, including the + * newline (if there is one) but not including the terminal null. + * + * Returns: The number of characters actually read. + */ +glui32 +glk_get_line_stream(strid_t str, char *buf, glui32 len) +{ + VALID_STREAM(str, return 0); + g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); + g_return_val_if_fail(buf != NULL, 0); + + switch(str->type) + { + case STREAM_TYPE_MEMORY: + { + int copycount = 0; + if(str->unicode) + { + /* Do it character-by-character */ + while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) + { + glui32 ch = str->ubuffer[str->mark++]; + /* Check for Unicode newline; slightly different than + in file streams */ + if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) + { + buf[copycount++] = '\n'; + break; + } + if(ch == 0x0D) + { + if(str->ubuffer[str->mark] == 0x0A) + str->mark++; /* skip past next newline */ + buf[copycount++] = '\n'; + break; + } + buf[copycount++] = (ch > 0xFF)? '?' : (char)ch; + } + buf[copycount] = '\0'; + } + else + { + if(str->buffer) /* if not, copycount stays 0 */ + copycount = MIN(len - 1, str->buflen - str->mark); + char *endptr = memccpy(buf, str->buffer + str->mark, '\n', copycount); + if(endptr) /* newline was found */ + copycount = endptr - buf; /* Real copy count */ + buf[copycount] = '\0'; + str->mark += copycount; + } + + str->read_count += copycount; + return copycount; + } + case STREAM_TYPE_FILE: + if(str->binary) + { + if(str->unicode) /* Binary file with 4-byte characters */ + { + /* Do it character-by-character */ + int foo; + for(foo = 0; foo < len - 1; foo++) + { + glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); + if(ch == -1) + { + buf[foo] = '\0'; + return foo - 1; + } + str->read_count++; + if(is_unicode_newline(ch, str->file_pointer, FALSE)) + { + buf[foo] = '\n'; + buf[foo + 1] = '\0'; + return foo; + } + buf[foo] = (ch > 0xFF)? '?' : (char)ch; + } + buf[len] = '\0'; + return foo; + } + else /* Regular binary file */ + { + fgets(buf, len, str->file_pointer); + str->read_count += strlen(buf); + return strlen(buf); + } + } + else /* Text mode is the same for Unicode and regular files */ + { + /* Do it character-by-character */ + int foo; + for(foo = 0; foo < len - 1; foo++) + { + glsi32 ch = read_utf8_char_from_file(str->file_pointer); + if(ch == -1) + { + buf[foo] = '\0'; + return foo - 1; + } + str->read_count++; + if(is_unicode_newline(ch, str->file_pointer, TRUE)) + { + buf[foo] = '\n'; + buf[foo + 1] = '\0'; + return foo; + } + buf[foo] = (ch > 0xFF)? 0x3F : (char)ch; + } + buf[len] = '\0'; + return foo; + } + default: + ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); + return 0; + } +} + +/** + * glk_get_line_stream_uni: + * @str: An input stream. + * @buf: A buffer with space for at least @len Unicode code points. + * @len: The number of characters to read, plus one. + * + * Reads Unicode characters from @str, until either + * + * @len - 1 + * @len - 1 + * + * Unicode characters have been read or a newline has been read. It then puts a + * terminal null (a zero value) on the end. + * + * Returns: The number of characters actually read, including the newline (if + * there is one) but not including the terminal null. + */ +glui32 +glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len) +{ + VALID_STREAM(str, return 0); + g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); + g_return_val_if_fail(buf != NULL, 0); + + switch(str->type) + { + case STREAM_TYPE_MEMORY: + { + int copycount = 0; + if(str->unicode) + { + /* Do it character-by-character */ + while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) + { + glui32 ch = str->ubuffer[str->mark++]; + /* Check for Unicode newline; slightly different than + in file streams */ + if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) + { + buf[copycount++] = '\n'; + break; + } + if(ch == 0x0D) + { + if(str->ubuffer[str->mark] == 0x0A) + str->mark++; /* skip past next newline */ + buf[copycount++] = '\n'; + break; + } + buf[copycount++] = ch; + } + buf[copycount] = '\0'; + } + else + { + /* No recourse to memccpy(), so do it character-by-character */ + while(copycount < len - 1 && str->buffer && str->mark < str->buflen) + { + gchar ch = str->buffer[str->mark++]; + /* Check for newline */ + if(ch == '\n') /* Also check for \r and \r\n? */ + { + buf[copycount++] = '\n'; + break; + } + buf[copycount++] = (unsigned char)ch; + } + buf[copycount] = 0; + } + + str->read_count += copycount; + return copycount; + } + case STREAM_TYPE_FILE: + if(str->binary) + { + if(str->unicode) /* Binary file with 4-byte characters */ + { + /* Do it character-by-character */ + int foo; + for(foo = 0; foo < len - 1; foo++) + { + glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); + if(ch == -1) + { + buf[foo] = 0; + return foo - 1; + } + str->read_count++; + if(is_unicode_newline(ch, str->file_pointer, FALSE)) + { + buf[foo] = ch; /* Preserve newline types??? */ + buf[foo + 1] = 0; + return foo; + } + buf[foo] = ch; + } + buf[len] = 0; + return foo; + } + else /* Regular binary file */ + { + gchar *readbuffer = g_new0(gchar, len); + fgets(readbuffer, len, str->file_pointer); + glui32 count = strlen(readbuffer) + 1; /* Copy terminator */ + int foo; + for(foo = 0; foo < count; foo++) + buf[foo] = (unsigned char)(readbuffer[foo]); + str->read_count += count; + return count; + } + } + else /* Text mode is the same for Unicode and regular files */ + { + /* Do it character-by-character */ + int foo; + for(foo = 0; foo < len - 1; foo++) + { + glsi32 ch = read_utf8_char_from_file(str->file_pointer); + if(ch == -1) + { + buf[foo] = 0; + return foo - 1; + } + str->read_count++; + if(is_unicode_newline(ch, str->file_pointer, TRUE)) + { + buf[foo] = ch; /* Preserve newline types??? */ + buf[foo + 1] = 0; + return foo; + } + buf[foo] = ch; + } + buf[len] = 0; + return foo; + } + default: + ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); + return 0; + } +} + +/* + * + **************** SEEKING FUNCTIONS ******************************************** + * + */ + +/** + * glk_stream_get_position: + * @str: A file or memory stream. + * + * Returns the position of the read/write mark in @str. For memory streams and + * binary file streams, this is exactly the number of characters read or written + * from the beginning of the stream (unless you have moved the mark with + * glk_stream_set_position().) For text file streams, matters are more + * ambiguous, since (for example) writing one byte to a text file may store more + * than one character in the platform's native encoding. You can only be sure + * that the position increases as you read or write to the file. + * + * Additional complication: for Latin-1 memory and file streams, a character is + * a byte. For Unicode memory and file streams (those created by + * glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is + * a 32-bit word. So in a binary Unicode file, positions are multiples of four + * bytes. + * + * + * If this bothers you, don't use binary Unicode files. I don't think they're + * good for much anyhow. + * + * + * Returns: position of the read/write mark in @str. + */ +glui32 +glk_stream_get_position(strid_t str) +{ + VALID_STREAM(str, return 0); + + switch(str->type) + { + case STREAM_TYPE_MEMORY: + return str->mark; + case STREAM_TYPE_FILE: + return ftell(str->file_pointer); + default: + ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type); + return 0; + } +} + +/** + * glk_stream_set_position: + * @str: A file or memory stream. + * @pos: The position to set the mark to, relative to @seekmode. + * @seekmode: One of #seekmode_Start, #seekmode_Current, or #seekmode_End. + * + * Sets the position of the read/write mark in @str. The position is controlled + * by @pos, and the meaning of @pos is controlled by @seekmode. See the + * seekmode_ constants below. + * + * It is illegal to specify a position before the beginning or after the end of + * the file. + * + * In binary files, the mark position is exact — it corresponds with the + * number of characters you have read or written. In text files, this mapping + * can vary, because of linefeed conventions or other character-set + * approximations. See Streams. + * glk_stream_set_position() and glk_stream_get_position() measure positions in + * the platform's native encoding — after character cookery. Therefore, + * in a text stream, it is safest to use glk_stream_set_position() only to move + * to the beginning or end of a file, or to a position determined by + * glk_stream_get_position(). + * + * Again, in Latin-1 streams, characters are bytes. In Unicode streams, + * characters are 32-bit words, or four bytes each. + */ +void +glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode) +{ + VALID_STREAM(str, return); + g_return_if_fail(!(seekmode == seekmode_Start && pos < 0)); + g_return_if_fail(!(seekmode == seekmode_End || pos > 0)); + + switch(str->type) + { + case STREAM_TYPE_MEMORY: + switch(seekmode) + { + case seekmode_Start: str->mark = pos; break; + case seekmode_Current: str->mark += pos; break; + case seekmode_End: str->mark = str->buflen + pos; break; + default: + g_return_if_reached(); + return; + } + break; + case STREAM_TYPE_FILE: + { + int whence; + switch(seekmode) + { + case seekmode_Start: whence = SEEK_SET; break; + case seekmode_Current: whence = SEEK_CUR; break; + case seekmode_End: whence = SEEK_END; break; + default: + g_return_if_reached(); + return; + } + if(fseek(str->file_pointer, pos, whence) == -1) + WARNING("Seek failed on file stream"); + break; + } + default: + ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type); + return; + } +} + diff --git a/libchimara/style.c b/libchimara/style.c new file mode 100644 index 0000000..d2eb235 --- /dev/null +++ b/libchimara/style.c @@ -0,0 +1,197 @@ +#include "style.h" + +extern ChimaraGlkPrivate *glk_data; + +/** + * glk_set_style: + * @styl The style to apply + * + * This changes the style of the current output stream. After a style change, + * new text which is printed to that stream will be given the new style. For a + * window stream, the text will appear in that style. For other types of + * streams, this has no effect. + */ +void +glk_set_style(glui32 style) +{ + g_return_if_fail(glk_data->current_stream != NULL); + glk_set_style_stream(glk_data->current_stream, style); +} + +/* Internal function: mapping from style enum to tag name */ +gchar* +get_tag_name(glui32 style) +{ + switch(style) { + case style_Normal: return "normal"; + case style_Emphasized: return "emphasized"; + case style_Preformatted: return "preformatted"; + case style_Header: return "header"; + case style_Subheader: return "subheader"; + case style_Alert: return "alert"; + case style_Note: return "note"; + case style_BlockQuote: return "block-quote"; + case style_Input: return "input"; + case style_User1: return "user1"; + case style_User2: return "user2"; + } + + WARNING("Unsupported style"); + return "normal"; +} + +void +glk_set_style_stream(strid_t stream, glui32 style) { + stream->style = get_tag_name(style); +} + +/* Internal function: call this to initialize the default styles to a textbuffer. */ +void +style_init_textbuffer(GtkTextBuffer *buffer) +{ + g_return_if_fail(buffer != NULL); + + gtk_text_buffer_create_tag(buffer, "normal", NULL); + gtk_text_buffer_create_tag(buffer, "emphasized", "style", PANGO_STYLE_ITALIC, NULL); + gtk_text_buffer_create_tag(buffer, "preformatted", "font-desc", glk_data->monospace_font_desc, NULL); + gtk_text_buffer_create_tag(buffer, "header", "size-points", 16.0, "weight", PANGO_WEIGHT_BOLD, NULL); + gtk_text_buffer_create_tag(buffer, "subheader", "size-points", 12.0, "weight", PANGO_WEIGHT_BOLD, NULL); + gtk_text_buffer_create_tag(buffer, "alert", "foreground", "#aa0000", "weight", PANGO_WEIGHT_BOLD, NULL); + gtk_text_buffer_create_tag(buffer, "note", "foreground", "#aaaa00", "weight", PANGO_WEIGHT_BOLD, NULL); + gtk_text_buffer_create_tag(buffer, "block-quote", "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, NULL); + gtk_text_buffer_create_tag(buffer, "input", NULL); + gtk_text_buffer_create_tag(buffer, "user1", NULL); + gtk_text_buffer_create_tag(buffer, "user2", NULL); +} + +void +color_format(glui32 val, gchar *buffer) +{ + sprintf(buffer, "#%02X%02X%02X", + ((val & 0xff0000) >> 16), + ((val & 0x00ff00) >> 8), + (val & 0x0000ff) + ); +} + +/* Internal function: changes a GTK tag to correspond with the given style. */ +void +apply_stylehint_to_tag(GtkTextTag *tag, glui32 hint, glsi32 val) +{ + g_return_if_fail(tag != NULL); + + GObject *tag_object = G_OBJECT(tag); + gint reverse_color = 0; + + /* FIXME where should we keep track of this? + g_object_get(tag, "reverse_color", &reverse_color, NULL); + */ + + int i = 0; + gchar color[20]; + switch(hint) { + case stylehint_Indentation: + g_object_set(tag_object, "left_margin", 5*val, NULL); + g_object_set(tag_object, "right_margin", 5*val, NULL); + break; + + case stylehint_ParaIndentation: + g_object_set(tag_object, "indent", 5*val, NULL); + break; + + case stylehint_Justification: + switch(val) { + case stylehint_just_LeftFlush: i = GTK_JUSTIFY_LEFT; break; + case stylehint_just_LeftRight: i = GTK_JUSTIFY_FILL; break; + case stylehint_just_Centered: i = GTK_JUSTIFY_CENTER; break; + case stylehint_just_RightFlush: i = GTK_JUSTIFY_RIGHT; break; + default: + WARNING("Unknown justification"); + i = GTK_JUSTIFY_LEFT; + } + g_object_set(tag_object, "justification", i, NULL); + break; + + case stylehint_Weight: + switch(val) { + case -1: i = PANGO_WEIGHT_LIGHT; break; + case 0: i = PANGO_WEIGHT_NORMAL; break; + case 1: i = PANGO_WEIGHT_BOLD; break; + default: WARNING("Unknown font weight"); + } + g_object_set(tag_object, "weight", i, NULL); + break; + + case stylehint_Size: + g_object_set(tag_object, "size", 14+(2*val), NULL); + break; + + case stylehint_Oblique: + g_object_set(tag_object, "style", val ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL, NULL); + break; + + case stylehint_Proportional: + g_object_set(tag_object, "font-desc", val ? glk_data->default_font_desc : glk_data->monospace_font_desc, NULL); + break; + + case stylehint_TextColor: + color_format(val, color); + + if(!reverse_color) + g_object_set(tag_object, "foreground", color, NULL); + else + g_object_set(tag_object, "background", color, NULL); + + break; + + case stylehint_BackColor: + color_format(val, color); + + if(!reverse_color) + g_object_set(tag_object, "background", color, NULL); + else + g_object_set(tag_object, "foreground", color, NULL); + + break; + + case stylehint_ReverseColor: + if(reverse_color != val) { + /* Flip the fore- and background colors */ + gchar* foreground_color; + gchar* background_color; + g_object_get(tag_object, "foreground", &foreground_color, NULL); + g_object_get(tag_object, "background", &background_color, NULL); + g_object_set(tag_object, "foreground", background_color, NULL); + g_object_set(tag_object, "background", foreground_color, NULL); + g_free(foreground_color); + g_free(background_color); + } + break; + + default: + WARNING("Unknown style hint"); + } +} + +void +glk_stylehint_set(glui32 wintype, glui32 style, glui32 hint, glsi32 val) +{ + + gchar *tag_name = get_tag_name(style); + + /* Iterate over all the window and update their styles if nessecary */ + winid_t win = glk_window_iterate(NULL, NULL); + while(win != NULL) { + if(wintype != wintype_TextBuffer) + continue; /* FIXME: add support for text grid windows */ + + if(wintype == wintype_AllTypes || glk_window_get_type(win) == wintype) { + GtkWidget *textview = win->widget; + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) ); + GtkTextTagTable *table = gtk_text_buffer_get_tag_table(textbuffer); + GtkTextTag *to_change = gtk_text_tag_table_lookup(table, tag_name); + + apply_stylehint_to_tag(to_change, hint, val); + } + } +} diff --git a/libchimara/style.h b/libchimara/style.h new file mode 100644 index 0000000..fcd5302 --- /dev/null +++ b/libchimara/style.h @@ -0,0 +1,12 @@ +#ifndef STYLE_H +#define STYLE_H + +#include +#include "glk.h" +#include "magic.h" +#include "chimara-glk-private.h" +#include "stream.h" + +G_GNUC_INTERNAL void style_init_textbuffer(GtkTextBuffer *buffer); + +#endif diff --git a/libchimara/timer.c b/libchimara/timer.c new file mode 100644 index 0000000..8d2087a --- /dev/null +++ b/libchimara/timer.c @@ -0,0 +1,67 @@ +#include "timer.h" + +extern ChimaraGlkPrivate *glk_data; + +/** + * You can request that an event be sent at fixed intervals, regardless of what + * the player does. Unlike input events, timer events can be tested for with + * glk_select_poll() as well as glk_select(). + * + * Initially, there is no timer and you get no timer events. If you call + * glk_request_timer_events(N), with N not 0, you will get timer events about + * every N milliseconds thereafter. (Assuming that they are supported -- if + * not, glk_request_timer_events() has no effect.) Unlike keyboard and mouse + * events, timer events will continue until you shut them off. You do not have + * to re-request them every time you get one. Call glk_request_timer_events(0) + * to stop getting timer events. + * + * The rule is that when you call glk_select() or glk_select_poll(), if it has + * been more than N milliseconds since the last timer event, and (for + * glk_select()) if there is no player input, you will receive an event whose + * type is evtype_Timer. (win, val1, and val2 will all be 0.) + * + * Timer events do not stack up. If you spend 10N milliseconds doing + * computation, and then call glk_select(), you will not get ten timer events + * in a row. The library will simply note that it has been more than N + * milliseconds, and return a timer event right away. If you call glk_select() + * again immediately, it will be N milliseconds before the next timer event. + * + * This means that the timing of timer events is approximate, and the library + * will err on the side of being late. If there is a conflict between player + * input events and timer events, the player input takes precedence. [This + * prevents the user from being locked out by overly enthusiastic timer events. + * Unfortunately, it also means that your timer can be locked out on slower + * machines, if the player pounds too enthusiastically on the keyboard. Sorry. + * If you want a real-time operating system, talk to Wind River.] + * + * [I don't have to tell you that a millisecond is one thousandth of a second, + * do I?] + * + * NOTE: setting a new timer will overwrite the old timer if one was in place. + */ +void +glk_request_timer_events(glui32 millisecs) +{ + // Stop any existing timer + if(glk_data->timer_id != 0) { + g_source_remove(glk_data->timer_id); + glk_data->timer_id = 0; + } + + if(millisecs == 0) + return; + + glk_data->timer_id = g_timeout_add(millisecs, push_timer_event, NULL); +} + +/** + * Internal function: push a new timer event on the event stack. + * Will always return TRUE + */ +gboolean +push_timer_event(gpointer data) +{ + event_throw(evtype_Timer, NULL, 0, 0); + + return TRUE; +} diff --git a/libchimara/timer.h b/libchimara/timer.h new file mode 100644 index 0000000..1edb670 --- /dev/null +++ b/libchimara/timer.h @@ -0,0 +1,10 @@ +#ifndef TIMER_H +#define TIMER_H + +#include +#include "event.h" +#include "chimara-glk-private.h" + +G_GNUC_INTERNAL gboolean push_timer_event(gpointer data); + +#endif diff --git a/libchimara/window.c b/libchimara/window.c new file mode 100644 index 0000000..5f2bd5b --- /dev/null +++ b/libchimara/window.c @@ -0,0 +1,1104 @@ +#include "window.h" +#include "magic.h" +#include "chimara-glk-private.h" + +extern ChimaraGlkPrivate *glk_data; + +/** + * glk_window_iterate: + * @win: A window, or %NULL. + * @rockptr: Return location for the next window's rock, or %NULL. + * + * This function can be used to iterate through the list of all open windows + * (including pair windows.) See Iterating Through Opaque + * Objects. + * + * As that section describes, the order in which windows are returned is + * arbitrary. The root window is not necessarily first, nor is it necessarily + * last. + * + * Returns: the next window, or %NULL if there are no more. + */ +winid_t +glk_window_iterate(winid_t win, glui32 *rockptr) +{ + VALID_WINDOW_OR_NULL(win, return NULL); + + GNode *retnode; + + if(win == NULL) + retnode = glk_data->root_window; + else + { + GNode *node = win->window_node; + if( G_NODE_IS_LEAF(node) ) + { + while(node && node->next == NULL) + node = node->parent; + if(node) + retnode = node->next; + else + retnode = NULL; + } + else + retnode = g_node_first_child(node); + } + winid_t retval = retnode? (winid_t)retnode->data : NULL; + + /* Store the window's rock in rockptr */ + if(retval && rockptr) + *rockptr = glk_window_get_rock(retval); + + return retval; +} + +/** + * glk_window_get_rock: + * @win: A window. + * + * Returns @win's rock value. Pair windows always have rock 0; all other windows + * return whatever rock value you created them with. + * + * Returns: A rock value. + */ +glui32 +glk_window_get_rock(winid_t win) +{ + VALID_WINDOW(win, return 0); + return win->rock; +} + +/** + * glk_window_get_type: + * @win: A window. + * + * Returns @win's type, one of #wintype_Blank, #wintype_Pair, + * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics. + * + * Returns: The window's type. + */ +glui32 +glk_window_get_type(winid_t win) +{ + VALID_WINDOW(win, return 0); + return win->type; +} + +/** + * glk_window_get_parent: + * @win: A window. + * + * Returns the window which is the parent of @win. If @win is the root window, + * this returns %NULL, since the root window has no parent. Remember that the + * parent of every window is a pair window; other window types are always + * childless. + * + * Returns: A window, or %NULL. + */ +winid_t +glk_window_get_parent(winid_t win) +{ + VALID_WINDOW(win, return NULL); + /* Value will also be NULL if win is the root window */ + return (winid_t)win->window_node->parent->data; +} + +/** + * glk_window_get_sibling: + * @win: A window. + * + * Returns the other child of @win's parent. If @win is the root window, this + * returns %NULL. + * + * Returns: A window, or %NULL. + */ +winid_t +glk_window_get_sibling(winid_t win) +{ + VALID_WINDOW(win, return NULL); + + if(G_NODE_IS_ROOT(win->window_node)) + return NULL; + if(win->window_node->next) + return (winid_t)win->window_node->next; + return (winid_t)win->window_node->prev; +} + +/** + * glk_window_get_root: + * + * Returns the root window. If there are no windows, this returns %NULL. + * + * Returns: A window, or %NULL. + */ +winid_t +glk_window_get_root() +{ + if(glk_data->root_window == NULL) + return NULL; + return (winid_t)glk_data->root_window->data; +} + +/** + * glk_window_open: + * @split: The window to split to create the new window. Must be 0 if there + * are no windows yet. + * @method: Position of the new window and method of size computation. One of + * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right + * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is + * #wintype_Blank, then #winmethod_Fixed is not allowed. + * @size: Size of the new window, in percentage points if @method is + * #winmethod_Proportional, otherwise in characters if @wintype is + * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is + * #wintype_Graphics. + * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid, + * #wintype_TextBuffer, or #wintype_Graphics. + * @rock: The new window's rock value. + * + * Creates a new window. If there are no windows, the first three arguments are + * meaningless. @split must be 0, and @method and @size + * are ignored. @wintype is the type of window you're creating, and @rock is + * the rock (see Rocks). + * + * If any windows exist, new windows must be created by splitting existing + * ones. @split is the window you want to split; this must + * not be zero. @method is a mask of constants to specify the + * direction and the split method (see below). @size is the size of the split. + * @wintype is the type of window you're creating, and @rock is the rock. + * + * Remember that it is possible that the library will be unable to create a new + * window, in which case glk_window_open() will return %NULL. + * + * + * It is acceptable to gracefully exit, if the window you are creating is an + * important one — such as your first window. But you should not try to + * perform any window operation on the id until you have tested to make sure + * it is non-zero. + * + * + * The examples we've seen so far have the simplest kind of size control. (Yes, + * this is below.) Every pair is a percentage split, with + * + * X + * X + * + * percent going to one side, and + * + * (100-X) + * (100 - X) + * + * percent going to the other side. If the player resizes the window, the whole + * mess expands, contracts, or stretches in a uniform way. + * + * As I said above, you can also make fixed-size splits. This is a little more + * complicated, because you have to know how this fixed size is measured. + * + * Sizes are measured in a way which is different for each window type. For + * example, a text grid window is measured by the size of its fixed-width font. + * You can make a text grid window which is fixed at a height of four rows, or + * ten columns. A text buffer window is measured by the size of its font. + * + * + * Remember that different windows may use different size fonts. Even two + * text grid windows may use fixed-size fonts of different sizes. + * + * + * Graphics windows are measured in pixels, not characters. Blank windows + * aren't measured at all; there's no meaningful way to measure them, and + * therefore you can't create a blank window of a fixed size, only of a + * proportional (percentage) size. + * + * So to create a text buffer window which takes the top 40% of the original + * window's space, you would execute + * |[ newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0); ]| + * + * To create a text grid which is always five lines high, at the bottom of the + * original window, you would do + * |[ newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0); ]| + * + * Note that the meaning of the @size argument depends on the @method argument. + * If the method is #winmethod_Fixed, it also depends on the @wintype argument. + * The new window is then called the key window of this split, + * because its window type determines how the split size is computed. + * + * + * For #winmethod_Proportional splits, you can still call the new window the + * key window. But the key window is not important for + * proportional splits, because the size will always be computed as a simple + * ratio of the available space, not a fixed size of one child window. + * + * + * This system is more or less peachy as long as all the constraints work out. + * What happens when there is a conflict? The rules are simple. Size control + * always flows down the tree, and the player is at the top. Let's bring out an + * example: + * + * + * + * + * O + * / \ + * O B + * / \ + * A C + * + * + * + * First we split A into A and B, with a 50% proportional split. Then we split + * A into A and C, with C above, C being a text grid window, and C gets a fixed + * size of two rows (as measured in its own font size). A gets whatever remains + * of the 50% it had before. + * + * Now the player stretches the window vertically. + * + * + * + * The library figures: the topmost split, the original A/B split, is 50-50. So + * B gets half the screen space, and the pair window next to it (the lower + * O) gets the other half. Then it looks at the lower + * O. C gets two rows; A gets the rest. All done. + * + * Then the user maliciously starts squeezing the window down, in stages: + * + * + * + * + * + * + * + * + * + * + * + * + * + * The logic remains the same. B always gets half the space. At stage 3, + * there's no room left for A, so it winds up with zero height. Nothing + * displayed in A will be visible. At stage 4, there isn't even room in the + * upper 50% to give C its two rows; so it only gets one. Finally, C is + * squashed out of existence as well. + * + * When a window winds up undersized, it remembers what size it should be. In + * the example above, A remembers that it should be two rows; if the user + * expands the window to the original size, it would return to the original + * layout. + * + * The downward flow of control is a bit harsh. After all, in stage 4, there's + * room for C to have its two rows if only B would give up some of its 50%. But + * this does not happen. + * + * + * This makes life much easier for the Glk library. To determine the + * configuration of a window, it only needs to look at the window's + * ancestors, never at its descendants. So window layout is a simple + * recursive algorithm, no backtracking. + * + * + * What happens when you split a fixed-size window? The resulting pair window + * — that is, the two new parts together — retain the same size + * constraint as the original window that was split. The key window for the + * original split is still the key window for that split, even though it's now + * a grandchild instead of a child. + * + * The easy, and correct, way to think about this is that the size constraint + * is stored by a window's parent, not the window itself; and a constraint + * consists of a pointer to a key window plus a size value. + * + * + * + * + * + * A + * + * + * + * + * O1 + * / \ + * A B + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A C + * + * + * After the first split, the new pair window (O1, which covers the whole + * screen) knows that its first child (A) is above the second, and gets 50% of + * its own area. (A is the key window for this split, but a proportional split + * doesn't care about key windows.) + * + * After the second split, all this remains true; O1 knows that its first child + * gets 50% of its space, and A is O1's key window. But now O1's first child is + * O2 instead of A. The newer pair window (O2) knows that its first child (C) + * is above the second, and gets a fixed size of two rows. (As measured in C's + * font, because C is O2's key window.) + * + * If we split C, now, the resulting pair will still be two C-font rows high + * — that is, tall enough for two lines of whatever font C displays. For + * the sake of example, we'll do this vertically. + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A O3 + * / \ + * C D + * + * + * + * O3 now knows that its children have a 50-50 left-right split. O2 is still + * committed to giving its upper child, O3, two C-font rows. Again, this is + * because C is O2's key window. + * + * + * This turns out to be a good idea, because it means that C, the text grid + * window, is still two rows high. If O3 had been a upper-lower split, things + * wouldn't work out so neatly. But the rules would still apply. If you don't + * like this, don't do it. + * + * + * Returns: the new window, or %NULL on error. + */ +winid_t +glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, + glui32 rock) +{ + VALID_WINDOW_OR_NULL(split, return NULL); + + if(split == NULL && glk_data->root_window != NULL) + { + ILLEGAL("Tried to open a new root window, but there is already a root window"); + return NULL; + } + + gdk_threads_enter(); + + /* Create the new window */ + winid_t win = g_new0(struct glk_window_struct, 1); + win->magic = MAGIC_WINDOW; + win->rock = rock; + win->type = wintype; + win->window_node = g_node_new(win); + + switch(wintype) + { + case wintype_Blank: + { + /* A blank window will be a label without any text */ + GtkWidget *label = gtk_label_new(""); + gtk_widget_show(label); + + win->widget = label; + win->frame = label; + /* A blank window has no size */ + win->unit_width = 0; + win->unit_height = 0; + /* You can print to a blank window's stream, but it does nothing */ + win->window_stream = window_stream_new(win); + win->echo_stream = NULL; + } + break; + + case wintype_TextGrid: + { + GtkWidget *textview = gtk_text_view_new(); + + gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE ); + gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE ); + gtk_widget_show(textview); + + /* Set the window's font */ + gtk_widget_modify_font(textview, glk_data->monospace_font_desc); + + win->widget = textview; + win->frame = textview; + + /* Determine the size of a "0" character in pixels */ + PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); + pango_layout_set_font_description(zero, glk_data->monospace_font_desc); + pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height)); + g_object_unref(zero); + + /* Set the other parameters (width and height are set later) */ + win->window_stream = window_stream_new(win); + win->echo_stream = NULL; + win->input_request_type = INPUT_REQUEST_NONE; + win->line_input_buffer = NULL; + win->line_input_buffer_unicode = NULL; + + /* Connect signal handlers */ + win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win ); + g_signal_handler_block( G_OBJECT(textview), win->keypress_handler ); + } + break; + + case wintype_TextBuffer: + { + GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL); + GtkWidget *textview = gtk_text_view_new(); + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) ); + + gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC ); + + gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR ); + gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE ); + + gtk_container_add( GTK_CONTAINER(scrolledwindow), textview ); + gtk_widget_show_all(scrolledwindow); + + /* Set the window's font */ + gtk_widget_modify_font(textview, glk_data->default_font_desc); + + win->widget = textview; + win->frame = scrolledwindow; + + /* Determine the size of a "0" character in pixels */ + PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); + pango_layout_set_font_description(zero, glk_data->default_font_desc); + pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height)); + g_object_unref(zero); + + /* Set the other parameters */ + win->window_stream = window_stream_new(win); + win->echo_stream = NULL; + win->input_request_type = INPUT_REQUEST_NONE; + win->line_input_buffer = NULL; + win->line_input_buffer_unicode = NULL; + + /* Connect signal handlers */ + win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win ); + g_signal_handler_block( G_OBJECT(textview), win->keypress_handler ); + + win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win ); + g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler ); + + /* Create an editable tag to indicate uneditable parts of the window + (for line input) */ + gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL); + + /* Create the default styles available to the window stream */ + style_init_textbuffer(textbuffer); + + /* Mark the position where the user will input text */ + GtkTextIter end; + gtk_text_buffer_get_end_iter(textbuffer, &end); + gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE); + } + break; + + default: + gdk_threads_leave(); + ILLEGAL_PARAM("Unknown window type: %u", wintype); + g_free(win); + g_node_destroy(glk_data->root_window); + glk_data->root_window = NULL; + return NULL; + } + + /* Set the minimum size to "as small as possible" so it doesn't depend on + the size of the window contents */ + gtk_widget_set_size_request(win->widget, 0, 0); + gtk_widget_set_size_request(win->frame, 0, 0); + + if(split) + { + /* When splitting, construct a new parent window + * copying most characteristics from the window that is being split */ + winid_t pair = g_new0(struct glk_window_struct, 1); + pair->magic = MAGIC_WINDOW; + pair->rock = 0; + pair->type = wintype_Pair; + pair->window_node = g_node_new(pair); + /* You can print to a pair window's window stream, but it has no effect */ + pair->window_stream = window_stream_new(pair); + pair->echo_stream = NULL; + + /* The pair window must know about its children's split method */ + pair->key_window = win; + pair->split_method = method; + pair->constraint_size = size; + + /* Insert the new window into the window tree */ + if(split->window_node->parent == NULL) + glk_data->root_window = pair->window_node; + else + { + if( split->window_node == g_node_first_sibling(split->window_node) ) + g_node_prepend(split->window_node->parent, pair->window_node); + else + g_node_append(split->window_node->parent, pair->window_node); + g_node_unlink(split->window_node); + } + /* Place the windows in the correct order */ + switch(method & winmethod_DirMask) + { + case winmethod_Left: + case winmethod_Above: + g_node_append(pair->window_node, win->window_node); + g_node_append(pair->window_node, split->window_node); + break; + case winmethod_Right: + case winmethod_Below: + g_node_append(pair->window_node, split->window_node); + g_node_append(pair->window_node, win->window_node); + break; + } + + } else { + /* Set the window as root window */ + glk_data->root_window = win->window_node; + } + + /* Set the window as a child of the Glk widget */ + gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self)); + gtk_widget_queue_resize(GTK_WIDGET(glk_data->self)); + + gdk_threads_leave(); + + /* For blank or pair windows, this is almost a no-op. For text grid and + text buffer windows, this will wait for GTK to draw the window. Otherwise, + opening a window and getting its size immediately will give you the wrong + size. */ + glk_window_get_size(win, NULL, NULL); + + /* For text grid windows, fill the buffer with blanks. */ + if(wintype == wintype_TextGrid) + { + /* Create the cursor position mark */ + gdk_threads_enter(); + GtkTextIter begin; + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + gtk_text_buffer_get_start_iter(buffer, &begin); + gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE); + gdk_threads_leave(); + + /* Fill the buffer with blanks and move the cursor to the upper left */ + glk_window_clear(win); + } + + return win; +} + +/* Internal function: if node's key window is closing_win or one of its + children, set node's key window to NULL. */ +static gboolean +remove_key_windows(GNode *node, winid_t closing_win) +{ + winid_t win = (winid_t)node->data; + if(win->key_window && (win->key_window == closing_win || g_node_is_ancestor(closing_win->window_node, win->key_window->window_node))) + win->key_window = NULL; + return FALSE; /* Don't stop the traversal */ +} + +/* Internal function: destroy this window's GTK widgets, window streams, + and those of all its children */ +static void +destroy_windows_below(winid_t win, stream_result_t *result) +{ + switch(win->type) + { + case wintype_Blank: + gdk_threads_enter(); + gtk_widget_unparent(win->widget); + gdk_threads_leave(); + break; + + case wintype_TextGrid: + case wintype_TextBuffer: + gdk_threads_enter(); + gtk_widget_unparent(win->frame); + gdk_threads_leave(); + /* TODO: Cancel all input requests */ + break; + + case wintype_Pair: + destroy_windows_below(win->window_node->children->data, NULL); + destroy_windows_below(win->window_node->children->next->data, NULL); + break; + + default: + ILLEGAL_PARAM("Unknown window type: %u", win->type); + return; + } + stream_close_common(win->window_stream, result); +} + +/* Internal function: free the winid_t structure of this window and those of all its children */ +static void +free_winids_below(winid_t win) +{ + if(win->type == wintype_Pair) { + free_winids_below(win->window_node->children->data); + free_winids_below(win->window_node->children->next->data); + } + win->magic = MAGIC_FREE; + g_free(win); +} + +/** + * glk_window_close: + * @win: Window to close. + * @result: Pointer to a #stream_result_t in which to store the write count. + * + * Closes @win, which is pretty much exactly the opposite of opening a window. + * It is legal to close all your windows, or to close the root window (which is + * the same thing.) + * + * The @result argument is filled with the output character count of the window + * stream. See Streams and Closing Streams. + * + * When you close a window (and it is not the root window), the other window + * in its pair takes over all the freed-up area. Let's close D, in the current + * example: + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A C + * + * + * + * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right + * split has gone with it. The other size constraints are unchanged; O2 is + * still committed to giving its upper child two rows, as measured in the font + * of O2's key window, which is C. Conveniently, O2's upper child is C, just as + * it was before we created D. In fact, now that D is gone, everything is back + * to the way it was before we created D. + * + * But what if we had closed C instead of D? We would have gotten this: + * + * + * + * + * O1 + * / \ + * O2 B + * / \ + * A D + * + * + * + * Again, O3 is gone. But D has collapsed to zero height. This is because its + * height is controlled by O2, and O2's key window was C, and C is now gone. O2 + * no longer has a key window at all, so it cannot compute a height for its + * upper child, so it defaults to zero. + * + * + * This may seem to be an inconvenient choice. That is deliberate. You should + * not leave a pair window with no key, and the zero-height default reminds + * you not to. You can use glk_window_set_arrangement() to set a new split + * measurement and key window. See Changing Window + * Constraints. + * + */ +void +glk_window_close(winid_t win, stream_result_t *result) +{ + VALID_WINDOW(win, return); + + /* If any pair windows have this window or its children as a key window, + set their key window to NULL */ + g_node_traverse(glk_data->root_window, G_IN_ORDER, G_TRAVERSE_NON_LEAVES, -1, (GNodeTraverseFunc)remove_key_windows, win); + + /* Close all the window streams and destroy the widgets of this window + and below, before trashing the window tree */ + destroy_windows_below(win, result); + + /* Then free the winid_t structures below this node, but not this one itself */ + if(win->type == wintype_Pair) { + free_winids_below(win->window_node->children->data); + free_winids_below(win->window_node->children->next->data); + } + /* So now we should be left with a skeleton tree hanging off this node */ + + /* Parent window changes from a split window into the sibling window */ + /* The parent of any window is either a pair window or NULL */ + GNode *pair_node = win->window_node->parent; + g_node_destroy(win->window_node); + /* If win was not the root window: */ + if(pair_node != NULL) + { + gboolean new_child_on_left = ( pair_node == g_node_first_sibling(pair_node) ); + GNode *sibling_node = pair_node->children; /* only one child left */ + GNode *new_parent_node = pair_node->parent; + g_node_unlink(pair_node); + g_node_unlink(sibling_node); + /* pair_node and sibling_node should now be totally unconnected to the tree */ + + if(new_parent_node == NULL) + { + glk_data->root_window = sibling_node; + } + else + { + if(new_child_on_left) + g_node_prepend(new_parent_node, sibling_node); + else + g_node_append(new_parent_node, sibling_node); + } + + winid_t pair = (winid_t) pair_node->data; + g_node_destroy(pair_node); + + pair->magic = MAGIC_FREE; + g_free(pair); + } + else /* it was the root window */ + { + glk_data->root_window = NULL; + } + + win->magic = MAGIC_FREE; + g_free(win); + + /* Schedule a redraw */ + gdk_threads_enter(); + gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) ); + gdk_window_process_all_updates(); + gdk_threads_leave(); +} + +/** + * glk_window_clear: + * @win: A window. + * + * Erases @win. The meaning of this depends on the window type. + * + * + * Text buffer + * + * This may do any number of things, such as delete all text in the window, or + * print enough blank lines to scroll all text beyond visibility, or insert a + * page-break marker which is treated specially by the display part of the + * library. + * + * + * + * Text grid + * + * This will clear the window, filling all positions with blanks. The window + * cursor is moved to the top left corner (position 0,0). + * + * + * + * Graphics + * + * Clears the entire window to its current background color. See Graphics Windows. + * + * + * + * Other window types + * No effect. + * + * + * + * It is illegal to erase a window which has line input pending. + */ +void +glk_window_clear(winid_t win) +{ + VALID_WINDOW(win, return); + g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE); + + switch(win->type) + { + case wintype_Blank: + case wintype_Pair: + /* do nothing */ + break; + + case wintype_TextGrid: + /* fill the buffer with blanks */ + { + gdk_threads_enter(); + + /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */ + gchar *blanks = g_strnfill(win->width, ' '); + gchar **blanklines = g_new0(gchar *, win->height + 1); + int count; + for(count = 0; count < win->height; count++) + blanklines[count] = blanks; + blanklines[win->height] = NULL; + gchar *text = g_strjoinv("\n", blanklines); + g_free(blanklines); /* not g_strfreev() */ + g_free(blanks); + + GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + gtk_text_buffer_set_text(textbuffer, text, -1); + g_free(text); + + GtkTextIter begin; + gtk_text_buffer_get_start_iter(textbuffer, &begin); + gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin); + + gdk_threads_leave(); + } + break; + + case wintype_TextBuffer: + /* delete all text in the window */ + { + gdk_threads_enter(); + + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_delete(buffer, &start, &end); + + gdk_threads_leave(); + } + break; + + default: + ILLEGAL_PARAM("Unknown window type: %d", win->type); + } +} + +/** + * glk_set_window: + * @win: A window. + * + * Sets the current stream to @win's window stream. It is exactly equivalent to + * #glk_stream_set_current(#glk_window_get_stream(@win)). + */ +void +glk_set_window(winid_t win) +{ + VALID_WINDOW_OR_NULL(win, return); + glk_stream_set_current( glk_window_get_stream(win) ); +} + +/** + * glk_window_get_stream: + * @win: A window. + * + * Returns the stream which is associated with @win. (See Window Streams.) Every window has a + * stream which can be printed to, but this may not be useful, depending on the + * window type. + * + * + * For example, printing to a blank window's stream has no effect. + * + * + * Returns: A window stream. + */ +strid_t glk_window_get_stream(winid_t win) +{ + VALID_WINDOW(win, return NULL); + return win->window_stream; +} + +/** + * glk_window_set_echo_stream: + * @win: A window. + * @str: A stream to attach to the window, or %NULL. + * + * Sets @win's echo stream to @str, which can be any valid output stream. You + * can reset a window to stop echoing by calling + * #glk_window_set_echo_stream(@win, %NULL). + * + * It is illegal to set a window's echo stream to be its + * own window stream. That would create an infinite loop, + * and is nearly certain to crash the Glk library. It is similarly illegal to + * create a longer loop (two or more windows echoing to each other.) + */ +void +glk_window_set_echo_stream(winid_t win, strid_t str) +{ + VALID_WINDOW(win, return); + VALID_STREAM_OR_NULL(str, return); + + /* Test for an infinite loop */ + strid_t next = str; + for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream) + { + if(next == win->window_stream) + { + ILLEGAL("Infinite loop detected"); + win->echo_stream = NULL; + return; + } + } + + win->echo_stream = str; +} + +/** + * glk_window_get_echo_stream: + * @win: A window. + * + * Returns the echo stream of window @win. Initially, a window has no echo + * stream, so #glk_window_get_echo_stream(@win) will return %NULL. + * + * Returns: A stream, or %NULL. + */ +strid_t +glk_window_get_echo_stream(winid_t win) +{ + VALID_WINDOW(win, return NULL); + return win->echo_stream; +} + +/** + * glk_window_get_size: + * @win: A window. + * @widthptr: Pointer to a location to store the window's width, or %NULL. + * @heightptr: Pointer to a location to store the window's height, or %NULL. + * + * Simply returns the actual size of the window, in its measurement system. + * As described in Other API + * Conventions, either @widthptr or @heightptr can be %NULL, if you + * only want one measurement. + * + * Or, in fact, both, if you want to waste time. + */ +void +glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) +{ + VALID_WINDOW(win, return); + + switch(win->type) + { + case wintype_Blank: + case wintype_Pair: + if(widthptr != NULL) + *widthptr = 0; + if(heightptr != NULL) + *heightptr = 0; + break; + + case wintype_TextGrid: + gdk_threads_enter(); + /* Wait for the window to be drawn, and then cache the width and height */ + gdk_window_process_all_updates(); + while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) + { + /* Release the GDK lock momentarily */ + gdk_threads_leave(); + gdk_threads_enter(); + while(gtk_events_pending()) + gtk_main_iteration(); + } + + win->width = (glui32)(win->widget->allocation.width / win->unit_width); + win->height = (glui32)(win->widget->allocation.height / win->unit_height); + gdk_threads_leave(); + + if(widthptr != NULL) + *widthptr = win->width; + if(heightptr != NULL) + *heightptr = win->height; + break; + + case wintype_TextBuffer: + /* TODO: Glk wants to be able to get its windows' sizes as soon as they are created, but GTK doesn't decide on their sizes until they are drawn. The drawing happens somewhere in an idle function. A good method would be to make an educated guess of the window's size using the ChimaraGlk widget's size. */ + gdk_threads_enter(); + /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) + { + g_warning("glk_window_get_size: The Glk program requested the size of a window before it was allocated screen space by GTK. The window size is just an educated guess."); + guess the size from the parent window; + break; + } */ + + /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */ + gdk_window_process_all_updates(); + while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) + { + /* Release the GDK lock momentarily */ + gdk_threads_leave(); + gdk_threads_enter(); + while(gtk_events_pending()) + gtk_main_iteration(); + } + + if(widthptr != NULL) + *widthptr = (glui32)(win->widget->allocation.width / win->unit_width); + if(heightptr != NULL) + *heightptr = (glui32)(win->widget->allocation.height / win->unit_height); + gdk_threads_leave(); + + break; + + default: + ILLEGAL_PARAM("Unknown window type: %u", win->type); + } +} + +/** + * glk_window_move_cursor: + * @win: A text grid window. + * @xpos: Horizontal cursor position. + * @ypos: Vertical cursor position. + * + * Sets the cursor position. If you move the cursor right past the end of a + * line, it wraps; the next character which is printed will appear at the + * beginning of the next line. + * + * If you move the cursor below the last line, or when the cursor reaches the + * end of the last line, it goes off the screen and further + * output has no effect. You must call glk_window_move_cursor() or + * glk_window_clear() to move the cursor back into the visible region. + * + * + * Note that the arguments of glk_window_move_cursor() are unsigned + * ints. This is okay, since there are no negative positions. If you try + * to pass a negative value, Glk will interpret it as a huge positive value, + * and it will wrap or go off the last line. + * + * + * + * Also note that the output cursor is not necessarily visible. In particular, + * when you are requesting line or character input in a grid window, you cannot + * rely on the cursor position to prompt the player where input is indicated. + * You should print some character prompt at that spot — a + * > character, for example. + * + */ +void +glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos) +{ + VALID_WINDOW(win, return); + g_return_if_fail(win->type == wintype_TextGrid); + + /* Calculate actual position if cursor is moved past the right edge */ + if(xpos >= win->width) + { + ypos += xpos / win->width; + xpos %= win->width; + } + /* Go to the end if the cursor is moved off the bottom edge */ + if(ypos >= win->height) + { + xpos = win->width - 1; + ypos = win->height - 1; + } + + gdk_threads_enter(); + + GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); + GtkTextIter newpos; + /* There must actually be a character at xpos, or the following function will choke */ + gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos); + gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos); + + gdk_threads_leave(); +} + diff --git a/libchimara/window.h b/libchimara/window.h new file mode 100644 index 0000000..1a0b2e9 --- /dev/null +++ b/libchimara/window.h @@ -0,0 +1,69 @@ +#ifndef WINDOW_H +#define WINDOW_H + +#include +#include "glk.h" + +#include "stream.h" +#include "error.h" +#include "input.h" +#include "style.h" + + +enum InputRequestType +{ + INPUT_REQUEST_NONE, + INPUT_REQUEST_CHARACTER, + INPUT_REQUEST_CHARACTER_UNICODE, + INPUT_REQUEST_LINE, + INPUT_REQUEST_LINE_UNICODE +}; + +/** + * glk_window_struct: + * + * This is an opaque structure (see + * Opaque Structures and should not be accessed directly. + */ +struct glk_window_struct +{ + /*< private >*/ + glui32 magic, rock; + /* Pointer to the node in the global tree that contains this window */ + GNode *window_node; + /* Window parameters */ + glui32 type; + /* "widget" is the actual widget with the window's functionality */ + GtkWidget *widget; + /* "frame" is the widget that is the child of the ChimaraGlk container, such + as a scroll window. It may be the same as "widget". */ + GtkWidget *frame; + /* Width and height of the window's size units, in pixels */ + int unit_width; + int unit_height; + /* Streams associated with the window */ + strid_t window_stream; + strid_t echo_stream; + /* Width and height of the window, in characters (text grids only) */ + glui32 width; + glui32 height; + /* Window split data (pair windows only) */ + winid_t key_window; + glui32 split_method; + glui32 constraint_size; + /* Input request stuff */ + enum InputRequestType input_request_type; + gchar *line_input_buffer; + glui32 *line_input_buffer_unicode; + glui32 line_input_buffer_max_len; + gboolean mouse_input_requested; + /* Line input field (text grids only) */ + glui32 input_length; + GtkTextChildAnchor *input_anchor; + GtkWidget *input_entry; + /* Signal handlers */ + gulong keypress_handler; + gulong insert_text_handler; +}; + +#endif diff --git a/src/.svnignore b/src/.svnignore deleted file mode 100644 index af9133f..0000000 --- a/src/.svnignore +++ /dev/null @@ -1,6 +0,0 @@ -.deps -.libs -Makefile -Makefile.in -test-chimara -chimara.ui diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index 4a2806e..0000000 --- a/src/Makefile.am +++ /dev/null @@ -1,69 +0,0 @@ -## Process this file with automake to produce Makefile.in - -AM_CFLAGS = -Wall - -data_DATA = chimara.ui - -noinst_PROGRAMS = test-chimara - -test_chimara_SOURCES = main.c callbacks.c callbacks.h error.c error.h -test_chimara_CPPFLAGS = \ - -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ - -DPACKAGE_SRC_DIR=\""$(srcdir)"\" \ - -DPACKAGE_DATA_DIR=\""$(datadir)"\" -test_chimara_CFLAGS = @TEST_CFLAGS@ $(AM_CFLAGS) -test_chimara_LDADD = @TEST_LIBS@ -lchimara - -lib_LTLIBRARIES = libchimara.la - -libchimara_la_SOURCES = \ - abort.c abort.h \ - case.c \ - charset.c charset.h \ - chimara-glk.c chimara-glk.h chimara-glk-private.h \ - event.c event.h \ - fileref.c fileref.h \ - gestalt.c \ - glk.c glk.h \ - magic.c magic.h \ - input.c input.h \ - stream.c stream.h \ - strio.c \ - timer.c timer.h \ - window.c window.h \ - gi_blorb.c gi_blorb.h \ - resource.c resource.h \ - style.c style.h \ - glkstart.h -libchimara_la_CPPFLAGS = \ - -DG_LOG_DOMAIN=\"Chimara\" -libchimara_la_CFLAGS = @CHIMARA_CFLAGS@ $(AM_CFLAGS) -libchimara_la_LIBADD = @CHIMARA_LIBS@ -libchimara_la_LDFLAGS = -no-undefined -export-symbols-regex "^(chimara_)?glk_" -libchimara_includedir = $(includedir)/chimara/chimara -libchimara_include_HEADERS = chimara-glk.h - -pkglib_LTLIBRARIES = first.la model.la gridtest.la splittest.la multiwin.la -PLUGIN_LIBTOOL_FLAGS=-module -avoid-version -export-symbols-regex "^glk_main$$" - -first_la_SOURCES = first.c -first_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) - -model_la_SOURCES = model.c -model_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) - -gridtest_la_SOURCES = gridtest.c -gridtest_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) - -splittest_la_SOURCES = splittest.c -splittest_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) - -multiwin_la_SOURCES = multiwin.c -multiwin_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) - -CLEANFILES = chimara.ui - -chimara.ui: chimara.glade - gtk-builder-convert $< $@ - -EXTRA_DIST = doc.c chimara.glade diff --git a/src/abort.c b/src/abort.c deleted file mode 100644 index 61234a0..0000000 --- a/src/abort.c +++ /dev/null @@ -1,71 +0,0 @@ -#include "event.h" -#include -#include - -#include "chimara-glk-private.h" - -extern ChimaraGlkPrivate *glk_data; - -/** - * glk_set_interrupt_handler: - * @func: A pointer to an interrupt handler function. - * - * Sets @func to be the interrupt handler. @func should be a pointer to a - * function which takes no argument and returns no result. If Glk receives an - * interrupt, and you have set an interrupt handler, your handler will be - * called, before the process is shut down. - * - * Initially there is no interrupt handler. You can reset to not having any by - * calling #glk_set_interrupt_handler(%NULL). - * - * If you call glk_set_interrupt_handler() with a new handler function while an - * older one is set, the new one replaces the old one. Glk does not try to queue - * interrupt handlers. - * - * You should not try to interact with the player in your interrupt handler. Do - * not call glk_select() or glk_select_poll(). Anything you print to a window - * may not be visible to the player. - */ -void -glk_set_interrupt_handler(void (*func)(void)) -{ - glk_data->interrupt_handler = func; -} - -/* Internal function: abort this Glk program, freeing resources and calling the -user's interrupt handler. */ -static void -abort_glk() -{ - if(glk_data->interrupt_handler) - (*(glk_data->interrupt_handler))(); - g_signal_emit_by_name(glk_data->self, "stopped"); - g_thread_exit(NULL); -} - -/* Internal function: Signal this Glk thread to abort. Does nothing if the abort -mutex has already been freed. (That means the thread already ended.) */ -void -signal_abort() -{ - if(glk_data && glk_data->abort_lock) { - g_mutex_lock(glk_data->abort_lock); - glk_data->abort_signalled = TRUE; - g_mutex_unlock(glk_data->abort_lock); - /* Stop blocking on the event queue condition */ - event_throw(evtype_Abort, NULL, 0, 0); - } -} - -/* Internal function: check if the Glk program has been interrupted. */ -void -check_for_abort() -{ - g_mutex_lock(glk_data->abort_lock); - if(glk_data->abort_signalled) - { - g_mutex_unlock(glk_data->abort_lock); - abort_glk(); - } - g_mutex_unlock(glk_data->abort_lock); -} diff --git a/src/abort.h b/src/abort.h deleted file mode 100644 index 1ba2bcc..0000000 --- a/src/abort.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef ABORT_H -#define ABORT_H - -G_GNUC_INTERNAL void check_for_abort(); -G_GNUC_INTERNAL void signal_abort(); - -#endif - diff --git a/src/callbacks.c b/src/callbacks.c deleted file mode 100644 index 6067526..0000000 --- a/src/callbacks.c +++ /dev/null @@ -1,47 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ -/* - * callbacks.c - * Copyright (C) Philip en Marijn 2008 <> - * - * callbacks.c is free software copyrighted by Philip en Marijn. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name ``Philip en Marijn'' nor the name of any other - * contributor may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * callbacks.c IS PROVIDED BY Philip en Marijn ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL Philip en Marijn OR ANY OTHER CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "callbacks.h" - -void on_save_tool_button_clicked(GtkToolButton *toolbutton, gpointer user_data) { - error_dialog( GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbutton))), NULL, "Not implemented yet" ); -} - -gboolean on_window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) { - gtk_main_quit(); - return TRUE; -} - -void on_file_quit_activate(GtkMenuItem *menuitem, gpointer user_data) { - gtk_main_quit(); -} - diff --git a/src/callbacks.h b/src/callbacks.h deleted file mode 100644 index 313ca00..0000000 --- a/src/callbacks.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ -/* - * callbacks.h - * Copyright (C) Philip en Marijn 2008 <> - * - * callbacks.h is free software copyrighted by Philip en Marijn. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name ``Philip en Marijn'' nor the name of any other - * contributor may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * callbacks.h IS PROVIDED BY Philip en Marijn ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL Philip en Marijn OR ANY OTHER CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include "error.h" -#include "event.h" - -void on_save_tool_button_clicked(GtkToolButton *toolbutton, gpointer user_data); -gboolean on_window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data); -void on_file_quit_activate(GtkMenuItem *menuitem, gpointer user_data); diff --git a/src/case.c b/src/case.c deleted file mode 100644 index 47086e1..0000000 --- a/src/case.c +++ /dev/null @@ -1,172 +0,0 @@ -#include -#include "glk.h" - -/** - * glk_char_to_lower: - * @ch: A Latin-1 character. - * - * You can convert Latin-1 characters between upper and lower case with two Glk - * utility functions, glk_char_to_lower() and glk_char_to_upper(). These have a - * few advantages over the standard ANSI tolower() and - * toupper() macros. They work for the entire Latin-1 - * character set, including accented letters; they behave consistently on all - * platforms, since they're part of the Glk library; and they are safe for all - * characters. That is, if you call glk_char_to_lower() on a lower-case - * character, or a character which is not a letter, you'll get the argument - * back unchanged. - * - * The case-sensitive characters in Latin-1 are the ranges 0x41..0x5A, - * 0xC0..0xD6, 0xD8..0xDE (upper case) and the ranges 0x61..0x7A, 0xE0..0xF6, - * 0xF8..0xFE (lower case). These are arranged in parallel; so - * glk_char_to_lower() will add 0x20 to values in the upper-case ranges, and - * glk_char_to_upper() will subtract 0x20 from values in the lower-case ranges. - * - * Returns: A lowercase or non-letter Latin-1 character. - */ -unsigned char -glk_char_to_lower(unsigned char ch) -{ - if( (ch >= 0x41 && ch <= 0x5A) || (ch >= 0xC0 && ch <= 0xD6) || (ch >= 0xD8 && ch <= 0xDE) ) - return ch + 0x20; - return ch; -} - -/** - * glk_char_to_upper: - * @ch: A Latin-1 character. - * - * If @ch is a lowercase character in the Latin-1 character set, converts it to - * uppercase. Otherwise, leaves it unchanged. See glk_char_to_lower(). - * - * Returns: An uppercase or non-letter Latin-1 character. - */ -unsigned char -glk_char_to_upper(unsigned char ch) -{ - if( (ch >= 0x61 && ch <= 0x7A) || (ch >= 0xE0 && ch <= 0xF6) || (ch >= 0xF8 && ch <= 0xFE) ) - return ch - 0x20; - return ch; -} - -/** - * glk_buffer_to_lower_case_uni: - * @buf: A character array in UCS-4. - * @len: Available length of @buf. - * @numchars: Number of characters in @buf. - * - * Unicode character conversion is trickier, and must be applied to character - * arrays, not single characters. These functions - * (glk_buffer_to_lower_case_uni(), glk_buffer_to_upper_case_uni(), and - * glk_buffer_to_title_case_uni()) provide two length arguments because a - * string of Unicode characters may expand when its case changes. The @len - * argument is the available length of the buffer; @numchars is the number of - * characters in the buffer initially. (So @numchars must be less than or equal - * to @len. The contents of the buffer after @numchars do not affect the - * operation.) - * - * The functions return the number of characters after conversion. If this is - * greater than @len, the characters in the array will be safely truncated at - * @len, but the true count will be returned. (The contents of the buffer after - * the returned count are undefined.) - * - * The lower_case and upper_case functions do what - * you'd expect: they convert every character in the buffer (the first @numchars - * of them) to its upper or lower-case equivalent, if there is such a thing. - * - * See the Unicode spec (chapter 3.13, chapter 4.2, etc) for the exact - * definitions of upper, lower, and title-case mapping. - * - * - * Unicode has some strange case cases. For example, a combined character - * that looks like ss might properly be upper-cased into - * two characters S. Title-casing is even - * stranger; ss (at the beginning of a word) might be - * title-cased into a different combined character that looks like - * Ss. The glk_buffer_to_title_case_uni() function is actually - * title-casing the first character of the buffer. If it makes a difference. - * - * - * Returns: The number of characters after conversion. - */ -glui32 -glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, glui32 numchars) -{ - g_return_val_if_fail(buf != NULL && (len > 0 || numchars > 0), 0); - g_return_val_if_fail(numchars <= len, 0); - - /* GLib has a function that converts _one_ UCS-4 character to _one_ - lowercase UCS-4 character; so apparently we don't have to worry about the - string length changing... */ - glui32 *ptr; - for(ptr = buf; ptr < buf + numchars; ptr++) - *ptr = g_unichar_tolower(*ptr); - - return numchars; -} - -/** - * glk_buffer_to_upper_case_uni: - * @buf: A character array in UCS-4. - * @len: Available length of @buf. - * @numchars: Number of characters in @buf. - * - * Converts the first @numchars characters of @buf to their uppercase - * equivalents, if there is such a thing. See glk_buffer_to_lower_case_uni(). - * - * Returns: The number of characters after conversion. - */ -glui32 -glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, glui32 numchars) -{ - g_return_val_if_fail(buf != NULL && (len > 0 || numchars > 0), 0); - g_return_val_if_fail(numchars <= len, 0); - - /* GLib has a function that converts _one_ UCS-4 character to _one_ - uppercase UCS-4 character; so apparently we don't have to worry about the - string length changing... */ - glui32 *ptr; - for(ptr = buf; ptr < buf + numchars; ptr++) - *ptr = g_unichar_toupper(*ptr); - - return numchars; -} - -/** - * glk_buffer_to_title_case_uni: - * @buf: A character array in UCS-4. - * @len: Available length of @buf. - * @numchars: Number of characters in @buf. - * @lowerrest: %TRUE if the rest of @buf should be lowercased, %FALSE - * otherwise. - * - * See glk_buffer_to_lower_case_uni(). The title_case function has - * an additional (boolean) flag. Its basic function is to change the first - * character of the buffer to upper-case, and leave the rest of the buffer - * unchanged. If @lowerrest is true, it changes all the non-first characters to - * lower-case (instead of leaving them alone.) - * - * - * Earlier drafts of this spec had a separate function which title-cased the - * first character of every word in the buffer. I took - * this out after reading Unicode Standard Annex #29, which explains how - * to divide a string into words. If you want it, feel free to implement it. - * - * - * Returns: The number of characters after conversion. - */ -glui32 -glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, glui32 numchars, glui32 lowerrest) -{ - g_return_val_if_fail(buf != NULL && (len > 0 || numchars > 0), 0); - g_return_val_if_fail(numchars <= len, 0); - - /* GLib has a function that converts _one_ UCS-4 character to _one_ - titlecase UCS-4 character; so apparently we don't have to worry about the - string length changing... */ - *buf = g_unichar_totitle(*buf); - /* Call lowercase on the rest of the string */ - if(lowerrest) - return glk_buffer_to_lower_case_uni(buf + 1, len - 1, numchars - 1) + 1; - return numchars; -} - diff --git a/src/charset.c b/src/charset.c deleted file mode 100644 index 3f047cc..0000000 --- a/src/charset.c +++ /dev/null @@ -1,129 +0,0 @@ -#include "charset.h" -#include "magic.h" -#include - -/* Internal function: change illegal (control) characters in a string to a -placeholder character. Must free returned string afterwards. */ -static gchar * -remove_latin1_control_characters(const unsigned char *s, const gsize len) -{ - /* If len == 0, then return an empty string, not NULL */ - if(len == 0) - return g_strdup(""); - - gchar *retval = g_new0(gchar, len); - int i; - for(i = 0; i < len; i++) - if( (s[i] < 32 && s[i] != 10) || (s[i] >= 127 && s[i] <= 159) ) - retval[i] = PLACEHOLDER; - else - retval[i] = s[i]; - return retval; -} - -/* Internal function: convert a Latin-1 string to a UTF-8 string, replacing -Latin-1 control characters by a placeholder first. The UTF-8 string must be -freed afterwards. Returns NULL on error. */ -gchar * -convert_latin1_to_utf8(const gchar *s, const gsize len) -{ - GError *error = NULL; - gchar *canonical = remove_latin1_control_characters( (unsigned char *)s, - len); - gchar *retval = g_convert(canonical, len, "UTF-8", "ISO-8859-1", NULL, NULL, &error); - g_free(canonical); - - if(retval == NULL) - IO_WARNING("Error during latin1->utf8 conversion of string", s, error->message); - - return retval; -} - -/* Internal function: convert a Latin-1 string to a four-byte-per-character -big-endian string of gchars. The string must be freed afterwards. */ -gchar * -convert_latin1_to_ucs4be_string(const gchar *s, const gsize len) -{ - /* "UCS-4BE" is also a conversion type in g_convert()... but this may be more efficient */ - gchar *retval = g_new0(gchar, len * 4); - int i; - for(i = 0; i < len; i++) - retval[i * 4 + 3] = s[i]; - return retval; -} - -/* Internal function: convert a null-terminated UTF-8 string to a -null-terminated Latin-1 string, replacing characters that cannot be represented -in Latin-1 by a placeholder. If bytes_written is not NULL it will be filled with -the number of bytes returned, not counting the NULL terminator. The returned -string must be freed afterwards. Returns NULL on error. */ -gchar * -convert_utf8_to_latin1(const gchar *s, gsize *bytes_written) -{ - GError *error = NULL; - gchar *retval = g_convert_with_fallback(s, -1, "ISO-8859-1", "UTF-8", PLACEHOLDER_STRING, NULL, bytes_written, &error); - - if(retval == NULL) - IO_WARNING("Error during utf8->latin1 conversion of string", s, error->message); - - return retval; -} - -/* Internal function: convert a null-terminated UTF-8 string to a -null-terminated UCS4 string. If items_written is not NULL it will be filled with -the number of code points returned, not counting the terminator. The returned -string must be freed afterwards. Returns NULL on error. */ -gunichar * -convert_utf8_to_ucs4(const gchar *s, glong *items_written) -{ - gunichar *retval = g_utf8_to_ucs4_fast(s, -1, items_written); - - if(retval == NULL) - WARNING_S("Error during utf8->unicode conversion of string", s); - - return retval; -} - -/* Internal function: Convert a Unicode buffer to a null-terminated UTF-8 -string. The returned string must be freed afterwards. Returns NULL on error. */ -gchar * -convert_ucs4_to_utf8(const gunichar *buf, const glong len) -{ - GError *error = NULL; - gchar *retval = g_ucs4_to_utf8(buf, len, NULL, NULL, &error); - - if(retval == NULL) - WARNING_S("Error during unicode->utf8 conversion", error->message); - - return retval; -} - -/* Internal function: Convert a Unicode buffer to a Latin-1 string. Do not do -any character processing, just return values > 255 as the placeholder character. -The returned string must be freed afterwards.*/ -gchar * -convert_ucs4_to_latin1_binary(const gunichar *buf, const glong len) -{ - gchar *retval = g_new0(gchar, len); - int foo; - for(foo = 0; foo < len; foo++) - retval[foo] = (buf[foo] > 255)? PLACEHOLDER : buf[foo]; - return retval; -} - -/* Internal function: convert a Unicode buffer to a four-byte-per-character -big-endian string of gchars. The string must be freed afterwards. */ -gchar * -convert_ucs4_to_ucs4be_string(const gunichar *buf, const glong len) -{ - gchar *retval = g_new0(gchar, len * 4); - int i; - for(i = 0; i < len; i++) - { - retval[i * 4] = buf[i] >> 24 ; - retval[i * 4 + 1] = buf[i] >> 16 & 0xFF; - retval[i * 4 + 2] = buf[i] >> 8 & 0xFF; - retval[i * 4 + 3] = buf[i] & 0xFF; - } - return retval; -} diff --git a/src/charset.h b/src/charset.h deleted file mode 100644 index c170e93..0000000 --- a/src/charset.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef CHARSET_H -#define CHARSET_H - -#include - -#define PLACEHOLDER '?' -#define PLACEHOLDER_STRING "?" -/* Our placeholder character is '?'; other options are possible, like printing "0x7F" or something */ - -G_GNUC_INTERNAL gchar *convert_latin1_to_utf8(const gchar *s, const gsize len); -G_GNUC_INTERNAL gchar *convert_latin1_to_ucs4be_string(const gchar *s, const gsize len); -G_GNUC_INTERNAL gchar *convert_utf8_to_latin1(const gchar *s, gsize *bytes_written); -G_GNUC_INTERNAL gunichar *convert_utf8_to_ucs4(const gchar *s, glong *items_written); -G_GNUC_INTERNAL gchar *convert_ucs4_to_utf8(const gunichar *buf, const glong len); -G_GNUC_INTERNAL gchar *convert_ucs4_to_latin1_binary(const gunichar *buf, const glong len); -G_GNUC_INTERNAL gchar *convert_ucs4_to_ucs4be_string(const gunichar *buf, const glong len); - -#endif /* CHARSET_H */ diff --git a/src/chimara-glk-private.h b/src/chimara-glk-private.h deleted file mode 100644 index ba638a0..0000000 --- a/src/chimara-glk-private.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef __CHIMARA_GLK_PRIVATE_H__ -#define __CHIMARA_GLK_PRIVATE_H__ - -#include -#include -#include -#include "glk.h" -#include "gi_blorb.h" -#include "chimara-glk.h" - -G_BEGIN_DECLS - -typedef struct _ChimaraGlkPrivate ChimaraGlkPrivate; - -struct _ChimaraGlkPrivate { - /* Pointer back to the widget itself for use in thread */ - ChimaraGlk *self; - /* Whether user input is expected */ - gboolean interactive; - /* Whether file operations are allowed */ - gboolean protect; - /* Font description of proportional font */ - PangoFontDescription *default_font_desc; - /* Font description of monospace font */ - PangoFontDescription *monospace_font_desc; - /* Spacing between Glk windows */ - guint spacing; - /* Glk program loaded in widget */ - GModule *program; - /* Thread in which Glk program is run */ - GThread *thread; - /* Event queue and threading stuff */ - GQueue *event_queue; - GMutex *event_lock; - GCond *event_queue_not_empty; - GCond *event_queue_not_full; - /* Abort mechanism */ - GMutex *abort_lock; - gboolean abort_signalled; - /* User-defined interrupt handler */ - void (*interrupt_handler)(void); - /* Global tree of all windows */ - GNode *root_window; - /* List of filerefs currently in existence */ - GList *fileref_list; - /* Current stream */ - strid_t current_stream; - /* List of streams currently in existence */ - GList *stream_list; - /* Current timer */ - guint timer_id; - /* Current resource blorb map */ - giblorb_map_t *resource_map; - /* File stream pointing to the blorb used as current resource map */ - strid_t resource_file; -}; - -#define CHIMARA_GLK_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE((obj), CHIMARA_TYPE_GLK, ChimaraGlkPrivate)) - -G_END_DECLS - -#endif /* __CHIMARA_GLK_PRIVATE_H__ */ diff --git a/src/chimara-glk.c b/src/chimara-glk.c deleted file mode 100644 index 16e6749..0000000 --- a/src/chimara-glk.c +++ /dev/null @@ -1,963 +0,0 @@ -/* licensing and copyright information here */ - -#include -#include -#include -#include -#include -#include "chimara-glk.h" -#include "chimara-glk-private.h" -#include "glk.h" -#include "abort.h" -#include "window.h" - -#define CHIMARA_GLK_MIN_WIDTH 0 -#define CHIMARA_GLK_MIN_HEIGHT 0 - -/** - * SECTION:chimara-glk - * @short_description: Widget which executes a Glk program - * @stability: Unstable - * @include: chimara/chimara-glk.h - * - * The ChimaraGlk widget opens and runs a Glk program. The program must be - * compiled as a plugin module, with a function glk_main() - * that the Glk library can hook into. - * - * On Linux systems, this is a file with a name like - * plugin.so. For portability, you can use libtool and - * automake: - * |[ - * pkglib_LTLIBRARIES = plugin.la - * plugin_la_SOURCES = plugin.c foo.c bar.c - * plugin_la_LDFLAGS = -module -shared -avoid-version -export-symbols-regex "^glk_main$$" - * ]| - * This will produce plugin.la which is a text file - * containing the correct plugin file to open (see the relevant section of the - * - * Libtool manual). - */ - -typedef void (* glk_main_t) (void); - -enum { - PROP_0, - PROP_INTERACTIVE, - PROP_PROTECT, - PROP_DEFAULT_FONT_DESCRIPTION, - PROP_MONOSPACE_FONT_DESCRIPTION, - PROP_SPACING -}; - -enum { - STOPPED, - STARTED, - - LAST_SIGNAL -}; - -static guint chimara_glk_signals[LAST_SIGNAL] = { 0 }; - -G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER); - -static void -chimara_glk_init(ChimaraGlk *self) -{ - GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); - - priv->self = self; - priv->interactive = TRUE; - priv->protect = FALSE; - priv->default_font_desc = pango_font_description_from_string("Sans"); - priv->monospace_font_desc = pango_font_description_from_string("Monospace"); - priv->program = NULL; - priv->thread = NULL; - priv->event_queue = NULL; - priv->event_lock = NULL; - priv->event_queue_not_empty = NULL; - priv->event_queue_not_full = NULL; - priv->abort_lock = NULL; - priv->abort_signalled = FALSE; - priv->interrupt_handler = NULL; - priv->root_window = NULL; - priv->fileref_list = NULL; - priv->current_stream = NULL; - priv->stream_list = NULL; - priv->timer_id = 0; -} - -static void -chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) -{ - ChimaraGlk *glk = CHIMARA_GLK(object); - - switch(prop_id) - { - case PROP_INTERACTIVE: - chimara_glk_set_interactive( glk, g_value_get_boolean(value) ); - break; - case PROP_PROTECT: - chimara_glk_set_protect( glk, g_value_get_boolean(value) ); - break; - case PROP_DEFAULT_FONT_DESCRIPTION: - chimara_glk_set_default_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) ); - break; - case PROP_MONOSPACE_FONT_DESCRIPTION: - chimara_glk_set_monospace_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) ); - break; - case PROP_SPACING: - chimara_glk_set_spacing( glk, g_value_get_uint(value) ); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - } -} - -static void -chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) -{ - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object); - - switch(prop_id) - { - case PROP_INTERACTIVE: - g_value_set_boolean(value, priv->interactive); - break; - case PROP_PROTECT: - g_value_set_boolean(value, priv->protect); - break; - case PROP_DEFAULT_FONT_DESCRIPTION: - g_value_set_pointer(value, priv->default_font_desc); - break; - case PROP_MONOSPACE_FONT_DESCRIPTION: - g_value_set_pointer(value, priv->monospace_font_desc); - break; - case PROP_SPACING: - g_value_set_uint(value, priv->spacing); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - } -} - -static void -chimara_glk_finalize(GObject *object) -{ - ChimaraGlk *self = CHIMARA_GLK(object); - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); - - /* Free the event queue */ - g_mutex_lock(priv->event_lock); - g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL); - g_queue_free(priv->event_queue); - g_cond_free(priv->event_queue_not_empty); - g_cond_free(priv->event_queue_not_full); - priv->event_queue = NULL; - g_mutex_unlock(priv->event_lock); - g_mutex_free(priv->event_lock); - - /* Free the abort signalling mechanism */ - g_mutex_lock(priv->abort_lock); - /* Make sure no other thread is busy with this */ - g_mutex_unlock(priv->abort_lock); - g_mutex_free(priv->abort_lock); - priv->abort_lock = NULL; - - /* Free private data */ - pango_font_description_free(priv->default_font_desc); - pango_font_description_free(priv->monospace_font_desc); - - G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object); -} - -/* Internal function: Recursively get the Glk window tree's size request */ -static void -request_recurse(winid_t win, GtkRequisition *requisition, guint spacing) -{ - if(win->type == wintype_Pair) - { - /* Get children's size requests */ - GtkRequisition child1, child2; - request_recurse(win->window_node->children->data, &child1, spacing); - request_recurse(win->window_node->children->next->data, &child2, spacing); - - /* If the split is fixed, get the size of the fixed child */ - if((win->split_method & winmethod_DivisionMask) == winmethod_Fixed) - { - switch(win->split_method & winmethod_DirMask) - { - case winmethod_Left: - child1.width = win->key_window? - win->constraint_size * win->key_window->unit_width - : 0; - break; - case winmethod_Right: - child2.width = win->key_window? - win->constraint_size * win->key_window->unit_width - : 0; - break; - case winmethod_Above: - child1.height = win->key_window? - win->constraint_size * win->key_window->unit_height - : 0; - break; - case winmethod_Below: - child2.height = win->key_window? - win->constraint_size * win->key_window->unit_height - : 0; - break; - } - } - - /* Add the children's requests */ - switch(win->split_method & winmethod_DirMask) - { - case winmethod_Left: - case winmethod_Right: - requisition->width = child1.width + child2.width + spacing; - requisition->height = MAX(child1.height, child2.height); - break; - case winmethod_Above: - case winmethod_Below: - requisition->width = MAX(child1.width, child2.width); - requisition->height = child1.height + child2.height + spacing; - break; - } - } - - /* For non-pair windows, just use the size that GTK requests */ - else - gtk_widget_size_request(win->frame, requisition); -} - -/* Overrides gtk_widget_size_request */ -static void -chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition) -{ - g_return_if_fail(widget); - g_return_if_fail(requisition); - g_return_if_fail(CHIMARA_IS_GLK(widget)); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); - - /* For now, just pass the size request on to the root Glk window */ - if(priv->root_window) - { - request_recurse(priv->root_window->data, requisition, priv->spacing); - requisition->width += 2 * GTK_CONTAINER(widget)->border_width; - requisition->height += 2 * GTK_CONTAINER(widget)->border_width; - } - else - { - requisition->width = CHIMARA_GLK_MIN_WIDTH + 2 * GTK_CONTAINER(widget)->border_width; - requisition->height = CHIMARA_GLK_MIN_HEIGHT + 2 * GTK_CONTAINER(widget)->border_width; - } -} - -/* Recursively give the Glk windows their allocated space */ -static void -allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing) -{ - if(win->type == wintype_Pair) - { - GtkAllocation child1, child2; - child1.x = allocation->x; - child1.y = allocation->y; - - if((win->split_method & winmethod_DivisionMask) == winmethod_Fixed) - { - /* If the key window has been closed, then default to 0; otherwise - use the key window to determine the size */ - switch(win->split_method & winmethod_DirMask) - { - case winmethod_Left: - child1.width = win->key_window? - CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing) - : 0; - break; - case winmethod_Right: - child2.width = win->key_window? - CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing) - : 0; - break; - case winmethod_Above: - child1.height = win->key_window? - CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing) - : 0; - break; - case winmethod_Below: - child2.height = win->key_window? - CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing) - : 0; - break; - } - } - else /* proportional */ - { - gdouble fraction = win->constraint_size / 100.0; - switch(win->split_method & winmethod_DirMask) - { - case winmethod_Left: - child1.width = (glui32) ceil( fraction * (allocation->width - spacing) ); - break; - case winmethod_Right: - child2.width = (glui32) ceil( fraction * (allocation->width - spacing) ); - break; - case winmethod_Above: - child1.height = (glui32) ceil( fraction * (allocation->height - spacing) ); - break; - case winmethod_Below: - child2.height = (glui32) ceil( fraction * (allocation->height - spacing) ); - break; - } - } - - /* Fill in the rest of the size requisitions according to the child specified above */ - switch(win->split_method & winmethod_DirMask) - { - case winmethod_Left: - child2.width = allocation->width - spacing - child1.width; - child2.x = child1.x + child1.width + spacing; - child2.y = child1.y; - child1.height = child2.height = allocation->height; - break; - case winmethod_Right: - child1.width = allocation->width - spacing - child2.width; - child2.x = child1.x + child1.width + spacing; - child2.y = child1.y; - child1.height = child2.height = allocation->height; - break; - case winmethod_Above: - child2.height = allocation->height - spacing - child1.height; - child2.x = child1.x; - child2.y = child1.y + child1.height + spacing; - child1.width = child2.width = allocation->width; - break; - case winmethod_Below: - child1.height = allocation->height - spacing - child2.height; - child2.x = child1.x; - child2.y = child1.y + child1.height + spacing; - child1.width = child2.width = allocation->width; - break; - } - - /* Recurse */ - allocate_recurse(win->window_node->children->data, &child1, spacing); - allocate_recurse(win->window_node->children->next->data, &child2, spacing); - } - - else if(win->type == wintype_TextGrid) - { - /* Pass the size allocation on to the framing widget */ - gtk_widget_size_allocate(win->frame, allocation); - /* It says in the spec that when a text grid window is resized smaller, - the bottom or right area is thrown away; when it is resized larger, the - bottom or right area is filled with blanks. */ - glui32 newwidth = (glui32)(win->widget->allocation.width / win->unit_width); - glui32 newheight = (glui32)(win->widget->allocation.height / win->unit_height); - gint line; - GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - GtkTextIter start, end; - - for(line = 0; line < win->height; line++) - { - gtk_text_buffer_get_iter_at_line(textbuffer, &start, line); - /* If this line is going to fall off the bottom, delete it */ - if(line >= newheight) - { - end = start; - gtk_text_iter_forward_to_line_end(&end); - gtk_text_iter_forward_char(&end); - gtk_text_buffer_delete(textbuffer, &start, &end); - break; - } - /* If this line is not long enough, add spaces on the end */ - if(newwidth > win->width) - { - gchar *spaces = g_strnfill(newwidth - win->width, ' '); - gtk_text_iter_forward_to_line_end(&start); - gtk_text_buffer_insert(textbuffer, &start, spaces, -1); - g_free(spaces); - } - /* But if it's too long, delete characters from the end */ - else if(newwidth < win->width) - { - end = start; - gtk_text_iter_forward_chars(&start, newwidth); - gtk_text_iter_forward_to_line_end(&end); - gtk_text_buffer_delete(textbuffer, &start, &end); - } - /* Note: if the widths are equal, do nothing */ - } - /* Add blank lines if there aren't enough lines to fit the new size */ - if(newheight > win->height) - { - gchar *blanks = g_strnfill(win->width, ' '); - gchar **blanklines = g_new0(gchar *, (newheight - win->height) + 1); - int count; - for(count = 0; count < newheight - win->height; count++) - blanklines[count] = blanks; - blanklines[newheight - win->height] = NULL; - gchar *text = g_strjoinv("\n", blanklines); - g_free(blanklines); /* not g_strfreev() */ - g_free(blanks); - - gtk_text_buffer_get_end_iter(textbuffer, &start); - gtk_text_buffer_insert(textbuffer, &start, "\n", -1); - gtk_text_buffer_insert(textbuffer, &start, text, -1); - g_free(text); - } - - win->width = newwidth; - win->height = newheight; - } - - /* For non-pair, non-text-grid windows, just give them the size */ - else - gtk_widget_size_allocate(win->frame, allocation); -} - -/* Overrides gtk_widget_size_allocate */ -static void -chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation) -{ - g_return_if_fail(widget); - g_return_if_fail(allocation); - g_return_if_fail(CHIMARA_IS_GLK(widget)); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); - - widget->allocation = *allocation; - - if(priv->root_window) { - GtkAllocation child; - child.x = allocation->x + GTK_CONTAINER(widget)->border_width; - child.y = allocation->y + GTK_CONTAINER(widget)->border_width; - child.width = CLAMP(allocation->width - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->width); - child.height = CLAMP(allocation->height - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->height); - allocate_recurse(priv->root_window->data, &child, priv->spacing); - } -} - -/* Recursively invoke callback() on the GtkWidget of each non-pair window in the tree */ -static void -forall_recurse(winid_t win, GtkCallback callback, gpointer callback_data) -{ - if(win->type == wintype_Pair) - { - forall_recurse(win->window_node->children->data, callback, callback_data); - forall_recurse(win->window_node->children->next->data, callback, callback_data); - } - else - (*callback)(win->frame, callback_data); -} - -/* Overrides gtk_container_forall */ -static void -chimara_glk_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) -{ - g_return_if_fail(container); - g_return_if_fail(CHIMARA_IS_GLK(container)); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container); - - /* All the children are "internal" */ - if(!include_internals) - return; - - if(priv->root_window) - forall_recurse(priv->root_window->data, callback, callback_data); -} - -static void -chimara_glk_stopped(ChimaraGlk *self) -{ - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); - - /* Free the plugin */ - if( priv->program && !g_module_close(priv->program) ) - g_warning( "Error closing module: %s", g_module_error() ); -} - -static void -chimara_glk_started(ChimaraGlk *self) -{ - /* TODO: Add default signal handler implementation here */ -} - -/* G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */ -#ifndef G_PARAM_STATIC_STRINGS -#define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB) -#endif - -static void -chimara_glk_class_init(ChimaraGlkClass *klass) -{ - /* Override methods of parent classes */ - GObjectClass *object_class = G_OBJECT_CLASS(klass); - object_class->set_property = chimara_glk_set_property; - object_class->get_property = chimara_glk_get_property; - object_class->finalize = chimara_glk_finalize; - - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); - widget_class->size_request = chimara_glk_size_request; - widget_class->size_allocate = chimara_glk_size_allocate; - - GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); - container_class->forall = chimara_glk_forall; - - /* Signals */ - klass->stopped = chimara_glk_stopped; - klass->started = chimara_glk_started; - /** - * ChimaraGlk::stopped: - * @glk: The widget that received the signal - * - * The ::stopped signal is emitted when the a Glk program finishes - * executing in the widget, whether it ended normally, or was interrupted. - */ - chimara_glk_signals[STOPPED] = g_signal_new("stopped", - G_OBJECT_CLASS_TYPE(klass), 0, - G_STRUCT_OFFSET(ChimaraGlkClass, stopped), NULL, NULL, - g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); - /** - * ChimaraGlk::started: - * @glk: The widget that received the signal - * - * The ::started signal is emitted when a Glk program starts executing in - * the widget. - */ - chimara_glk_signals[STARTED] = g_signal_new ("started", - G_OBJECT_CLASS_TYPE (klass), 0, - G_STRUCT_OFFSET(ChimaraGlkClass, started), NULL, NULL, - g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); - - /* Properties */ - /** - * ChimaraGlk:interactive: - * - * Sets whether the widget is interactive. A Glk widget is normally - * interactive, but in non-interactive mode, keyboard and mouse input are - * ignored and the Glk program is controlled by chimara_glk_feed_text(). - * More prompts when a lot of text is printed to a text - * buffer are also disabled. This is typically used when you wish to control - * an interpreter program by feeding it a predefined list of commands. - */ - g_object_class_install_property( object_class, PROP_INTERACTIVE, - g_param_spec_boolean("interactive", _("Interactive"), - _("Whether user input is expected in the Glk program"), - TRUE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); - - /** - * ChimaraGlk:protect: - * - * Sets whether the Glk program is allowed to do file operations. In protect - * mode, all file operations will fail. - */ - g_object_class_install_property(object_class, PROP_PROTECT, - g_param_spec_boolean("protect", _("Protected"), - _("Whether the Glk program is barred from doing file operations"), - FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); - - /* We can't use G_PARAM_CONSTRUCT on these because then the constructor will - initialize them with NULL */ - /** - * ChimaraGlk:default-font-description: - * - * Pointer to a #PangoFontDescription describing the default proportional - * font, to be used in text buffer windows for example. - * - * Default value: font description created from the string - * Sans - */ - g_object_class_install_property(object_class, PROP_DEFAULT_FONT_DESCRIPTION, - g_param_spec_pointer("default-font-description", _("Default Font"), - _("Font description of the default proportional font"), - G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); - - /** - * ChimaraGlk:monospace-font-description: - * - * Pointer to a #PangoFontDescription describing the default monospace font, - * to be used in text grid windows and #style_Preformatted, for example. - * - * Default value: font description created from the string - * Monospace - */ - g_object_class_install_property(object_class, PROP_MONOSPACE_FONT_DESCRIPTION, - g_param_spec_pointer("monospace-font-description", _("Monospace Font"), - _("Font description of the default monospace font"), - G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); - - /** - * ChimaraGlk:spacing: - * - * The amount of space between the Glk windows. - */ - g_object_class_install_property(object_class, PROP_SPACING, - g_param_spec_uint("spacing", _("Spacing"), - _("The amount of space between Glk windows"), - 0, G_MAXUINT, 0, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) ); - - /* Private data */ - g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate)); -} - -/* PUBLIC FUNCTIONS */ - -/** - * chimara_glk_new: - * - * Creates and initializes a new #ChimaraGlk widget. - * - * Return value: a #ChimaraGlk widget, with a floating reference. - */ -GtkWidget * -chimara_glk_new(void) -{ - ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL)); - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); - - priv->event_queue = g_queue_new(); - priv->event_lock = g_mutex_new(); - priv->event_queue_not_empty = g_cond_new(); - priv->event_queue_not_full = g_cond_new(); - priv->abort_lock = g_mutex_new(); - - return GTK_WIDGET(self); -} - -/** - * chimara_glk_set_interactive: - * @glk: a #ChimaraGlk widget - * @interactive: whether the widget should expect user input - * - * Sets the #ChimaraGlk:interactive property of @glk. - */ -void -chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - priv->interactive = interactive; -} - -/** - * chimara_glk_get_interactive: - * @glk: a #ChimaraGlk widget - * - * Returns whether @glk is interactive (expecting user input). See - * #ChimaraGlk:interactive. - * - * Return value: %TRUE if @glk is interactive. - */ -gboolean -chimara_glk_get_interactive(ChimaraGlk *glk) -{ - g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - return priv->interactive; -} - -/** - * chimara_glk_set_protect: - * @glk: a #ChimaraGlk widget - * @protect: whether the widget should allow the Glk program to do file - * operations - * - * Sets the #ChimaraGlk:protect property of @glk. In protect mode, the Glk - * program is not allowed to do file operations. - */ -void -chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - priv->protect = protect; -} - -/** - * chimara_glk_get_protect: - * @glk: a #ChimaraGlk widget - * - * Returns whether @glk is in protect mode (banned from doing file operations). - * See #ChimaraGlk:protect. - * - * Return value: %TRUE if @glk is in protect mode. - */ -gboolean -chimara_glk_get_protect(ChimaraGlk *glk) -{ - g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - return priv->protect; -} - -/** - * chimara_glk_set_default_font_description: - * @glk: a #ChimaraGlk widget - * @font: a #PangoFontDescription - * - * Sets @glk's default proportional font. See - * #ChimaraGlk:default-font-description. - */ -void -chimara_glk_set_default_font_description(ChimaraGlk *glk, PangoFontDescription *font) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - g_return_if_fail(font); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - pango_font_description_free(priv->default_font_desc); - priv->default_font_desc = pango_font_description_copy(font); - - /* TODO: Apply the font description to all the windows and recalculate the sizes */ -} - -/** - * chimara_glk_set_default_font_string: - * @glk: a #ChimaraGlk widget - * @font: string representation of a font description - * - * Sets @glk's default proportional font according to the string @font, which - * must be a string in the form FAMILY-LIST - * [STYLE-OPTIONS] - * [SIZE], such as Charter,Utopia - * Italic 12 or Sans. See - * #ChimaraGlk:default-font-description. - */ -void -chimara_glk_set_default_font_string(ChimaraGlk *glk, const gchar *font) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - g_return_if_fail(font || *font); - - PangoFontDescription *fontdesc = pango_font_description_from_string(font); - g_return_if_fail(fontdesc); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - pango_font_description_free(priv->default_font_desc); - priv->default_font_desc = fontdesc; - - /* TODO: Apply the font description to all the windows and recalculate the sizes */ -} - -/** - * chimara_glk_get_default_font_description: - * @glk: a #ChimaraGlk widget - * - * Returns @glk's default proportional font. - * - * Return value: a newly-allocated #PangoFontDescription which must be freed - * using pango_font_description_free(), or %NULL on error. - */ -PangoFontDescription * -chimara_glk_get_default_font_description(ChimaraGlk *glk) -{ - g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - return pango_font_description_copy(priv->default_font_desc); -} - -/** - * chimara_glk_set_monospace_font_description: - * @glk: a #ChimaraGlk widget - * @font: a #PangoFontDescription - * - * Sets @glk's default monospace font. See - * #ChimaraGlk:monospace-font-description. - */ -void -chimara_glk_set_monospace_font_description(ChimaraGlk *glk, PangoFontDescription *font) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - g_return_if_fail(font); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - pango_font_description_free(priv->monospace_font_desc); - priv->monospace_font_desc = pango_font_description_copy(font); - - /* TODO: Apply the font description to all the windows and recalculate the sizes */ -} - -/** - * chimara_glk_set_monospace_font_string: - * @glk: a #ChimaraGlk widget - * @font: string representation of a font description - * - * Sets @glk's default monospace font according to the string @font, which must - * be a string in the form FAMILY-LIST - * [STYLE-OPTIONS] - * [SIZE], such as Courier - * Bold 12 or Monospace. See - * #ChimaraGlk:monospace-font-description. - */ -void -chimara_glk_set_monospace_font_string(ChimaraGlk *glk, const gchar *font) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - g_return_if_fail(font || *font); - - PangoFontDescription *fontdesc = pango_font_description_from_string(font); - g_return_if_fail(fontdesc); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - pango_font_description_free(priv->monospace_font_desc); - priv->monospace_font_desc = fontdesc; - - /* TODO: Apply the font description to all the windows and recalculate the sizes */ -} - -/** - * chimara_glk_get_monospace_font_description: - * @glk: a #ChimaraGlk widget - * - * Returns @glk's default monospace font. - * - * Return value: a newly-allocated #PangoFontDescription which must be freed - * using pango_font_description_free(), or %NULL on error. - */ -PangoFontDescription * -chimara_glk_get_monospace_font_description(ChimaraGlk *glk) -{ - g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - return pango_font_description_copy(priv->monospace_font_desc); -} - -/** - * chimara_glk_set_spacing: - * @glk: a #ChimaraGlk widget - * @spacing: the number of pixels to put between Glk windows - * - * Sets the #ChimaraGlk:spacing property of @glk, which is the border width in - * pixels between Glk windows. - */ -void -chimara_glk_set_spacing(ChimaraGlk *glk, guint spacing) -{ - g_return_if_fail( glk || CHIMARA_IS_GLK(glk) ); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - priv->spacing = spacing; -} - -/** - * chimara_glk_get_spacing: - * @glk: a #ChimaraGlk widget - * - * Gets the value set by chimara_glk_set_spacing(). - * - * Return value: pixels of spacing between Glk windows - */ -guint -chimara_glk_get_spacing(ChimaraGlk *glk) -{ - g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), 0); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - return priv->spacing; -} - -/* glk_enter() is the actual function called in the new thread in which glk_main() runs. */ -static gpointer -glk_enter(gpointer glk_main) -{ - extern ChimaraGlkPrivate *glk_data; - g_signal_emit_by_name(glk_data->self, "started"); - ((glk_main_t)glk_main)(); - g_signal_emit_by_name(glk_data->self, "stopped"); - return NULL; -} - -/** - * chimara_glk_run: - * @glk: a #ChimaraGlk widget - * @plugin: path to a plugin module compiled with glk.h - * @error: location to store a GError, or - * %NULL - * - * Opens a Glk program compiled as a plugin and runs its glk_main() function in - * a separate thread. On failure, returns %FALSE and sets @error. - * - * Return value: %TRUE if the Glk program was started successfully. - */ -gboolean -chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error) -{ - g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE); - g_return_val_if_fail(plugin, FALSE); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - - /* Open the module to run */ - glk_main_t glk_main; - g_assert( g_module_supported() ); - priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY); - - if(!priv->program) - { - g_warning( "Error opening module: %s", g_module_error() ); - return FALSE; - } - if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &glk_main) ) - { - g_warning( "Error finding glk_main(): %s", g_module_error() ); - return FALSE; - } - - extern ChimaraGlkPrivate *glk_data; - /* Set the thread's private data */ - /* TODO: Do this with a GPrivate */ - glk_data = priv; - - /* Run in a separate thread */ - priv->thread = g_thread_create(glk_enter, glk_main, TRUE, error); - - return !(priv->thread == NULL); -} - -/** - * chimara_glk_stop: - * @glk: a #ChimaraGlk widget - * - * Signals the Glk program running in @glk to abort. Note that if the program is - * caught in an infinite loop in which glk_tick() is not called, this may not - * work. - */ -void -chimara_glk_stop(ChimaraGlk *glk) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - /* TODO: check if glk is actually running a program */ - signal_abort(); -} - -/** - * chimara_glk_wait: - * @glk: a #ChimaraGlk widget - * - * Holds up the main thread and waits for the Glk program running in @glk to - * finish. - */ -void -chimara_glk_wait(ChimaraGlk *glk) -{ - g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); - - ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk); - g_thread_join(priv->thread); -} diff --git a/src/chimara-glk.h b/src/chimara-glk.h deleted file mode 100644 index ef121a2..0000000 --- a/src/chimara-glk.h +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright / licensing information here. */ - -#ifndef __CHIMARA_GLK_H__ -#define __CHIMARA_GLK_H__ - -#include -#include -#include - -G_BEGIN_DECLS - -#define CHIMARA_TYPE_GLK (chimara_glk_get_type()) -#define CHIMARA_GLK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ - CHIMARA_TYPE_GLK, ChimaraGlk)) -#define CHIMARA_GLK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ - CHIMARA_TYPE_GLK, ChimaraGlkClass)) -#define CHIMARA_IS_GLK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), \ - CHIMARA_TYPE_GLK)) -#define CHIMARA_IS_GLK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), \ - CHIMARA_TYPE_GLK)) -#define CHIMARA_GLK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ - CHIMARA_TYPE_GLK, ChimaraGlkClass)) - -/** - * ChimaraGlk: - * - * This structure contains no public members. - */ -typedef struct _ChimaraGlk { - GtkContainer parent_instance; - - /*< public >*/ -} ChimaraGlk; - -typedef struct _ChimaraGlkClass { - GtkContainerClass parent_class; - /* Signals */ - void(* stopped) (ChimaraGlk *self); - void(* started) (ChimaraGlk *self); -} ChimaraGlkClass; - -GType chimara_glk_get_type(void) G_GNUC_CONST; -GtkWidget *chimara_glk_new(void); -void chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive); -gboolean chimara_glk_get_interactive(ChimaraGlk *glk); -void chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect); -gboolean chimara_glk_get_protect(ChimaraGlk *glk); -void chimara_glk_set_default_font_description(ChimaraGlk *glk, PangoFontDescription *font); -void chimara_glk_set_default_font_string(ChimaraGlk *glk, const gchar *font); -PangoFontDescription *chimara_glk_get_default_font_description(ChimaraGlk *glk); -void chimara_glk_set_monospace_font_description(ChimaraGlk *glk, PangoFontDescription *font); -void chimara_glk_set_monospace_font_string(ChimaraGlk *glk, const gchar *font); -PangoFontDescription *chimara_glk_get_monospace_font_description(ChimaraGlk *glk); -void chimara_glk_set_spacing(ChimaraGlk *glk, guint spacing); -guint chimara_glk_get_spacing(ChimaraGlk *glk); -gboolean chimara_glk_run(ChimaraGlk *glk, gchar *plugin, GError **error); -void chimara_glk_stop(ChimaraGlk *glk); -void chimara_glk_wait(ChimaraGlk *glk); - -G_END_DECLS - -#endif /* __CHIMARA_GLK_H__ */ diff --git a/src/chimara.glade b/src/chimara.glade deleted file mode 100644 index 558f45b..0000000 --- a/src/chimara.glade +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - Gargoyle GTK - 500 - 600 - - - - True - - - True - - - True - _File - True - - - True - - - True - Opens an interactive fiction game - gtk-open - True - True - - - - - True - - - - - True - gtk-quit - True - True - - - - - - - - - - True - _Edit - True - - - True - - - True - gtk-cut - True - True - - - - - True - gtk-copy - True - True - - - - - True - gtk-paste - True - True - - - - - True - gtk-delete - True - True - - - - - - - - - True - _Game - True - - - True - - - True - Saves your progress in the game - _Save - True - - - gtk-save - - - - - - - True - Restores a previously saved game - _Restore - True - - - gtk-open - - - - - - - True - Restart the game - Res_tart - True - - - True - gtk-refresh - - - - - - - - - - - True - _Help - True - - - True - - - True - gtk-about - True - True - - - - - - - - - False - - - - - True - - - True - Save - gtk-save - - - - True - - - - - True - Restore - gtk-open - - - True - - - - - False - 1 - - - - - - diff --git a/src/doc.c b/src/doc.c deleted file mode 100644 index 2b60373..0000000 --- a/src/doc.c +++ /dev/null @@ -1,1733 +0,0 @@ -/* - * doc.c - Contains the short and long descriptions of all the documentation - * sections in the Glk spec, as well as the GtkDoc comments for symbols - * defined only in glk.h. - */ - -/** - * SECTION:glk-exiting - * @short_description: How to terminate a Glk program cleanly - * @include: glk.h - * - * A Glk program usually ends when the end of the glk_main() function is - * reached. You can also terminate it earlier. - */ - -/** - * SECTION:glk-interrupt - * @short_description: Specifying an interrupt handler for cleaning up critical - * resources - * @include: glk.h - * - * Most platforms have some provision for interrupting a program — - * command - * period on the Macintosh, controlC - * in Unix, possibly a window manager item, or other possibilities. - * This can happen at any time, including while execution is nested inside one - * of your own functions, or inside a Glk library function. - * - * If you need to clean up critical resources, you can specify an interrupt - * handler function. - */ - -/** - * SECTION:glk-tick - * @short_description: Yielding time to the operating system - * @include: glk.h - * - * Many platforms have some annoying thing that has to be done every so often, - * or the gnurrs come from the voodvork out and eat your computer. - * - * Well, not really. But you should call glk_tick() every so often, just in - * case. It may be necessary to yield time to other applications in a - * cooperative-multitasking OS, or to check for player interrupts in an infinite - * loop. - */ - -/** - * SECTION:glk-types - * @short_description: Basic types used in Glk - * @include: glk.h - * - * For simplicity, all the arguments used in Glk calls are of a very few types. - * - * - * 32-bit unsigned integer - * Unsigned integers are used wherever possible, which is - * nearly everywhere. This type is called #glui32. - * - * - * 32-bit signed integer - * This type is called #glsi32. Rarely used. - * - * - * - * References to library objects - * These are pointers to opaque C structures; each library - * will use different structures, so you can not and should not try to - * manipulate their contents. See Opaque Objects. - * - * - * Pointer to one of the above types - * Pointer to a structure which consists entirely of the - * above types. - * - * - * unsigned char - * This is used only for Latin-1 text characters; see - * Character Encoding. - * - * - * - * Pointer to char - * Sometimes this means a null-terminated string; sometimes - * an unterminated buffer, with length as a separate #glui32 argument. The - * documentation says which. - * - * - * Pointer to void - * When nothing else will do. - * - * - */ - -/** - * SECTION:glk-opaque-objects - * @short_description: Complex objects in Glk - * @include: glk.h - * - * Glk keeps track of a few classes of special objects. These are opaque to your - * program; you always refer to them using pointers to opaque C structures. - * - * Currently, these classes are: - * - * - * Windows - * Screen panels, used to input or output information. - * - * - * - * Streams - * Data streams, to which you can input or output text. - * - * There are file streams and window streams, since you can - * output data to windows or files. - * - * - * - * File references - * Pointers to files in permanent storage. - * In Unix a file reference is a pathname; on the Mac, an - * FSSpec. Actually there's a little more information included, - * such as file type and whether it is a text or binary file. - * - * - * - * Sound channels - * Audio output channels. - * Not all Glk libraries support sound. - * - * - * - * - * - * Note that there may be more object classes in future versions of the Glk API. - * - * - * When you create one of these objects, it is always possible that the creation - * will fail (due to lack of memory, or some other OS error.) When this happens, - * the allocation function will return %NULL (0) instead of a valid pointer. You - * should always test for this possibility. - * - * %NULL is never the identifier of any object (window, stream, file reference, - * or sound channel). The value %NULL is often used to indicate no - * object or nothing, but it is not a valid reference. If - * a Glk function takes an object reference as an argument, it is illegal to - * pass in %NULL unless the function definition says otherwise. - * - * The glk.h file defines types - * #winid_t, #strid_t, #frefid_t, #schanid_t to store references. These are - * pointers to struct #glk_window_struct, #glk_stream_struct, - * #glk_fileref_struct, and #glk_schannel_struct respectively. It is, of course, - * illegal to pass one kind of pointer to a function which expects another. - * - * - * This is how you deal with opaque objects from a C program. If you are using - * Glk through a virtual machine, matters will probably be different. Opaque - * objects may be represented as integers, or as VM objects of some sort. - * - * - * Rocks - * - * Every one of these objects (window, stream, file reference, or sound channel) - * has a rock value. This is simply a 32-bit integer value which - * you provide, for your own purposes, when you create the object. - * - * The library — so to speak — stuffs this value under a - * rock for safe-keeping, and gives it back to you when you ask for it. - * - * If you don't know what to use the rocks for, provide 0 and forget - * about it. - * - * - * Iteration Through Opaque Objects - * - * For each class of opaque objects, there is an iterate function, which you can - * use to obtain a list of all existing objects of that class. It takes the form - * |[ - * CLASSid_t glk_CLASS_iterate(CLASSid_t obj, #glui32 *rockptr); - * ]| - * ...where CLASS represents one of the - * opaque object classes. - * - * - * So, at the current time, these are the functions glk_window_iterate(), - * glk_stream_iterate(), glk_fileref_iterate(), and glk_schannel_iterate(). - * There may be more classes in future versions of the spec; they all behave - * the same. - * - * - * Calling glk_CLASS_iterate(%NULL, r) - * returns the first object; calling - * glk_CLASS_iterate(obj, r) returns - * the next object, until there aren't any more, at which time it returns %NULL. - * - * - * The @rockptr argument is a pointer to a location; whenever - * glk_CLASS_iterate() returns an - * object, the object's rock is stored in the location (*@rockptr). - * If you don't want the rocks to be returned, you may set @rockptr to %NULL. - * - * - * You usually use this as follows: - * |[ - * obj = glk_CLASS_iterate(NULL, NULL); - * while (obj) { - * /* ...do something with obj... */ - * obj = glk_CLASS_iterate(obj, NULL); - * } - * ]| - * - * - * If you create or destroy objects inside this loop, obviously, the results are - * unpredictable. However it is always legal to call - * glk_CLASS_iterate(obj, r) as long as - * @obj is a valid object id, or %NULL. - * - * - * The order in which objects are returned is entirely arbitrary. The library - * may even rearrange the order every time you create or destroy an object of - * the given class. As long as you do not create or destroy any object, the rule - * is that glk_CLASS_iterate(obj, r) has - * a fixed result, and iterating through the results as above will list every - * object exactly once. - * - * - */ - -/** - * SECTION:glk-gestalt - * @short_description: Testing Glk's capabilities - * @include: glk.h - * - * The gestalt mechanism (cheerfully stolen from the Mac OS) is a - * system by which the Glk API can be upgraded without making your life - * impossible. New capabilities (graphics, sound, or so on) can be added without - * changing the basic specification. The system also allows for - * optional capabilities — those which not all Glk library - * implementations will support — and allows you to check for their - * presence without trying to infer them from a version number. - * - * The basic idea is that you can request information about the capabilities of - * the API, by calling the gestalt functions. - */ - -/** - * SECTION:glk-character-input - * @short_description: Waiting for a single keystroke - * @include: glk.h - * - * You can request that the player hit a single key. See Character Input Events. - * - * If you use the basic text API, the character code which is returned can be - * any value from 0 to 255. The printable character codes have already been - * described. The remaining codes are typically control codes: control - * A to controlZ and a few - * others. - * - * There are also a number of special codes, representing special keyboard - * keys, which can be returned from a char-input event. These are represented - * as 32-bit integers, starting with 4294967295 (0xFFFFFFFF) and working down. - * The special key codes are defined in the glk.h file. They include one code for return or enter, - * one for delete or backspace, twelve function keys, and one code - * for any key which has no Latin-1 or special code. The full list of key codes - * is included below. - * - * Various implementations of Glk will vary widely in which characters the - * player can enter. The most obvious limitation is that some characters are - * mapped to others. For example, most keyboards return a controlI - * code when the tab key is - * pressed. The Glk library, if it can recognize this at all, will generate a - * #keycode_Tab event (value 0xFFFFFFF7) when this occurs. - * Therefore, for these keyboards, no keyboard key will generate a controlI - * event (value 9.) The Glk library will probably map many of the - * control codes to the other special keycodes. - * - * - * On the other hand, the library may be very clever and discriminate between - * tab and controlI. This is - * legal. The idea is, however, that if your program asks the player to - * press the tab - * key, you should check for a - * #keycode_Tab event as opposed to a control - * I event. - * - * - * Some characters may not be enterable simply because they do not exist. - * - * - * Not all keyboards have a home or end key. A pen-based platform may not recognize - * any control characters at all. - * - * - * Some characters may not be enterable because they are reserved for the - * purposes of the interface. For example, the Mac Glk library reserves the - * tab key for switching between different Glk - * windows. Therefore, on the Mac, the library will never generate a - * #keycode_Tab event or a - * controlI - * event. - * - * - * Note that the linefeed or controlJ - * character, which is the only printable control character, is probably not - * typable. This is because, in most libraries, it will be converted to - * #keycode_Return. Again, you should check for - * #keycode_Return if your program asks the player to - * press the return - * key. - * - * - * - * The delete and backspace keys are merged into a single - * keycode because they have such an astonishing history of being confused in - * the first place... this spec formally waives any desire to define the - * difference. Of course, a library is free to distinguish delete and backspace during line input. This is when it - * matters most; conflating the two during character input should not be a - * large problem. - * - * - * You can test for this by using the #gestalt_CharInput selector. - * - * - * Glk porters take note: it is not a goal to be able to generate every - * single possible key event. If the library says that it can generate a - * particular keycode, then game programmers will assume that it is - * available, and ask players to use it. If a #keycode_Home - * event can only be generated by typing escapecontrolA - * , and the player does not know this, the player will be lost - * when the game says Press the home key to see the next - * hint. It is better for the library to say that it - * cannot generate a #keycode_Home event; that way the game - * can detect the situation and ask the user to type H - * instead. - * - * - * Of course, it is better not to rely on obscure keys in any case. The arrow - * keys and return are nearly certain to be - * available; the others are of gradually decreasing reliability, and you - * (the game programmer) should not depend on them. You must be certain to - * check for the ones you want to use, including the arrow keys and return, and be prepared to use different keys in - * your interface if #gestalt_CharInput says they are not available. - * - */ - -/** - * SECTION:glk-case - * @short_description: Changing the case of strings - * @include: glk.h - * - * Glk has functions to manipulate the case of both Latin-1 and Unicode strings. - * One Latin-1 lowercase character corresponds to one uppercase character, and - * vice versa, so the Latin-1 functions act on single characters. The Unicode - * functions act on whole strings, since the length of the string may change. - */ - -/** - * SECTION:glk-window-opening - * @short_description: Creating new windows and closing them - * @include: glk.h - * - * You can open a new window using glk_window_open() and close it again using - * glk_window_close(). - */ - -/** - * SECTION:glk-window-constraints - * @short_description: Manipulating the size of a window - * @include: glk.h - * - * There are library functions to change and to measure the size of a window. - */ - -/** - * SECTION:glk-window-types - * @short_description: Blank, pair, text grid, text buffer, and graphics windows - * @include: glk.h - * - * A technical description of all the window types, and exactly how they behave. - */ - -/** - * SECTION:glk-echo-streams - * @short_description: Creating a copy of a window's output - * @include: glk.h - * - * Every window has an associated window stream; you print to the window by - * printing to this stream. However, it is possible to attach a second stream to - * a window. Any text printed to the window is also echoed to this second - * stream, which is called the window's echo stream. - * - * Effectively, any call to glk_put_char() (or the other output commands) which - * is directed to the window's window stream, is replicated to the window's echo - * stream. This also goes for the style commands such as glk_set_style(). - * - * Note that the echoing is one-way. You can still print text directly to the - * echo stream, and it will go wherever the stream is bound, but it does not - * back up and appear in the window. - * - * An echo stream can be of any type, even another window's window stream. - * - * - * This would be somewhat silly, since it would mean that any text printed to - * the window would be duplicated in another window. More commonly, you would - * set a window's echo stream to be a file stream, in order to create a - * transcript file from that window. - * - * - * A window can only have one echo stream. But a single stream can be the echo - * stream of any number of windows, sequentially or simultaneously. - * - * If a window is closed, its echo stream remains open; it is not automatically - * closed. - * - * - * Do not confuse the window's window stream with its echo stream. The window - * stream is owned by the window, and dies with it. The echo - * stream is merely temporarily associated with the window. - * - * - * If a stream is closed, and it is the echo stream of one or more windows, - * those windows are reset to not echo anymore. (So then calling - * glk_window_get_echo_stream() on them will return %NULL.) - */ - -/** - * SECTION:glk-window-other - * @short_description: Miscellaneous functions for windows - * @include: glk.h - * - * This section contains functions for windows that don't fit anywhere else. - */ - -/** - * SECTION:glk-events - * @short_description: Waiting for events - * @include: glk.h - * - * As described in Your - * Program's Main Function, all player input is handed to your program by - * the glk_select() call, in the form of events. You should write at least one - * event loop to retrieve these events. - */ - -/** - * SECTION:glk-character-input-events - * @short_description: Events representing a single keystroke - * @include: glk.h - * - * You can request character input from text buffer and text grid windows. See - * #evtype_CharInput. There are separate functions for requesting Latin-1 input - * and Unicode input; see #gestalt_Unicode. - */ - -/** - * SECTION:glk-line-input-events - * @short_description: Events representing a line of user input - * @include: glk.h - * - * You can request line input from text buffer and text grid windows. See - * #evtype_LineInput. There are separate functions for requesting Latin-1 input - * and Unicode input; see #gestalt_Unicode. - */ - -/** - * SECTION:glk-streams - * @short_description: Input and output abstractions - * @include: glk.h - * - * All character output in Glk is done through streams. Every window has an - * output stream associated with it. You can also write to files on disk; every - * open file is represented by an output stream as well. - * - * There are also input streams; these are used for reading from files on disk. - * It is possible for a stream to be both an input and an output stream. - * - * - * Player input is done through line and character input events, not streams. - * This is a small inelegance in theory. In practice, player input is slow and - * things can interrupt it, whereas file input is immediate. If a network - * extension to Glk were proposed, it would probably use events and not - * streams, since network communication is not immediate. - * - * - * It is also possible to create a stream that reads or writes to a buffer in - * memory. - * - * Finally, there may be platform-specific types of streams, which are created - * before your program starts running. - * - * - * For example, a program running under Unix may have access to standard input - * as a stream, even though there is no Glk call to explicitly open standard - * input. On the Mac, data in a Mac resource may be available through a - * resource-reading stream. - * - * - * You do not need to worry about the origin of such streams; just read or write - * them as usual. For information about how platform-specific streams come to - * be, see Startup Options. - * - * A stream is opened with a particular file mode, see the - * filemode_ constants below. - * - * For information on opening streams, see the discussion of each specific type - * of stream in The Types of - * Streams. Remember that it is always possible that opening a stream - * will fail, in which case the creation function will return %NULL. - * - * Each stream remembers two character counts, the number of characters printed - * to and read from that stream. The write-count is exactly one per - * glk_put_char() call; it is figured before any platform-dependent character - * cookery. - * - * - * For example, if a newline character is converted to - * linefeed-plus-carriage-return, the stream's count still only goes up by - * one; similarly if an accented character is displayed as two characters. - * - * - * The read-count is exactly one per glk_get_char_stream() call, as long as the - * call returns an actual character (as opposed to an end-of-file token.) - * - * Glk has a notion of the current (output) stream. If you print - * text without specifying a stream, it goes to the current output stream. The - * current output stream may be %NULL, meaning that there isn't one. It is - * illegal to print text to stream %NULL, or to print to the current stream when - * there isn't one. - * - * If the stream which is the current stream is closed, the current stream - * becomes %NULL. - */ - -/** - * SECTION:glk-print - * @short_description: Printing to streams - * @include: glk.h - * - * You can print Latin-1 and Unicode characters, null-terminated strings, or - * buffers to any stream. The characters will be converted into the appropriate - * format for that stream. - */ - -/** - * SECTION:glk-read - * @short_description: Reading from streams - * @include: glk.h - * - * You can read Latin-1 or Unicode characters, buffers, or whole lines from any - * stream. The characters will be converted into the form in which you request - * them. - */ - -/** - * SECTION:glk-closing-streams - * @short_description: Closing streams and retrieving their character counts - * @include: glk.h - * - * When you close a Glk stream, you have the opportunity to examine the - * character counts — the number of characters written to or read from the - * stream. - */ - -/** - * SECTION:glk-stream-positions - * @short_description: Moving the read/write mark - * @include: glk.h - * - * You can set the position of the read/write mark in a stream. - * - * - * Which makes one wonder why they're called streams in the - * first place. Oh well. - * - */ - -/** - * SECTION:glk-stream-types - * @short_description: Window, memory, and file streams - * @include: glk.h - * - * Window Streams - * - * Every window has an output stream associated with it. This is created - * automatically, with #filemode_Write, when you open the window. You get it - * with glk_window_get_stream(). - * - * A window stream cannot be closed with glk_stream_close(). It is closed - * automatically when you close its window with glk_window_close(). - * - * Only printable characters (including newline) may be printed to a window - * stream. See Character - * Encoding. - * - * - * Memory Streams - * - * You can open a stream which reads from or writes to a space in memory. See - * glk_stream_open_memory() and glk_stream_open_memory_uni(). When opening a - * memory stream, you specify a buffer to which the stream's output will be - * written, and its length @buflen. - * - * When outputting, if more than @buflen characters are written to the stream, - * all of them beyond the buffer length will be thrown away, so as not to - * overwrite the buffer. (The character count of the stream will still be - * maintained correctly. That is, it will count the number of characters written - * into the stream, not the number that fit into the buffer.) - * - * If the buffer is %NULL, or for that matter if @buflen is zero, then - * everything written to the stream is thrown away. This - * may be useful if you are interested in the character count. - * - * When inputting, if more than @buflen characters are read from the stream, the - * stream will start returning -1 (signalling end-of-file.) If the buffer is - * %NULL, the stream will always return end-of-file. - * - * The data is written to the buffer exactly as it was passed to the printing - * functions (glk_put_char(), etc.); input functions will read the data exactly - * as it exists in memory. No platform-dependent cookery will be done on it. - * - * - * You can write a disk file in text mode, but a memory stream is effectively - * always in binary mode. - * - * - * Whether reading or writing, the contents of the buffer are undefined until - * the stream is closed. The library may store the data there as it is written, - * or deposit it all in a lump when the stream is closed. It is illegal to - * change the contents of the buffer while the stream is open. - * - * - * File Streams - * - * You can open a stream which reads from or writes to a disk file. See - * glk_stream_open_file() and glk_stream_open_file_uni(). - * - * The file may be written in text or binary mode; this is determined by the - * file reference you open the stream with. Similarly, platform-dependent - * attributes such as file type are determined by the file reference. See File References. - * - * - */ - -/** - * SECTION:glk-stream-other - * @short_description: Miscellaneous functions for streams - * @include: glk.h - * - * This section includes functions for streams that don't fit anywhere else. - */ - -/** - * SECTION:glk-fileref - * @short_description: A platform-independent way to refer to disk files - * @include: glk.h - * - * You deal with disk files using file references. Each fileref is an opaque C - * structure pointer; see Opaque - * Objects. - * - * A file reference contains platform-specific information about the name and - * location of the file, and possibly its type, if the platform has a notion of - * file type. It also includes a flag indication whether the file is a text file - * or binary file. - * - * - * Note that this is different from the standard C I/O library, in which you - * specify text or binary mode when the file is opened. - * - * - * A fileref does not have to refer to a file which actually exists. You can - * create a fileref for a nonexistent file, and then open it in write mode to - * create a new file. - * - * You always provide a usage argument when you create a fileref. The usage is a - * mask of constants (see below) to indicate the file type and the mode (text or - * binary.) These values are used when you create a new file, and also to filter - * file lists when the player is selecting a file to load. - * - * In general, you should use text mode if the player expects to read the file - * with a platform-native text editor; you should use binary mode if the file is - * to be read back by your program, or if the data must be stored exactly. Text - * mode is appropriate for #fileusage_Transcript; binary mode is appropriate for - * #fileusage_SavedGame and probably for #fileusage_InputRecord. #fileusage_Data - * files may be text or binary, depending on what you use them for. - */ - -/** - * SECTION:glk-fileref-types - * @short_description: Four different ways to create a file reference - * @include: glk.h - * - * There are four different functions for creating a fileref, depending on how - * you wish to specify it. Remember that it is always possible that a fileref - * creation will fail and return %NULL. - */ - -/** - * SECTION:glk-fileref-other - * @short_description: Miscellaneous functions for file references - * @include: glk.h - * - * This section includes functions for file references that don't fit anywhere - * else. - */ - -/*---------------- TYPES AND CONSTANTS FROM GLK.H ----------------------------*/ - -/** - * glui32: - * - * A 32-bit unsigned integer type, used wherever possible in Glk. - */ - -/** - * glsi32: - * - * A 32-bit signed integer type, rarely used. - */ - -/** - * GLK_MODULE_UNICODE: - * - * If this preprocessor symbol is defined, so are all the Unicode functions and - * constants (see #gestalt_Unicode). If not, not. - */ - -/** - * winid_t: - * - * Opaque structure representing a Glk window. It has no user-accessible - * members. - */ - -/** - * strid_t: - * - * Opaque structure representing an input or output stream. It has no - * user-accessible members. - */ - -/** - * frefid_t: - * - * Opaque structure representing a file reference. It has no user-accessible - * members. - */ - -/** - * gestalt_Version: - * - * For an example of the gestalt mechanism, consider the selector - * #gestalt_Version. If you do - * |[ - * #glui32 res; - * res = #glk_gestalt(#gestalt_Version, 0); - * ]| - * res will be set to a 32-bit number which encodes the version of - * the Glk spec which the library implements. The upper 16 bits stores the major - * version number; the next 8 bits stores the minor version number; the low 8 - * bits stores an even more minor version number, if any. - * - * - * So the version number 78.2.11 would be encoded as 0x004E020B. - * - * - * The current Glk specification version is 0.7.0, so this selector will return - * 0x00000700. - * - * |[ - * #glui32 res; - * res = #glk_gestalt_ext(#gestalt_Version, 0, NULL, 0); - * ]| - * does exactly the same thing. Note that, in either case, the second argument - * is not used; so you should always pass 0 to avoid future surprises. - */ - -/** - * gestalt_CharInput: - * - * If you set ch to a character code, or a special code (from - * 0xFFFFFFFF down), and call - * |[ - * #glui32 res; - * res = #glk_gestalt(#gestalt_CharInput, ch); - * ]| - * then res will be %TRUE (1) if that character can be typed by - * the player in character input, and %FALSE (0) if not. See Character Input. - */ - -/** - * gestalt_LineInput: - * - * If you set ch to a character code, and call - * |[ - * #glui32 res; - * res = #glk_gestalt(#gestalt_LineInput, ch); - * ]| - * then res will be %TRUE (1) if that character can be typed by the - * player in line input, and %FALSE (0) if not. Note that if ch is - * a nonprintable Latin-1 character (0 to 31, 127 to 159), then this is - * guaranteed to return %FALSE. See Line - * Input. - */ - -/** - * gestalt_CharOutput: - * - * If you set ch to a character code (Latin-1 or higher), and call - * |[ - * #glui32 res, len; - * res = #glk_gestalt_ext(#gestalt_CharOutput, ch, &len, 1); - * ]| - * then res will be one of #gestalt_CharOutput_CannotPrint, - * #gestalt_CharOutput_ExactPrint, or #gestalt_CharOutput_ApproxPrint (see - * below.) - * - * In all cases, len (the #glui32 value pointed at by the third - * argument) will be the number of actual glyphs which will be used to represent - * the character. In the case of #gestalt_CharOutput_ExactPrint, this will - * always be 1; for #gestalt_CharOutput_CannotPrint, it may be 0 (nothing - * printed) or higher; for #gestalt_CharOutput_ApproxPrint, it may be 1 or - * higher. This information may be useful when printing text in a fixed-width - * font. - * - * - * As described in Other API - * Conventions, you may skip this information by passing %NULL as the - * third argument in glk_gestalt_ext(), or by calling glk_gestalt() instead. - * - * - * This selector will always return #gestalt_CharOutput_CannotPrint if - * ch is an unprintable eight-bit character (0 to 9, 11 to 31, 127 - * to 159.) - * - * - * Make sure you do not get confused by signed byte values. If you set a - * char variable ch to 0xFE, the - * small-thorn character (þ), and then call - * |[ res = #glk_gestalt(#gestalt_CharOutput, ch); ]| - * then (by the definition of C/C++) ch will be sign-extended to - * 0xFFFFFFFE, which is not a legitimate character, even in Unicode. You - * should write - * |[ res = #glk_gestalt(#gestalt_CharOutput, (unsigned char)ch); ]| - * instead. - * - * - * Unicode includes the concept of non-spacing or combining characters, which - * do not represent glyphs; and double-width characters, whose glyphs take up - * two spaces in a fixed-width font. Future versions of this spec may - * recognize these concepts by returning a len of 0 or 2 when - * #gestalt_CharOutput_ExactPrint is used. For the moment, we are adhering to - * a policy of simple stuff first. - * - */ - -/** - * gestalt_CharOutput_CannotPrint: - * - * When the #gestalt_CharOutput selector returns this for a character, the - * character cannot be meaningfully printed. If you try, the player may see - * nothing, or may see a placeholder. - */ - -/** - * gestalt_CharOutput_ApproxPrint: - * - * When the #gestalt_CharOutput selector returns this for a character, the - * library will print some approximation of the character. It will be more or - * less right, but it may not be precise, and it may not be distinguishable from - * other, similar characters. (Examples: - * ae for the one-character - * æ ligature, - * e for - * è, | - * for a broken vertical bar (¦).) - */ - -/** - * gestalt_CharOutput_ExactPrint: - * - * When the #gestalt_CharOutput selector returns this for a character, the - * character will be printed exactly as defined. - */ - -/** - * gestalt_Unicode: - * - * The basic text functions will be available in every Glk library. The Unicode - * functions may or may not be available. Before calling them, you should use - * the following gestalt selector: - * |[ - * glui32 res; - * res = #glk_gestalt(#gestalt_Unicode, 0); - * ]| - * - * This returns 1 if the Unicode functions are available. If it returns 0, you - * should not try to call them. They may print nothing, print gibberish, or - * cause a run-time error. The Unicode functions include - * glk_buffer_to_lower_case_uni(), glk_buffer_to_upper_case_uni(), - * glk_buffer_to_title_case_uni(), glk_put_char_uni(), glk_put_string_uni(), - * glk_put_buffer_uni(), glk_put_char_stream_uni(), glk_put_string_stream_uni(), - * glk_put_buffer_stream_uni(), glk_get_char_stream_uni(), - * glk_get_buffer_stream_uni(), glk_get_line_stream_uni(), - * glk_request_char_event_uni(), glk_request_line_event_uni(), - * glk_stream_open_file_uni(), glk_stream_open_memory_uni(). - * - * If you are writing a C program, there is an additional complication. A - * library which does not support Unicode may not implement the Unicode - * functions at all. Even if you put gestalt tests around your Unicode calls, - * you may get link-time errors. If the - * glk.h file is so old that it does not - * declare the Unicode functions and constants, you may even get compile-time - * errors. - * - * To avoid this, you can perform a preprocessor test for the existence of - * #GLK_MODULE_UNICODE. - */ - -/** - * evtype_None: - * - * No event. This is a placeholder, and glk_select() never returns it. - */ - -/** - * evtype_Timer: - * - * An event that repeats at fixed intervals. See Timer Events. - */ - -/** - * evtype_CharInput: - * - * A keystroke event in a window. See Character Input Events. - * - * If a window has a pending request for character input, and the player hits a - * key in that window, glk_select() will return an event whose type is - * #evtype_CharInput. Once this happens, the request is complete; it is no - * longer pending. You must call glk_request_char_event() or - * glk_request_char_event_uni() if you want another character from that window. - * - * In the event structure, @win tells what window the event came from. @val1 - * tells what character was entered; this will be a character code, or a special - * keycode. (See Character - * Input.) If you called glk_request_char_event(), @val1 will be in - * 0..255, or else a special keycode. In any case, @val2 will be 0. - */ - -/** - * evtype_LineInput: - * - * A full line of input completed in a window. See Line Input Events. - * - * If a window has a pending request for line input, and the player hits - * enter in that window (or whatever action is appropriate to - * enter his input), glk_select() will return an event whose type is - * #evtype_LineInput. Once this happens, the request is complete; it is no - * longer pending. You must call glk_request_line_event() if you want another - * line of text from that window. - * - * In the event structure, @win tells what window the event came from. @val1 - * tells how many characters were entered. @val2 will be 0. The characters - * themselves are stored in the buffer specified in the original - * glk_request_line_event() or glk_request_line_event_uni() call. - * - * There is no null terminator stored in the buffer. - * - * It is illegal to print anything to a window which has line input pending. - * - * - * This is because the window may be displaying and editing the player's - * input, and printing anything would make life unnecessarily complicated for - * the library. - * - */ - -/** - * evtype_MouseInput: - * - * A mouse click in a window. See Mouse Input Events. - */ - -/** - * evtype_Arrange: - * - * An event signalling that the sizes of some windows have changed. - * - * Some platforms allow the player to resize the Glk window during play. This - * will naturally change the sizes of your windows. If this occurs, then - * immediately after all the rearrangement, glk_select() will return an event - * whose type is #evtype_Arrange. You can use this notification to redisplay the - * contents of a graphics or text grid window whose size has changed. - * - * - * The display of a text buffer window is entirely up to the library, so you - * don't need to worry about those. - * - * - * In the event structure, @win will be %NULL if all windows are affected. If - * only some windows are affected, @win will refer to a window which contains - * all the affected windows. @val1 and @val2 will be 0. - * - * - * You can always play it safe, ignore @win, and redraw every graphics and - * text grid window. - * - * - * An arrangement event is guaranteed to occur whenever the player causes any - * window to change size, as measured by its own metric. - * - * - * Size changes caused by you — for example, if you open, close, or - * resize a window — do not trigger arrangement events. You must be - * aware of the effects of your window management, and redraw the windows that - * you affect. - * - * - * - * It is possible that several different player actions can cause windows to - * change size. For example, if the player changes the screen resolution, an - * arrangement event might be triggered. This might also happen if the player - * changes his display font to a different size; the windows would then be - * different sizes in the metric of rows and columns, which is - * the important metric and the only one you have access to. - * - * - * Arrangement events, like timer events, can be returned by glk_select_poll(). - * But this will not occur on all platforms. You must be ready to receive an - * arrangement event when you call glk_select_poll(), but it is possible that it - * will not arrive until the next time you call glk_select(). - * - * - * This is because on some platforms, window resizing is handled as part of - * player input; on others, it can be triggered by an external process such as - * a window manager. - * - */ - -/** - * evtype_Redraw: - * - * An event signalling that graphics windows must be redrawn. - * - * On platforms that support graphics, it is possible that the contents of a - * graphics window will be lost, and have to be redrawn from scratch. If this - * occurs, then glk_select() will return an event whose type is #evtype_Redraw. - * - * In the event structure, @win will be %NULL if all windows are affected. If - * only some windows are affected, @win will refer to a window which contains - * all the affected windows. @val1 and @val2 will be 0. - * - * - * You can always play it safe, ignore @win, and redraw every graphics window. - * - * - * Affected windows are already cleared to their background color when you - * receive the redraw event. - * - * Redraw events can be returned by glk_select_poll(). But, like arrangement - * events, this is platform-dependent. See #evtype_Arrange. - * - * For more about redraw events and how they affect graphics windows, see Graphics Windows. - */ - -/** - * evtype_SoundNotify: - * - * On platforms that support sound, you can request to receive an - * #evtype_SoundNotify event when a sound finishes playing. See Playing Sounds. - */ - -/** - * evtype_Hyperlink: - * - * On platforms that support hyperlinks, you can request to receive an - * #evtype_Hyperlink event when the player selects a link. See Accepting Hyperlink - * Events. - */ - -/** - * event_t: - * @type: the event type - * @win: the window that spawned the event, or %NULL - * @val1: information, the meaning of which depends on the type of event - * @val2: more information, the meaning of which depends on the type of event - * - * The event structure is self-explanatory. @type is the event type. The window - * that spawned the event, if relevant, is in @win. The remaining fields contain - * more information specific to the event. - * - * The event types are described below. Note that #evtype_None is zero, and the - * other values are positive. Negative event types (0x80000000 to 0xFFFFFFFF) - * are reserved for implementation-defined events. - */ - -/** - * keycode_Unknown: - * - * Represents any key that has no Latin-1 or special code. - */ - -/** - * keycode_Left: - * - * Represents the left arrow key. - */ - -/** - * keycode_Right: - * - * Represents the right arrow key. - */ - -/** - * keycode_Up: - * - * Represents the up arrow key. - */ - -/** - * keycode_Down: - * - * Represents the down arrow key. - */ - -/** - * keycode_Return: - * - * Represents the return or enter keys. - */ - -/** - * keycode_Delete: - * - * Represents the delete or backspace keys. - */ - -/** - * keycode_Escape: - * - * Represents the escape key. - */ - -/** - * keycode_Tab: - * - * Represents the tab key. - */ - -/** - * keycode_PageUp: - * - * Represents the page up key. - */ - -/** - * keycode_PageDown: - * - * Represents the page down key. - */ - -/** - * keycode_Home: - * - * Represents the home key. - */ - -/** - * keycode_End: - * - * Represents the end key. - */ - -/** - * keycode_Func1: - * - * Represents the F1 key. - */ - -/** - * keycode_Func2: - * - * Represents the F2 key. - */ - -/** - * keycode_Func3: - * - * Represents the F3 key. - */ - -/** - * keycode_Func4: - * - * Represents the F4 key. - */ - -/** - * keycode_Func5: - * - * Represents the F5 key. - */ - -/** - * keycode_Func6: - * - * Represents the F6 key. - */ - -/** - * keycode_Func7: - * - * Represents the F7 key. - */ - -/** - * keycode_Func8: - * - * Represents the F8 key. - */ - -/** - * keycode_Func9: - * - * Represents the F9 key. - */ - -/** - * keycode_Func10: - * - * Represents the F10 key. - */ - -/** - * keycode_Func11: - * - * Represents the F11 key. - */ - -/** - * keycode_Func12: - * - * Represents the F12 key. - */ - -/** - * keycode_MAXVAL: - * - * This value is equal to the number of special keycodes. The last keycode is - * The last keycode is always - * - * (0x100000000 - keycode_MAXVAL) - * (0x100000000 - keycode_MAXVAL) - * - * . - */ - -/** - * stream_result_t: - * @readcount: Number of characters read from the stream. - * @writecount: Number of characters printed to the stream, including ones that - * were thrown away. - * - * If you are interested in the character counts of a stream (see Streams), then you can pass a pointer to - * #stream_result_t as an argument of glk_stream_close() or glk_window_close(). - * The structure will be filled with the stream's final character counts. - */ - -/** - * wintype_AllTypes: - * - * A constant representing all window types, which may be used as the @wintype - * argument in glk_stylehint_set(). - */ - -/** - * wintype_Pair: - * - * A pair window is completely filled by the two windows it contains. It - * supports no input and no output, and it has no size. - * - * You cannot directly create a pair window; one is automatically created - * every time you split a window with glk_window_open(). Pair windows are - * always created with a rock value of 0. - * - * You can close a pair window with glk_window_close(); this also closes every - * window contained within the pair window. - * - * It is legal to split a pair window when you call glk_window_open(). - */ - -/** - * wintype_Blank: - * - * A blank window is always blank. It supports no input and no output. (You - * can call glk_window_get_stream() on it, as you can with any window, but - * printing to the resulting stream has no effect.) A blank window has no - * size; glk_window_get_size() will return (0,0), and it is illegal to set a - * window split with a fixed size in the measurement system of a blank window. - * - * - * A blank window is not the same as there being no windows. When Glk starts - * up, there are no windows at all, not even a window of the blank type. - * - */ - -/** - * wintype_TextBuffer: - * - * A text buffer window contains a linear stream of text. It supports output; - * when you print to it, the new text is added to the end. There is no way for - * you to affect text which has already been printed. There are no guarantees - * about how much text the window keeps; old text may be stored forever, so - * that the user can scroll back to it, or it may be thrown away as soon as it - * scrolls out of the window. - * - * - * Therefore, there may or may not be a player-controllable scroll bar or - * other scrolling widget. - * - * - * The display of the text in a text buffer is up to the library. Lines will - * probably not be broken in the middles of words — but if they are, the - * library is not doing anything illegal, only ugly. Text selection and copying - * to a clipboard, if available, are handled however is best on the player's - * machine. Paragraphs (as defined by newline characters in the output) may be - * indented. - * - * - * You should not, in general, fake this by printing spaces before each - * paragraph of prose text. Let the library and player preferences handle - * that. Special cases (like indented lists) are of course up to you. - * - * - * When a text buffer is cleared (with glk_window_clear()), the library will do - * something appropriate; the details may vary. It may clear the window, with - * later text appearing at the top — or the bottom. It may simply print - * enough blank lines to scroll the current text out of the window. It may - * display a distinctive page-break symbol or divider. - * - * The size of a text buffer window is necessarily imprecise. Calling - * glk_window_get_size() will return the number of rows and columns that would - * be available if the window was filled with - * 0 (zero) characters in the normal font. - * However, the window may use a non-fixed-width font, so that number of - * characters in a line could vary. The window might even support - * variable-height text (say, if the player is using large text for emphasis); - * that would make the number of lines in the window vary as well. - * - * Similarly, when you set a fixed-size split in the measurement system of a - * text buffer, you are setting a window which can handle a fixed number of rows - * (or columns) of 0 characters. The number of rows (or - * characters) that will actually be displayed depends on font variances. - * - * A text buffer window supports both character and line input, but not mouse - * input. - * - * In character input, there will be some visible signal that the window is - * waiting for a keystroke. (Typically, a cursor at the end of the text.) When - * the player hits a key in that window, an event is generated, but the key is - * not printed in the window. - * - * In line input, again, there will be some visible signal. It is most common - * for the player to compose input in the window itself, at the end of the text. - * (This is how IF story input usually looks.) But it's not strictly required. - * An alternative approach is the way MUD clients usually work: there is a - * dedicated one-line input window, outside of Glk's window space, and the user - * composes input there. - * - * - * If this approach is used, there will still be some way to handle input from - * two windows at once. It is the library's responsibility to make this - * available to the player. You only need request line input and wait for the - * result. - * - * - * When the player finishes his line of input, the library will display the - * input text at the end of the buffer text (if it wasn't there already.) It - * will be followed by a newline, so that the next text you print will start a - * new line (paragraph) after the input. - * - * If you call glk_cancel_line_event(), the same thing happens; whatever text - * the user was composing is visible at the end of the buffer text, followed by - * a newline. - */ - -/** - * wintype_TextGrid: - * - * A text grid contains a rectangular array of characters, in a fixed-width - * font. Its size is the number of columns and rows of the array. - * - * A text grid window supports output. It maintains knowledge of an output - * cursor position. When the window is opened, it is filled with blanks (space - * characters), and the output cursor starts in the top left corner — - * character (0,0). If the window is cleared with glk_window_clear(), the window - * is filled with blanks again, and the cursor returns to the top left corner. - * - * When you print, the characters of the output are laid into the array in - * order, left to right and top to bottom. When the cursor reaches the end of a - * line, it goes to the beginning of the next line. The library makes no attempt - * to wrap lines at word breaks. - * - * - * Note that printing fancy characters may cause the cursor to advance more - * than one position per character. (For example, the æ - * ligature may print as two characters.) See Output, for how to test this situation. - * - * - * You can set the cursor position with glk_window_move_cursor(). - * - * When a text grid window is resized smaller, the bottom or right area is - * thrown away, but the remaining area stays unchanged. When it is resized - * larger, the new bottom or right area is filled with blanks. - * - * - * You may wish to watch for #evtype_Arrange events, and clear-and-redraw your - * text grid windows when you see them change size. - * - * - * Text grid window support character and line input, as well as mouse input (if - * a mouse is available.) - * - * Mouse input returns the position of the character that was touched, from - * (0,0) to - * - * (width-1,height-1) - * (width - 1, height - 1) - * - * . - * - * Character input is as described in the previous section. - * - * Line input is slightly different; it is guaranteed to take place in the - * window, at the output cursor position. The player can compose input only to - * the right edge of the window; therefore, the maximum input length is - * - * (windowwidth - 1 - cursorposition) - * (windowwidth - 1 - cursorposition) - * - * . If the maxlen argument of glk_request_line_event() is smaller than this, - * the library will not allow the input cursor to go more than maxlen characters - * past its start point. - * - * - * This allows you to enter text in a fixed-width field, without the player - * being able to overwrite other parts of the window. - * - * - * When the player finishes his line of input, it will remain visible in the - * window, and the output cursor will be positioned at the beginning of the - * next row. Again, if you glk_cancel_line_event(), the - * same thing happens. - */ - -/** - * wintype_Graphics: - * - * A graphics window contains a rectangular array of pixels. Its size is the - * number of columns and rows of the array. - * - * Each graphics window has a background color, which is initially white. You - * can change this; see Graphics in Graphics - * Windows. - * - * When a text grid window is resized smaller, the bottom or right area is - * thrown away, but the remaining area stays unchanged. When it is resized - * larger, the new bottom or right area is filled with the background color. - * - * - * You may wish to watch for #evtype_Arrange events, and clear-and-redraw your - * graphics windows when you see them change size. - * - * - * In some libraries, you can receive a graphics-redraw event (#evtype_Redraw) - * at any time. This signifies that the window in question has been cleared to - * its background color, and must be redrawn. If you create any graphics - * windows, you must handle these events. - * - * - * Redraw events can be triggered when a Glk window is uncovered or made - * visible by the platform's window manager. On the other hand, some Glk - * libraries handle these problem automatically — for example, with a - * backing store — and do not send you redraw events. On the third hand, - * the backing store may be discarded if memory is low, or for other reasons - * — perhaps the screen's color depth has changed. So redraw events are - * always a possibility, even in clever libraries. This is why you must be - * prepared to handle them. - * - * However, you will not receive a redraw event when you create a graphics - * window. It is assumed that you will do the initial drawing of your own - * accord. You also do not get redraw events when a graphics window is - * enlarged. If you ordered the enlargement, you already know about it; if the - * player is responsible, you receive a window-arrangement event, which covers - * the situation. - * - * - * For a description of the drawing functions that apply to graphics windows, - * see Graphics in Graphics - * Windows. - * - * Graphics windows support no text input or output. - * - * Not all libraries support graphics windows. You can test whether Glk graphics - * are available using the gestalt system. In a C program, you can also test - * whether the graphics functions are defined at compile-time. See Testing for Graphics - * Capabilities. - * - * - * As with all windows, you should also test for %NULL when you create a - * graphics window. - * - */ - -/** - * winmethod_Left: - * - * When calling glk_window_open() with this @method, the new window will be - * to the left of the old one which was split. - */ - -/** - * winmethod_Right: - * - * When calling glk_window_open() with this @method, the new window will be - * to the right of the old one which was split. - */ - -/** - * winmethod_Above: - * - * When calling glk_window_open() with this @method, the new window will be - * above the old one which was split. - */ - -/** - * winmethod_Below: - * - * When calling glk_window_open() with this @method, the new window will be - * below the old one which was split. - */ - -/** - * winmethod_DirMask: - * - * Bitwise AND this value with a window splitting method argument to find - * whether the split is #winmethod_Left, #winmethod_Right, #winmethod_Above, or - * #winmethod_Below. - */ - -/** - * winmethod_Fixed: - * - * When calling glk_window_open() with this @method, the new window will be - * a fixed size. (See glk_window_open()). - */ - -/** - * winmethod_Proportional: - * - * When calling glk_window_open() with this @method, the new window will be - * a given proportion of the old window's size. (See glk_window_open()). - */ - -/** - * winmethod_DivisionMask: - * - * Bitwise AND this value with a window splitting method argument to find - * whether the new window has #winmethod_Fixed or #winmethod_Proportional. - */ - -/** - * fileusage_Data: - * - * Any other kind of file (preferences, statistics, arbitrary data.) - */ - -/** - * fileusage_SavedGame: - * - * A file which stores game state. - */ - -/** - * fileusage_Transcript: - * - * A file which contains a stream of text from the game (often an echo stream - * from a window.) - */ - -/** - * fileusage_InputRecord: - * - * A file which records player input. - */ - -/** - * fileusage_TextMode: - * - * The file contents will be transformed to a platform-native text file as they - * are written out. Newlines may be converted to linefeeds or - * linefeed-plus-carriage-return combinations; Latin-1 characters may be - * converted to native character codes. When reading a file in text mode, native - * line breaks will be converted back to newline (0x0A) characters, and native - * character codes may be converted to Latin-1. - * - * - * Line breaks will always be converted; other conversions are more - * questionable. If you write out a file in text mode, and then read it back - * in text mode, high-bit characters (128 to 255) may be transformed or lost. - * - * Chimara - * - * Text mode files in Chimara are in UTF-8, which is GTK+'s native file - * encoding. - * - */ - -/** - * fileusage_BinaryMode: - * - * The file contents will be stored exactly as they are written, and read back - * in the same way. The resulting file may not be viewable on platform-native - * text file viewers. - */ - -/** - * fileusage_TypeMask: - * - * Bitwise AND this value with a file usage argument to find whether the file - * type is #fileusage_SavedGame, #fileusage_Transcript, #fileusage_InputRecord, - * or #fileusage_Data. - */ - -/** - * filemode_Write: - * - * An output stream. - * - * - * Corresponds to mode "w" in the stdio library, using fopen(). - * - */ - -/** - * filemode_Read: - * - * An input stream. - * - * - * Corresponds to mode "r" in the stdio library, using fopen(). - * - */ - -/** - * filemode_ReadWrite: - * - * Both an input and an output stream. - * - * - * Corresponds to mode "r+" in the stdio library, using fopen(). - * - */ - -/** - * filemode_WriteAppend: - * - * An output stream, but the data will added to the end of whatever already - * existed in the destination, instead of replacing it. - * - * - * Corresponds to mode "a" in the stdio library, using fopen(). - * - */ - -/** - * seekmode_Start: - * - * In glk_stream_set_position(), signifies that @pos is counted in characters - * after the beginning of the file. - */ - -/** - * seekmode_Current: - * - * In glk_stream_set_position(), signifies that @pos is counted in characters - * after the current position (moving backwards if @pos is negative.) - */ - -/** - * seekmode_End: - * - * In glk_stream_set_position(), signifies that @pos is counted in characters - * after the end of the file. (@pos should always be zero or negative, so that - * this will move backwards to a position within the file. - */ - diff --git a/src/error.c b/src/error.c deleted file mode 100644 index c9b6f08..0000000 --- a/src/error.c +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright 2006 P.F. Chimento - * This file is part of GNOME Inform 7. - * - * GNOME Inform 7 is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * GNOME Inform 7 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GNOME Inform 7; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include -#include - -#include "error.h" - -/* Create and display an error dialog box, with parent window parent, and -message format string msg. If err is not NULL, tack the error message on to the -end of the format string. */ -void -error_dialog(GtkWindow *parent, GError *err, const gchar *msg, ...) -{ - va_list ap; - - va_start(ap, msg); - gchar buffer[1024]; - g_vsnprintf(buffer, 1024, msg, ap); - va_end(ap); - - gchar *message; - if(err) { - message = g_strconcat(buffer, err->message, NULL); - g_error_free(err); - } else - message = g_strdup(buffer); - - GtkWidget *dialog = gtk_message_dialog_new(parent, - parent? GTK_DIALOG_DESTROY_WITH_PARENT : 0, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, - message); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - g_free(message); -} diff --git a/src/error.h b/src/error.h deleted file mode 100644 index bb05fbe..0000000 --- a/src/error.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright 2006 P.F. Chimento - * This file is part of GNOME Inform 7. - * - * GNOME Inform 7 is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * GNOME Inform 7 is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GNOME Inform 7; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef ERROR_H -#define ERROR_H - -#include -#include - -void error_dialog(GtkWindow *parent, GError *err, const gchar *msg, ...); - -#endif diff --git a/src/event.c b/src/event.c deleted file mode 100644 index d7cb659..0000000 --- a/src/event.c +++ /dev/null @@ -1,94 +0,0 @@ -#include "event.h" -#include "magic.h" -#include "glk.h" -#include - -#include "chimara-glk-private.h" - -extern ChimaraGlkPrivate *glk_data; - -#define EVENT_TIMEOUT_MICROSECONDS (3000000) - -/* Internal function: push an event onto the event queue. If the event queue is -full, wait for max three seconds and then drop the event. If the event queue is -NULL, i.e. freed, then fail silently. */ -void -event_throw(glui32 type, winid_t win, glui32 val1, glui32 val2) -{ - if(!glk_data->event_queue) - return; - - GTimeVal timeout; - g_get_current_time(&timeout); - g_time_val_add(&timeout, EVENT_TIMEOUT_MICROSECONDS); - - g_mutex_lock(glk_data->event_lock); - - /* Wait for room in the event queue */ - while( g_queue_get_length(glk_data->event_queue) >= EVENT_QUEUE_MAX_LENGTH ) - if( !g_cond_timed_wait(glk_data->event_queue_not_full, glk_data->event_lock, &timeout) ) - { - /* Drop the event after 3 seconds */ - g_mutex_unlock(glk_data->event_lock); - return; - } - - event_t *event = g_new0(event_t, 1); - event->type = type; - event->win = win; - event->val1 = val1; - event->val2 = val2; - g_queue_push_head(glk_data->event_queue, event); - - /* Signal that there is an event */ - g_cond_signal(glk_data->event_queue_not_empty); - - g_mutex_unlock(glk_data->event_lock); -} - -/** - * glk_select: - * @event: Pointer to an #event_t. - * - * Causes the program to wait for an event, and then store it in the structure - * pointed to by @event. Unlike most Glk functions that take pointers, the - * argument of glk_select() may not be %NULL. - * - * Most of the time, you only get the events that you request. However, there - * are some events which can arrive at any time. This is why you must always - * call glk_select() in a loop, and continue the loop until you get the event - * you really want. - */ -void -glk_select(event_t *event) -{ - g_return_if_fail(event != NULL); - - g_mutex_lock(glk_data->event_lock); - - /* Wait for an event */ - while( g_queue_is_empty(glk_data->event_queue) ) - g_cond_wait(glk_data->event_queue_not_empty, glk_data->event_lock); - - event_t *retrieved_event = g_queue_pop_tail(glk_data->event_queue); - if(retrieved_event == NULL) - { - g_mutex_unlock(glk_data->event_lock); - WARNING("Retrieved NULL event from non-empty event queue"); - return; - } - memcpy(event, retrieved_event, sizeof(event_t)); - g_free(retrieved_event); - - /* Signal that the event queue is no longer full */ - g_cond_signal(glk_data->event_queue_not_full); - - g_mutex_unlock(glk_data->event_lock); - - /* Check for interrupt */ - glk_tick(); - - /* If an abort event was generated, the thread should have exited by now */ - g_assert(event->type != evtype_Abort); -} - diff --git a/src/event.h b/src/event.h deleted file mode 100644 index 7cf80bd..0000000 --- a/src/event.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef EVENT_H -#define EVENT_H - -#include -#include "glk.h" - -#define EVENT_QUEUE_MAX_LENGTH (100) -#define evtype_Abort (-1) - -G_GNUC_INTERNAL void event_throw(glui32 type, winid_t win, glui32 val1, glui32 val2); - -#endif diff --git a/src/fileref.c b/src/fileref.c deleted file mode 100644 index 2b5cf53..0000000 --- a/src/fileref.c +++ /dev/null @@ -1,406 +0,0 @@ -#include -#include -#include -#include -#include -#include "fileref.h" -#include "magic.h" -#include "chimara-glk-private.h" - -extern ChimaraGlkPrivate *glk_data; - -/** - * glk_fileref_iterate: - * @fref: A file reference, or %NULL. - * @rockptr: Return location for the next fileref's rock, or %NULL. - * - * Iterates through all the existing filerefs. See Iterating Through Opaque - * Objects. - * - * Returns: the next file reference, or %NULL if there are no more. - */ -frefid_t -glk_fileref_iterate(frefid_t fref, glui32 *rockptr) -{ - VALID_FILEREF_OR_NULL(fref, return NULL); - - GList *retnode; - - if(fref == NULL) - retnode = glk_data->fileref_list; - else - retnode = fref->fileref_list->next; - frefid_t retval = retnode? (frefid_t)retnode->data : NULL; - - /* Store the fileref's rock in rockptr */ - if(retval && rockptr) - *rockptr = glk_fileref_get_rock(retval); - - return retval; -} - -/** - * glk_fileref_get_rock: - * @fref: A file reference. - * - * Retrieves the file reference @fref's rock value. See Rocks. - * - * Returns: A rock value. - */ -glui32 -glk_fileref_get_rock(frefid_t fref) -{ - VALID_FILEREF(fref, return 0); - return fref->rock; -} - -/* Internal function: create a fileref using the given parameters. */ -static frefid_t -fileref_new(gchar *filename, glui32 rock, glui32 usage, glui32 orig_filemode) -{ - g_return_val_if_fail(filename != NULL, NULL); - - frefid_t f = g_new0(struct glk_fileref_struct, 1); - f->magic = MAGIC_FILEREF; - f->rock = rock; - f->filename = g_strdup(filename); - f->usage = usage; - f->orig_filemode = orig_filemode; - - /* Add it to the global fileref list */ - glk_data->fileref_list = g_list_prepend(glk_data->fileref_list, f); - f->fileref_list = glk_data->fileref_list; - - return f; -} - -/** - * glk_fileref_create_temp: - * @usage: Bitfield with one or more of the fileusage_ constants. - * @rock: The new fileref's rock value. - * - * Creates a reference to a temporary file. It is always a new file (one which - * does not yet exist). The file (once created) will be somewhere out of the - * player's way. - * - * - * This is why no name is specified; the player will never need to know it. - * - * - * A temporary file should never be used for long-term storage. It may be - * deleted automatically when the program exits, or at some later time, say - * when the machine is turned off or rebooted. You do not have to worry about - * deleting it yourself. - * - * Returns: A new fileref, or #NULL if the fileref creation failed. - */ -frefid_t -glk_fileref_create_temp(glui32 usage, glui32 rock) -{ - /* Get a temp file */ - GError *error = NULL; - gchar *filename = NULL; - gint handle = g_file_open_tmp("glkXXXXXX", &filename, &error); - if(handle == -1) - { - WARNING_S("Error creating temporary file", error->message); - if(filename) - g_free(filename); - return NULL; - } - if(close(handle) == -1) /* There is no g_close() */ - { - IO_WARNING( "Error closing temporary file", filename, g_strerror(errno) ); - if(filename) - g_free(filename); - return NULL; - } - - frefid_t f = fileref_new(filename, rock, usage, filemode_Write); - g_free(filename); - return f; -} - -/** - * glk_fileref_create_by_prompt: - * @usage: Bitfield with one or more of the fileusage_ constants. - * @fmode: File mode, contolling the dialog's behavior. - * @rock: The new fileref's rock value. - * - * Creates a reference to a file by asking the player to locate it. The library - * may simply prompt the player to type a name, or may use a platform-native - * file navigation tool. (The prompt, if any, is inferred from the usage - * argument.) - * - * Chimara - * - * Chimara uses a GtkFileChooserDialog. - * - * - * @fmode must be one of these values: - * - * - * #filemode_Read - * The file must already exist; and the player will be asked - * to select from existing files which match the usage. - * - * - * #filemode_Write - * The file should not exist; if the player selects an - * existing file, he will be warned that it will be replaced. - * - * - * - * #filemode_ReadWrite - * The file may or may not exist; if it already exists, the - * player will be warned that it will be modified. - * - * - * #filemode_WriteAppend - * Same behavior as #filemode_ReadWrite. - * - * - * - * The @fmode argument should generally match the @fmode which will be used to - * open the file. - * - * - * It is possible that the prompt or file tool will have a - * cancel option. If the player chooses this, - * glk_fileref_create_by_prompt() will return %NULL. This is a major reason - * why you should make sure the return value is valid before you use it. - * - * - * Returns: A new fileref, or #NULL if the fileref creation failed or the - * dialog was canceled. - */ -frefid_t -glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, glui32 rock) -{ - /* TODO: Remember current working directory and last used filename - for each usage */ - GtkWidget *chooser; - - gdk_threads_enter(); - - switch(fmode) - { - case filemode_Read: - chooser = gtk_file_chooser_dialog_new("Select a file to open", NULL, - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), - GTK_FILE_CHOOSER_ACTION_OPEN); - break; - case filemode_Write: - chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL, - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), - GTK_FILE_CHOOSER_ACTION_SAVE); - gtk_file_chooser_set_do_overwrite_confirmation( - GTK_FILE_CHOOSER(chooser), TRUE); - break; - case filemode_ReadWrite: - case filemode_WriteAppend: - chooser = gtk_file_chooser_dialog_new("Select a file to save to", NULL, - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_file_chooser_set_action(GTK_FILE_CHOOSER(chooser), - GTK_FILE_CHOOSER_ACTION_SAVE); - break; - default: - ILLEGAL_PARAM("Unknown file mode: %u", fmode); - gdk_threads_leave(); - return NULL; - } - - if(gtk_dialog_run( GTK_DIALOG(chooser) ) != GTK_RESPONSE_ACCEPT) - { - gtk_widget_destroy(chooser); - gdk_threads_leave(); - return NULL; - } - gchar *filename = - gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(chooser) ); - frefid_t f = fileref_new(filename, rock, usage, fmode); - g_free(filename); - gtk_widget_destroy(chooser); - - gdk_threads_leave(); - return f; -} - -/** - * glk_fileref_create_by_name: - * @usage: Bitfield with one or more of the fileusage_ constants. - * @name: A filename. - * @rock: The new fileref's rock value. - * - * This creates a reference to a file with a specific name. The file will be - * in a fixed location relevant to your program, and visible to the player. - * - * - * This usually means in the same directory as your program. - * - * Chimara - * - * In Chimara, the file is created in the current working directory. - * - * - * Since filenames are highly platform-specific, you should use - * glk_fileref_create_by_name() with care. It is legal to pass any string in the - * name argument. However, the library may have to mangle, transform, or - * truncate the string to make it a legal native filename. - * - * - * For example, if you create two filerefs with the names File - * and FILE, they may wind up pointing to the same file; the - * platform may not support case distinctions in file names. Another example: - * on a platform where file type is specified by filename suffix, the library - * will add an appropriate suffix based on the usage; any suffix in the string - * will be overwritten or added to. For that matter, remember that the period - * is not a legal character in Acorn filenames... - * - * - * The most conservative approach is to pass a string of no more than 8 - * characters, consisting entirely of upper-case letters and numbers, starting - * with a letter. You can then be reasonably sure that the resulting filename - * will display all the characters you specify — in some form. - * - * Returns: A new fileref, or %NULL if the fileref creation failed. - */ -frefid_t -glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock) -{ - g_return_val_if_fail(name != NULL && strlen(name) > 0, NULL); - - /* Find out what encoding filenames are in */ - const gchar **charsets; /* Do not free */ - g_get_filename_charsets(&charsets); - - /* Convert name to that encoding */ - GError *error = NULL; - gchar *osname = g_convert(name, -1, charsets[0], "ISO-8859-1", NULL, NULL, - &error); - if(osname == NULL) - { - WARNING_S("Error during latin1->filename conversion", error->message); - return NULL; - } - - /* Do any string-munging here to remove illegal characters from filename. - On ext3, the only illegal characters are '/' and '\0'. TODO: Should this - function be allowed to reference files in other directories, or should we - disallow '/'? */ - - frefid_t f = fileref_new(osname, rock, usage, filemode_ReadWrite); - g_free(osname); - return f; -} - -/** - * glk_fileref_create_from_fileref: - * @usage: Bitfield with one or more of the fileusage_ constants. - * @fref: Fileref to copy. - * @rock: The new fileref's rock value. - * - * This copies an existing file reference @fref, but changes the usage. (The - * original fileref is not modified.) - * - * The use of this function can be tricky. If you change the type of the fileref - * (#fileusage_Data, #fileusage_SavedGame, etc), the new reference may or may - * not point to the same actual disk file. - * - * - * This generally depends on whether the platform uses suffixes to indicate - * file type. - * - * - * If you do this, and open both file references for writing, the results are - * unpredictable. It is safest to change the type of a fileref only if it refers - * to a nonexistent file. - * - * If you change the mode of a fileref (#fileusage_TextMode, - * #fileusage_BinaryMode), but leave the rest of the type unchanged, the new - * fileref will definitely point to the same disk file as the old one. - * - * Obviously, if you write to a file in text mode and then read from it in - * binary mode, the results are platform-dependent. - * - * Returns: A new fileref, or %NULL if the fileref creation failed. - */ -frefid_t -glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock) -{ - VALID_FILEREF(fref, return NULL); - return fileref_new(fref->filename, rock, usage, fref->orig_filemode); -} - -/** - * glk_fileref_destroy: - * @fref: Fileref to destroy. - * - * Destroys a fileref which you have created. This does not - * affect the disk file; it just reclaims the resources allocated by the - * glk_fileref_create... function. - * - * It is legal to destroy a fileref after opening a file with it (while the - * file is still open.) The fileref is only used for the opening operation, - * not for accessing the file stream. - */ -void -glk_fileref_destroy(frefid_t fref) -{ - VALID_FILEREF(fref, return); - - glk_data->fileref_list = g_list_delete_link(glk_data->fileref_list, fref->fileref_list); - if(fref->filename) - g_free(fref->filename); - - fref->magic = MAGIC_FREE; - g_free(fref); -} - -/** - * glk_fileref_delete_file: - * @fref: A refrence to the file to delete. - * - * Deletes the file referred to by @fref. It does not destroy @fref itself. - */ -void -glk_fileref_delete_file(frefid_t fref) -{ - VALID_FILEREF(fref, return); - if( glk_fileref_does_file_exist(fref) ) - if(g_unlink(fref->filename) == -1) - IO_WARNING( "Error deleting file", fref->filename, g_strerror(errno) ); -} - -/** - * glk_fileref_does_file_exist: - * @fref: A fileref to check. - * - * Checks whether the file referred to by @fref exists. - * - * Returns: %TRUE (1) if @fref refers to an existing file, and %FALSE (0) if - * not. - */ -glui32 -glk_fileref_does_file_exist(frefid_t fref) -{ - VALID_FILEREF(fref, return 0); - if( g_file_test(fref->filename, G_FILE_TEST_EXISTS) ) - return 1; - return 0; -} - diff --git a/src/fileref.h b/src/fileref.h deleted file mode 100644 index cfbdcca..0000000 --- a/src/fileref.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef FILEREF_H -#define FILEREF_H - -#include -#include "glk.h" - -/** - * glk_fileref_struct: - * - * This is an opaque structure (see - * Opaque Structures and should not be accessed directly. - */ -struct glk_fileref_struct -{ - /*< private >*/ - glui32 magic, rock; - /* Pointer to the list node in the global fileref list that contains this - fileref */ - GList* fileref_list; - /* Fileref parameters */ - gchar *filename; /* Always stored in the default filename encoding, not - UTF8 or Latin-1 */ - glui32 orig_filemode; /* Used to check if the user gets a fileref in read - mode and then tries to open it in write mode */ - glui32 usage; -}; - -#endif diff --git a/src/first.c b/src/first.c deleted file mode 100644 index fdb97a4..0000000 --- a/src/first.c +++ /dev/null @@ -1,611 +0,0 @@ -#include "glk.h" - -/* model.c: Model program for Glk API, version 0.5. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - This program is in the public domain. -*/ - -/* This is a simple model of a text adventure which uses the Glk API. - It shows how to input a line of text, display results, maintain a - status window, write to a transcript file, and so on. */ - -/* This is the cleanest possible form of a Glk program. It includes only - "glk.h", and doesn't call any functions outside Glk at all. We even - define our own str_eq() and str_len(), rather than relying on the - standard libraries. */ - -/* We also define our own TRUE and FALSE and NULL. */ -#ifndef TRUE -#define TRUE 1 -#endif -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef NULL -#define NULL 0 -#endif - -/* The story, status, and quote windows. */ -static winid_t mainwin = NULL; -static winid_t statuswin = NULL; -static winid_t quotewin = NULL; - -/* A file reference for the transcript file. */ -static frefid_t scriptref = NULL; -/* A stream for the transcript file, when it's open. */ -static strid_t scriptstr = NULL; - -/* Your location. This determines what appears in the status line. */ -static int current_room; - -/* A flag indicating whether you should look around. */ -static int need_look; - -/* Forward declarations */ -void glk_main(void); - -static void draw_statuswin(void); -static int yes_or_no(void); - -static int str_eq(char *s1, char *s2); -static int str_len(char *s1); - -static void verb_help(void); -static void verb_jump(void); -static void verb_yada(void); -static void verb_quote(void); -static void verb_move(void); -static void verb_quit(void); -static void verb_script(void); -static void verb_unscript(void); -static void verb_save(void); -static void verb_restore(void); -static void verb_alert(void); -static void verb_notice(void); - -/* The glk_main() function is called by the Glk system; it's the main entry - point for your program. */ -void glk_main(void) -{ - /* Open the main window. */ - mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); - if (!mainwin) { - /* It's possible that the main window failed to open. There's - nothing we can do without it, so exit. */ - return; - } - - /* Set the current output stream to print to it. */ - glk_set_window(mainwin); - - /* Open a second window: a text grid, above the main window, three lines - high. It is possible that this will fail also, but we accept that. */ - statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed, - 3, wintype_TextGrid, 0); - - /* The third window, quotewin, isn't opened immediately. We'll do - that in verb_quote(). */ - - glk_put_string("Model Glk Program\nAn Interactive Model Glk Program\n"); - glk_put_string("By Andrew Plotkin.\nRelease 7.\n"); - glk_put_string("Type \"help\" for a list of commands.\n"); - - current_room = 0; /* Set initial location. */ - need_look = TRUE; - - while (1) { - char commandbuf[256]; - char *cx, *cmd; - int gotline, len; - event_t ev; - - draw_statuswin(); - - if (need_look) { - need_look = FALSE; - glk_put_string("\n"); - glk_set_style(style_Subheader); - if (current_room == 0) - glk_put_string("The Room\n"); - else - glk_put_string("A Different Room\n"); - glk_set_style(style_Normal); - glk_put_string("You're in a room of some sort.\n"); - } - - glk_put_string("\n>"); - /* We request up to 255 characters. The buffer can hold 256, but we - are going to stick a null character at the end, so we have to - leave room for that. Note that the Glk library does *not* - put on that null character. */ - glk_request_line_event(mainwin, commandbuf, 255, 0); - - gotline = FALSE; - while (!gotline) { - - /* Grab an event. */ - glk_select(&ev); - - switch (ev.type) { - - case evtype_LineInput: - if (ev.win == mainwin) { - gotline = TRUE; - /* Really the event can *only* be from mainwin, - because we never request line input from the - status window. But we do a paranoia test, - because commandbuf is only filled if the line - event comes from the mainwin request. If the - line event comes from anywhere else, we ignore - it. */ - } - break; - - case evtype_Arrange: - /* Windows have changed size, so we have to redraw the - status window. */ - draw_statuswin(); - break; - } - } - - /* commandbuf now contains a line of input from the main window. - You would now run your parser and do something with it. */ - - /* First, if there's a blockquote window open, let's close it. - This ensures that quotes remain visible for exactly one - command. */ - if (quotewin) { - glk_window_close(quotewin, NULL); - quotewin = 0; - } - - /* The line we have received in commandbuf is not null-terminated. - We handle that first. */ - len = ev.val1; /* Will be between 0 and 255, inclusive. */ - commandbuf[len] = '\0'; - - /* Then squash to lower-case. */ - for (cx = commandbuf; *cx; cx++) { - *cx = glk_char_to_lower(*cx); - } - - /* Then trim whitespace before and after. */ - - for (cx = commandbuf; *cx == ' '; cx++) { }; - - cmd = cx; - - for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { }; - *(cx+1) = '\0'; - - /* cmd now points to a nice null-terminated string. We'll do the - simplest possible parsing. */ - if (str_eq(cmd, "")) { - glk_put_string("Excuse me?\n"); - } - else if (str_eq(cmd, "help")) { - verb_help(); - } - else if (str_eq(cmd, "move")) { - verb_move(); - } - else if (str_eq(cmd, "jump")) { - verb_jump(); - } - else if (str_eq(cmd, "yada")) { - verb_yada(); - } - else if (str_eq(cmd, "quote")) { - verb_quote(); - } - else if (str_eq(cmd, "quit")) { - verb_quit(); - } - else if (str_eq(cmd, "save")) { - verb_save(); - } - else if (str_eq(cmd, "restore")) { - verb_restore(); - } - else if (str_eq(cmd, "script")) { - verb_script(); - } - else if (str_eq(cmd, "unscript")) { - verb_unscript(); - } - else if (str_eq(cmd, "alert")) { - verb_alert(); - } - else if (str_eq(cmd, "notice")) { - verb_notice(); - } - else { - glk_put_string("I don't understand the command \""); - glk_put_string(cmd); - glk_put_string("\".\n"); - } - } -} - -static void draw_statuswin(void) -{ - char *roomname; - glui32 width, height; - - if (!statuswin) { - /* It is possible that the window was not successfully - created. If that's the case, don't try to draw it. */ - return; - } - - if (current_room == 0) - roomname = "The Room"; - else - roomname = "A Different Room"; - - glk_set_window(statuswin); - glk_window_clear(statuswin); - - glk_window_get_size(statuswin, &width, &height); - - /* Print the room name, centered. */ - glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1); - glk_put_string(roomname); - - /* Draw a decorative compass rose in the upper right. */ - glk_window_move_cursor(statuswin, width - 3, 0); - glk_put_string("\\|/"); - glk_window_move_cursor(statuswin, width - 3, 1); - glk_put_string("-*-"); - glk_window_move_cursor(statuswin, width - 3, 2); - glk_put_string("/|\\"); - - glk_set_window(mainwin); -} - -static int yes_or_no(void) -{ - char commandbuf[256]; - char *cx; - int gotline, len; - event_t ev; - - draw_statuswin(); - - /* This loop is identical to the main command loop in glk_main(). */ - - while (1) { - glk_request_line_event(mainwin, commandbuf, 255, 0); - - gotline = FALSE; - while (!gotline) { - - glk_select(&ev); - - switch (ev.type) { - case evtype_LineInput: - if (ev.win == mainwin) { - gotline = TRUE; - } - break; - - case evtype_Arrange: - draw_statuswin(); - break; - } - } - - len = ev.val1; - commandbuf[len] = '\0'; - for (cx = commandbuf; *cx == ' '; cx++) { }; - - if (*cx == 'y' || *cx == 'Y') - return TRUE; - if (*cx == 'n' || *cx == 'N') - return FALSE; - - glk_put_string("Please enter \"yes\" or \"no\": "); - } - -} - -static void verb_help(void) -{ - glk_put_string("This model only understands the following commands:\n"); - glk_put_string("HELP: Display this list.\n"); - glk_put_string("JUMP: A verb which just prints some text.\n"); - glk_put_string("YADA: A verb which prints a very long stream of text.\n"); - glk_put_string("MOVE: A verb which prints some text, and also changes the status line display.\n"); - glk_put_string("QUOTE: A verb which displays a block quote in a temporary third window.\n"); - glk_put_string("SCRIPT: Turn on transcripting, so that output will be echoed to a text file.\n"); - glk_put_string("UNSCRIPT: Turn off transcripting.\n"); - glk_put_string("SAVE: Write fake data to a save file.\n"); - glk_put_string("RESTORE: Read it back in.\n"); - glk_put_string("ALERT: Print a frightful message.\n"); - glk_put_string("NOTICE: Print an interesting message.\n"); - glk_put_string("QUIT: Quit and exit.\n"); -} - -static void verb_jump(void) -{ - glk_put_string("You jump on the fruit, spotlessly.\n"); -} - -static void verb_yada(void) -{ - /* This is a goofy (and overly ornate) way to print a long paragraph. - It just shows off line wrapping in the Glk implementation. */ - #define NUMWORDS (13) - static char *wordcaplist[NUMWORDS] = { - "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po", - "Ha", "Ni", "Na" - }; - static char *wordlist[NUMWORDS] = { - "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble", - "gazoon", "ting", "floo", "zonk", "loof", "lob", - }; - static int wcount1 = 0; - static int wcount2 = 0; - static int wstep = 1; - static int jx = 0; - int ix; - int first = TRUE; - - for (ix=0; ix<85; ix++) { - if (ix > 0) { - glk_put_string(" "); - } - - if (first) { - glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]); - first = FALSE; - } - - glk_put_string(wordlist[jx]); - jx = (jx + wstep) % NUMWORDS; - wcount1++; - if (wcount1 >= NUMWORDS) { - wcount1 = 0; - wstep++; - wcount2++; - if (wcount2 >= NUMWORDS-2) { - wcount2 = 0; - wstep = 1; - } - } - - if ((ix % 17) == 16) { - glk_put_string("."); - first = TRUE; - } - } - - glk_put_char('\n'); -} - -static void verb_quote(void) -{ - glk_put_string("Someone quotes some poetry.\n"); - - /* Open a third window, or clear it if it's already open. Actually, - since quotewin is closed right after line input, we know it - can't be open. But better safe, etc. */ - if (!quotewin) { - /* A five-line window above the main window, fixed size. */ - quotewin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed, - 5, wintype_TextBuffer, 0); - if (!quotewin) { - /* It's possible the quotewin couldn't be opened. In that - case, just give up. */ - return; - } - } - else { - glk_window_clear(quotewin); - } - - /* Print some quote. */ - glk_set_window(quotewin); - glk_set_style(style_BlockQuote); - glk_put_string("Tomorrow probably never rose "); - glk_put_string("or set\n Or went out and bought cheese, or anything like that\n" - "And anyway, what light through yonder quote box breaks\n" - "Handle to my hand?\n"); - glk_put_string(" -- Fred\n"); - - glk_set_window(mainwin); -} - -static void verb_move(void) -{ - current_room = (current_room+1) % 2; - need_look = TRUE; - - glk_put_string("You "); - glk_set_style(style_Emphasized); - glk_put_string("walk"); - glk_set_style(style_Normal); - glk_put_string(" for a while.\n"); -} - -static void verb_quit(void) -{ - glk_put_string("Are you sure you want to quit? "); - if (yes_or_no()) { - glk_put_string("Thanks for playing.\n"); - glk_exit(); - /* glk_exit() actually stops the process; it does not return. */ - } -} - -static void verb_script(void) -{ - if (scriptstr) { - glk_put_string("Scripting is already on.\n"); - return; - } - - /* If we've turned on scripting before, use the same file reference; - otherwise, prompt the player for a file. */ - if (!scriptref) { - scriptref = glk_fileref_create_by_prompt( - fileusage_Transcript | fileusage_TextMode, - filemode_WriteAppend, 0); - if (!scriptref) { - glk_put_string("Unable to place script file.\n"); - return; - } - } - - /* Open the file. */ - scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0); - if (!scriptstr) { - glk_put_string("Unable to write to script file.\n"); - return; - } - glk_put_string("Scripting on.\n"); - glk_window_set_echo_stream(mainwin, scriptstr); - glk_put_string_stream(scriptstr, - "This is the beginning of a transcript.\n"); -} - -static void verb_unscript(void) -{ - if (!scriptstr) { - glk_put_string("Scripting is already off.\n"); - return; - } - - /* Close the file. */ - glk_put_string_stream(scriptstr, - "This is the end of a transcript.\n\n"); - glk_stream_close(scriptstr, NULL); - glk_put_string("Scripting off.\n"); - scriptstr = NULL; -} - -static void verb_save(void) -{ - int ix; - frefid_t saveref; - strid_t savestr; - - saveref = glk_fileref_create_by_prompt( - fileusage_SavedGame | fileusage_BinaryMode, - filemode_Write, 0); - if (!saveref) { - glk_put_string("Unable to place save file.\n"); - return; - } - - savestr = glk_stream_open_file(saveref, filemode_Write, 0); - if (!savestr) { - glk_put_string("Unable to write to save file.\n"); - glk_fileref_destroy(saveref); - return; - } - - glk_fileref_destroy(saveref); /* We're done with the file ref now. */ - - /* Write some binary data. */ - for (ix=0; ix<256; ix++) { - glk_put_char_stream(savestr, (unsigned char)ix); - } - - glk_stream_close(savestr, NULL); - - glk_put_string("Game saved.\n"); -} - -static void verb_restore(void) -{ - int ix; - int err; - glui32 ch; - frefid_t saveref; - strid_t savestr; - - saveref = glk_fileref_create_by_prompt( - fileusage_SavedGame | fileusage_BinaryMode, - filemode_Read, 0); - if (!saveref) { - glk_put_string("Unable to find save file.\n"); - return; - } - - savestr = glk_stream_open_file(saveref, filemode_Read, 0); - if (!savestr) { - glk_put_string("Unable to read from save file.\n"); - glk_fileref_destroy(saveref); - return; - } - - glk_fileref_destroy(saveref); /* We're done with the file ref now. */ - - /* Read some binary data. */ - err = FALSE; - - for (ix=0; ix<256; ix++) { - ch = glk_get_char_stream(savestr); - if (ch == (glui32)(-1)) { - glk_put_string("Unexpected end of file.\n"); - err = TRUE; - break; - } - if (ch != (glui32)ix) { - glk_put_string("This does not appear to be a valid saved game.\n"); - err = TRUE; - break; - } - } - - glk_stream_close(savestr, NULL); - - if (err) { - glk_put_string("Failed.\n"); - return; - } - - glk_put_string("Game restored.\n"); -} - -void verb_alert() -{ - glk_set_style(style_Alert); - glk_put_string("O noes!\n"); - glk_set_style(style_Normal); -} - -void verb_notice() -{ - glk_set_style(style_Note); - glk_put_string("The answer... is 42!\n"); - glk_set_style(style_Normal); -} - - -/* simple string length test */ -static int str_len(char *s1) -{ - int len; - for (len = 0; *s1; s1++) - len++; - return len; -} - -/* simple string comparison test */ -static int str_eq(char *s1, char *s2) -{ - for (; *s1 && *s2; s1++, s2++) { - if (*s1 != *s2) - return FALSE; - } - - if (*s1 || *s2) - return FALSE; - else - return TRUE; -} - diff --git a/src/gestalt.c b/src/gestalt.c deleted file mode 100644 index 398aede..0000000 --- a/src/gestalt.c +++ /dev/null @@ -1,123 +0,0 @@ -#include /* Surprisingly, the only symbol needed is NULL */ -#include "glk.h" - -/* Version of the Glk specification implemented by this library */ -#define MAJOR_VERSION 0 -#define MINOR_VERSION 7 -#define SUB_VERSION 0 - -/** - * glk_gestalt: - * @sel: A selector, representing which capability to request information - * about. - * @val: Extra information, depending on the value of @sel. - * - * Calls the gestalt system to request information about selector @sel, without - * passing an array to store extra information in (see glk_gestalt_ext()). - * - * Returns: an integer, depending on what selector was called. - */ -glui32 -glk_gestalt(glui32 sel, glui32 val) -{ - return glk_gestalt_ext(sel, val, NULL, 0); -} - -/** - * glk_gestalt_ext: - * @sel: A selector, representing which capability to request information - * about. - * @val: Extra information, depending on the value of @sel. - * @arr: Location of an array to store extra information in, or %NULL. - * @arrlen: Length of @arr, or 0 if @arr is %NULL. - * - * Calls the gestalt system to request information about the capabilities of the - * API. The selector @sel tells which capability you are requesting information - * about; the other three arguments are additional information, which may or may - * not be meaningful. The @arr and @arrlen arguments of glk_gestalt_ext() are - * always optional; you may always pass %NULL and 0, if you do not want whatever - * information they represent. glk_gestalt() is simply a shortcut for this; - * #glk_gestalt(x, y) is exactly the same as - * #glk_gestalt_ext(x, y, %NULL, 0). - * - * The critical point is that if the Glk library has never heard of the selector - * @sel, it will return 0. It is always safe to call - * #glk_gestalt(x, y) (or #glk_gestalt_ext(x, y, %NULL, - * 0)). Even if you are using an old library, which was compiled before - * the given capability was imagined, you can test for the capability by calling - * glk_gestalt(); the library will correctly indicate that it does not support - * it, by returning 0. - * - * (It is also safe to call #glk_gestalt_ext(x, y, z, zlen) for an - * unknown selector x, where z is not %NULL, as long - * as z points at an array of at least zlen elements. - * The selector will be careful not to write beyond that point in the array, if - * it writes to the array at all.) - * - * (If a selector does not use the second argument, you should always pass 0; do - * not assume that the second argument is simply ignored. This is because the - * selector may be extended in the future. You will continue to get the current - * behavior if you pass 0 as the second argument, but other values may produce - * other behavior.) - * - * Returns: an integer, depending on what selector was called. - */ -glui32 -glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen) -{ - switch(sel) - { - /* Version number */ - case gestalt_Version: - return (MAJOR_VERSION << 16) + (MINOR_VERSION << 8) + SUB_VERSION; - - /* Which characters can the player type in line input? */ - case gestalt_LineInput: - /* Does not accept control chars */ - if( val < 32 || (val >= 127 && val <= 159) ) - return 0; - return 1; - - /* Which characters can the player type in char input? */ - case gestalt_CharInput: - /* Does not accept control chars or unknown */ - if( val < 32 || (val >= 127 && val <= 159) || val == keycode_Unknown ) - return 0; - return 1; - - /* Which characters can we print? */ - case gestalt_CharOutput: - /* All characters are printed as one character, in any case */ - if(arr && arrlen > 0) - *arr = 1; - /* Cannot print control chars except \n, or chars > 255 */ - if( (val < 32 && val != 10) || (val >= 127 && val <= 159) || (val > 255) ) - return gestalt_CharOutput_CannotPrint; - /* Can print all other Latin-1 characters */ - return gestalt_CharOutput_ExactPrint; - - /* Unicode capabilities present */ - case gestalt_Unicode: - return 1; - - /* Timer capabilities present */ - case gestalt_Timer: - return 1; - - /* Unsupported capabilities */ - case gestalt_MouseInput: - case gestalt_Graphics: - case gestalt_DrawImage: - case gestalt_Sound: - case gestalt_SoundVolume: - case gestalt_SoundNotify: - case gestalt_Hyperlinks: - case gestalt_HyperlinkInput: - case gestalt_SoundMusic: - case gestalt_GraphicsTransparency: - /* Selector not supported */ - default: - return 0; - } -} - diff --git a/src/gi_blorb.c b/src/gi_blorb.c deleted file mode 100644 index 329280d..0000000 --- a/src/gi_blorb.c +++ /dev/null @@ -1,603 +0,0 @@ -/* gi_blorb.c: Blorb library layer for Glk API. - gi_blorb version 1.4. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - - This file is copyright 1998-2000 by Andrew Plotkin. You may copy, - distribute, and incorporate it into your own programs, by any means - and under any conditions, as long as you do not modify it. You may - also modify this file, incorporate it into your own programs, - and distribute the modified version, as long as you retain a notice - in your program or documentation which mentions my name and the URL - shown above. -*/ - -#include "glk.h" -#include "gi_blorb.h" - -#ifndef NULL -#define NULL 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif -#ifndef FALSE -#define FALSE 0 -#endif - -/* The magic macro of endian conversion. */ - -#define giblorb_native4(v) \ - ( (((glui32)((v)[3]) ) & 0x000000ff) \ - | (((glui32)((v)[2]) << 8) & 0x0000ff00) \ - | (((glui32)((v)[1]) << 16) & 0x00ff0000) \ - | (((glui32)((v)[0]) << 24) & 0xff000000)) - -/* More four-byte constants. */ - -#define giblorb_ID_FORM (giblorb_make_id('F', 'O', 'R', 'M')) -#define giblorb_ID_IFRS (giblorb_make_id('I', 'F', 'R', 'S')) -#define giblorb_ID_RIdx (giblorb_make_id('R', 'I', 'd', 'x')) - -/* giblorb_chunkdesc_t: Describes one chunk of the Blorb file. */ -typedef struct giblorb_chunkdesc_struct { - glui32 type; - glui32 len; - glui32 startpos; /* start of chunk header */ - glui32 datpos; /* start of data (either startpos or startpos+8) */ - - void *ptr; /* pointer to malloc'd data, if loaded */ - int auxdatnum; /* entry in the auxsound/auxpict array; -1 if none. - This only applies to chunks that represent resources; */ - -} giblorb_chunkdesc_t; - -/* giblorb_resdesc_t: Describes one resource in the Blorb file. */ -typedef struct giblorb_resdesc_struct { - glui32 usage; - glui32 resnum; - glui32 chunknum; -} giblorb_resdesc_t; - -/* giblorb_map_t: Holds the complete description of an open Blorb file. */ -struct giblorb_map_struct { - glui32 inited; /* holds giblorb_Inited_Magic if the map structure is - valid */ - strid_t file; - - int numchunks; - giblorb_chunkdesc_t *chunks; /* list of chunk descriptors */ - - int numresources; - giblorb_resdesc_t *resources; /* list of resource descriptors */ - giblorb_resdesc_t **ressorted; /* list of pointers to descriptors - in map->resources -- sorted by usage and resource number. */ -}; - -#define giblorb_Inited_Magic (0xB7012BED) - -/* Static variables. */ - -static int lib_inited = FALSE; - -static giblorb_err_t giblorb_initialize(void); -static giblorb_err_t giblorb_initialize_map(giblorb_map_t *map); -static void giblorb_qsort(giblorb_resdesc_t **list, int len); -static giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample, - giblorb_resdesc_t **list, int len); -static void *giblorb_malloc(glui32 len); -static void *giblorb_realloc(void *ptr, glui32 len); -static void giblorb_free(void *ptr); - -static giblorb_err_t giblorb_initialize() -{ - return giblorb_err_None; -} - -giblorb_err_t giblorb_create_map(strid_t file, giblorb_map_t **newmap) -{ - giblorb_err_t err; - giblorb_map_t *map; - glui32 readlen; - glui32 nextpos, totallength; - giblorb_chunkdesc_t *chunks; - int chunks_size, numchunks; - char buffer[16]; - - *newmap = NULL; - - if (!lib_inited) { - err = giblorb_initialize(); - if (err) - return err; - lib_inited = TRUE; - } - - /* First, chew through the file and index the chunks. */ - - glk_stream_set_position(file, 0, seekmode_Start); - - readlen = glk_get_buffer_stream(file, buffer, 12); - if (readlen != 12) - return giblorb_err_Read; - - if (giblorb_native4(buffer+0) != giblorb_ID_FORM) - return giblorb_err_Format; - if (giblorb_native4(buffer+8) != giblorb_ID_IFRS) - return giblorb_err_Format; - - totallength = giblorb_native4(buffer+4) + 8; - nextpos = 12; - - chunks_size = 8; - numchunks = 0; - chunks = (giblorb_chunkdesc_t *)giblorb_malloc(sizeof(giblorb_chunkdesc_t) - * chunks_size); - - while (nextpos < totallength) { - glui32 type, len; - int chunum; - giblorb_chunkdesc_t *chu; - - glk_stream_set_position(file, nextpos, seekmode_Start); - - readlen = glk_get_buffer_stream(file, buffer, 8); - if (readlen != 8) - return giblorb_err_Read; - - type = giblorb_native4(buffer+0); - len = giblorb_native4(buffer+4); - - if (numchunks >= chunks_size) { - chunks_size *= 2; - chunks = (giblorb_chunkdesc_t *)giblorb_realloc(chunks, - sizeof(giblorb_chunkdesc_t) * chunks_size); - } - - chunum = numchunks; - chu = &(chunks[chunum]); - numchunks++; - - chu->type = type; - chu->startpos = nextpos; - if (type == giblorb_ID_FORM) { - chu->datpos = nextpos; - chu->len = len+8; - } - else { - chu->datpos = nextpos+8; - chu->len = len; - } - chu->ptr = NULL; - chu->auxdatnum = -1; - - nextpos = nextpos + len + 8; - if (nextpos & 1) - nextpos++; - - if (nextpos > totallength) - return giblorb_err_Format; - } - - /* The basic IFF structure seems to be ok, and we have a list of - chunks. Now we allocate the map structure itself. */ - - map = (giblorb_map_t *)giblorb_malloc(sizeof(giblorb_map_t)); - if (!map) { - giblorb_free(chunks); - return giblorb_err_Alloc; - } - - map->inited = giblorb_Inited_Magic; - map->file = file; - map->chunks = chunks; - map->numchunks = numchunks; - map->resources = NULL; - map->ressorted = NULL; - map->numresources = 0; - /*map->releasenum = 0; - map->zheader = NULL; - map->resolution = NULL; - map->palettechunk = -1; - map->palette = NULL; - map->auxsound = NULL; - map->auxpict = NULL;*/ - - /* Now we do everything else involved in loading the Blorb file, - such as building resource lists. */ - - err = giblorb_initialize_map(map); - if (err) { - giblorb_destroy_map(map); - return err; - } - - *newmap = map; - return giblorb_err_None; -} - -static giblorb_err_t giblorb_initialize_map(giblorb_map_t *map) -{ - /* It is important that the map structure be kept valid during this - function. If this returns an error, giblorb_destroy_map() will - be called. */ - - int ix, jx; - giblorb_result_t chunkres; - giblorb_err_t err; - char *ptr; - glui32 len; - glui32 val; - glui32 numres; - int gotindex = FALSE; - - for (ix=0; ixnumchunks; ix++) { - giblorb_chunkdesc_t *chu = &map->chunks[ix]; - - switch (chu->type) { - - case giblorb_ID_RIdx: - /* Resource index chunk: build the resource list and - sort it. */ - - if (gotindex) - return giblorb_err_Format; /* duplicate index chunk */ - err = giblorb_load_chunk_by_number(map, giblorb_method_Memory, - &chunkres, ix); - if (err) - return err; - - ptr = chunkres.data.ptr; - len = chunkres.length; - numres = giblorb_native4(ptr+0); - - if (numres) { - int ix2; - giblorb_resdesc_t *resources; - giblorb_resdesc_t **ressorted; - - if (len != numres*12+4) - return giblorb_err_Format; /* bad length field */ - - resources = (giblorb_resdesc_t *)giblorb_malloc(numres - * sizeof(giblorb_resdesc_t)); - ressorted = (giblorb_resdesc_t **)giblorb_malloc(numres - * sizeof(giblorb_resdesc_t *)); - if (!ressorted || !resources) - return giblorb_err_Alloc; - - ix2 = 0; - for (jx=0; jxusage = giblorb_native4(ptr+jx*12+4); - res->resnum = giblorb_native4(ptr+jx*12+8); - respos = giblorb_native4(ptr+jx*12+12); - - while (ix2 < map->numchunks - && map->chunks[ix2].startpos < respos) - ix2++; - - if (ix2 >= map->numchunks - || map->chunks[ix2].startpos != respos) - return giblorb_err_Format; /* start pos does - not match a real chunk */ - - res->chunknum = ix2; - - ressorted[jx] = res; - } - - /* Sort a resource list (actually a list of pointers to - structures in map->resources.) This makes it easy - to find resources by usage and resource number. */ - giblorb_qsort(ressorted, numres); - - map->numresources = numres; - map->resources = resources; - map->ressorted = ressorted; - } - - giblorb_unload_chunk(map, ix); - gotindex = TRUE; - break; - - } - } - - return giblorb_err_None; -} - -giblorb_err_t giblorb_destroy_map(giblorb_map_t *map) -{ - int ix; - - if (!map || !map->chunks || map->inited != giblorb_Inited_Magic) - return giblorb_err_NotAMap; - - for (ix=0; ixnumchunks; ix++) { - giblorb_chunkdesc_t *chu = &(map->chunks[ix]); - if (chu->ptr) { - giblorb_free(chu->ptr); - chu->ptr = NULL; - } - } - - if (map->chunks) { - giblorb_free(map->chunks); - map->chunks = NULL; - } - - map->numchunks = 0; - - if (map->resources) { - giblorb_free(map->resources); - map->resources = NULL; - } - - if (map->ressorted) { - giblorb_free(map->ressorted); - map->ressorted = NULL; - } - - map->numresources = 0; - - map->file = NULL; - map->inited = 0; - - giblorb_free(map); - - return giblorb_err_None; -} - -/* Chunk-handling functions. */ - -giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 type, - glui32 count) -{ - int ix; - - for (ix=0; ix < map->numchunks; ix++) { - if (map->chunks[ix].type == type) { - if (count == 0) - break; - count--; - } - } - - if (ix >= map->numchunks) { - return giblorb_err_NotFound; - } - - return giblorb_load_chunk_by_number(map, method, res, ix); -} - -giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 chunknum) -{ - giblorb_chunkdesc_t *chu; - - if (chunknum < 0 || chunknum >= map->numchunks) - return giblorb_err_NotFound; - - chu = &(map->chunks[chunknum]); - - switch (method) { - - case giblorb_method_DontLoad: - /* do nothing */ - break; - - case giblorb_method_FilePos: - res->data.startpos = chu->datpos; - break; - - case giblorb_method_Memory: - if (!chu->ptr) { - giblorb_err_t err; - glui32 readlen; - void *dat = giblorb_malloc(chu->len); - - if (!dat) - return giblorb_err_Alloc; - - glk_stream_set_position(map->file, chu->datpos, - seekmode_Start); - - readlen = glk_get_buffer_stream(map->file, dat, - chu->len); - if (readlen != chu->len) - return giblorb_err_Read; - - chu->ptr = dat; - } - res->data.ptr = chu->ptr; - break; - } - - res->chunknum = chunknum; - res->length = chu->len; - res->chunktype = chu->type; - - return giblorb_err_None; -} - -giblorb_err_t giblorb_load_resource(giblorb_map_t *map, glui32 method, - giblorb_result_t *res, glui32 usage, glui32 resnum) -{ - giblorb_resdesc_t sample; - giblorb_resdesc_t *found; - - sample.usage = usage; - sample.resnum = resnum; - - found = giblorb_bsearch(&sample, map->ressorted, map->numresources); - - if (!found) - return giblorb_err_NotFound; - - return giblorb_load_chunk_by_number(map, method, res, found->chunknum); -} - -giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, glui32 chunknum) -{ - giblorb_chunkdesc_t *chu; - - if (chunknum < 0 || chunknum >= map->numchunks) - return giblorb_err_NotFound; - - chu = &(map->chunks[chunknum]); - - if (chu->ptr) { - giblorb_free(chu->ptr); - chu->ptr = NULL; - } - - return giblorb_err_None; -} - -giblorb_err_t giblorb_count_resources(giblorb_map_t *map, glui32 usage, - glui32 *num, glui32 *min, glui32 *max) -{ - int ix; - int count; - glui32 val; - glui32 minval, maxval; - - count = 0; - minval = 0; - maxval = 0; - - for (ix=0; ixnumresources; ix++) { - if (map->resources[ix].usage == usage) { - val = map->resources[ix].resnum; - if (count == 0) { - count++; - minval = val; - maxval = val; - } - else { - count++; - if (val < minval) - minval = val; - if (val > maxval) - maxval = val; - } - } - } - - if (num) - *num = count; - if (min) - *min = minval; - if (max) - *max = maxval; - - return giblorb_err_None; -} - -/* Sorting and searching. */ - -static int sortsplot(giblorb_resdesc_t *v1, giblorb_resdesc_t *v2) -{ - if (v1->usage < v2->usage) - return -1; - if (v1->usage > v2->usage) - return 1; - if (v1->resnum < v2->resnum) - return -1; - if (v1->resnum > v2->resnum) - return 1; - return 0; -} - -static void giblorb_qsort(giblorb_resdesc_t **list, int len) -{ - int ix, jx, res, val; - giblorb_resdesc_t *tmpptr, *pivot; - - if (len < 6) { - /* The list is short enough for a bubble-sort. */ - for (jx=len-1; jx>0; jx--) { - for (ix=0; ix 0) { - tmpptr = list[ix]; - list[ix] = list[ix+1]; - list[ix+1] = tmpptr; - } - } - } - } - else { - /* Split the list. */ - pivot = list[len/2]; - ix=0; - jx=len; - while (1) { - while (ix < jx-1 && sortsplot(list[ix], pivot) < 0) - ix++; - while (ix < jx-1 && sortsplot(list[jx-1], pivot) > 0) - jx--; - if (ix >= jx-1) - break; - tmpptr = list[ix]; - list[ix] = list[jx-1]; - list[jx-1] = tmpptr; - } - ix++; - /* Sort the halves. */ - giblorb_qsort(list+0, ix); - giblorb_qsort(list+ix, len-ix); - } -} - -giblorb_resdesc_t *giblorb_bsearch(giblorb_resdesc_t *sample, - giblorb_resdesc_t **list, int len) -{ - int top, bot, val, res; - - bot = 0; - top = len; - - while (bot < top) { - val = (top+bot) / 2; - res = sortsplot(list[val], sample); - if (res == 0) - return list[val]; - if (res < 0) { - bot = val+1; - } - else { - top = val; - } - } - - return NULL; -} - - -/* Boring utility functions. If your platform doesn't support ANSI - malloc(), feel free to edit these however you like. */ - -#include /* The OS-native header file -- you can edit - this too. */ - -static void *giblorb_malloc(glui32 len) -{ - return malloc(len); -} - -static void *giblorb_realloc(void *ptr, glui32 len) -{ - return realloc(ptr, len); -} - -static void giblorb_free(void *ptr) -{ - free(ptr); -} - - diff --git a/src/gi_blorb.h b/src/gi_blorb.h deleted file mode 100644 index ddec1bd..0000000 --- a/src/gi_blorb.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef _GI_BLORB_H -#define _GI_BLORB_H - -/* gi_blorb.h: Blorb library layer for Glk API. - gi_blorb version 1.4. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - - This file is copyright 1998-2000 by Andrew Plotkin. You may copy, - distribute, and incorporate it into your own programs, by any means - and under any conditions, as long as you do not modify it. You may - also modify this file, incorporate it into your own programs, - and distribute the modified version, as long as you retain a notice - in your program or documentation which mentions my name and the URL - shown above. -*/ - -/* Error type and error codes */ -typedef glui32 giblorb_err_t; -#define giblorb_err_None (0) -#define giblorb_err_CompileTime (1) -#define giblorb_err_Alloc (2) -#define giblorb_err_Read (3) -#define giblorb_err_NotAMap (4) -#define giblorb_err_Format (5) -#define giblorb_err_NotFound (6) - -/* Methods for loading a chunk */ -#define giblorb_method_DontLoad (0) -#define giblorb_method_Memory (1) -#define giblorb_method_FilePos (2) - -/* Four-byte constants */ - -#define giblorb_make_id(c1, c2, c3, c4) \ - (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4)) - -#define giblorb_ID_Snd (giblorb_make_id('S', 'n', 'd', ' ')) -#define giblorb_ID_Exec (giblorb_make_id('E', 'x', 'e', 'c')) -#define giblorb_ID_Pict (giblorb_make_id('P', 'i', 'c', 't')) -#define giblorb_ID_Copyright (giblorb_make_id('(', 'c', ')', ' ')) -#define giblorb_ID_AUTH (giblorb_make_id('A', 'U', 'T', 'H')) -#define giblorb_ID_ANNO (giblorb_make_id('A', 'N', 'N', 'O')) - -/* giblorb_map_t: Holds the complete description of an open Blorb - file. This type is opaque for normal interpreter use. */ -typedef struct giblorb_map_struct giblorb_map_t; - -/* giblorb_result_t: Result when you try to load a chunk. */ -typedef struct giblorb_result_struct { - glui32 chunknum; /* The chunk number (for use in - giblorb_unload_chunk(), etc.) */ - union { - void *ptr; /* A pointer to the data (if you used - giblorb_method_Memory) */ - glui32 startpos; /* The position in the file (if you - used giblorb_method_FilePos) */ - } data; - glui32 length; /* The length of the data */ - glui32 chunktype; /* The type of the chunk. */ -} giblorb_result_t; - -extern giblorb_err_t giblorb_create_map(strid_t file, - giblorb_map_t **newmap); -extern giblorb_err_t giblorb_destroy_map(giblorb_map_t *map); - -extern giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 chunktype, - glui32 count); -extern giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 chunknum); -extern giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, - glui32 chunknum); - -extern giblorb_err_t giblorb_load_resource(giblorb_map_t *map, - glui32 method, giblorb_result_t *res, glui32 usage, - glui32 resnum); -extern giblorb_err_t giblorb_count_resources(giblorb_map_t *map, - glui32 usage, glui32 *num, glui32 *min, glui32 *max); - -/* The following functions are part of the Glk library itself, not - the Blorb layer (whose code is in gi_blorb.c). These functions - are necessarily implemented in platform-dependent code. -*/ -extern giblorb_err_t giblorb_set_resource_map(strid_t file); -extern giblorb_map_t *giblorb_get_resource_map(void); - -#endif /* _GI_BLORB_H */ diff --git a/src/glk.c b/src/glk.c deleted file mode 100644 index c4f3954..0000000 --- a/src/glk.c +++ /dev/null @@ -1,108 +0,0 @@ -#include - -#include "glk.h" -#include "abort.h" -#include "chimara-glk.h" -#include "chimara-glk-private.h" -#include "gi_blorb.h" - -ChimaraGlkPrivate *glk_data = NULL; - -/** - * glk_exit: - * - * If you want to shut down your program in the middle of your - * glk_main() function, you can call glk_exit(). - * - * This function does not return. - * - * If you print some text to a window and then shut down your program, you can - * assume that the player will be able to read it. Most likely the Glk library - * will give a Hit any key to - * exit prompt. (There are other possiblities, however. - * A terminal-window version of Glk might simply exit and leave the last screen - * state visible in the terminal window.) - * - * - * You should only shut down your program with glk_exit() or by returning from - * your glk_main() function. If you call the ANSI - * exit() function, bad things may happen. Some versions of - * the Glk library may be designed for multiple sessions, for example, and you - * would be cutting off all the sessions instead of just yours. You would - * probably also prevent final text from being visible to the player. - * - * Chimara - * - * If there are any windows open at the time glk_exit() is called, then Chimara - * will leave them open. This way, the final text remains visible. Note that bad - * things most definitely will happen if you use the ANSI - * exit(). - * - */ -void -glk_exit(void) -{ - g_signal_emit_by_name(glk_data->self, "stopped"); - - /* Stop any timers */ - glk_request_timer_events(0); - - /* Close any open resource files */ - if(glk_data->resource_map != NULL) { - giblorb_destroy_map(glk_data->resource_map); - glk_stream_close(glk_data->resource_file, NULL); - } - - glk_data = NULL; - g_thread_exit(NULL); -} - -/** - * glk_tick: - * - * Carries out platform-dependent actions such as yielding time to the operating - * system and checking for interrupts. glk_tick() should be called every so - * often when there is a long interval between calls of glk_select() or - * glk_select_poll(). This call is fast; in fact, on average, it does nothing at - * all. So you can call it often. - * - * - * In a virtual machine interpreter, once per opcode is appropriate. In a - * program with lots of computation, pick a comparable rate. - * - * - * glk_tick() does not try to update the screen, or check for player input, or - * any other interface task. For that, you should call glk_select() or - * glk_select_poll(). See Events. - * - * - * Captious critics have pointed out that in the sample program - * model.c, I do not call glk_tick() at all. This is - * because model.c has no heavy loops. It does a bit of - * work for each command, and then cycles back to the top of the event loop. - * The glk_select() call, of course, blocks waiting for input, so it does all - * the yielding and interrupt-checking one could imagine. - * - * Basically, you must ensure there's some fixed upper bound on the - * amount of computation that can occur before a glk_tick() (or glk_select()) - * occurs. In a VM interpreter, where the VM code might contain an infinite - * loop, this is critical. In a C program, you can often eyeball it. - * - * But the next version of model.c will have a - * glk_tick() in the ornate printing loop of verb_yada(). - * Just to make the point. - * - * - */ -void -glk_tick() -{ - check_for_abort(); - - /* Do one iteration of the main loop if there are any events */ - gdk_threads_enter(); - if(gtk_events_pending()) - gtk_main_iteration(); - gdk_threads_leave(); -} - diff --git a/src/glk.h b/src/glk.h deleted file mode 100644 index 6b64f88..0000000 --- a/src/glk.h +++ /dev/null @@ -1,349 +0,0 @@ -#ifndef GLK_H -#define GLK_H - -/* glk.h: Header file for Glk API, version 0.7.0. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - - This file is copyright 1998-2004 by Andrew Plotkin. You may copy, - distribute, and incorporate it into your own programs, by any means - and under any conditions, as long as you do not modify it. You may - also modify this file, incorporate it into your own programs, - and distribute the modified version, as long as you retain a notice - in your program or documentation which mentions my name and the URL - shown above. -*/ - -/* You may have to edit the definition of glui32 to make sure it's really a - 32-bit unsigned integer type, and glsi32 to make sure it's really a - 32-bit signed integer type. If they're not, horrible things will happen. */ -#include -typedef uint32_t glui32; -typedef int32_t glsi32; - -/* These are the compile-time conditionals that reveal various Glk optional - modules. */ -#define GLK_MODULE_UNICODE -#define GLK_MODULE_IMAGE -#define GLK_MODULE_SOUND -#define GLK_MODULE_HYPERLINKS - -/* These types are opaque object identifiers. They're pointers to opaque - C structures, which are defined differently by each library. */ -typedef struct glk_window_struct *winid_t; -typedef struct glk_stream_struct *strid_t; -typedef struct glk_fileref_struct *frefid_t; -typedef struct glk_schannel_struct *schanid_t; - -#define gestalt_Version (0) -#define gestalt_CharInput (1) -#define gestalt_LineInput (2) -#define gestalt_CharOutput (3) -#define gestalt_CharOutput_CannotPrint (0) -#define gestalt_CharOutput_ApproxPrint (1) -#define gestalt_CharOutput_ExactPrint (2) -#define gestalt_MouseInput (4) -#define gestalt_Timer (5) -#define gestalt_Graphics (6) -#define gestalt_DrawImage (7) -#define gestalt_Sound (8) -#define gestalt_SoundVolume (9) -#define gestalt_SoundNotify (10) -#define gestalt_Hyperlinks (11) -#define gestalt_HyperlinkInput (12) -#define gestalt_SoundMusic (13) -#define gestalt_GraphicsTransparency (14) -#define gestalt_Unicode (15) - -#define evtype_None (0) -#define evtype_Timer (1) -#define evtype_CharInput (2) -#define evtype_LineInput (3) -#define evtype_MouseInput (4) -#define evtype_Arrange (5) -#define evtype_Redraw (6) -#define evtype_SoundNotify (7) -#define evtype_Hyperlink (8) - -typedef struct event_struct { - glui32 type; - winid_t win; - glui32 val1, val2; -} event_t; - -#define keycode_Unknown (0xffffffff) -#define keycode_Left (0xfffffffe) -#define keycode_Right (0xfffffffd) -#define keycode_Up (0xfffffffc) -#define keycode_Down (0xfffffffb) -#define keycode_Return (0xfffffffa) -#define keycode_Delete (0xfffffff9) -#define keycode_Escape (0xfffffff8) -#define keycode_Tab (0xfffffff7) -#define keycode_PageUp (0xfffffff6) -#define keycode_PageDown (0xfffffff5) -#define keycode_Home (0xfffffff4) -#define keycode_End (0xfffffff3) -#define keycode_Func1 (0xffffffef) -#define keycode_Func2 (0xffffffee) -#define keycode_Func3 (0xffffffed) -#define keycode_Func4 (0xffffffec) -#define keycode_Func5 (0xffffffeb) -#define keycode_Func6 (0xffffffea) -#define keycode_Func7 (0xffffffe9) -#define keycode_Func8 (0xffffffe8) -#define keycode_Func9 (0xffffffe7) -#define keycode_Func10 (0xffffffe6) -#define keycode_Func11 (0xffffffe5) -#define keycode_Func12 (0xffffffe4) -/* The last keycode is always (0x100000000 - keycode_MAXVAL) */ -#define keycode_MAXVAL (28) - -#define style_Normal (0) -#define style_Emphasized (1) -#define style_Preformatted (2) -#define style_Header (3) -#define style_Subheader (4) -#define style_Alert (5) -#define style_Note (6) -#define style_BlockQuote (7) -#define style_Input (8) -#define style_User1 (9) -#define style_User2 (10) -#define style_NUMSTYLES (11) - -typedef struct stream_result_struct { - glui32 readcount; - glui32 writecount; -} stream_result_t; - -#define wintype_AllTypes (0) -#define wintype_Pair (1) -#define wintype_Blank (2) -#define wintype_TextBuffer (3) -#define wintype_TextGrid (4) -#define wintype_Graphics (5) - -#define winmethod_Left (0x00) -#define winmethod_Right (0x01) -#define winmethod_Above (0x02) -#define winmethod_Below (0x03) -#define winmethod_DirMask (0x0f) - -#define winmethod_Fixed (0x10) -#define winmethod_Proportional (0x20) -#define winmethod_DivisionMask (0xf0) - -#define fileusage_Data (0x00) -#define fileusage_SavedGame (0x01) -#define fileusage_Transcript (0x02) -#define fileusage_InputRecord (0x03) -#define fileusage_TypeMask (0x0f) - -#define fileusage_TextMode (0x100) -#define fileusage_BinaryMode (0x000) - -#define filemode_Write (0x01) -#define filemode_Read (0x02) -#define filemode_ReadWrite (0x03) -#define filemode_WriteAppend (0x05) - -#define seekmode_Start (0) -#define seekmode_Current (1) -#define seekmode_End (2) - -#define stylehint_Indentation (0) -#define stylehint_ParaIndentation (1) -#define stylehint_Justification (2) -#define stylehint_Size (3) -#define stylehint_Weight (4) -#define stylehint_Oblique (5) -#define stylehint_Proportional (6) -#define stylehint_TextColor (7) -#define stylehint_BackColor (8) -#define stylehint_ReverseColor (9) -#define stylehint_NUMHINTS (10) - -#define stylehint_just_LeftFlush (0) -#define stylehint_just_LeftRight (1) -#define stylehint_just_Centered (2) -#define stylehint_just_RightFlush (3) - -/* glk_main() is the top-level function which you define. The Glk library - calls it. */ -extern void glk_main(void); - -extern void glk_exit(void); -extern void glk_set_interrupt_handler(void (*func)(void)); -extern void glk_tick(void); - -extern glui32 glk_gestalt(glui32 sel, glui32 val); -extern glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, - glui32 arrlen); - -extern unsigned char glk_char_to_lower(unsigned char ch); -extern unsigned char glk_char_to_upper(unsigned char ch); - -extern winid_t glk_window_get_root(void); -extern winid_t glk_window_open(winid_t split, glui32 method, glui32 size, - glui32 wintype, glui32 rock); -extern void glk_window_close(winid_t win, stream_result_t *result); -extern void glk_window_get_size(winid_t win, glui32 *widthptr, - glui32 *heightptr); -extern void glk_window_set_arrangement(winid_t win, glui32 method, - glui32 size, winid_t keywin); -extern void glk_window_get_arrangement(winid_t win, glui32 *methodptr, - glui32 *sizeptr, winid_t *keywinptr); -extern winid_t glk_window_iterate(winid_t win, glui32 *rockptr); -extern glui32 glk_window_get_rock(winid_t win); -extern glui32 glk_window_get_type(winid_t win); -extern winid_t glk_window_get_parent(winid_t win); -extern winid_t glk_window_get_sibling(winid_t win); -extern void glk_window_clear(winid_t win); -extern void glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos); - -extern strid_t glk_window_get_stream(winid_t win); -extern void glk_window_set_echo_stream(winid_t win, strid_t str); -extern strid_t glk_window_get_echo_stream(winid_t win); -extern void glk_set_window(winid_t win); - -extern strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode, - glui32 rock); -extern strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, - glui32 rock); -extern void glk_stream_close(strid_t str, stream_result_t *result); -extern strid_t glk_stream_iterate(strid_t str, glui32 *rockptr); -extern glui32 glk_stream_get_rock(strid_t str); -extern void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode); -extern glui32 glk_stream_get_position(strid_t str); -extern void glk_stream_set_current(strid_t str); -extern strid_t glk_stream_get_current(void); - -extern void glk_put_char(unsigned char ch); -extern void glk_put_char_stream(strid_t str, unsigned char ch); -extern void glk_put_string(char *s); -extern void glk_put_string_stream(strid_t str, char *s); -extern void glk_put_buffer(char *buf, glui32 len); -extern void glk_put_buffer_stream(strid_t str, char *buf, glui32 len); -extern void glk_set_style(glui32 styl); -extern void glk_set_style_stream(strid_t str, glui32 styl); - -extern glsi32 glk_get_char_stream(strid_t str); -extern glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len); -extern glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len); - -extern void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, - glsi32 val); -extern void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint); -extern glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2); -extern glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint, - glui32 *result); - -extern frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock); -extern frefid_t glk_fileref_create_by_name(glui32 usage, char *name, - glui32 rock); -extern frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, - glui32 rock); -extern frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, - glui32 rock); -extern void glk_fileref_destroy(frefid_t fref); -extern frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr); -extern glui32 glk_fileref_get_rock(frefid_t fref); -extern void glk_fileref_delete_file(frefid_t fref); -extern glui32 glk_fileref_does_file_exist(frefid_t fref); - -extern void glk_select(event_t *event); -extern void glk_select_poll(event_t *event); - -extern void glk_request_timer_events(glui32 millisecs); - -extern void glk_request_line_event(winid_t win, char *buf, glui32 maxlen, - glui32 initlen); -extern void glk_request_char_event(winid_t win); -extern void glk_request_mouse_event(winid_t win); - -extern void glk_cancel_line_event(winid_t win, event_t *event); -extern void glk_cancel_char_event(winid_t win); -extern void glk_cancel_mouse_event(winid_t win); - -#ifdef GLK_MODULE_UNICODE - -extern glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, - glui32 numchars); -extern glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, - glui32 numchars); -extern glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, - glui32 numchars, glui32 lowerrest); - -extern void glk_put_char_uni(glui32 ch); -extern void glk_put_string_uni(glui32 *s); -extern void glk_put_buffer_uni(glui32 *buf, glui32 len); -extern void glk_put_char_stream_uni(strid_t str, glui32 ch); -extern void glk_put_string_stream_uni(strid_t str, glui32 *s); -extern void glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); - -extern glsi32 glk_get_char_stream_uni(strid_t str); -extern glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); -extern glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len); - -extern strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, - glui32 rock); -extern strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, - glui32 fmode, glui32 rock); - -extern void glk_request_char_event_uni(winid_t win); -extern void glk_request_line_event_uni(winid_t win, glui32 *buf, - glui32 maxlen, glui32 initlen); - -#endif /* GLK_MODULE_UNICODE */ - -#ifdef GLK_MODULE_IMAGE - -#define imagealign_InlineUp (0x01) -#define imagealign_InlineDown (0x02) -#define imagealign_InlineCenter (0x03) -#define imagealign_MarginLeft (0x04) -#define imagealign_MarginRight (0x05) - -extern glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2); -extern glui32 glk_image_draw_scaled(winid_t win, glui32 image, - glsi32 val1, glsi32 val2, glui32 width, glui32 height); -extern glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height); - -extern void glk_window_flow_break(winid_t win); - -extern void glk_window_erase_rect(winid_t win, - glsi32 left, glsi32 top, glui32 width, glui32 height); -extern void glk_window_fill_rect(winid_t win, glui32 color, - glsi32 left, glsi32 top, glui32 width, glui32 height); -extern void glk_window_set_background_color(winid_t win, glui32 color); - -#endif /* GLK_MODULE_IMAGE */ - -#ifdef GLK_MODULE_SOUND - -extern schanid_t glk_schannel_create(glui32 rock); -extern void glk_schannel_destroy(schanid_t chan); -extern schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr); -extern glui32 glk_schannel_get_rock(schanid_t chan); - -extern glui32 glk_schannel_play(schanid_t chan, glui32 snd); -extern glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, - glui32 notify); -extern void glk_schannel_stop(schanid_t chan); -extern void glk_schannel_set_volume(schanid_t chan, glui32 vol); - -extern void glk_sound_load_hint(glui32 snd, glui32 flag); - -#endif /* GLK_MODULE_SOUND */ - -#ifdef GLK_MODULE_HYPERLINKS - -extern void glk_set_hyperlink(glui32 linkval); -extern void glk_set_hyperlink_stream(strid_t str, glui32 linkval); -extern void glk_request_hyperlink_event(winid_t win); -extern void glk_cancel_hyperlink_event(winid_t win); - -#endif /* GLK_MODULE_HYPERLINKS */ - -#endif /* GLK_H */ diff --git a/src/glkstart.c b/src/glkstart.c deleted file mode 100644 index e9baf36..0000000 --- a/src/glkstart.c +++ /dev/null @@ -1,24 +0,0 @@ -/* glkstart.c: Unix-specific startup code -- sample file. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - - This is Unix startup code for the simplest possible kind of Glk - program -- no command-line arguments; no startup files; no nothing. - - Remember, this is a sample file. You should copy it into the Glk - program you are compiling, and modify it to your needs. This should - *not* be compiled into the Glk library itself. -*/ - -#include "glk.h" -#include "glkstart.h" - -glkunix_argumentlist_t glkunix_arguments[] = { - { NULL, glkunix_arg_End, NULL } -}; - -int glkunix_startup_code(glkunix_startup_t *data) -{ - return TRUE; -} - diff --git a/src/glkstart.h b/src/glkstart.h deleted file mode 100644 index e6b9010..0000000 --- a/src/glkstart.h +++ /dev/null @@ -1,57 +0,0 @@ -/* glkstart.h: Unix-specific header file for GlkTerm, CheapGlk, and XGlk - (Unix implementations of the Glk API). - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html -*/ - -/* This header defines an interface that must be used by program linked - with the various Unix Glk libraries -- at least, the three I wrote. - (I encourage anyone writing a Unix Glk library to use this interface, - but it's not part of the Glk spec.) - - Because Glk is *almost* perfectly portable, this interface *almost* - doesn't have to exist. In practice, it's small. -*/ - -#ifndef GT_START_H -#define GT_START_H - -/* We define our own TRUE and FALSE and NULL, because ANSI - is a strange world. */ -#ifndef TRUE -#define TRUE 1 -#endif -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef NULL -#define NULL 0 -#endif - -#define glkunix_arg_End (0) -#define glkunix_arg_ValueFollows (1) -#define glkunix_arg_NoValue (2) -#define glkunix_arg_ValueCanFollow (3) -#define glkunix_arg_NumberValue (4) - -typedef struct glkunix_argumentlist_struct { - char *name; - int argtype; - char *desc; -} glkunix_argumentlist_t; - -typedef struct glkunix_startup_struct { - int argc; - char **argv; -} glkunix_startup_t; - -extern glkunix_argumentlist_t glkunix_arguments[]; - -extern int glkunix_startup_code(glkunix_startup_t *data); - -extern void glkunix_set_base_file(char *filename); -extern strid_t glkunix_stream_open_pathname(char *pathname, glui32 textmode, - glui32 rock); - -#endif /* GT_START_H */ - diff --git a/src/gridtest.c b/src/gridtest.c deleted file mode 100644 index cec6004..0000000 --- a/src/gridtest.c +++ /dev/null @@ -1,84 +0,0 @@ -#include -#include -#include -#include -#include "glk.h" - -void glk_main(void) -{ - event_t ev; - winid_t mainwin = glk_window_open(0, 0, 0, wintype_TextGrid, 0); - if(!mainwin) - return; - - glk_set_window(mainwin); - glk_put_string("Philip en Marijn zijn vet goed.\n"); - glk_put_string("A veeeeeeeeeeeeeeeeeeeeeeeeeeeery looooooooooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiing.\n"); - - int count; - for(count = 0; count < 30; count++) - glk_put_string("I want to write past the end of this text buffer! "); - - glui32 width, height; - glk_window_get_size(mainwin, &width, &height); - fprintf(stderr, "\nWidth: %d\nHeight: %d\nPress a key in the window, not in the terminal.\n", width, height); - glk_request_char_event(mainwin); - while(1) { - glk_select(&ev); - if(ev.type == evtype_CharInput) - break; - } - - glk_window_move_cursor(mainwin, 15, 15); - glk_put_string(". . "); - glk_window_move_cursor(mainwin, 15, 16); - glk_put_string(" . ."); - glk_window_move_cursor(mainwin, 15, 17); - glk_put_string(". . "); - glk_window_move_cursor(mainwin, 15, 18); - glk_put_string(" . ."); - fprintf(stderr, "Cursor location test.\nPress another key.\n"); - glk_request_char_event(mainwin); - while(1) { - glk_select(&ev); - if(ev.type == evtype_CharInput) - break; - } - - char *buffer = calloc(256, sizeof(char)); - assert(buffer); - - fprintf(stderr, "Line input field until end of line\n"); - glk_window_move_cursor(mainwin, 10, 20); - glk_request_line_event(mainwin, buffer, 256, 0); - while(1) { - glk_select(&ev); - if(ev.type == evtype_LineInput) - break; - } - - fprintf(stderr, "Now edit your previous line input\n"); - glk_window_move_cursor(mainwin, 10, 22); - glk_request_line_event(mainwin, buffer, 256, strlen(buffer)); - while(1) { - glk_select(&ev); - if(ev.type == evtype_LineInput) - break; - } - - char *text = calloc(ev.val1 + 1, sizeof(char)); - assert(text); - strncpy(text, buffer, ev.val1); - text[ev.val1] = '\0'; - fprintf(stderr, "Your string was: '%s'.\nPress another key to clear the window and exit.\n", text); - free(text); - glk_request_char_event(mainwin); - while(1) { - glk_select(&ev); - if(ev.type == evtype_CharInput) - break; - } - - glk_window_clear(mainwin); - free(buffer); -} diff --git a/src/input.c b/src/input.c deleted file mode 100644 index 18a7ecb..0000000 --- a/src/input.c +++ /dev/null @@ -1,551 +0,0 @@ -#include "charset.h" -#include "magic.h" -#include "input.h" - -/* Forward declarations */ -static int flush_text_buffer(winid_t win); -static int flush_text_grid(winid_t win); - -/** - * glk_request_char_event: - * @win: A window to request char events from. - * - * Request input of a Latin-1 character or special key. A window cannot have - * requests for both character and line input at the same time. Nor can it have - * requests for character input of both types (Latin-1 and Unicode). It is - * illegal to call glk_request_char_event() if the window already has a pending - * request for either character or line input. - */ -void -glk_request_char_event(winid_t win) -{ - VALID_WINDOW(win, return); - g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); - g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - - win->input_request_type = INPUT_REQUEST_CHARACTER; - g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler ); -} - -/** - * glk_request_char_event_uni: - * @win: A window to request char events from. - * - * Request input of a Unicode character or special key. See - * glk_request_char_event(). - */ -void -glk_request_char_event_uni(winid_t win) -{ - VALID_WINDOW(win, return); - g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); - g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - - win->input_request_type = INPUT_REQUEST_CHARACTER_UNICODE; - g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler ); -} - -/** - * glk_cancel_char_event: - * @win: A window to cancel the latest char event request on. - * - * This cancels a pending request for character input. (Either Latin-1 or - * Unicode.) For convenience, it is legal to call glk_cancel_char_event() even - * if there is no charcter input request on that window. Glk will ignore the - * call in this case. - */ -void -glk_cancel_char_event(winid_t win) -{ - VALID_WINDOW(win, return); - g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - - if(win->input_request_type == INPUT_REQUEST_CHARACTER || win->input_request_type == INPUT_REQUEST_CHARACTER_UNICODE) - { - win->input_request_type = INPUT_REQUEST_NONE; - g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); - } -} - -/* Internal function: Request either latin-1 or unicode line input, in a text grid window. */ -static void -text_grid_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext) -{ - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - - GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position"); - GtkTextIter start_iter, end_iter; - gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, cursor); - - /* Determine the maximum length of the line input */ - gint cursorpos = gtk_text_iter_get_line_offset(&start_iter); - /* Odd; the Glk spec says the maximum input length is - windowwidth - 1 - cursorposition. I say no, because if cursorposition is - zero, then the input should fill the whole line. FIXME??? */ - win->input_length = MIN(win->width - cursorpos, win->line_input_buffer_max_len); - end_iter = start_iter; - gtk_text_iter_set_line_offset(&end_iter, cursorpos + win->input_length); - - /* If the buffer currently has a selection with one bound in the middle of - the input field, then deselect it. Otherwise the input field gets trashed */ - GtkTextIter start_sel, end_sel; - if( gtk_text_buffer_get_selection_bounds(buffer, &start_sel, &end_sel) ) - { - if( gtk_text_iter_in_range(&start_sel, &start_iter, &end_iter) ) - gtk_text_buffer_place_cursor(buffer, &end_sel); - if( gtk_text_iter_in_range(&end_sel, &start_iter, &end_iter) ) - gtk_text_buffer_place_cursor(buffer, &start_sel); - } - - /* Erase the text currently in the input field and replace it with a GtkEntry */ - gtk_text_buffer_delete(buffer, &start_iter, &end_iter); - win->input_anchor = gtk_text_buffer_create_child_anchor(buffer, &start_iter); - win->input_entry = gtk_entry_new(); - /* Set the entry's font to match that of the window */ - GtkRcStyle *style = gtk_widget_get_modifier_style(win->widget); /* Don't free */ - gtk_widget_modify_font(win->input_entry, style->font_desc); - /* Make the entry as small as possible to fit with the text */ - gtk_entry_set_has_frame(GTK_ENTRY(win->input_entry), FALSE); - GtkBorder border = { 0, 0, 0, 0 }; - gtk_entry_set_inner_border(GTK_ENTRY(win->input_entry), &border); - gtk_entry_set_max_length(GTK_ENTRY(win->input_entry), win->input_length); - gtk_entry_set_width_chars(GTK_ENTRY(win->input_entry), win->input_length); - - /* Insert pre-entered text if needed */ - if(insert) - gtk_entry_set_text(GTK_ENTRY(win->input_entry), inserttext); - - /* Set background color of entry (TODO: implement as property) */ - GdkColor background; - gdk_color_parse("grey", &background); - gtk_widget_modify_base(win->input_entry, GTK_STATE_NORMAL, &background); - - g_signal_connect(win->input_entry, "activate", G_CALLBACK(on_input_entry_activate), win); - - gtk_widget_show(win->input_entry); - gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(win->widget), win->input_entry, win->input_anchor); - - g_signal_handler_unblock( G_OBJECT(win->widget), win->keypress_handler ); -} - -/* Internal function: Request either latin-1 or unicode line input, in a text buffer window. */ -static void -text_buffer_request_line_event_common(winid_t win, glui32 maxlen, gboolean insert, gchar *inserttext) -{ - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - - /* Move the input_position mark to the end of the window_buffer */ - GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position"); - GtkTextIter end_iter; - gtk_text_buffer_get_end_iter(buffer, &end_iter); - gtk_text_buffer_move_mark(buffer, input_position, &end_iter); - - /* Set the entire contents of the window_buffer as uneditable - * (so input can only be entered at the end) */ - GtkTextIter start_iter; - gtk_text_buffer_get_start_iter(buffer, &start_iter); - gtk_text_buffer_remove_tag_by_name(buffer, "uneditable", &start_iter, &end_iter); - gtk_text_buffer_apply_tag_by_name(buffer, "uneditable", &start_iter, &end_iter); - - /* Insert pre-entered text if needed */ - if(insert) - gtk_text_buffer_insert(buffer, &end_iter, inserttext, -1); - - /* Scroll to input point */ - gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), input_position); - - gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), TRUE); - g_signal_handler_unblock(buffer, win->insert_text_handler); -} - -/** - * glk_request_line_event: - * @win: A text buffer or text grid window to request line input on. - * @buf: A buffer of at least @maxlen bytes. - * @maxlen: Length of the buffer. - * @initlen: The number of characters in @buf to pre-enter. - * - * Requests input of a line of Latin-1 characters. A window cannot have requests - * for both character and line input at the same time. Nor can it have requests - * for line input of both types (Latin-1 and Unicode). It is illegal to call - * glk_request_line_event() if the window already has a pending request for - * either character or line input. - * - * The @buf argument is a pointer to space where the line input will be stored. - * (This may not be %NULL.) @maxlen is the length of this space, in bytes; the - * library will not accept more characters than this. If @initlen is nonzero, - * then the first @initlen bytes of @buf will be entered as pre-existing input - * — just as if the player had typed them himself. (The player can continue - * composing after this pre-entered input, or delete it or edit as usual.) - * - * The contents of the buffer are undefined until the input is completed (either - * by a line input event, or glk_cancel_line_event(). The library may or may not - * fill in the buffer as the player composes, while the input is still pending; - * it is illegal to change the contents of the buffer yourself. - */ -void -glk_request_line_event(winid_t win, char* buf, glui32 maxlen, glui32 initlen) -{ - VALID_WINDOW(win, return); - g_return_if_fail(buf); - g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); - g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - g_return_if_fail(initlen <= maxlen); - - win->input_request_type = INPUT_REQUEST_LINE; - win->line_input_buffer = buf; - win->line_input_buffer_max_len = maxlen; - - gchar *inserttext = (initlen > 0)? g_strndup(buf, initlen) : g_strdup(""); - switch(win->type) - { - case wintype_TextBuffer: - text_buffer_request_line_event_common(win, maxlen, (initlen > 0), inserttext); - break; - case wintype_TextGrid: - text_grid_request_line_event_common(win, maxlen, (initlen > 0), inserttext); - break; - } - g_free(inserttext); -} - -/** - * glk_request_line_event_uni: - * @win: A text buffer or text grid window to request line input on. - * @buf: A buffer of at least @maxlen characters. - * @maxlen: Length of the buffer. - * @initlen: The number of characters in @buf to pre-enter. - * - * Request input of a line of Unicode characters. This works the same as - * glk_request_line_event(), except the result is stored in an array of - * glui32 values instead of an array of characters, and the values - * may be any valid Unicode code points. - * - * The result will be in Unicode Normalization Form C. This basically means that - * composite characters will be single characters where possible, instead of - * sequences of base and combining marks. See - * Unicode Standard Annex #15 - * for the details. - */ -void -glk_request_line_event_uni(winid_t win, glui32 *buf, glui32 maxlen, glui32 initlen) -{ - VALID_WINDOW(win, return); - g_return_if_fail(buf); - g_return_if_fail(win->input_request_type == INPUT_REQUEST_NONE); - g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - g_return_if_fail(initlen <= maxlen); - - win->input_request_type = INPUT_REQUEST_LINE_UNICODE; - win->line_input_buffer_unicode = buf; - win->line_input_buffer_max_len = maxlen; - - gchar *utf8; - if(initlen > 0) { - utf8 = convert_ucs4_to_utf8(buf, initlen); - if(utf8 == NULL) - return; - } - else - utf8 = g_strdup(""); - - switch(win->type) - { - case wintype_TextBuffer: - text_buffer_request_line_event_common(win, maxlen, (initlen > 0), utf8); - break; - case wintype_TextGrid: - text_grid_request_line_event_common(win, maxlen, (initlen > 0), utf8); - break; - } - g_free(utf8); -} - -/** - * glk_cancel_line_event: - * @win: A text buffer or text grid window to cancel line input on. - * @event: Will be filled in if the user had already input something. - * - * This cancels a pending request for line input. (Either Latin-1 or Unicode.) - * - * The event pointed to by the event argument will be filled in as if the - * player had hit enter, and the input composed so far will be stored in the - * buffer; see below. If you do not care about this information, pass %NULL as - * the @event argument. (The buffer will still be filled.) - * - * For convenience, it is legal to call glk_cancel_line_event() even if there - * is no line input request on that window. The event type will be set to - * #evtype_None in this case. - */ -void -glk_cancel_line_event(winid_t win, event_t *event) -{ - VALID_WINDOW(win, return); - g_return_if_fail(win->type != wintype_TextBuffer || win->type != wintype_TextGrid); - - if(event != NULL) { - event->type = evtype_None; - event->win = win; - event->val1 = 0; - event->val2 = 0; - } - - if(win->input_request_type == INPUT_REQUEST_NONE) - return; - - g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); - - int chars_written = 0; - - if(win->type == wintype_TextGrid) { - g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); - chars_written = flush_text_grid(win); - } else if(win->type == wintype_TextBuffer) { - GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - g_signal_handler_block(window_buffer, win->insert_text_handler); - chars_written = flush_text_buffer(win); - } - - if(event != NULL && chars_written > 0) { - event->type = evtype_LineInput; - event->val1 = chars_written; - } -} - -/* Internal function: General callback for signal key-press-event on a text buffer or text grid window. Used in character input on both text buffers and grids, and also in line input on grids, to redirect keystrokes to the line input field. Blocked when not in use. */ -gboolean -on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win) -{ - /* If this is a text grid window, and line input is active, then redirect the key press to the line input GtkEntry */ - if( win->type == wintype_TextGrid && (win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) ) - { - if(event->keyval == GDK_Up || event->keyval == GDK_KP_Up - || event->keyval == GDK_Down || event->keyval == GDK_KP_Down - || event->keyval == GDK_Left || event->keyval == GDK_KP_Left - || event->keyval == GDK_Right || event->keyval == GDK_KP_Right - || event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab - || event->keyval == GDK_Page_Up || event->keyval == GDK_KP_Page_Up - || event->keyval == GDK_Page_Down || event->keyval == GDK_KP_Page_Down - || event->keyval == GDK_Home || event->keyval == GDK_KP_Home - || event->keyval == GDK_End || event->keyval == GDK_KP_End) - return FALSE; /* Don't redirect these keys */ - gtk_widget_grab_focus(win->input_entry); - gtk_editable_set_position(GTK_EDITABLE(win->input_entry), -1); - gboolean retval = TRUE; - g_signal_emit_by_name(win->input_entry, "key-press-event", event, &retval); - return retval; /* Block this key event if the entry handled it */ - } - if(win->input_request_type != INPUT_REQUEST_CHARACTER && - win->input_request_type != INPUT_REQUEST_CHARACTER_UNICODE) - return FALSE; - - int keycode; - - switch(event->keyval) { - case GDK_Up: - case GDK_KP_Up: keycode = keycode_Up; break; - case GDK_Down: - case GDK_KP_Down: keycode = keycode_Down; break; - case GDK_Left: - case GDK_KP_Left: keycode = keycode_Left; break; - case GDK_Right: - case GDK_KP_Right: keycode = keycode_Right; break; - case GDK_Linefeed: - case GDK_Return: - case GDK_KP_Enter: keycode = keycode_Return; break; - case GDK_Delete: - case GDK_BackSpace: - case GDK_KP_Delete: keycode = keycode_Delete; break; - case GDK_Escape: keycode = keycode_Escape; break; - case GDK_Tab: - case GDK_KP_Tab: keycode = keycode_Tab; break; - case GDK_Page_Up: - case GDK_KP_Page_Up: keycode = keycode_PageUp; break; - case GDK_Page_Down: - case GDK_KP_Page_Down: keycode = keycode_PageDown; break; - case GDK_Home: - case GDK_KP_Home: keycode = keycode_Home; break; - case GDK_End: - case GDK_KP_End: keycode = keycode_End; break; - case GDK_F1: - case GDK_KP_F1: keycode = keycode_Func1; break; - case GDK_F2: - case GDK_KP_F2: keycode = keycode_Func2; break; - case GDK_F3: - case GDK_KP_F3: keycode = keycode_Func3; break; - case GDK_F4: - case GDK_KP_F4: keycode = keycode_Func4; break; - case GDK_F5: keycode = keycode_Func5; break; - case GDK_F6: keycode = keycode_Func6; break; - case GDK_F7: keycode = keycode_Func7; break; - case GDK_F8: keycode = keycode_Func8; break; - case GDK_F9: keycode = keycode_Func9; break; - case GDK_F10: keycode = keycode_Func10; break; - case GDK_F11: keycode = keycode_Func11; break; - case GDK_F12: keycode = keycode_Func12; break; - default: - keycode = gdk_keyval_to_unicode(event->keyval); - /* If keycode is 0, then keyval was not recognized; also return - unknown if Latin-1 input was requested and the character is not in - Latin-1 */ - if(keycode == 0 || (win->input_request_type == INPUT_REQUEST_CHARACTER && keycode > 255)) - keycode = keycode_Unknown; - } - - event_throw(evtype_CharInput, win, keycode, 0); - - /* Only one keypress will be handled */ - win->input_request_type = INPUT_REQUEST_NONE; - g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); - - return TRUE; -} - -/* Internal function: finish handling a line input request, for both text grid and text buffer windows. */ -static int -write_to_window_buffer(winid_t win, const gchar *inserted_text) -{ - int copycount = 0; - - /* Convert the string from UTF-8 to Latin-1 or Unicode */ - if(win->input_request_type == INPUT_REQUEST_LINE) - { - gsize bytes_written; - gchar *latin1 = convert_utf8_to_latin1(inserted_text, &bytes_written); - - if(latin1 == NULL) - return 0; - - /* Place input in the echo stream */ - if(win->echo_stream != NULL) - glk_put_string_stream(win->echo_stream, latin1); - - /* Copy the string (bytes_written does not include the NULL at the end) */ - copycount = MIN(win->line_input_buffer_max_len, bytes_written); - memcpy(win->line_input_buffer, latin1, copycount); - g_free(latin1); - } - else if(win->input_request_type == INPUT_REQUEST_LINE_UNICODE) - { - glong items_written; - gunichar *unicode = convert_utf8_to_ucs4(inserted_text, &items_written); - - if(unicode == NULL) - return 0; - - /* Place input in the echo stream */ - if(win->echo_stream != NULL) - glk_put_string_stream_uni(win->echo_stream, unicode); - - /* Copy the string (but not the NULL at the end) */ - copycount = MIN(win->line_input_buffer_max_len, items_written); - memcpy(win->line_input_buffer_unicode, unicode, copycount * sizeof(gunichar)); - g_free(unicode); - } - else - WARNING("Wrong input request type"); - - win->input_request_type = INPUT_REQUEST_NONE; - return copycount; -} - -/* Internal function: Retrieves the input of a TextBuffer window and stores it in the window buffer. - * Returns the number of characters written, suitable for inclusion in a line input event. */ -static int -flush_text_buffer(winid_t win) -{ - VALID_WINDOW(win, return 0); - g_return_val_if_fail(win->type == wintype_TextBuffer, 0); - - GtkTextIter start_iter, end_iter, last_character; - - GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - GtkTextMark *input_position = gtk_text_buffer_get_mark(window_buffer, "input_position"); - gtk_text_buffer_get_iter_at_mark(window_buffer, &start_iter, input_position); - gtk_text_buffer_get_end_iter(window_buffer, &end_iter); - gtk_text_buffer_get_end_iter(window_buffer, &last_character); - gtk_text_iter_backward_cursor_position(&last_character); - - gchar* last_char = gtk_text_buffer_get_text(window_buffer, &last_character, &end_iter, FALSE); - - if( strchr(last_char, '\n') != NULL ) - gtk_text_iter_backward_cursor_position(&end_iter); - - gchar* inserted_text = gtk_text_buffer_get_text(window_buffer, &start_iter, &end_iter, FALSE); - - int chars_written = write_to_window_buffer(win, inserted_text); - g_free(inserted_text); - - return chars_written; -} - -/* Internal function: Retrieves the input of a TextGrid window and stores it in the window buffer. - * Returns the number of characters written, suitable for inclusion in a line input event. */ -static int -flush_text_grid(winid_t win) -{ - VALID_WINDOW(win, return 0); - g_return_val_if_fail(win->type == wintype_TextBuffer, 0); - - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - - gchar *text = g_strdup( gtk_entry_get_text(GTK_ENTRY(win->input_entry)) ); - /* Move the focus back into the text view */ - gtk_widget_grab_focus(win->widget); - /* Remove entry widget from text view */ - /* Should be ok even though this is the widget's own signal handler */ - gtk_container_remove( GTK_CONTAINER(win->widget), GTK_WIDGET(win->input_entry) ); - win->input_entry = NULL; - /* Delete the child anchor */ - GtkTextIter start, end; - gtk_text_buffer_get_iter_at_child_anchor(buffer, &start, win->input_anchor); - end = start; - gtk_text_iter_forward_char(&end); /* Point after the child anchor */ - gtk_text_buffer_delete(buffer, &start, &end); - win->input_anchor = NULL; - - gchar *spaces = g_strnfill(win->input_length - g_utf8_strlen(text, -1), ' '); - gchar *text_to_insert = g_strconcat(text, spaces, NULL); - g_free(spaces); - gtk_text_buffer_insert(buffer, &start, text_to_insert, -1); - g_free(text_to_insert); - - int chars_written = write_to_window_buffer(win, text); - g_free(text); - - return chars_written; -} - -/* Internal function: Callback for signal insert-text on a text buffer window. -Runs after the default handler has already inserted the text. -FIXME: This function assumes that newline was the last character typed into the -window. That assumption is wrong if, for example, text containing a newline was -pasted into the window. */ -void -after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win) -{ - if( strchr(text, '\n') != NULL ) - { - /* Remove signal handlers */ - GtkTextBuffer *window_buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - g_signal_handler_block(window_buffer, win->insert_text_handler); - - /* Make the window uneditable again and retrieve the text that was input */ - gtk_text_view_set_editable(GTK_TEXT_VIEW(win->widget), FALSE); - - int chars_written = flush_text_buffer(win); - event_throw(evtype_LineInput, win, chars_written, 0); - } -} - -/* Internal function: Callback for signal activate on the line input GtkEntry -in a text grid window. */ -void -on_input_entry_activate(GtkEntry *input_entry, winid_t win) -{ - g_signal_handler_block( G_OBJECT(win->widget), win->keypress_handler ); - - int chars_written = flush_text_grid(win); - event_throw(evtype_LineInput, win, chars_written, 0); -} - diff --git a/src/input.h b/src/input.h deleted file mode 100644 index e4080fd..0000000 --- a/src/input.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef INPUT_H -#define INPUT_H - -#include -#include -#include -#include - -#include "window.h" -#include "event.h" - -G_GNUC_INTERNAL gboolean on_window_key_press_event(GtkWidget *widget, GdkEventKey *event, winid_t win); -G_GNUC_INTERNAL void after_window_insert_text(GtkTextBuffer *textbuffer, GtkTextIter *location, gchar *text, gint len, winid_t win); -G_GNUC_INTERNAL void on_input_entry_activate(GtkEntry *input_entry, winid_t win); - -#endif diff --git a/src/magic.c b/src/magic.c deleted file mode 100644 index a385b96..0000000 --- a/src/magic.c +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include "glk.h" -#include "magic.h" - -/* The "magic" mechanism was stolen from Evin Robertson's GtkGlk. */ - -static gchar * -magic_to_string(glui32 magic) -{ - switch(magic) - { - case MAGIC_WINDOW: - return "winid_t"; - case MAGIC_STREAM: - return "strid_t"; - case MAGIC_FILEREF: - return "frefid_t"; - case MAGIC_SCHANNEL: - return "schanid_t"; - default: - g_return_val_if_reached("unknown"); - } -} - -/* Internal function: check the object's magic number to make sure it is the - right type, and not freed. */ -gboolean -magic_is_valid_or_null(const glui32 goodmagic, const glui32 realmagic, const gchar *function) -{ - if(realmagic != MAGIC_NULL) - { - if(realmagic != goodmagic) - { - if(realmagic == MAGIC_FREE) - g_critical("%s: Using a freed object", function); - else - g_critical( "%s: %s object not a %s", function, magic_to_string(realmagic), magic_to_string(goodmagic) ); - return FALSE; - } - } - return TRUE; -} - - -/* Internal function: check the object's magic number to make sure it is - not NULL, the right type, and not freed. */ -gboolean -magic_is_valid(const void *obj, const glui32 goodmagic, const glui32 realmagic, const gchar *function) -{ - if(obj == NULL) - { - g_critical( "%s: NULL %s pointer", function, magic_to_string(goodmagic) ); - return FALSE; - } - return magic_is_valid_or_null(goodmagic, realmagic, function); -} diff --git a/src/magic.h b/src/magic.h deleted file mode 100644 index 406b570..0000000 --- a/src/magic.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef __MAGIC_H__ -#define __MAGIC_H__ - -#include -#include "glk.h" - -#define MAGIC_FREE 0x46524545 /* "FREE" */ -#define MAGIC_NULL 0x4E554C4C /* "NULL" */ -#define MAGIC_WINDOW 0x57494E44 /* "WIND" */ -#define MAGIC_STREAM 0x53545245 /* "STRE" */ -#define MAGIC_FILEREF 0x46494C45 /* "FILE" */ -#define MAGIC_SCHANNEL 0x53434841 /* "SCHA" */ - -G_GNUC_INTERNAL gboolean magic_is_valid_or_null(const glui32 goodmagic, const glui32 realmagic, const gchar *function); -G_GNUC_INTERNAL gboolean magic_is_valid(const void *obj, const glui32 goodmagic, const glui32 realmagic, const gchar *function); - -#define VALID_MAGIC(obj, goodmagic, die) \ - if( !magic_is_valid(obj, goodmagic, obj->magic, G_STRFUNC) ) die -#define VALID_MAGIC_OR_NULL(obj, goodmagic, die) \ - if( !magic_is_valid_or_null(goodmagic, obj? obj->magic : MAGIC_NULL, G_STRFUNC) ) die - -#define VALID_WINDOW(o, d) VALID_MAGIC(o, MAGIC_WINDOW, d) -#define VALID_WINDOW_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_WINDOW, d) -#define VALID_STREAM(o, d) VALID_MAGIC(o, MAGIC_STREAM, d) -#define VALID_STREAM_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_STREAM, d) -#define VALID_FILEREF(o, d) VALID_MAGIC(o, MAGIC_FILEREF, d) -#define VALID_FILEREF_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_FILEREF, d) -#define VALID_SCHANNEL(o, d) VALID_MAGIC(o, MAGIC_SCHANNEL, d) -#define VALID_SCHANNEL_OR_NULL(o, d) VALID_MAGIC_OR_NULL(o, MAGIC_SCHANNEL, d) - -/* This works with string variables as well as literal strings */ -#define ILLEGAL(str) g_critical("%s: %s", G_STRFUNC, str) -/* This only works with literal strings */ -#define ILLEGAL_PARAM(str, param) g_critical("%s: " str, G_STRFUNC, param) - -#define WARNING(msg) g_warning("%s: %s", G_STRFUNC, msg) -#define WARNING_S(msg, str) g_warning("%s: %s: %s", G_STRFUNC, msg, str) -#define IO_WARNING(msg, str, errmsg) \ - g_warning("%s: %s \"%s\": %s", G_STRFUNC, msg, str, errmsg) - -#endif /* __MAGIC_H__ */ diff --git a/src/main.c b/src/main.c deleted file mode 100644 index c34e4c5..0000000 --- a/src/main.c +++ /dev/null @@ -1,134 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ -/* - * main.c - * Copyright (C) Philip en Marijn 2008 <> - * - * main.c is free software copyrighted by Philip en Marijn. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name ``Philip en Marijn'' nor the name of any other - * contributor may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * main.c IS PROVIDED BY Philip en Marijn ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL Philip en Marijn OR ANY OTHER CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "callbacks.h" -#include "error.h" -#include "chimara-glk.h" - -/* Global pointers to widgets */ -GtkBuilder *builder = NULL; -GtkWidget *window = NULL; -GtkWidget *glk = NULL; - -static void -on_started(ChimaraGlk *glk) -{ - g_printerr("Started!\n"); -} - -static void -on_stopped(ChimaraGlk *glk) -{ - g_printerr("Stopped!\n"); -} - -static void -create_window(void) -{ - if( (window = GTK_WIDGET(gtk_builder_get_object(builder, "gargoyle-gtk"))) == NULL ) { - error_dialog(NULL, NULL, "Error while getting main window object"); - return; - } - - gtk_builder_connect_signals(builder, NULL); - - glk = chimara_glk_new(); - g_object_set(glk, "border-width", 6, "spacing", 6, NULL); - chimara_glk_set_default_font_string(CHIMARA_GLK(glk), "Sans 11"); - chimara_glk_set_monospace_font_string(CHIMARA_GLK(glk), "Monospace 10"); - g_signal_connect(glk, "started", G_CALLBACK(on_started), NULL); - g_signal_connect(glk, "stopped", G_CALLBACK(on_stopped), NULL); - - GtkBox *vbox = GTK_BOX( gtk_builder_get_object(builder, "vbox") ); - if(vbox == NULL) - { - error_dialog(NULL, NULL, "Could not find vbox"); - return; - } - - gtk_box_pack_end(vbox, glk, TRUE, TRUE, 0); -} - -int -main(int argc, char *argv[]) -{ - GError *error = NULL; - -#ifdef ENABLE_NLS - bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); - bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); - textdomain(GETTEXT_PACKAGE); -#endif - - if( !g_thread_supported() ) - g_thread_init(NULL); - - gdk_threads_init(); - - gtk_set_locale(); - gtk_init(&argc, &argv); - - builder = gtk_builder_new(); - if( !gtk_builder_add_from_file(builder, "chimara.ui", &error) ) { - error_dialog(NULL, error, "Error while building interface: "); - return 1; - } - - create_window(); - gtk_widget_show_all(window); - - g_object_unref( G_OBJECT(builder) ); - - if( !chimara_glk_run(CHIMARA_GLK(glk), ".libs/first.so", &error) ) { - error_dialog(GTK_WINDOW(window), error, "Error starting Glk library: "); - return 1; - } - - gdk_threads_enter(); - gtk_main(); - gdk_threads_leave(); - - chimara_glk_stop(CHIMARA_GLK(glk)); - chimara_glk_wait(CHIMARA_GLK(glk)); - - return 0; -} diff --git a/src/model.c b/src/model.c deleted file mode 100644 index 4cf2609..0000000 --- a/src/model.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "stdio.h" -#include "glk.h" - -static winid_t mainwin = NULL; - -void sayit(void) -{ - fprintf(stderr, "I'm the interrupt handler!\n"); -} - -void glk_main(void) -{ - /* Open the main window. */ - mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); - if (!mainwin) { - /* It's possible that the main window failed to open. There's - nothing we can do without it, so exit. */ - return; - } - - glui32 buffer[1024]; - int i; - for(i = 0; i < 512; i++) { - buffer[i * 2] = i + 33; - buffer[i * 2 + 1] = 32; - } - -/* frefid_t f = glk_fileref_create_temp(fileusage_BinaryMode, 0); - if(f) - { - strid_t s = glk_stream_open_file(f, filemode_ReadWrite, 0);*/ - glui32 membuf[512]; - strid_t s = glk_stream_open_memory_uni(membuf, 512, filemode_ReadWrite, 0); - glk_stream_set_current(s); - - glk_put_char_uni('X'); - glk_put_string("Philip en Marijn zijn vet goed.\n"); - glk_put_buffer_uni(buffer, 1024); - - glk_stream_set_position(s, 0, seekmode_Start); - glk_set_window(mainwin); - glk_put_char_uni( glk_get_char_stream_uni(s) ); - glk_put_char('\n'); - g_printerr( "Line read: %d\n", glk_get_line_stream_uni(s, buffer, 1024) ); - g_printerr("string[5] = %X\n", buffer[5]); - glk_put_string_uni(buffer); - int count = glk_get_buffer_stream_uni(s, buffer, 1024); - g_printerr("Buffer read: %d\n", count); - glk_put_string("\n---SOME CHARACTERS---\n"); - glk_put_buffer_uni(buffer, count); - glk_put_string("\n---THE SAME CHARACTERS IN UPPERCASE---\n"); - int newcount = glk_buffer_to_upper_case_uni(buffer, 1024, 1024); - glk_put_buffer_uni(buffer, newcount); - - stream_result_t result; - glk_stream_close(s, &result); - - g_printerr("Read count: %d\nWrite count: %d\n", result.readcount, result.writecount); -/* glk_fileref_destroy(f); - }*/ - - glk_set_interrupt_handler(&sayit); - - event_t ev; - while(1) { - glk_put_string("\nprompt> "); - glk_request_line_event_uni(mainwin, buffer, 1024, 0); - glk_select(&ev); - switch(ev.type) { - default: - printf("Received event:\n"); - printf("Type: %d\n", ev.type); - printf("Win: %d\n", glk_window_get_rock(ev.win) ); - printf("Var1: %d\n", ev.val1); - printf("Var2: %d\n", ev.val2); - } - } - - /* Bye bye */ - glk_exit(); -} diff --git a/src/multiwin.c b/src/multiwin.c deleted file mode 100644 index 261d7fb..0000000 --- a/src/multiwin.c +++ /dev/null @@ -1,843 +0,0 @@ -#include "glk.h" - -/* multiwin.c: Sample program for Glk API, version 0.5. - Designed by Andrew Plotkin - http://www.eblong.com/zarf/glk/index.html - This program is in the public domain. -*/ - -/* This example demonstrates multiple windows and timed input in the - Glk API. */ - -/* This is the cleanest possible form of a Glk program. It includes only - "glk.h", and doesn't call any functions outside Glk at all. We even - define our own string functions, rather than relying on the - standard libraries. */ - -/* We also define our own TRUE and FALSE and NULL. */ -#ifndef TRUE -#define TRUE 1 -#endif -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef NULL -#define NULL 0 -#endif - -/* The story and status windows. */ -static winid_t mainwin1 = NULL; -static winid_t mainwin2 = NULL; -static winid_t statuswin = NULL; - -/* Key windows don't get stored in a global variable; we'll find them - by iterating over the list and looking for this rock value. */ -#define KEYWINROCK (97) - -/* For the two main windows, we keep a flag saying whether that window - has a line input request pending. (Because if it does, we need to - cancel the line input before printing to that window.) */ -static int inputpending1, inputpending2; -/* When we cancel line input, we should remember how many characters - had been typed. This lets us restart the input with those characters - already in place. */ -static int already1, already2; - -/* There's a three-second timer which can be on or off. */ -static int timer_on = FALSE; - -/* Forward declarations */ -void glk_main(void); - -static void draw_statuswin(void); -static void draw_keywins(void); -static void perform_key(winid_t win, glui32 key); -static void perform_timer(void); - -static int str_eq(char *s1, char *s2); -static int str_len(char *s1); -static char *str_cpy(char *s1, char *s2); -static char *str_cat(char *s1, char *s2); -static void num_to_str(char *buf, int num); - -static void verb_help(winid_t win); -static void verb_jump(winid_t win); -static void verb_yada(winid_t win); -static void verb_both(winid_t win); -static void verb_clear(winid_t win); -static void verb_page(winid_t win); -static void verb_pageboth(winid_t win); -static void verb_timer(winid_t win); -static void verb_untimer(winid_t win); -static void verb_chars(winid_t win); -static void verb_quit(winid_t win); - -/* The glk_main() function is called by the Glk system; it's the main entry - point for your program. */ -void glk_main(void) -{ - char commandbuf1[256]; /* For mainwin1 */ - char commandbuf2[256]; /* For mainwin2 */ - - /* Open the main windows. */ - mainwin1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); - if (!mainwin1) { - /* It's possible that the main window failed to open. There's - nothing we can do without it, so exit. */ - return; - } - - /* Open a second window: a text grid, above the main window, five - lines high. It is possible that this will fail also, but we accept - that. */ - statuswin = glk_window_open(mainwin1, - winmethod_Above | winmethod_Fixed, - 5, wintype_TextGrid, 0); - - /* And a third window, a second story window below the main one. */ - mainwin2 = glk_window_open(mainwin1, - winmethod_Below | winmethod_Proportional, - 50, wintype_TextBuffer, 0); - - /* We're going to be switching from one window to another all the - time. So we'll be setting the output stream on a case-by-case - basis. Every function that prints must set the output stream - first. (Contrast model.c, where the output stream is always the - main window, and every function that changes that must set it - back afterwards.) */ - - glk_set_window(mainwin1); - glk_put_string("Multiwin\nAn Interactive Sample Glk Program\n"); - glk_put_string("By Andrew Plotkin.\nRelease 3.\n"); - glk_put_string("Type \"help\" for a list of commands.\n"); - - glk_set_window(mainwin2); - glk_put_string("Note that the upper left-hand window accepts character"); - glk_put_string(" input. Hit 'h' to split the window horizontally, 'v' to"); - glk_put_string(" split the window vertically, 'c' to close a window,"); - glk_put_string(" and any other key (including special keys) to display"); - glk_put_string(" key codes. All new windows accept these same keys as"); - glk_put_string(" well.\n\n"); - glk_put_string("This bottom window accepts normal line input.\n"); - - if (statuswin) { - /* For fun, let's open a fourth window now, splitting the status - window. */ - winid_t keywin; - keywin = glk_window_open(statuswin, - winmethod_Left | winmethod_Proportional, - 66, wintype_TextGrid, KEYWINROCK); - if (keywin) { - glk_request_char_event(keywin); - } - } - - /* Draw the key window now, since we don't draw it every input (as - we do the status window. */ - draw_keywins(); - - inputpending1 = FALSE; - inputpending2 = FALSE; - already1 = 0; - already2 = 0; - - while (1) { - char *cx, *cmd; - int doneloop, len; - winid_t whichwin; - event_t ev; - - draw_statuswin(); - /* We're not redrawing the key windows every command. */ - - /* Either main window, or both, could already have line input - pending. If so, leave that window alone. If there is no - input pending on a window, set a line input request, but - keep around any characters that were in the buffer already. */ - - if (mainwin1 && !inputpending1) { - glk_set_window(mainwin1); - glk_put_string("\n>"); - /* We request up to 255 characters. The buffer can hold 256, - but we are going to stick a null character at the end, so - we have to leave room for that. Note that the Glk library - does *not* put on that null character. */ - glk_request_line_event(mainwin1, commandbuf1, 255, already1); - inputpending1 = TRUE; - } - - if (mainwin2 && !inputpending2) { - glk_set_window(mainwin2); - glk_put_string("\n>"); - /* See above. */ - glk_request_line_event(mainwin2, commandbuf2, 255, already2); - inputpending2 = TRUE; - } - - doneloop = FALSE; - while (!doneloop) { - - /* Grab an event. */ - glk_select(&ev); - - switch (ev.type) { - - case evtype_LineInput: - /* If the event comes from one main window or the other, - we mark that window as no longer having line input - pending. We also set commandbuf to point to the - appropriate buffer. Then we leave the event loop. */ - if (mainwin1 && ev.win == mainwin1) { - whichwin = mainwin1; - inputpending1 = FALSE; - cmd = commandbuf1; - doneloop = TRUE; - } - else if (mainwin2 && ev.win == mainwin2) { - whichwin = mainwin2; - inputpending2 = FALSE; - cmd = commandbuf2; - doneloop = TRUE; - } - break; - - case evtype_CharInput: - /* It's a key event, from one of the keywins. We - call a subroutine rather than exiting the - event loop (although I could have done it - that way too.) */ - perform_key(ev.win, ev.val1); - break; - - case evtype_Timer: - /* It's a timer event. This does exit from the event - loop, since we're going to interrupt input in - mainwin1 and then re-print the prompt. */ - whichwin = NULL; - cmd = NULL; - doneloop = TRUE; - break; - - case evtype_Arrange: - /* Windows have changed size, so we have to redraw the - status window and key window. But we stay in the - event loop. */ - draw_statuswin(); - draw_keywins(); - break; - } - } - - if (cmd == NULL) { - /* It was a timer event. */ - perform_timer(); - continue; - } - - /* It was a line input event. cmd now points at a line of input - from one of the main windows. */ - - /* The line we have received in commandbuf is not null-terminated. - We handle that first. */ - len = ev.val1; /* Will be between 0 and 255, inclusive. */ - cmd[len] = '\0'; - - /* Then squash to lower-case. */ - for (cx = cmd; *cx; cx++) { - *cx = glk_char_to_lower(*cx); - } - - /* Then trim whitespace before and after. */ - - for (cx = cmd; *cx == ' '; cx++, len--) { }; - - cmd = cx; - - for (cx = cmd+len-1; cx >= cmd && *cx == ' '; cx--) { }; - *(cx+1) = '\0'; - - /* cmd now points to a nice null-terminated string. We'll do the - simplest possible parsing. */ - if (str_eq(cmd, "")) { - glk_set_window(whichwin); - glk_put_string("Excuse me?\n"); - } - else if (str_eq(cmd, "help")) { - verb_help(whichwin); - } - else if (str_eq(cmd, "yada")) { - verb_yada(whichwin); - } - else if (str_eq(cmd, "both")) { - verb_both(whichwin); - } - else if (str_eq(cmd, "clear")) { - verb_clear(whichwin); - } - else if (str_eq(cmd, "page")) { - verb_page(whichwin); - } - else if (str_eq(cmd, "pageboth")) { - verb_pageboth(whichwin); - } - else if (str_eq(cmd, "timer")) { - verb_timer(whichwin); - } - else if (str_eq(cmd, "untimer")) { - verb_untimer(whichwin); - } - else if (str_eq(cmd, "chars")) { - verb_chars(whichwin); - } - else if (str_eq(cmd, "jump")) { - verb_jump(whichwin); - } - else if (str_eq(cmd, "quit")) { - verb_quit(whichwin); - } - else { - glk_set_window(whichwin); - glk_put_string("I don't understand the command \""); - glk_put_string(cmd); - glk_put_string("\".\n"); - } - - if (whichwin == mainwin1) - already1 = 0; - else if (whichwin == mainwin2) - already2 = 0; - } -} - -static void draw_statuswin(void) -{ - glui32 width, height; - - if (!statuswin) { - /* It is possible that the window was not successfully - created. If that's the case, don't try to draw it. */ - return; - } - - glk_set_window(statuswin); - glk_window_clear(statuswin); - - glk_window_get_size(statuswin, &width, &height); - - /* Draw a decorative compass rose in the center. */ - width = (width/2); - if (width > 0) - width--; - height = (height/2); - if (height > 0) - height--; - - glk_window_move_cursor(statuswin, width, height+0); - glk_put_string("\\|/"); - glk_window_move_cursor(statuswin, width, height+1); - glk_put_string("-*-"); - glk_window_move_cursor(statuswin, width, height+2); - glk_put_string("/|\\"); - -} - -/* This draws some corner decorations in *every* key window -- the - one created at startup, and any later ones. It finds them all - with glk_window_iterate. */ -static void draw_keywins(void) -{ - winid_t win; - glui32 rock; - glui32 width, height; - - for (win = glk_window_iterate(NULL, &rock); - win; - win = glk_window_iterate(win, &rock)) { - if (rock == KEYWINROCK) { - glk_set_window(win); - glk_window_clear(win); - glk_window_get_size(win, &width, &height); - glk_window_move_cursor(win, 0, 0); - glk_put_char('O'); - glk_window_move_cursor(win, width-1, 0); - glk_put_char('O'); - glk_window_move_cursor(win, 0, height-1); - glk_put_char('O'); - glk_window_move_cursor(win, width-1, height-1); - glk_put_char('O'); - } - } -} - -/* React to character input in a key window. */ -static void perform_key(winid_t win, glui32 key) -{ - glui32 width, height, len; - int ix; - char buf[128], keyname[64]; - - if (key == 'h' || key == 'v') { - winid_t newwin; - glui32 loc; - /* Open a new keywindow. */ - if (key == 'h') - loc = winmethod_Right | winmethod_Proportional; - else - loc = winmethod_Below | winmethod_Proportional; - newwin = glk_window_open(win, - loc, 50, wintype_TextGrid, KEYWINROCK); - /* Since the new window has rock value KEYWINROCK, the - draw_keywins() routine will redraw it. */ - if (newwin) { - /* Request character input. In this program, only keywins - get char input, so the CharInput events always call - perform_key() -- and so the new window will respond - to keys just as this one does. */ - glk_request_char_event(newwin); - /* We now have to redraw the keywins, because any or all of - them could have changed size when we opened newwin. - glk_window_open() does not generate Arrange events; we - have to do the redrawing manually. */ - draw_keywins(); - } - /* Re-request character input for this window, so that future - keys are accepted. */ - glk_request_char_event(win); - return; - } - else if (key == 'c') { - /* Close this keywindow. */ - glk_window_close(win, NULL); - /* Again, any key windows could have changed size. Also the - status window could have (if this was the last key window). */ - draw_keywins(); - draw_statuswin(); - return; - } - - /* Print a string naming the key that was just hit. */ - - switch (key) { - case ' ': - str_cpy(keyname, "space"); - break; - case keycode_Left: - str_cpy(keyname, "left"); - break; - case keycode_Right: - str_cpy(keyname, "right"); - break; - case keycode_Up: - str_cpy(keyname, "up"); - break; - case keycode_Down: - str_cpy(keyname, "down"); - break; - case keycode_Return: - str_cpy(keyname, "return"); - break; - case keycode_Delete: - str_cpy(keyname, "delete"); - break; - case keycode_Escape: - str_cpy(keyname, "escape"); - break; - case keycode_Tab: - str_cpy(keyname, "tab"); - break; - case keycode_PageUp: - str_cpy(keyname, "page up"); - break; - case keycode_PageDown: - str_cpy(keyname, "page down"); - break; - case keycode_Home: - str_cpy(keyname, "home"); - break; - case keycode_End: - str_cpy(keyname, "end"); - break; - default: - if (key >= keycode_Func1 && key < keycode_Func12) { - str_cpy(keyname, "function key"); - } - else if (key < 32) { - str_cpy(keyname, "ctrl-"); - keyname[5] = '@' + key; - keyname[6] = '\0'; - } - else if (key <= 255) { - keyname[0] = key; - keyname[1] = '\0'; - } - else { - str_cpy(keyname, "unknown key"); - } - break; - } - - str_cpy(buf, "Key: "); - str_cat(buf, keyname); - - len = str_len(buf); - - /* Print the string centered in this window. */ - glk_set_window(win); - glk_window_get_size(win, &width, &height); - glk_window_move_cursor(win, 0, height/2); - for (ix=0; ix len) - width = width-len; - else - width = 0; - - glk_window_move_cursor(win, width, height/2); - glk_put_string(buf); - - /* Re-request character input for this window, so that future - keys are accepted. */ - glk_request_char_event(win); -} - -/* React to a timer event. This just prints "Tick" in mainwin1, but it - first has to cancel line input if any is pending. */ -static void perform_timer() -{ - event_t ev; - - if (!mainwin1) - return; - - if (inputpending1) { - glk_cancel_line_event(mainwin1, &ev); - if (ev.type == evtype_LineInput) - already1 = ev.val1; - inputpending1 = FALSE; - } - - glk_set_window(mainwin1); - glk_put_string("Tick.\n"); -} - -/* This is a utility function. Given a main window, it finds the - "other" main window (if both actually exist) and cancels line - input in that other window (if input is pending.) It does not - set the output stream to point there, however. If there is only - one main window, this returns 0. */ -static winid_t print_to_otherwin(winid_t win) -{ - winid_t otherwin = NULL; - event_t ev; - - if (win == mainwin1) { - if (mainwin2) { - otherwin = mainwin2; - glk_cancel_line_event(mainwin2, &ev); - if (ev.type == evtype_LineInput) - already2 = ev.val1; - inputpending2 = FALSE; - } - } - else if (win == mainwin2) { - if (mainwin1) { - otherwin = mainwin1; - glk_cancel_line_event(mainwin1, &ev); - if (ev.type == evtype_LineInput) - already1 = ev.val1; - inputpending1 = FALSE; - } - } - - return otherwin; -} - -static void verb_help(winid_t win) -{ - glk_set_window(win); - - glk_put_string("This model only understands the following commands:\n"); - glk_put_string("HELP: Display this list.\n"); - glk_put_string("JUMP: Print a short message.\n"); - glk_put_string("YADA: Print a long paragraph.\n"); - glk_put_string("BOTH: Print a short message in both main windows.\n"); - glk_put_string("CLEAR: Clear one window.\n"); - glk_put_string("PAGE: Print thirty lines, demonstrating paging.\n"); - glk_put_string("PAGEBOTH: Print thirty lines in each window.\n"); - glk_put_string("TIMER: Turn on a timer, which ticks in the upper "); - glk_put_string("main window every three seconds.\n"); - glk_put_string("UNTIMER: Turns off the timer.\n"); - glk_put_string("CHARS: Prints the entire Latin-1 character set.\n"); - glk_put_string("QUIT: Quit and exit.\n"); -} - -static void verb_jump(winid_t win) -{ - glk_set_window(win); - - glk_put_string("You jump on the fruit, spotlessly.\n"); -} - -/* Print some text in both windows. This uses print_to_otherwin() to - find the other window and prepare it for printing. */ -static void verb_both(winid_t win) -{ - winid_t otherwin; - - glk_set_window(win); - glk_put_string("Something happens in this window.\n"); - - otherwin = print_to_otherwin(win); - - if (otherwin) { - glk_set_window(otherwin); - glk_put_string("Something happens in the other window.\n"); - } -} - -/* Clear a window. */ -static void verb_clear(winid_t win) -{ - glk_window_clear(win); -} - -/* Print thirty lines. */ -static void verb_page(winid_t win) -{ - int ix; - char buf[32]; - - glk_set_window(win); - for (ix=0; ix<30; ix++) { - num_to_str(buf, ix); - glk_put_string(buf); - glk_put_char('\n'); - } -} - -/* Print thirty lines in both windows. This gets fancy by printing - to each window alternately, without setting the output stream, - by using glk_put_string_stream() instead of glk_put_string(). - There's no particular difference; this is just a demonstration. */ -static void verb_pageboth(winid_t win) -{ - int ix; - winid_t otherwin; - strid_t str, otherstr; - char buf[32]; - - str = glk_window_get_stream(win); - otherwin = print_to_otherwin(win); - if (otherwin) - otherstr = glk_window_get_stream(otherwin); - else - otherstr = NULL; - - for (ix=0; ix<30; ix++) { - num_to_str(buf, ix); - str_cat(buf, "\n"); - glk_put_string_stream(str, buf); - if (otherstr) - glk_put_string_stream(otherstr, buf); - } -} - -/* Turn on the timer. The timer prints a tick in mainwin1 every three - seconds. */ -static void verb_timer(winid_t win) -{ - glk_set_window(win); - - if (timer_on) { - glk_put_string("The timer is already running.\n"); - return; - } - - if (glk_gestalt(gestalt_Timer, 0) == 0) { - glk_put_string("Your Glk library does not support timer events.\n"); - return; - } - - glk_put_string("A timer starts running in the upper window.\n"); - glk_request_timer_events(3000); /* Every three seconds. */ - timer_on = TRUE; -} - -/* Turn off the timer. */ -static void verb_untimer(winid_t win) -{ - glk_set_window(win); - - if (!timer_on) { - glk_put_string("The timer is not currently running.\n"); - return; - } - - glk_put_string("The timer stops running.\n"); - glk_request_timer_events(0); - timer_on = FALSE; -} - -/* Print every character, or rather try to. */ -static void verb_chars(winid_t win) -{ - int ix; - char buf[16]; - - glk_set_window(win); - - for (ix=0; ix<256; ix++) { - num_to_str(buf, ix); - glk_put_string(buf); - glk_put_string(": "); - glk_put_char(ix); - glk_put_char('\n'); - } -} - -static void verb_yada(winid_t win) -{ - /* This is a goofy (and overly ornate) way to print a long paragraph. - It just shows off line wrapping in the Glk implementation. */ - #define NUMWORDS (13) - static char *wordcaplist[NUMWORDS] = { - "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po", - "Ha", "Ni", "Na" - }; - static char *wordlist[NUMWORDS] = { - "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble", - "gazoon", "ting", "floo", "zonk", "loof", "lob", - }; - static int wcount1 = 0; - static int wcount2 = 0; - static int wstep = 1; - static int jx = 0; - int ix; - int first = TRUE; - - glk_set_window(win); - - for (ix=0; ix<85; ix++) { - if (ix > 0) { - glk_put_string(" "); - } - - if (first) { - glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]); - first = FALSE; - } - - glk_put_string(wordlist[jx]); - jx = (jx + wstep) % NUMWORDS; - wcount1++; - if (wcount1 >= NUMWORDS) { - wcount1 = 0; - wstep++; - wcount2++; - if (wcount2 >= NUMWORDS-2) { - wcount2 = 0; - wstep = 1; - } - } - - if ((ix % 17) == 16) { - glk_put_string("."); - first = TRUE; - } - } - - glk_put_char('\n'); -} - -static void verb_quit(winid_t win) -{ - glk_set_window(win); - - glk_put_string("Thanks for playing.\n"); - glk_exit(); - /* glk_exit() actually stops the process; it does not return. */ -} - -/* simple string length test */ -static int str_len(char *s1) -{ - int len; - for (len = 0; *s1; s1++) - len++; - return len; -} - -/* simple string comparison test */ -static int str_eq(char *s1, char *s2) -{ - for (; *s1 && *s2; s1++, s2++) { - if (*s1 != *s2) - return FALSE; - } - - if (*s1 || *s2) - return FALSE; - else - return TRUE; -} - -/* simple string copy */ -static char *str_cpy(char *s1, char *s2) -{ - char *orig = s1; - - for (; *s2; s1++, s2++) - *s1 = *s2; - *s1 = '\0'; - - return orig; -} - -/* simple string concatenate */ -static char *str_cat(char *s1, char *s2) -{ - char *orig = s1; - - while (*s1) - s1++; - for (; *s2; s1++, s2++) - *s1 = *s2; - *s1 = '\0'; - - return orig; -} - -/* simple number printer */ -static void num_to_str(char *buf, int num) -{ - int ix; - int size = 0; - char tmpc; - - if (num == 0) { - str_cpy(buf, "0"); - return; - } - - if (num < 0) { - buf[0] = '-'; - buf++; - num = -num; - } - - while (num) { - buf[size] = '0' + (num % 10); - size++; - num /= 10; - } - for (ix=0; ixresource_map != NULL) { - WARNING("Overwriting existing resource map.\n"); - giblorb_destroy_map(glk_data->resource_map); - glk_stream_close(glk_data->resource_file, NULL); - } - - glk_data->resource_map = newmap; - glk_data->resource_file = file; - return giblorb_err_None; -} - -/** - * giblorb_get_resource_map: - * - * This function returns the current resource map being used. Returns NULL - * if #giblorb_set_resource_map() has not been called yet. - */ -giblorb_map_t* -giblorb_get_resource_map() -{ - if(glk_data->resource_map == NULL) { - WARNING("Resource map not set yet.\n"); - } - - return glk_data->resource_map; -} diff --git a/src/resource.h b/src/resource.h deleted file mode 100644 index 7db741e..0000000 --- a/src/resource.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef RESOURCE_H -#define RESOURCE_H - -#include -#include "glk.h" -#include "gi_blorb.h" -#include "chimara-glk-private.h" -#include "magic.h" - -#endif diff --git a/src/splittest.c b/src/splittest.c deleted file mode 100644 index 7448437..0000000 --- a/src/splittest.c +++ /dev/null @@ -1,331 +0,0 @@ -#include -#include -#include "glk.h" - -#define SPACE_FACTOR 1.8 - -void center_text(winid_t win, char *text) -{ - glui32 width, height; - glk_window_get_size(win, &width, &height); - - glk_set_window(win); - glk_window_clear(win); - - if(glk_window_get_type(win) == wintype_TextGrid) { - glk_window_move_cursor(win, width / 2 - strlen(text) / 2, height / 2); - glk_put_string(text); - } else if(glk_window_get_type(win) == wintype_TextBuffer) { - int count; - for(count = 0; count < height / 2; count++) - glk_put_char('\n'); - for(count = 0; - count < (int)(SPACE_FACTOR * (width / 2 - strlen(text) / 2)); - count++) - glk_put_char(' '); - glk_put_string(text); - } -} - -void print_two_rows(winid_t win) -{ - glui32 width, height; - glk_window_get_size(win, &width, &height); - - glk_set_window(win); - glk_window_clear(win); - - glui32 x = width / 2 - 3; - glui32 y = (height - 1) / 2; - if(x < 0) - x = 0; - if(y < 0) - y = 0; - - glk_window_move_cursor(win, x, y); - glk_put_string("C: 2"); - glk_window_move_cursor(win, x + 3, y + 1); - glk_put_string("rows"); -} - -void wait_for_key(winid_t win) -{ - event_t ev; - glk_request_char_event(win); - do - glk_select(&ev); - while(ev.type != evtype_CharInput); -} - -void glk_main(void) -{ - winid_t win_a = NULL, win_b = NULL, win_c = NULL, win_d = NULL; - - fprintf(stderr, "TEST CASES FROM GLK SPEC\n\n" - "(Press a key in window A to continue each time)\n\n" - "Say you do two splits, each a 50-50 percentage split. You start\n" - "with the original window A, and split that into A and B; then\n" - "you split B into B and C.\n\n"); - - win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); - win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0); - win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0); - if(!win_a || !win_b || !win_c) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - - wait_for_key(win_a); - glk_window_close(glk_window_get_root(), NULL); - - fprintf(stderr, "Or, you could split A into A and B, and then split A\n" - "again into A and C.\n\n"); - - win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); - win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0); - win_c = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0); - if(!win_a || !win_b || !win_c) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - - wait_for_key(win_a); - glk_window_close(glk_window_get_root(), NULL); - - fprintf(stderr, "Here are more ways to perform the first example; all of\n" - "them have the same tree structure, but look different on the\n" - "screen. Here, we turn the second split (B into B/C) upside down;\n" - "we put the new window (C) above the old window (B).\n\n"); - - win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); - win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0); - win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Above, - 50, wintype_TextBuffer, 0); - if(!win_a || !win_b || !win_c) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - - wait_for_key(win_a); - glk_window_close(glk_window_get_root(), NULL); - - fprintf(stderr, "Here, we mess with the percentages. The first split (A\n" - "into A/B) is a 25-75 split, which makes B three times the size\n" - "of A. The second (B into B/C) is a 33-66 split, which makes C\n" - "twice the size of B. This looks rather like the second example,\n" - "but has a different internal structure.\n\n"); - - win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); - win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, - 75, wintype_TextBuffer, 0); - win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Below, - 67, wintype_TextBuffer, 0); - if(!win_a || !win_b || !win_c) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - - wait_for_key(win_a); - glk_window_close(glk_window_get_root(), NULL); - - fprintf(stderr, "Here, the second split (B into B/C) is vertical instead\n" - "of horizontal, with the new window (C) on the left of the old\n" - "one.\n\n"); - - win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); - win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0); - win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Left, - 50, wintype_TextBuffer, 0); - if(!win_a || !win_b || !win_c) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - - wait_for_key(win_a); - glk_window_close(glk_window_get_root(), NULL); - - fprintf(stderr, "In the following two-split process, you can see that\n" - "when a window is split, it is replaced by a new pair window, and\n" - "moves down to become one of its two children.\n\n"); - - if(!(win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0))) - return; - center_text(win_a, "A"); - wait_for_key(win_a); - - if(!(win_b = glk_window_open(win_a, - winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0))) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - wait_for_key(win_a); - - if(!(win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Left, - 50, wintype_TextBuffer, 0))) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - wait_for_key(win_a); - - glk_window_close(glk_window_get_root(), NULL); - - fprintf(stderr, "What happens when there is a conflict? The rules are\n" - "simple. Size control always flows down the tree, and the player\n" - "is at the top. Let's bring out an example: first we split A into\n" - "A and B, with a 50%% proportional split. Then we split A into A\n" - "and C, with C above, being a text grid window, and C gets a\n" - "fixed size of two rows (as measured in its own font size). A\n" - "gets whatever remains of the 50%% it had before.\n\n"); - - win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); - win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0); - win_c = glk_window_open(win_a, winmethod_Fixed | winmethod_Above, - 2, wintype_TextGrid, 0); - if(!win_a || !win_b || !win_c) - return; - center_text(win_a, "A"); - center_text(win_b, "B: 50%"); - print_two_rows(win_c); - wait_for_key(win_a); - - fprintf(stderr, "(Stage 1) Now the player stretches the window\n" - "vertically.\n\n"); - - wait_for_key(win_a); - - fprintf(stderr, "(Stage 2) Then the user maliciously starts squeezing the\n" - "window down, in stages.\n\n"); - - center_text(win_a, "A"); - center_text(win_b, "B: 50%"); - print_two_rows(win_c); - wait_for_key(win_a); - - fprintf(stderr, "(Stage 3) The logic remains the same. At stage 3,\n" - "there's no room left for A, so it winds up with zero height.\n" - "Nothing displayed in A will be visible.\n\n"); - - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - wait_for_key(win_a); - - fprintf(stderr, "(Stage 4) At stage 4, there isn't even room in the upper\n" - "50%% to give C its two rows; so it only gets one.\n\n"); - - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - wait_for_key(win_a); - - fprintf(stderr, "(Stage 5) Finally, C is squashed out of existence as\n" - "well.\n\n"); - - center_text(win_a, "A"); - center_text(win_b, "B"); - wait_for_key(win_a); - - glk_window_close(glk_window_get_root(), NULL); - - fprintf(stderr, "What happens when you split a fixed-size window? The\n" - "resulting pair window retains the same size constraint as the\n" - "original window that was split. The key window for the original\n" - "split is still the key window for that split, even though it's\n" - "now a grandchild instead of a child.\n\n"); - - if(!(win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0))) - return; - center_text(win_a, "A"); - wait_for_key(win_a); - - fprintf(stderr, "After the first split, the new pair window (O1, which\n" - "covers the whole screen) knows that its first child (A) is above\n" - "the second, and gets 50%% of its own area. (A is the key window\n" - "for this split, but a proportional split doesn't care about key\n" - "windows.)\n\n"); - - if(!(win_b = glk_window_open(win_a, - winmethod_Proportional | winmethod_Below, - 50, wintype_TextBuffer, 0))) - return; - center_text(win_a, "A: 50%"); - center_text(win_b, "B"); - wait_for_key(win_a); - - fprintf(stderr, "After the second split, all this remains true; O1 knows\n" - "that its first child gets 50%% of its space, and A is O1's key\n" - "window. But now O1's first child is O2 instead of A. The newer\n" - "pair window (O2) knows that its first child (C) is above the\n" - "second, and gets a fixed size of two rows. (As measured in C's\n" - "font, because C is O2's key window.)\n\n"); - - if(!(win_c = glk_window_open(win_a, winmethod_Fixed | winmethod_Above, 2, - wintype_TextGrid, 0))) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - print_two_rows(win_c); - wait_for_key(win_a); - - fprintf(stderr, "If we split C, now, the resulting pair will still be two\n" - "C-font rows high -- that is, tall enough for two lines of\n" - "whatever font C displays. For the sake of example, we'll do this\n" - "vertically.\n\n"); - - if(!(win_d = glk_window_open(win_c, - winmethod_Proportional | winmethod_Right, - 50, wintype_TextBuffer, 0))) - return; - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - center_text(win_d, "D"); - wait_for_key(win_a); - - fprintf(stderr, "When you close a window (and it is not the root window),\n" - "the other window in its pair takes over all the freed-up area.\n" - "Let's close D, in the current example:\n\n"); - - glk_window_close(win_d, NULL); - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_c, "C"); - wait_for_key(win_a); - - fprintf(stderr, "But what if we had closed C instead of D? We would have\n" - "gotten this:\n\n"); - - glk_window_close(glk_window_get_root(), NULL); - win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); - win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, 50, - wintype_TextBuffer, 0); - win_c = glk_window_open(win_a, winmethod_Fixed | winmethod_Above, 2, - wintype_TextGrid, 0); - win_d = glk_window_open(win_c, winmethod_Proportional | winmethod_Right, 50, - wintype_TextBuffer, 0); - glk_window_close(win_c, NULL); - center_text(win_a, "A"); - center_text(win_b, "B"); - center_text(win_d, "D"); - wait_for_key(win_a); - - glk_window_close(win_d, NULL); - glk_window_close(win_b, NULL); - glk_window_clear(win_a); - glk_set_window(win_a); - glk_put_string("That's all, folks..."); -} diff --git a/src/stream.c b/src/stream.c deleted file mode 100644 index 8043992..0000000 --- a/src/stream.c +++ /dev/null @@ -1,478 +0,0 @@ -#include "stream.h" -#include "fileref.h" -#include "magic.h" -#include -#include -#include -#include - -#include "chimara-glk-private.h" -extern ChimaraGlkPrivate *glk_data; - -/* Internal function: create a stream with a specified rock value */ -static strid_t -stream_new_common(glui32 rock, glui32 fmode, enum StreamType type) -{ - strid_t str = g_new0(struct glk_stream_struct, 1); - str->magic = MAGIC_STREAM; - str->rock = rock; - str->file_mode = fmode; - str->type = type; - - /* Add it to the global stream list */ - glk_data->stream_list = g_list_prepend(glk_data->stream_list, str); - str->stream_list = glk_data->stream_list; - - return str; -} - -/* Internal function: create a window stream to go with window. */ -strid_t -window_stream_new(winid_t window) -{ - /* Create stream and connect it to window */ - strid_t str = stream_new_common(0, filemode_Write, STREAM_TYPE_WINDOW); - str->window = window; - str->style = "normal"; - return str; -} - -/** - * glk_stream_iterate: - * @str: A stream, or %NULL. - * @rockptr: Return location for the next window's rock, or %NULL. - * - * Iterates through all the existing streams. See Iterating Through Opaque - * Objects. - * - * Returns: the next stream, or %NULL if there are no more. - */ -strid_t -glk_stream_iterate(strid_t str, glui32 *rockptr) -{ - VALID_STREAM_OR_NULL(str, return NULL); - - GList *retnode; - - if(str == NULL) - retnode = glk_data->stream_list; - else - retnode = str->stream_list->next; - strid_t retval = retnode? (strid_t)retnode->data : NULL; - - /* Store the stream's rock in rockptr */ - if(retval && rockptr) - *rockptr = glk_stream_get_rock(retval); - - return retval; -} - -/** - * glk_stream_get_rock: - * @str: A stream. - * - * Retrieves the stream @str's rock value. See Rocks. - * - * Returns: A rock value. - */ -glui32 -glk_stream_get_rock(strid_t str) -{ - VALID_STREAM(str, return 0); - return str->rock; -} - -/** - * glk_stream_set_current: - * @str: An output stream, or %NULL. - * - * Sets the current stream to @str, which must be an output stream. You may set - * the current stream to %NULL, which means the current stream is not set to - * anything. - */ -void -glk_stream_set_current(strid_t str) -{ - VALID_STREAM_OR_NULL(str, return); - - if(str != NULL && str->file_mode == filemode_Read) - { - ILLEGAL("Cannot set current stream to non output stream"); - return; - } - - glk_data->current_stream = str; -} - -/** - * glk_stream_get_current: - * - * Returns the current stream, or %NULL if there is none. - * - * Returns: A stream, or %NULL. - */ -strid_t -glk_stream_get_current() -{ - return glk_data->current_stream; -} - -/** - * glk_put_char: - * @ch: A character in Latin-1 encoding. - * - * Prints one character to the current stream. As with all basic functions, the - * character is assumed to be in the Latin-1 character encoding. See Character Encoding. - */ -void -glk_put_char(unsigned char ch) -{ - VALID_STREAM(glk_data->current_stream, return); - glk_put_char_stream(glk_data->current_stream, ch); -} - -/** - * glk_put_char_uni: - * @ch: A Unicode code point. - * - * Prints one character to the current stream. The character is assumed to be a - * Unicode code point. See Character - * Encoding. - */ -void -glk_put_char_uni(glui32 ch) -{ - VALID_STREAM(glk_data->current_stream, return); - glk_put_char_stream_uni(glk_data->current_stream, ch); -} - -/** - * glk_put_string: - * @s: A null-terminated string in Latin-1 encoding. - * - * Prints a null-terminated string to the current stream. It is exactly - * equivalent to - * |[ - * for (ptr = @s; *ptr; ptr++) - * #glk_put_char(*ptr); - * ]| - * However, it may be more efficient. - */ -void -glk_put_string(char *s) -{ - VALID_STREAM(glk_data->current_stream, return); - glk_put_string_stream(glk_data->current_stream, s); -} - -/** - * glk_put_string_uni: - * @s: A zero-terminated string of Unicode code points. - * - * Prints a string of Unicode characters to the current stream. It is equivalent - * to a series of glk_put_char_uni() calls. A string ends on a #glui32 whose - * value is 0. - */ -void -glk_put_string_uni(glui32 *s) -{ - VALID_STREAM(glk_data->current_stream, return); - glk_put_string_stream_uni(glk_data->current_stream, s); -} - -/** - * glk_put_buffer: - * @buf: An array of characters in Latin-1 encoding. - * @len: Length of @buf. - * - * Prints a block of characters to the current stream. It is exactly equivalent - * to: - * |[ - * for (i = 0; i < @len; i++) - * #glk_put_char(@buf[i]); - * ]| - * However, it may be more efficient. - */ -void -glk_put_buffer(char *buf, glui32 len) -{ - VALID_STREAM(glk_data->current_stream, return); - glk_put_buffer_stream(glk_data->current_stream, buf, len); -} - -/** - * glk_put_buffer_uni: - * @buf: An array of Unicode code points. - * @len: Length of @buf. - * - * Prints a block of Unicode characters to the current stream. It is equivalent - * to a series of glk_put_char_uni() calls. - */ -void -glk_put_buffer_uni(glui32 *buf, glui32 len) -{ - VALID_STREAM(glk_data->current_stream, return); - glk_put_buffer_stream_uni(glk_data->current_stream, buf, len); -} - -/** - * glk_stream_open_memory: - * @buf: An allocated buffer, or %NULL. - * @buflen: Length of @buf. - * @fmode: Mode in which the buffer will be opened. Must be one of - * #filemode_Read, #filemode_Write, or #filemode_ReadWrite. - * @rock: The new stream's rock value. - * - * Opens a stream which reads from or writes to a space in memory. @buf points - * to the buffer where output will be read from or written to. @buflen is the - * length of the buffer. - * - * Unicode values (characters greater than 255) cannot be written to the buffer. - * If you try, they will be stored as 0x3F ("?") characters. - * - * Returns: the new stream, or %NULL on error. - */ -strid_t -glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock) -{ - g_return_val_if_fail(fmode != filemode_WriteAppend, NULL); - - strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_MEMORY); - str->buffer = buf; - str->mark = 0; - str->buflen = buflen; - str->unicode = FALSE; - return str; -} - -/** - * glk_stream_open_memory_uni: - * @buf: An allocated buffer, or %NULL. - * @buflen: Length of @buf. - * @fmode: Mode in which the buffer will be opened. Must be one of - * #filemode_Read, #filemode_Write, or #filemode_ReadWrite. - * @rock: The new stream's rock value. - * - * Works just like glk_stream_open_memory(), except that the buffer is an array - * of 32-bit words, instead of bytes. This allows you to write and read any - * Unicode character. The @buflen is the number of words, not the number of - * bytes. - * - * Returns: the new stream, or %NULL on error. - */ -strid_t -glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, glui32 fmode, glui32 rock) -{ - g_return_val_if_fail(fmode != filemode_WriteAppend, NULL); - - strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_MEMORY); - str->ubuffer = buf; - str->mark = 0; - str->buflen = buflen; - str->unicode = TRUE; - return str; -} - -/* Internal function: create a stream using the given parameters. */ -static strid_t -file_stream_new(frefid_t fileref, glui32 fmode, glui32 rock, gboolean unicode) -{ - VALID_FILEREF(fileref, return NULL); - - gchar *modestr; - /* Binary mode is 0x000, text mode 0x100 */ - gboolean binary = !(fileref->usage & fileusage_TextMode); - switch(fmode) - { - case filemode_Read: - if(!g_file_test(fileref->filename, G_FILE_TEST_EXISTS)) { - ILLEGAL_PARAM("Tried to open a nonexistent file, '%s', in read mode", fileref->filename); - return NULL; - } - modestr = g_strdup(binary? "rb" : "r"); - break; - case filemode_Write: - modestr = g_strdup(binary? "wb" : "w"); - break; - case filemode_WriteAppend: - modestr = g_strdup(binary? "ab" : "a"); - break; - case filemode_ReadWrite: - if( g_file_test(fileref->filename, G_FILE_TEST_EXISTS) ) { - modestr = g_strdup(binary? "r+b" : "r+"); - } else { - modestr = g_strdup(binary? "w+b" : "w+"); - } - break; - default: - ILLEGAL_PARAM("Invalid file mode: %u", fmode); - return NULL; - } - - FILE *fp = g_fopen(fileref->filename, modestr); - g_free(modestr); - if(fp == NULL) { - IO_WARNING( "Error opening file", fileref->filename, g_strerror(errno) ); - return NULL; - } - - /* If they opened a file in write mode but didn't specifically get - permission to do so, complain if the file already exists */ - if(fileref->orig_filemode == filemode_Read && fmode != filemode_Read) { - gdk_threads_enter(); - - GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, - GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, - "File '%s' already exists. Overwrite?", fileref->filename); - gint response = gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - - gdk_threads_leave(); - - if(response != GTK_RESPONSE_YES) { - if(fclose(fp) != 0) - IO_WARNING( "Error closing file", fileref->filename, g_strerror(errno) ); - return NULL; - } - } - - strid_t str = stream_new_common(rock, fmode, STREAM_TYPE_FILE); - str->file_pointer = fp; - str->binary = binary; - str->unicode = unicode; - str->filename = g_filename_to_utf8(fileref->filename, -1, NULL, NULL, NULL); - if(str->filename == NULL) - str->filename = g_strdup("Unknown file name"); /* fail silently */ - - return str; -} - -/** - * glk_stream_open_file: - * @fileref: Indicates the file which will be opened. - * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read, - * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite. - * @rock: The new stream's rock value. - * - * Opens a stream which reads to or writes from a disk file. If @fmode is - * #filemode_Read, the file must already exist; for the other modes, an empty - * file is created if none exists. If @fmode is #filemode_Write, and the file - * already exists, it is truncated down to zero length (an empty file). If - * @fmode is #filemode_WriteAppend, the file mark is set to the end of the - * file. - * - * When writing in binary mode, Unicode values (characters greater than 255) - * cannot be written to the file. If you try, they will be stored as 0x3F - * ("?") characters. In text mode, Unicode values may be stored - * exactly, approximated, or abbreviated, depending on what the platform's text - * files support. - * - * Returns: A new stream, or %NULL if the file operation failed. - */ -strid_t -glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock) -{ - return file_stream_new(fileref, fmode, rock, FALSE); -} - -/** - * glk_stream_open_file_uni: - * @fileref: Indicates the file which will be opened. - * @fmode: Mode in which the file will be opened. Can be any of #filemode_Read, - * #filemode_Write, #filemode_WriteAppend, or #filemode_ReadWrite. - * @rock: The new stream's rock value. - * - * This works just like glk_stream_open_file(), except that in binary mode, - * characters are written and read as four-byte (big-endian) values. This - * allows you to write any Unicode character. - * - * In text mode, the file is written and read in a platform-dependent way, which - * may or may not handle all Unicode characters. A text-mode file created with - * glk_stream_open_file_uni() may have the same format as a text-mode file - * created with glk_stream_open_file(); or it may use a more Unicode-friendly - * format. - * - * Returns: A new stream, or %NULL if the file operation failed. - */ -strid_t -glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, glui32 rock) -{ - return file_stream_new(fileref, fmode, rock, TRUE); -} - -/** - * glk_stream_close: - * @str: Stream to close. - * @result: Pointer to a #stream_result_t, or %NULL. - * - * Closes the stream @str. The @result argument points to a structure which is - * filled in with the final character counts of the stream. If you do not care - * about these, you may pass %NULL as the @result argument. - * - * If @str is the current output stream, the current output stream is set to - * %NULL. - * - * You cannot close window streams; use glk_window_close() instead. See Window Opening, - * Closing, and Constraints. - */ -void -glk_stream_close(strid_t str, stream_result_t *result) -{ - VALID_STREAM(str, return); - - /* Free resources associated with one specific type of stream */ - switch(str->type) - { - case STREAM_TYPE_WINDOW: - ILLEGAL("Attempted to close a window stream. Use glk_window_close() instead."); - return; - - case STREAM_TYPE_MEMORY: - /* Do nothing */ - break; - - case STREAM_TYPE_FILE: - if(fclose(str->file_pointer) != 0) - IO_WARNING( "Failed to close file", str->filename, g_strerror(errno) ); - g_free(str->filename); - break; - default: - ILLEGAL_PARAM("Unknown stream type: %u", str->type); - return; - } - - stream_close_common(str, result); -} - -/* Internal function: Stuff to do upon closing any type of stream. */ -void -stream_close_common(strid_t str, stream_result_t *result) -{ - /* Remove the stream from the global stream list */ - glk_data->stream_list = g_list_delete_link(glk_data->stream_list, str->stream_list); - - /* If it was the current output stream, set that to NULL */ - if(glk_data->current_stream == str) - glk_data->current_stream = NULL; - - /* If it was one or more windows' echo streams, set those to NULL */ - winid_t win; - for(win = glk_window_iterate(NULL, NULL); win; - win = glk_window_iterate(win, NULL)) - if(win->echo_stream == str) - win->echo_stream = NULL; - - /* Return the character counts */ - if(result) - { - result->readcount = str->read_count; - result->writecount = str->write_count; - } - - str->magic = MAGIC_FREE; - g_free(str); -} diff --git a/src/stream.h b/src/stream.h deleted file mode 100644 index 0ea398e..0000000 --- a/src/stream.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef STREAM_H -#define STREAM_H - -#include -#include "glk.h" -#include "window.h" - -enum StreamType -{ - STREAM_TYPE_WINDOW, - STREAM_TYPE_MEMORY, - STREAM_TYPE_FILE -}; - -/** - * glk_stream_struct: - * - * This is an opaque structure (see - * Opaque Structures and should not be accessed directly. - */ -struct glk_stream_struct -{ - /*< private >*/ - glui32 magic, rock; - /* Pointer to the list node in the global stream list that contains this - stream */ - GList* stream_list; - /* Stream parameters */ - glui32 file_mode; - glui32 read_count; - glui32 write_count; - enum StreamType type; - /* Specific to window stream: the window this stream is connected to */ - winid_t window; - /* For memory and file streams */ - gboolean unicode; - /* Specific to memory streams */ - gchar *buffer; - glui32 *ubuffer; - glui32 mark; - glui32 buflen; - /* Specific to file streams */ - FILE *file_pointer; - gboolean binary; - gchar *filename; /* Displayable filename in UTF-8 for error handling */ - - gchar *style; /* Name of the current style */ -}; - -G_GNUC_INTERNAL strid_t window_stream_new(winid_t window); -G_GNUC_INTERNAL void stream_close_common(strid_t str, stream_result_t *result); -#endif diff --git a/src/strio.c b/src/strio.c deleted file mode 100644 index 921b75c..0000000 --- a/src/strio.c +++ /dev/null @@ -1,1175 +0,0 @@ -#include "charset.h" -#include "magic.h" -#include "stream.h" -#include -#include -#include -#include -#include - -/* - * - **************** WRITING FUNCTIONS ******************************************** - * - */ - -/* Internal function: write a UTF-8 string to a text grid window's text buffer. */ -static void -write_utf8_to_grid(winid_t win, gchar *s) -{ - if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) - { - ILLEGAL("Tried to print to a text grid window with line input pending."); - return; - } - - /* Number of characters to insert */ - glong length = g_utf8_strlen(s, -1); - glong chars_left = length; - - gdk_threads_enter(); - - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - GtkTextMark *cursor = gtk_text_buffer_get_mark(buffer, "cursor_position"); - - /* Get cursor position */ - GtkTextIter start; - gtk_text_buffer_get_iter_at_mark(buffer, &start, cursor); - /* Spaces available on this line */ - gint available_space = win->width - gtk_text_iter_get_line_offset(&start); - - while(chars_left > available_space && !gtk_text_iter_is_end(&start)) - { - GtkTextIter end = start; - gtk_text_iter_forward_to_line_end(&end); - gtk_text_buffer_delete(buffer, &start, &end); - gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), available_space); - chars_left -= available_space; - gtk_text_iter_forward_line(&start); - available_space = win->width; - } - if(!gtk_text_iter_is_end(&start)) - { - GtkTextIter end = start; - gtk_text_iter_forward_chars(&end, chars_left); - gtk_text_buffer_delete(buffer, &start, &end); - gtk_text_buffer_insert(buffer, &start, s + (length - chars_left), -1); - } - - gtk_text_buffer_move_mark(buffer, cursor, &start); - - gdk_threads_leave(); -} - -/* Internal function: write a UTF-8 string to a text buffer window's text buffer. */ -static void -write_utf8_to_window(winid_t win, gchar *s) -{ - if(win->input_request_type == INPUT_REQUEST_LINE || win->input_request_type == INPUT_REQUEST_LINE_UNICODE) - { - ILLEGAL("Tried to print to a text buffer window with line input pending."); - return; - } - - gdk_threads_enter(); - - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - - GtkTextIter iter; - gtk_text_buffer_get_end_iter(buffer, &iter); - gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, s, -1, win->window_stream->style, NULL); - - gdk_threads_leave(); -} - -/* Internal function: write a Latin-1 buffer with length to a stream. */ -static void -write_buffer_to_stream(strid_t str, gchar *buf, glui32 len) -{ - switch(str->type) - { - case STREAM_TYPE_WINDOW: - /* Each window type has a different way of printing to it */ - switch(str->window->type) - { - /* Printing to these windows' streams does nothing */ - case wintype_Blank: - case wintype_Pair: - case wintype_Graphics: - str->write_count += len; - break; - - /* Text grid window */ - case wintype_TextGrid: - { - gchar *utf8 = convert_latin1_to_utf8(buf, len); - if(utf8 != NULL) - { - /* FIXME: What to do if string contains \n? Split the input string at newlines and write each string separately? */ - write_utf8_to_grid(str->window, utf8); - g_free(utf8); - } - } - str->write_count += len; - break; - - /* Text buffer window */ - case wintype_TextBuffer: - { - gchar *utf8 = convert_latin1_to_utf8(buf, len); - if(utf8 != NULL) - { - write_utf8_to_window(str->window, utf8); - g_free(utf8); - } - } - str->write_count += len; - break; - default: - ILLEGAL_PARAM("Unknown window type: %u", str->window->type); - } - - /* Now write the same buffer to the window's echo stream */ - if(str->window->echo_stream != NULL) - write_buffer_to_stream(str->window->echo_stream, buf, len); - - break; - - case STREAM_TYPE_MEMORY: - if(str->unicode && str->ubuffer) - { - int foo = 0; - while(str->mark < str->buflen && foo < len) - str->ubuffer[str->mark++] = (unsigned char)buf[foo++]; - } - if(!str->unicode && str->buffer) - { - int copycount = MIN(len, str->buflen - str->mark); - memmove(str->buffer + str->mark, buf, copycount); - str->mark += copycount; - } - - str->write_count += len; - break; - - case STREAM_TYPE_FILE: - if(str->binary) - { - if(str->unicode) - { - gchar *writebuffer = convert_latin1_to_ucs4be_string(buf, len); - fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer); - g_free(writebuffer); - } - else /* Regular file */ - { - fwrite(buf, sizeof(gchar), len, str->file_pointer); - } - } - else /* Text mode is the same for Unicode and regular files */ - { - gchar *utf8 = convert_latin1_to_utf8(buf, len); - if(utf8 != NULL) - { - g_fprintf(str->file_pointer, "%s", utf8); - g_free(utf8); - } - } - - str->write_count += len; - break; - default: - ILLEGAL_PARAM("Unknown stream type: %u", str->type); - } -} - -/* Internal function: write a Unicode buffer with length to a stream. */ -static void -write_buffer_to_stream_uni(strid_t str, glui32 *buf, glui32 len) -{ - switch(str->type) - { - case STREAM_TYPE_WINDOW: - /* Each window type has a different way of printing to it */ - switch(str->window->type) - { - /* Printing to these windows' streams does nothing */ - case wintype_Blank: - case wintype_Pair: - case wintype_Graphics: - str->write_count += len; - break; - - /* Text grid window */ - case wintype_TextGrid: - { - gchar *utf8 = convert_ucs4_to_utf8(buf, len); - if(utf8 != NULL) - { - /* FIXME: What to do if string contains \n? Split the input string at newlines and write each string separately? */ - write_utf8_to_grid(str->window, utf8); - g_free(utf8); - } - } - str->write_count += len; - break; - - /* Text buffer window */ - case wintype_TextBuffer: - { - gchar *utf8 = convert_ucs4_to_utf8(buf, len); - if(utf8 != NULL) - { - write_utf8_to_window(str->window, utf8); - g_free(utf8); - } - } - str->write_count += len; - break; - default: - ILLEGAL_PARAM("Unknown window type: %u", str->window->type); - } - - /* Now write the same buffer to the window's echo stream */ - if(str->window->echo_stream != NULL) - write_buffer_to_stream_uni(str->window->echo_stream, buf, len); - - break; - - case STREAM_TYPE_MEMORY: - if(str->unicode && str->ubuffer) - { - int copycount = MIN(len, str->buflen - str->mark); - memmove(str->ubuffer + str->mark, buf, copycount * sizeof(glui32)); - str->mark += copycount; - } - if(!str->unicode && str->buffer) - { - gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len); - int copycount = MIN(len, str->buflen - str->mark); - memmove(str->buffer + str->mark, latin1, copycount); - g_free(latin1); - str->mark += copycount; - } - - str->write_count += len; - break; - - case STREAM_TYPE_FILE: - if(str->binary) - { - if(str->unicode) - { - gchar *writebuffer = convert_ucs4_to_ucs4be_string(buf, len); - fwrite(writebuffer, sizeof(gchar), len * 4, str->file_pointer); - g_free(writebuffer); - } - else /* Regular file */ - { - gchar *latin1 = convert_ucs4_to_latin1_binary(buf, len); - fwrite(latin1, sizeof(gchar), len, str->file_pointer); - g_free(latin1); - } - } - else /* Text mode is the same for Unicode and regular files */ - { - gchar *utf8 = convert_ucs4_to_utf8(buf, len); - if(utf8 != NULL) - { - g_fprintf(str->file_pointer, "%s", utf8); - g_free(utf8); - } - } - - str->write_count += len; - break; - default: - ILLEGAL_PARAM("Unknown stream type: %u", str->type); - } -} - -/** - * glk_put_char_stream: - * @str: An output stream. - * @ch: A character in Latin-1 encoding. - * - * The same as glk_put_char(), except that you specify a stream @str to print - * to, instead of using the current stream. It is illegal for @str to be %NULL, - * or an input-only stream. - */ -void -glk_put_char_stream(strid_t str, unsigned char ch) -{ - VALID_STREAM(str, return); - g_return_if_fail(str->file_mode != filemode_Read); - - write_buffer_to_stream(str, (gchar *)&ch, 1); -} - -/** - * glk_put_char_stream_uni: - * @str: An output stream. - * @ch: A Unicode code point. - * - * The same as glk_put_char_uni(), except that you specify a stream @str to - * print to, instead of using the current stream. It is illegal for @str to be - * %NULL, or an input-only stream. - */ -void -glk_put_char_stream_uni(strid_t str, glui32 ch) -{ - VALID_STREAM(str, return); - g_return_if_fail(str->file_mode != filemode_Read); - - write_buffer_to_stream_uni(str, &ch, 1); -} - -/** - * glk_put_string_stream: - * @str: An output stream. - * @s: A null-terminated string in Latin-1 encoding. - * - * The same as glk_put_string(), except that you specify a stream @str to print - * to, instead of using the current stream. It is illegal for @str to be %NULL, - * or an input-only stream. - */ -void -glk_put_string_stream(strid_t str, char *s) -{ - VALID_STREAM(str, return); - g_return_if_fail(str->file_mode != filemode_Read); - - write_buffer_to_stream(str, s, strlen(s)); -} - -/** - * glk_put_string_stream_uni: - * @str: An output stream. - * @s: A null-terminated array of Unicode code points. - * - * The same as glk_put_string_uni(), except that you specify a stream @str to - * print to, instead of using the current stream. It is illegal for @str to be - * %NULL, or an input-only stream. - */ -void -glk_put_string_stream_uni(strid_t str, glui32 *s) -{ - VALID_STREAM(str, return); - g_return_if_fail(str->file_mode != filemode_Read); - - /* An impromptu strlen() for glui32 arrays */ - glong len = 0; - glui32 *ptr = s; - while(*ptr++) - len++; - write_buffer_to_stream_uni(str, s, len); -} - -/** - * glk_put_buffer_stream: - * @str: An output stream. - * @buf: An array of characters in Latin-1 encoding. - * @len: Length of @buf. - * - * The same as glk_put_buffer(), except that you specify a stream @str to print - * to, instead of using the current stream. It is illegal for @str to be %NULL, - * or an input-only stream. - */ -void -glk_put_buffer_stream(strid_t str, char *buf, glui32 len) -{ - VALID_STREAM(str, return); - g_return_if_fail(str->file_mode != filemode_Read); - - write_buffer_to_stream(str, buf, len); -} - -/** - * glk_put_buffer_stream_uni: - * @str: An output stream. - * @buf: An array of Unicode code points. - * @len: Length of @buf. - * - * The same as glk_put_buffer_uni(), except that you specify a stream @str to - * print to, instead of using the current stream. It is illegal for @str to be - * %NULL, or an input-only stream. - */ -void -glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) -{ - VALID_STREAM(str, return); - g_return_if_fail(str->file_mode != filemode_Read); - - write_buffer_to_stream_uni(str, buf, len); -} - -/* - * - **************** READING FUNCTIONS ******************************************** - * - */ - -/* Internal function: Read one big-endian four-byte character from file fp and -return it as a Unicode code point, or -1 on EOF */ -static glsi32 -read_ucs4be_char_from_file(FILE *fp) -{ - unsigned char readbuffer[4]; - if(fread(readbuffer, sizeof(unsigned char), 4, fp) < 4) - return -1; /* EOF */ - return - readbuffer[0] << 24 | - readbuffer[1] << 16 | - readbuffer[2] << 8 | - readbuffer[3]; -} - -/* Internal function: Read one UTF-8 character, which may be more than one byte, -from file fp and return it as a Unicode code point, or -1 on EOF */ -static glsi32 -read_utf8_char_from_file(FILE *fp) -{ - gchar readbuffer[4] = {0, 0, 0, 0}; /* Max UTF-8 width */ - int foo; - gunichar charresult = (gunichar)-2; - for(foo = 0; foo < 4 && charresult == (gunichar)-2; foo++) - { - int ch = fgetc(fp); - if(ch == EOF) - return -1; - readbuffer[foo] = (gchar)ch; - charresult = g_utf8_get_char_validated(readbuffer, foo + 1); - /* charresult is -1 if invalid, -2 if incomplete, and the unicode code - point otherwise */ - } - /* Silently return unknown characters as 0xFFFD, Replacement Character */ - if(charresult == (gunichar)-1 || charresult == (gunichar)-2) - return 0xFFFD; - return charresult; -} - -/* Internal function: Tell whether this code point is a Unicode newline. The -file pointer and eight-bit flag are included in case the newline is a CR -(U+000D). If the next character is LF (U+000A) then it also belongs to the -newline. */ -static gboolean -is_unicode_newline(glsi32 ch, FILE *fp, gboolean utf8) -{ - if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) - return TRUE; - if(ch == 0x0D) { - glsi32 ch2 = utf8? read_utf8_char_from_file(fp) : - read_ucs4be_char_from_file(fp); - if(ch2 != 0x0A) - if(fseek(fp, utf8? -1 : -4, SEEK_CUR) == -1); - WARNING_S("Seek failed on stream", g_strerror(errno) ); - return TRUE; - } - return FALSE; -} - -/* Internal function: Read one character from a stream. Returns a value which - can be returned unchanged by glk_get_char_stream_uni(), but - glk_get_char_stream() must replace high values by the placeholder character. */ -static glsi32 -get_char_stream_common(strid_t str) -{ - switch(str->type) - { - case STREAM_TYPE_MEMORY: - if(str->unicode) - { - if(!str->ubuffer || str->mark >= str->buflen) - return -1; - glui32 ch = str->ubuffer[str->mark++]; - str->read_count++; - return ch; - } - else - { - if(!str->buffer || str->mark >= str->buflen) - return -1; - unsigned char ch = str->buffer[str->mark++]; - str->read_count++; - return ch; - } - break; - - case STREAM_TYPE_FILE: - if(str->binary) - { - if(str->unicode) - { - glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); - if(ch == -1) - return -1; - str->read_count++; - return ch; - } - else /* Regular file */ - { - int ch = fgetc(str->file_pointer); - if(ch == EOF) - return -1; - - str->read_count++; - return ch; - } - } - else /* Text mode is the same for Unicode and regular files */ - { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); - if(ch == -1) - return -1; - - str->read_count++; - return ch; - } - default: - ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); - return -1; - } -} - -/** - * glk_get_char_stream: - * @str: An input stream. - * - * Reads one character from the stream @str. (There is no notion of a - * current input stream.) It is illegal for @str to be %NULL, or - * an output-only stream. - * - * The result will be between 0 and 255. As with all basic text functions, Glk - * assumes the Latin-1 encoding. See Character Encoding. If the end - * of the stream has been reached, the result will be -1. - * - * - * Note that high-bit characters (128..255) are not - * returned as negative numbers. - * - * - * If the stream contains Unicode data — for example, if it was created - * with glk_stream_open_file_uni() or glk_stream_open_memory_uni() — then - * characters beyond 255 will be returned as 0x3F ("?"). - * - * It is usually more efficient to read several characters at once with - * glk_get_buffer_stream() or glk_get_line_stream(), as opposed to calling - * glk_get_char_stream() several times. - * - * Returns: A character value between 0 and 255, or -1 on end of stream. - */ -glsi32 -glk_get_char_stream(strid_t str) -{ - VALID_STREAM(str, return -1); - g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1); - - glsi32 ch = get_char_stream_common(str); - return (ch > 0xFF)? PLACEHOLDER : ch; -} - -/** - * glk_get_char_stream_uni: - * @str: An input stream. - * - * Reads one character from the stream @str. The result will be between 0 and - * 0x7FFFFFFF. If the end of the stream has been reached, the result will be -1. - * - * Returns: A value between 0 and 0x7FFFFFFF, or -1 on end of stream. - */ -glsi32 -glk_get_char_stream_uni(strid_t str) -{ - VALID_STREAM(str, return -1); - g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, -1); - - return get_char_stream_common(str); -} - -/** - * glk_get_buffer_stream: - * @str: An input stream. - * @buf: A buffer with space for at least @len characters. - * @len: The number of characters to read. - * - * Reads @len characters from @str, unless the end of stream is reached first. - * No terminal null is placed in the buffer. - * - * Returns: The number of characters actually read. - */ -glui32 -glk_get_buffer_stream(strid_t str, char *buf, glui32 len) -{ - VALID_STREAM(str, return 0); - g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); - g_return_val_if_fail(buf != NULL, 0); - - switch(str->type) - { - case STREAM_TYPE_MEMORY: - { - int copycount = 0; - if(str->unicode) - { - while(copycount < len && str->ubuffer && str->mark < str->buflen) - { - glui32 ch = str->ubuffer[str->mark++]; - buf[copycount++] = (ch > 0xFF)? '?' : (char)ch; - } - } - else - { - if(str->buffer) /* if not, copycount stays 0 */ - copycount = MIN(len, str->buflen - str->mark); - memmove(buf, str->buffer + str->mark, copycount); - str->mark += copycount; - } - - str->read_count += copycount; - return copycount; - } - case STREAM_TYPE_FILE: - if(str->binary) - { - if(str->unicode) /* Binary file with 4-byte characters */ - { - /* Read len characters of 4 bytes each */ - unsigned char *readbuffer = g_new0(unsigned char, 4 * len); - size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer); - /* If there was an incomplete character */ - if(count % 4 != 0) - { - count -= count % 4; - WARNING("Incomplete character in binary Unicode file"); - } - - int foo; - for(foo = 0; foo < count; foo += 4) - { - glsi32 ch = readbuffer[foo] << 24 - | readbuffer[foo + 1] << 16 - | readbuffer[foo + 2] << 8 - | readbuffer[foo + 3]; - buf[foo / 4] = (ch > 255)? 0x3F : (char)ch; - } - g_free(readbuffer); - str->read_count += count / 4; - return count / 4; - } - else /* Regular binary file */ - { - size_t count = fread(buf, sizeof(char), len, str->file_pointer); - str->read_count += count; - return count; - } - } - else /* Text mode is the same for Unicode and regular files */ - { - /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len; foo++) - { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); - if(ch == -1) - break; - str->read_count++; - buf[foo] = (ch > 0xFF)? 0x3F : (gchar)ch; - } - return foo; - } - default: - ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); - return 0; - } -} - -/** - * glk_get_buffer_stream_uni: - * @str: An input stream. - * @buf: A buffer with space for at least @len Unicode code points. - * @len: The number of characters to read. - * - * Reads @len Unicode characters from @str, unless the end of stream is reached - * first. No terminal null is placed in the buffer. - * - * Returns: The number of Unicode characters actually read. - */ -glui32 -glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len) -{ - VALID_STREAM(str, return 0); - g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); - g_return_val_if_fail(buf != NULL, 0); - - switch(str->type) - { - case STREAM_TYPE_MEMORY: - { - int copycount = 0; - if(str->unicode) - { - if(str->ubuffer) /* if not, copycount stays 0 */ - copycount = MIN(len, str->buflen - str->mark); - memmove(buf, str->ubuffer + str->mark, copycount * 4); - str->mark += copycount; - } - else - { - while(copycount < len && str->buffer && str->mark < str->buflen) - { - unsigned char ch = str->buffer[str->mark++]; - buf[copycount++] = ch; - } - } - - str->read_count += copycount; - return copycount; - } - case STREAM_TYPE_FILE: - if(str->binary) - { - if(str->unicode) /* Binary file with 4-byte characters */ - { - /* Read len characters of 4 bytes each */ - unsigned char *readbuffer = g_new0(unsigned char, 4 * len); - size_t count = fread(readbuffer, sizeof(unsigned char), 4 * len, str->file_pointer); - /* If there was an incomplete character */ - if(count % 4 != 0) - { - count -= count % 4; - WARNING("Incomplete character in binary Unicode file"); - } - - int foo; - for(foo = 0; foo < count; foo += 4) - buf[foo / 4] = readbuffer[foo] << 24 - | readbuffer[foo + 1] << 16 - | readbuffer[foo + 2] << 8 - | readbuffer[foo + 3]; - g_free(readbuffer); - str->read_count += count / 4; - return count / 4; - } - else /* Regular binary file */ - { - unsigned char *readbuffer = g_new0(unsigned char, len); - size_t count = fread(readbuffer, sizeof(unsigned char), len, str->file_pointer); - int foo; - for(foo = 0; foo < count; foo++) - buf[foo] = readbuffer[foo]; - g_free(readbuffer); - str->read_count += count; - return count; - } - } - else /* Text mode is the same for Unicode and regular files */ - { - /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len; foo++) - { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); - if(ch == -1) - break; - str->read_count++; - buf[foo] = ch; - } - return foo; - } - default: - ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); - return 0; - } -} - -/** - * glk_get_line_stream: - * @str: An input stream. - * @buf: A buffer with space for at least @len characters. - * @len: The number of characters to read, plus one. - * - * Reads characters from @str, until either - * - * @len - 1 - * @len - 1 - * - * characters have been read or a newline has been read. It then puts a - * terminal null ('\0') aracter on - * the end. It returns the number of characters actually read, including the - * newline (if there is one) but not including the terminal null. - * - * Returns: The number of characters actually read. - */ -glui32 -glk_get_line_stream(strid_t str, char *buf, glui32 len) -{ - VALID_STREAM(str, return 0); - g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); - g_return_val_if_fail(buf != NULL, 0); - - switch(str->type) - { - case STREAM_TYPE_MEMORY: - { - int copycount = 0; - if(str->unicode) - { - /* Do it character-by-character */ - while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) - { - glui32 ch = str->ubuffer[str->mark++]; - /* Check for Unicode newline; slightly different than - in file streams */ - if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) - { - buf[copycount++] = '\n'; - break; - } - if(ch == 0x0D) - { - if(str->ubuffer[str->mark] == 0x0A) - str->mark++; /* skip past next newline */ - buf[copycount++] = '\n'; - break; - } - buf[copycount++] = (ch > 0xFF)? '?' : (char)ch; - } - buf[copycount] = '\0'; - } - else - { - if(str->buffer) /* if not, copycount stays 0 */ - copycount = MIN(len - 1, str->buflen - str->mark); - char *endptr = memccpy(buf, str->buffer + str->mark, '\n', copycount); - if(endptr) /* newline was found */ - copycount = endptr - buf; /* Real copy count */ - buf[copycount] = '\0'; - str->mark += copycount; - } - - str->read_count += copycount; - return copycount; - } - case STREAM_TYPE_FILE: - if(str->binary) - { - if(str->unicode) /* Binary file with 4-byte characters */ - { - /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len - 1; foo++) - { - glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); - if(ch == -1) - { - buf[foo] = '\0'; - return foo - 1; - } - str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, FALSE)) - { - buf[foo] = '\n'; - buf[foo + 1] = '\0'; - return foo; - } - buf[foo] = (ch > 0xFF)? '?' : (char)ch; - } - buf[len] = '\0'; - return foo; - } - else /* Regular binary file */ - { - fgets(buf, len, str->file_pointer); - str->read_count += strlen(buf); - return strlen(buf); - } - } - else /* Text mode is the same for Unicode and regular files */ - { - /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len - 1; foo++) - { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); - if(ch == -1) - { - buf[foo] = '\0'; - return foo - 1; - } - str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, TRUE)) - { - buf[foo] = '\n'; - buf[foo + 1] = '\0'; - return foo; - } - buf[foo] = (ch > 0xFF)? 0x3F : (char)ch; - } - buf[len] = '\0'; - return foo; - } - default: - ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); - return 0; - } -} - -/** - * glk_get_line_stream_uni: - * @str: An input stream. - * @buf: A buffer with space for at least @len Unicode code points. - * @len: The number of characters to read, plus one. - * - * Reads Unicode characters from @str, until either - * - * @len - 1 - * @len - 1 - * - * Unicode characters have been read or a newline has been read. It then puts a - * terminal null (a zero value) on the end. - * - * Returns: The number of characters actually read, including the newline (if - * there is one) but not including the terminal null. - */ -glui32 -glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len) -{ - VALID_STREAM(str, return 0); - g_return_val_if_fail(str->file_mode == filemode_Read || str->file_mode == filemode_ReadWrite, 0); - g_return_val_if_fail(buf != NULL, 0); - - switch(str->type) - { - case STREAM_TYPE_MEMORY: - { - int copycount = 0; - if(str->unicode) - { - /* Do it character-by-character */ - while(copycount < len - 1 && str->ubuffer && str->mark < str->buflen) - { - glui32 ch = str->ubuffer[str->mark++]; - /* Check for Unicode newline; slightly different than - in file streams */ - if(ch == 0x0A || ch == 0x85 || ch == 0x0C || ch == 0x2028 || ch == 0x2029) - { - buf[copycount++] = '\n'; - break; - } - if(ch == 0x0D) - { - if(str->ubuffer[str->mark] == 0x0A) - str->mark++; /* skip past next newline */ - buf[copycount++] = '\n'; - break; - } - buf[copycount++] = ch; - } - buf[copycount] = '\0'; - } - else - { - /* No recourse to memccpy(), so do it character-by-character */ - while(copycount < len - 1 && str->buffer && str->mark < str->buflen) - { - gchar ch = str->buffer[str->mark++]; - /* Check for newline */ - if(ch == '\n') /* Also check for \r and \r\n? */ - { - buf[copycount++] = '\n'; - break; - } - buf[copycount++] = (unsigned char)ch; - } - buf[copycount] = 0; - } - - str->read_count += copycount; - return copycount; - } - case STREAM_TYPE_FILE: - if(str->binary) - { - if(str->unicode) /* Binary file with 4-byte characters */ - { - /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len - 1; foo++) - { - glsi32 ch = read_ucs4be_char_from_file(str->file_pointer); - if(ch == -1) - { - buf[foo] = 0; - return foo - 1; - } - str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, FALSE)) - { - buf[foo] = ch; /* Preserve newline types??? */ - buf[foo + 1] = 0; - return foo; - } - buf[foo] = ch; - } - buf[len] = 0; - return foo; - } - else /* Regular binary file */ - { - gchar *readbuffer = g_new0(gchar, len); - fgets(readbuffer, len, str->file_pointer); - glui32 count = strlen(readbuffer) + 1; /* Copy terminator */ - int foo; - for(foo = 0; foo < count; foo++) - buf[foo] = (unsigned char)(readbuffer[foo]); - str->read_count += count; - return count; - } - } - else /* Text mode is the same for Unicode and regular files */ - { - /* Do it character-by-character */ - int foo; - for(foo = 0; foo < len - 1; foo++) - { - glsi32 ch = read_utf8_char_from_file(str->file_pointer); - if(ch == -1) - { - buf[foo] = 0; - return foo - 1; - } - str->read_count++; - if(is_unicode_newline(ch, str->file_pointer, TRUE)) - { - buf[foo] = ch; /* Preserve newline types??? */ - buf[foo + 1] = 0; - return foo; - } - buf[foo] = ch; - } - buf[len] = 0; - return foo; - } - default: - ILLEGAL_PARAM("Reading illegal on stream type: %u", str->type); - return 0; - } -} - -/* - * - **************** SEEKING FUNCTIONS ******************************************** - * - */ - -/** - * glk_stream_get_position: - * @str: A file or memory stream. - * - * Returns the position of the read/write mark in @str. For memory streams and - * binary file streams, this is exactly the number of characters read or written - * from the beginning of the stream (unless you have moved the mark with - * glk_stream_set_position().) For text file streams, matters are more - * ambiguous, since (for example) writing one byte to a text file may store more - * than one character in the platform's native encoding. You can only be sure - * that the position increases as you read or write to the file. - * - * Additional complication: for Latin-1 memory and file streams, a character is - * a byte. For Unicode memory and file streams (those created by - * glk_stream_open_file_uni() and glk_stream_open_memory_uni()), a character is - * a 32-bit word. So in a binary Unicode file, positions are multiples of four - * bytes. - * - * - * If this bothers you, don't use binary Unicode files. I don't think they're - * good for much anyhow. - * - * - * Returns: position of the read/write mark in @str. - */ -glui32 -glk_stream_get_position(strid_t str) -{ - VALID_STREAM(str, return 0); - - switch(str->type) - { - case STREAM_TYPE_MEMORY: - return str->mark; - case STREAM_TYPE_FILE: - return ftell(str->file_pointer); - default: - ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type); - return 0; - } -} - -/** - * glk_stream_set_position: - * @str: A file or memory stream. - * @pos: The position to set the mark to, relative to @seekmode. - * @seekmode: One of #seekmode_Start, #seekmode_Current, or #seekmode_End. - * - * Sets the position of the read/write mark in @str. The position is controlled - * by @pos, and the meaning of @pos is controlled by @seekmode. See the - * seekmode_ constants below. - * - * It is illegal to specify a position before the beginning or after the end of - * the file. - * - * In binary files, the mark position is exact — it corresponds with the - * number of characters you have read or written. In text files, this mapping - * can vary, because of linefeed conventions or other character-set - * approximations. See Streams. - * glk_stream_set_position() and glk_stream_get_position() measure positions in - * the platform's native encoding — after character cookery. Therefore, - * in a text stream, it is safest to use glk_stream_set_position() only to move - * to the beginning or end of a file, or to a position determined by - * glk_stream_get_position(). - * - * Again, in Latin-1 streams, characters are bytes. In Unicode streams, - * characters are 32-bit words, or four bytes each. - */ -void -glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode) -{ - VALID_STREAM(str, return); - g_return_if_fail(!(seekmode == seekmode_Start && pos < 0)); - g_return_if_fail(!(seekmode == seekmode_End || pos > 0)); - - switch(str->type) - { - case STREAM_TYPE_MEMORY: - switch(seekmode) - { - case seekmode_Start: str->mark = pos; break; - case seekmode_Current: str->mark += pos; break; - case seekmode_End: str->mark = str->buflen + pos; break; - default: - g_return_if_reached(); - return; - } - break; - case STREAM_TYPE_FILE: - { - int whence; - switch(seekmode) - { - case seekmode_Start: whence = SEEK_SET; break; - case seekmode_Current: whence = SEEK_CUR; break; - case seekmode_End: whence = SEEK_END; break; - default: - g_return_if_reached(); - return; - } - if(fseek(str->file_pointer, pos, whence) == -1) - WARNING("Seek failed on file stream"); - break; - } - default: - ILLEGAL_PARAM("Seeking illegal on stream type: %u", str->type); - return; - } -} - diff --git a/src/style.c b/src/style.c deleted file mode 100644 index d2eb235..0000000 --- a/src/style.c +++ /dev/null @@ -1,197 +0,0 @@ -#include "style.h" - -extern ChimaraGlkPrivate *glk_data; - -/** - * glk_set_style: - * @styl The style to apply - * - * This changes the style of the current output stream. After a style change, - * new text which is printed to that stream will be given the new style. For a - * window stream, the text will appear in that style. For other types of - * streams, this has no effect. - */ -void -glk_set_style(glui32 style) -{ - g_return_if_fail(glk_data->current_stream != NULL); - glk_set_style_stream(glk_data->current_stream, style); -} - -/* Internal function: mapping from style enum to tag name */ -gchar* -get_tag_name(glui32 style) -{ - switch(style) { - case style_Normal: return "normal"; - case style_Emphasized: return "emphasized"; - case style_Preformatted: return "preformatted"; - case style_Header: return "header"; - case style_Subheader: return "subheader"; - case style_Alert: return "alert"; - case style_Note: return "note"; - case style_BlockQuote: return "block-quote"; - case style_Input: return "input"; - case style_User1: return "user1"; - case style_User2: return "user2"; - } - - WARNING("Unsupported style"); - return "normal"; -} - -void -glk_set_style_stream(strid_t stream, glui32 style) { - stream->style = get_tag_name(style); -} - -/* Internal function: call this to initialize the default styles to a textbuffer. */ -void -style_init_textbuffer(GtkTextBuffer *buffer) -{ - g_return_if_fail(buffer != NULL); - - gtk_text_buffer_create_tag(buffer, "normal", NULL); - gtk_text_buffer_create_tag(buffer, "emphasized", "style", PANGO_STYLE_ITALIC, NULL); - gtk_text_buffer_create_tag(buffer, "preformatted", "font-desc", glk_data->monospace_font_desc, NULL); - gtk_text_buffer_create_tag(buffer, "header", "size-points", 16.0, "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "subheader", "size-points", 12.0, "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "alert", "foreground", "#aa0000", "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "note", "foreground", "#aaaa00", "weight", PANGO_WEIGHT_BOLD, NULL); - gtk_text_buffer_create_tag(buffer, "block-quote", "justification", GTK_JUSTIFY_CENTER, "style", PANGO_STYLE_ITALIC, NULL); - gtk_text_buffer_create_tag(buffer, "input", NULL); - gtk_text_buffer_create_tag(buffer, "user1", NULL); - gtk_text_buffer_create_tag(buffer, "user2", NULL); -} - -void -color_format(glui32 val, gchar *buffer) -{ - sprintf(buffer, "#%02X%02X%02X", - ((val & 0xff0000) >> 16), - ((val & 0x00ff00) >> 8), - (val & 0x0000ff) - ); -} - -/* Internal function: changes a GTK tag to correspond with the given style. */ -void -apply_stylehint_to_tag(GtkTextTag *tag, glui32 hint, glsi32 val) -{ - g_return_if_fail(tag != NULL); - - GObject *tag_object = G_OBJECT(tag); - gint reverse_color = 0; - - /* FIXME where should we keep track of this? - g_object_get(tag, "reverse_color", &reverse_color, NULL); - */ - - int i = 0; - gchar color[20]; - switch(hint) { - case stylehint_Indentation: - g_object_set(tag_object, "left_margin", 5*val, NULL); - g_object_set(tag_object, "right_margin", 5*val, NULL); - break; - - case stylehint_ParaIndentation: - g_object_set(tag_object, "indent", 5*val, NULL); - break; - - case stylehint_Justification: - switch(val) { - case stylehint_just_LeftFlush: i = GTK_JUSTIFY_LEFT; break; - case stylehint_just_LeftRight: i = GTK_JUSTIFY_FILL; break; - case stylehint_just_Centered: i = GTK_JUSTIFY_CENTER; break; - case stylehint_just_RightFlush: i = GTK_JUSTIFY_RIGHT; break; - default: - WARNING("Unknown justification"); - i = GTK_JUSTIFY_LEFT; - } - g_object_set(tag_object, "justification", i, NULL); - break; - - case stylehint_Weight: - switch(val) { - case -1: i = PANGO_WEIGHT_LIGHT; break; - case 0: i = PANGO_WEIGHT_NORMAL; break; - case 1: i = PANGO_WEIGHT_BOLD; break; - default: WARNING("Unknown font weight"); - } - g_object_set(tag_object, "weight", i, NULL); - break; - - case stylehint_Size: - g_object_set(tag_object, "size", 14+(2*val), NULL); - break; - - case stylehint_Oblique: - g_object_set(tag_object, "style", val ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL, NULL); - break; - - case stylehint_Proportional: - g_object_set(tag_object, "font-desc", val ? glk_data->default_font_desc : glk_data->monospace_font_desc, NULL); - break; - - case stylehint_TextColor: - color_format(val, color); - - if(!reverse_color) - g_object_set(tag_object, "foreground", color, NULL); - else - g_object_set(tag_object, "background", color, NULL); - - break; - - case stylehint_BackColor: - color_format(val, color); - - if(!reverse_color) - g_object_set(tag_object, "background", color, NULL); - else - g_object_set(tag_object, "foreground", color, NULL); - - break; - - case stylehint_ReverseColor: - if(reverse_color != val) { - /* Flip the fore- and background colors */ - gchar* foreground_color; - gchar* background_color; - g_object_get(tag_object, "foreground", &foreground_color, NULL); - g_object_get(tag_object, "background", &background_color, NULL); - g_object_set(tag_object, "foreground", background_color, NULL); - g_object_set(tag_object, "background", foreground_color, NULL); - g_free(foreground_color); - g_free(background_color); - } - break; - - default: - WARNING("Unknown style hint"); - } -} - -void -glk_stylehint_set(glui32 wintype, glui32 style, glui32 hint, glsi32 val) -{ - - gchar *tag_name = get_tag_name(style); - - /* Iterate over all the window and update their styles if nessecary */ - winid_t win = glk_window_iterate(NULL, NULL); - while(win != NULL) { - if(wintype != wintype_TextBuffer) - continue; /* FIXME: add support for text grid windows */ - - if(wintype == wintype_AllTypes || glk_window_get_type(win) == wintype) { - GtkWidget *textview = win->widget; - GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) ); - GtkTextTagTable *table = gtk_text_buffer_get_tag_table(textbuffer); - GtkTextTag *to_change = gtk_text_tag_table_lookup(table, tag_name); - - apply_stylehint_to_tag(to_change, hint, val); - } - } -} diff --git a/src/style.h b/src/style.h deleted file mode 100644 index fcd5302..0000000 --- a/src/style.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef STYLE_H -#define STYLE_H - -#include -#include "glk.h" -#include "magic.h" -#include "chimara-glk-private.h" -#include "stream.h" - -G_GNUC_INTERNAL void style_init_textbuffer(GtkTextBuffer *buffer); - -#endif diff --git a/src/timer.c b/src/timer.c deleted file mode 100644 index 8d2087a..0000000 --- a/src/timer.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "timer.h" - -extern ChimaraGlkPrivate *glk_data; - -/** - * You can request that an event be sent at fixed intervals, regardless of what - * the player does. Unlike input events, timer events can be tested for with - * glk_select_poll() as well as glk_select(). - * - * Initially, there is no timer and you get no timer events. If you call - * glk_request_timer_events(N), with N not 0, you will get timer events about - * every N milliseconds thereafter. (Assuming that they are supported -- if - * not, glk_request_timer_events() has no effect.) Unlike keyboard and mouse - * events, timer events will continue until you shut them off. You do not have - * to re-request them every time you get one. Call glk_request_timer_events(0) - * to stop getting timer events. - * - * The rule is that when you call glk_select() or glk_select_poll(), if it has - * been more than N milliseconds since the last timer event, and (for - * glk_select()) if there is no player input, you will receive an event whose - * type is evtype_Timer. (win, val1, and val2 will all be 0.) - * - * Timer events do not stack up. If you spend 10N milliseconds doing - * computation, and then call glk_select(), you will not get ten timer events - * in a row. The library will simply note that it has been more than N - * milliseconds, and return a timer event right away. If you call glk_select() - * again immediately, it will be N milliseconds before the next timer event. - * - * This means that the timing of timer events is approximate, and the library - * will err on the side of being late. If there is a conflict between player - * input events and timer events, the player input takes precedence. [This - * prevents the user from being locked out by overly enthusiastic timer events. - * Unfortunately, it also means that your timer can be locked out on slower - * machines, if the player pounds too enthusiastically on the keyboard. Sorry. - * If you want a real-time operating system, talk to Wind River.] - * - * [I don't have to tell you that a millisecond is one thousandth of a second, - * do I?] - * - * NOTE: setting a new timer will overwrite the old timer if one was in place. - */ -void -glk_request_timer_events(glui32 millisecs) -{ - // Stop any existing timer - if(glk_data->timer_id != 0) { - g_source_remove(glk_data->timer_id); - glk_data->timer_id = 0; - } - - if(millisecs == 0) - return; - - glk_data->timer_id = g_timeout_add(millisecs, push_timer_event, NULL); -} - -/** - * Internal function: push a new timer event on the event stack. - * Will always return TRUE - */ -gboolean -push_timer_event(gpointer data) -{ - event_throw(evtype_Timer, NULL, 0, 0); - - return TRUE; -} diff --git a/src/timer.h b/src/timer.h deleted file mode 100644 index 1edb670..0000000 --- a/src/timer.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TIMER_H -#define TIMER_H - -#include -#include "event.h" -#include "chimara-glk-private.h" - -G_GNUC_INTERNAL gboolean push_timer_event(gpointer data); - -#endif diff --git a/src/window.c b/src/window.c deleted file mode 100644 index 5f2bd5b..0000000 --- a/src/window.c +++ /dev/null @@ -1,1104 +0,0 @@ -#include "window.h" -#include "magic.h" -#include "chimara-glk-private.h" - -extern ChimaraGlkPrivate *glk_data; - -/** - * glk_window_iterate: - * @win: A window, or %NULL. - * @rockptr: Return location for the next window's rock, or %NULL. - * - * This function can be used to iterate through the list of all open windows - * (including pair windows.) See Iterating Through Opaque - * Objects. - * - * As that section describes, the order in which windows are returned is - * arbitrary. The root window is not necessarily first, nor is it necessarily - * last. - * - * Returns: the next window, or %NULL if there are no more. - */ -winid_t -glk_window_iterate(winid_t win, glui32 *rockptr) -{ - VALID_WINDOW_OR_NULL(win, return NULL); - - GNode *retnode; - - if(win == NULL) - retnode = glk_data->root_window; - else - { - GNode *node = win->window_node; - if( G_NODE_IS_LEAF(node) ) - { - while(node && node->next == NULL) - node = node->parent; - if(node) - retnode = node->next; - else - retnode = NULL; - } - else - retnode = g_node_first_child(node); - } - winid_t retval = retnode? (winid_t)retnode->data : NULL; - - /* Store the window's rock in rockptr */ - if(retval && rockptr) - *rockptr = glk_window_get_rock(retval); - - return retval; -} - -/** - * glk_window_get_rock: - * @win: A window. - * - * Returns @win's rock value. Pair windows always have rock 0; all other windows - * return whatever rock value you created them with. - * - * Returns: A rock value. - */ -glui32 -glk_window_get_rock(winid_t win) -{ - VALID_WINDOW(win, return 0); - return win->rock; -} - -/** - * glk_window_get_type: - * @win: A window. - * - * Returns @win's type, one of #wintype_Blank, #wintype_Pair, - * #wintype_TextBuffer, #wintype_TextGrid, or #wintype_Graphics. - * - * Returns: The window's type. - */ -glui32 -glk_window_get_type(winid_t win) -{ - VALID_WINDOW(win, return 0); - return win->type; -} - -/** - * glk_window_get_parent: - * @win: A window. - * - * Returns the window which is the parent of @win. If @win is the root window, - * this returns %NULL, since the root window has no parent. Remember that the - * parent of every window is a pair window; other window types are always - * childless. - * - * Returns: A window, or %NULL. - */ -winid_t -glk_window_get_parent(winid_t win) -{ - VALID_WINDOW(win, return NULL); - /* Value will also be NULL if win is the root window */ - return (winid_t)win->window_node->parent->data; -} - -/** - * glk_window_get_sibling: - * @win: A window. - * - * Returns the other child of @win's parent. If @win is the root window, this - * returns %NULL. - * - * Returns: A window, or %NULL. - */ -winid_t -glk_window_get_sibling(winid_t win) -{ - VALID_WINDOW(win, return NULL); - - if(G_NODE_IS_ROOT(win->window_node)) - return NULL; - if(win->window_node->next) - return (winid_t)win->window_node->next; - return (winid_t)win->window_node->prev; -} - -/** - * glk_window_get_root: - * - * Returns the root window. If there are no windows, this returns %NULL. - * - * Returns: A window, or %NULL. - */ -winid_t -glk_window_get_root() -{ - if(glk_data->root_window == NULL) - return NULL; - return (winid_t)glk_data->root_window->data; -} - -/** - * glk_window_open: - * @split: The window to split to create the new window. Must be 0 if there - * are no windows yet. - * @method: Position of the new window and method of size computation. One of - * #winmethod_Above, #winmethod_Below, #winmethod_Left, or #winmethod_Right - * OR'ed with #winmethod_Fixed or #winmethod_Proportional. If @wintype is - * #wintype_Blank, then #winmethod_Fixed is not allowed. - * @size: Size of the new window, in percentage points if @method is - * #winmethod_Proportional, otherwise in characters if @wintype is - * #wintype_TextBuffer or #wintype_TextGrid, or pixels if @wintype is - * #wintype_Graphics. - * @wintype: Type of the new window. One of #wintype_Blank, #wintype_TextGrid, - * #wintype_TextBuffer, or #wintype_Graphics. - * @rock: The new window's rock value. - * - * Creates a new window. If there are no windows, the first three arguments are - * meaningless. @split must be 0, and @method and @size - * are ignored. @wintype is the type of window you're creating, and @rock is - * the rock (see Rocks). - * - * If any windows exist, new windows must be created by splitting existing - * ones. @split is the window you want to split; this must - * not be zero. @method is a mask of constants to specify the - * direction and the split method (see below). @size is the size of the split. - * @wintype is the type of window you're creating, and @rock is the rock. - * - * Remember that it is possible that the library will be unable to create a new - * window, in which case glk_window_open() will return %NULL. - * - * - * It is acceptable to gracefully exit, if the window you are creating is an - * important one — such as your first window. But you should not try to - * perform any window operation on the id until you have tested to make sure - * it is non-zero. - * - * - * The examples we've seen so far have the simplest kind of size control. (Yes, - * this is below.) Every pair is a percentage split, with - * - * X - * X - * - * percent going to one side, and - * - * (100-X) - * (100 - X) - * - * percent going to the other side. If the player resizes the window, the whole - * mess expands, contracts, or stretches in a uniform way. - * - * As I said above, you can also make fixed-size splits. This is a little more - * complicated, because you have to know how this fixed size is measured. - * - * Sizes are measured in a way which is different for each window type. For - * example, a text grid window is measured by the size of its fixed-width font. - * You can make a text grid window which is fixed at a height of four rows, or - * ten columns. A text buffer window is measured by the size of its font. - * - * - * Remember that different windows may use different size fonts. Even two - * text grid windows may use fixed-size fonts of different sizes. - * - * - * Graphics windows are measured in pixels, not characters. Blank windows - * aren't measured at all; there's no meaningful way to measure them, and - * therefore you can't create a blank window of a fixed size, only of a - * proportional (percentage) size. - * - * So to create a text buffer window which takes the top 40% of the original - * window's space, you would execute - * |[ newwin = #glk_window_open(win, #winmethod_Above | #winmethod_Proportional, 40, #wintype_TextBuffer, 0); ]| - * - * To create a text grid which is always five lines high, at the bottom of the - * original window, you would do - * |[ newwin = #glk_window_open(win, #winmethod_Below | #winmethod_Fixed, 5, #wintype_TextGrid, 0); ]| - * - * Note that the meaning of the @size argument depends on the @method argument. - * If the method is #winmethod_Fixed, it also depends on the @wintype argument. - * The new window is then called the key window of this split, - * because its window type determines how the split size is computed. - * - * - * For #winmethod_Proportional splits, you can still call the new window the - * key window. But the key window is not important for - * proportional splits, because the size will always be computed as a simple - * ratio of the available space, not a fixed size of one child window. - * - * - * This system is more or less peachy as long as all the constraints work out. - * What happens when there is a conflict? The rules are simple. Size control - * always flows down the tree, and the player is at the top. Let's bring out an - * example: - * - * - * - * - * O - * / \ - * O B - * / \ - * A C - * - * - * - * First we split A into A and B, with a 50% proportional split. Then we split - * A into A and C, with C above, C being a text grid window, and C gets a fixed - * size of two rows (as measured in its own font size). A gets whatever remains - * of the 50% it had before. - * - * Now the player stretches the window vertically. - * - * - * - * The library figures: the topmost split, the original A/B split, is 50-50. So - * B gets half the screen space, and the pair window next to it (the lower - * O) gets the other half. Then it looks at the lower - * O. C gets two rows; A gets the rest. All done. - * - * Then the user maliciously starts squeezing the window down, in stages: - * - * - * - * - * - * - * - * - * - * - * - * - * - * The logic remains the same. B always gets half the space. At stage 3, - * there's no room left for A, so it winds up with zero height. Nothing - * displayed in A will be visible. At stage 4, there isn't even room in the - * upper 50% to give C its two rows; so it only gets one. Finally, C is - * squashed out of existence as well. - * - * When a window winds up undersized, it remembers what size it should be. In - * the example above, A remembers that it should be two rows; if the user - * expands the window to the original size, it would return to the original - * layout. - * - * The downward flow of control is a bit harsh. After all, in stage 4, there's - * room for C to have its two rows if only B would give up some of its 50%. But - * this does not happen. - * - * - * This makes life much easier for the Glk library. To determine the - * configuration of a window, it only needs to look at the window's - * ancestors, never at its descendants. So window layout is a simple - * recursive algorithm, no backtracking. - * - * - * What happens when you split a fixed-size window? The resulting pair window - * — that is, the two new parts together — retain the same size - * constraint as the original window that was split. The key window for the - * original split is still the key window for that split, even though it's now - * a grandchild instead of a child. - * - * The easy, and correct, way to think about this is that the size constraint - * is stored by a window's parent, not the window itself; and a constraint - * consists of a pointer to a key window plus a size value. - * - * - * - * - * - * A - * - * - * - * - * O1 - * / \ - * A B - * - * - * - * - * O1 - * / \ - * O2 B - * / \ - * A C - * - * - * After the first split, the new pair window (O1, which covers the whole - * screen) knows that its first child (A) is above the second, and gets 50% of - * its own area. (A is the key window for this split, but a proportional split - * doesn't care about key windows.) - * - * After the second split, all this remains true; O1 knows that its first child - * gets 50% of its space, and A is O1's key window. But now O1's first child is - * O2 instead of A. The newer pair window (O2) knows that its first child (C) - * is above the second, and gets a fixed size of two rows. (As measured in C's - * font, because C is O2's key window.) - * - * If we split C, now, the resulting pair will still be two C-font rows high - * — that is, tall enough for two lines of whatever font C displays. For - * the sake of example, we'll do this vertically. - * - * - * - * - * O1 - * / \ - * O2 B - * / \ - * A O3 - * / \ - * C D - * - * - * - * O3 now knows that its children have a 50-50 left-right split. O2 is still - * committed to giving its upper child, O3, two C-font rows. Again, this is - * because C is O2's key window. - * - * - * This turns out to be a good idea, because it means that C, the text grid - * window, is still two rows high. If O3 had been a upper-lower split, things - * wouldn't work out so neatly. But the rules would still apply. If you don't - * like this, don't do it. - * - * - * Returns: the new window, or %NULL on error. - */ -winid_t -glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, - glui32 rock) -{ - VALID_WINDOW_OR_NULL(split, return NULL); - - if(split == NULL && glk_data->root_window != NULL) - { - ILLEGAL("Tried to open a new root window, but there is already a root window"); - return NULL; - } - - gdk_threads_enter(); - - /* Create the new window */ - winid_t win = g_new0(struct glk_window_struct, 1); - win->magic = MAGIC_WINDOW; - win->rock = rock; - win->type = wintype; - win->window_node = g_node_new(win); - - switch(wintype) - { - case wintype_Blank: - { - /* A blank window will be a label without any text */ - GtkWidget *label = gtk_label_new(""); - gtk_widget_show(label); - - win->widget = label; - win->frame = label; - /* A blank window has no size */ - win->unit_width = 0; - win->unit_height = 0; - /* You can print to a blank window's stream, but it does nothing */ - win->window_stream = window_stream_new(win); - win->echo_stream = NULL; - } - break; - - case wintype_TextGrid: - { - GtkWidget *textview = gtk_text_view_new(); - - gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_NONE ); - gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE ); - gtk_widget_show(textview); - - /* Set the window's font */ - gtk_widget_modify_font(textview, glk_data->monospace_font_desc); - - win->widget = textview; - win->frame = textview; - - /* Determine the size of a "0" character in pixels */ - PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); - pango_layout_set_font_description(zero, glk_data->monospace_font_desc); - pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height)); - g_object_unref(zero); - - /* Set the other parameters (width and height are set later) */ - win->window_stream = window_stream_new(win); - win->echo_stream = NULL; - win->input_request_type = INPUT_REQUEST_NONE; - win->line_input_buffer = NULL; - win->line_input_buffer_unicode = NULL; - - /* Connect signal handlers */ - win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win ); - g_signal_handler_block( G_OBJECT(textview), win->keypress_handler ); - } - break; - - case wintype_TextBuffer: - { - GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL); - GtkWidget *textview = gtk_text_view_new(); - GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(textview) ); - - gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC ); - - gtk_text_view_set_wrap_mode( GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR ); - gtk_text_view_set_editable( GTK_TEXT_VIEW(textview), FALSE ); - - gtk_container_add( GTK_CONTAINER(scrolledwindow), textview ); - gtk_widget_show_all(scrolledwindow); - - /* Set the window's font */ - gtk_widget_modify_font(textview, glk_data->default_font_desc); - - win->widget = textview; - win->frame = scrolledwindow; - - /* Determine the size of a "0" character in pixels */ - PangoLayout *zero = gtk_widget_create_pango_layout(textview, "0"); - pango_layout_set_font_description(zero, glk_data->default_font_desc); - pango_layout_get_pixel_size(zero, &(win->unit_width), &(win->unit_height)); - g_object_unref(zero); - - /* Set the other parameters */ - win->window_stream = window_stream_new(win); - win->echo_stream = NULL; - win->input_request_type = INPUT_REQUEST_NONE; - win->line_input_buffer = NULL; - win->line_input_buffer_unicode = NULL; - - /* Connect signal handlers */ - win->keypress_handler = g_signal_connect( G_OBJECT(textview), "key-press-event", G_CALLBACK(on_window_key_press_event), win ); - g_signal_handler_block( G_OBJECT(textview), win->keypress_handler ); - - win->insert_text_handler = g_signal_connect_after( G_OBJECT(textbuffer), "insert-text", G_CALLBACK(after_window_insert_text), win ); - g_signal_handler_block( G_OBJECT(textbuffer), win->insert_text_handler ); - - /* Create an editable tag to indicate uneditable parts of the window - (for line input) */ - gtk_text_buffer_create_tag(textbuffer, "uneditable", "editable", FALSE, "editable-set", TRUE, NULL); - - /* Create the default styles available to the window stream */ - style_init_textbuffer(textbuffer); - - /* Mark the position where the user will input text */ - GtkTextIter end; - gtk_text_buffer_get_end_iter(textbuffer, &end); - gtk_text_buffer_create_mark(textbuffer, "input_position", &end, TRUE); - } - break; - - default: - gdk_threads_leave(); - ILLEGAL_PARAM("Unknown window type: %u", wintype); - g_free(win); - g_node_destroy(glk_data->root_window); - glk_data->root_window = NULL; - return NULL; - } - - /* Set the minimum size to "as small as possible" so it doesn't depend on - the size of the window contents */ - gtk_widget_set_size_request(win->widget, 0, 0); - gtk_widget_set_size_request(win->frame, 0, 0); - - if(split) - { - /* When splitting, construct a new parent window - * copying most characteristics from the window that is being split */ - winid_t pair = g_new0(struct glk_window_struct, 1); - pair->magic = MAGIC_WINDOW; - pair->rock = 0; - pair->type = wintype_Pair; - pair->window_node = g_node_new(pair); - /* You can print to a pair window's window stream, but it has no effect */ - pair->window_stream = window_stream_new(pair); - pair->echo_stream = NULL; - - /* The pair window must know about its children's split method */ - pair->key_window = win; - pair->split_method = method; - pair->constraint_size = size; - - /* Insert the new window into the window tree */ - if(split->window_node->parent == NULL) - glk_data->root_window = pair->window_node; - else - { - if( split->window_node == g_node_first_sibling(split->window_node) ) - g_node_prepend(split->window_node->parent, pair->window_node); - else - g_node_append(split->window_node->parent, pair->window_node); - g_node_unlink(split->window_node); - } - /* Place the windows in the correct order */ - switch(method & winmethod_DirMask) - { - case winmethod_Left: - case winmethod_Above: - g_node_append(pair->window_node, win->window_node); - g_node_append(pair->window_node, split->window_node); - break; - case winmethod_Right: - case winmethod_Below: - g_node_append(pair->window_node, split->window_node); - g_node_append(pair->window_node, win->window_node); - break; - } - - } else { - /* Set the window as root window */ - glk_data->root_window = win->window_node; - } - - /* Set the window as a child of the Glk widget */ - gtk_widget_set_parent(win->frame, GTK_WIDGET(glk_data->self)); - gtk_widget_queue_resize(GTK_WIDGET(glk_data->self)); - - gdk_threads_leave(); - - /* For blank or pair windows, this is almost a no-op. For text grid and - text buffer windows, this will wait for GTK to draw the window. Otherwise, - opening a window and getting its size immediately will give you the wrong - size. */ - glk_window_get_size(win, NULL, NULL); - - /* For text grid windows, fill the buffer with blanks. */ - if(wintype == wintype_TextGrid) - { - /* Create the cursor position mark */ - gdk_threads_enter(); - GtkTextIter begin; - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - gtk_text_buffer_get_start_iter(buffer, &begin); - gtk_text_buffer_create_mark(buffer, "cursor_position", &begin, TRUE); - gdk_threads_leave(); - - /* Fill the buffer with blanks and move the cursor to the upper left */ - glk_window_clear(win); - } - - return win; -} - -/* Internal function: if node's key window is closing_win or one of its - children, set node's key window to NULL. */ -static gboolean -remove_key_windows(GNode *node, winid_t closing_win) -{ - winid_t win = (winid_t)node->data; - if(win->key_window && (win->key_window == closing_win || g_node_is_ancestor(closing_win->window_node, win->key_window->window_node))) - win->key_window = NULL; - return FALSE; /* Don't stop the traversal */ -} - -/* Internal function: destroy this window's GTK widgets, window streams, - and those of all its children */ -static void -destroy_windows_below(winid_t win, stream_result_t *result) -{ - switch(win->type) - { - case wintype_Blank: - gdk_threads_enter(); - gtk_widget_unparent(win->widget); - gdk_threads_leave(); - break; - - case wintype_TextGrid: - case wintype_TextBuffer: - gdk_threads_enter(); - gtk_widget_unparent(win->frame); - gdk_threads_leave(); - /* TODO: Cancel all input requests */ - break; - - case wintype_Pair: - destroy_windows_below(win->window_node->children->data, NULL); - destroy_windows_below(win->window_node->children->next->data, NULL); - break; - - default: - ILLEGAL_PARAM("Unknown window type: %u", win->type); - return; - } - stream_close_common(win->window_stream, result); -} - -/* Internal function: free the winid_t structure of this window and those of all its children */ -static void -free_winids_below(winid_t win) -{ - if(win->type == wintype_Pair) { - free_winids_below(win->window_node->children->data); - free_winids_below(win->window_node->children->next->data); - } - win->magic = MAGIC_FREE; - g_free(win); -} - -/** - * glk_window_close: - * @win: Window to close. - * @result: Pointer to a #stream_result_t in which to store the write count. - * - * Closes @win, which is pretty much exactly the opposite of opening a window. - * It is legal to close all your windows, or to close the root window (which is - * the same thing.) - * - * The @result argument is filled with the output character count of the window - * stream. See Streams and Closing Streams. - * - * When you close a window (and it is not the root window), the other window - * in its pair takes over all the freed-up area. Let's close D, in the current - * example: - * - * - * - * - * O1 - * / \ - * O2 B - * / \ - * A C - * - * - * - * Notice what has happened. D is gone. O3 is gone, and its 50-50 left-right - * split has gone with it. The other size constraints are unchanged; O2 is - * still committed to giving its upper child two rows, as measured in the font - * of O2's key window, which is C. Conveniently, O2's upper child is C, just as - * it was before we created D. In fact, now that D is gone, everything is back - * to the way it was before we created D. - * - * But what if we had closed C instead of D? We would have gotten this: - * - * - * - * - * O1 - * / \ - * O2 B - * / \ - * A D - * - * - * - * Again, O3 is gone. But D has collapsed to zero height. This is because its - * height is controlled by O2, and O2's key window was C, and C is now gone. O2 - * no longer has a key window at all, so it cannot compute a height for its - * upper child, so it defaults to zero. - * - * - * This may seem to be an inconvenient choice. That is deliberate. You should - * not leave a pair window with no key, and the zero-height default reminds - * you not to. You can use glk_window_set_arrangement() to set a new split - * measurement and key window. See Changing Window - * Constraints. - * - */ -void -glk_window_close(winid_t win, stream_result_t *result) -{ - VALID_WINDOW(win, return); - - /* If any pair windows have this window or its children as a key window, - set their key window to NULL */ - g_node_traverse(glk_data->root_window, G_IN_ORDER, G_TRAVERSE_NON_LEAVES, -1, (GNodeTraverseFunc)remove_key_windows, win); - - /* Close all the window streams and destroy the widgets of this window - and below, before trashing the window tree */ - destroy_windows_below(win, result); - - /* Then free the winid_t structures below this node, but not this one itself */ - if(win->type == wintype_Pair) { - free_winids_below(win->window_node->children->data); - free_winids_below(win->window_node->children->next->data); - } - /* So now we should be left with a skeleton tree hanging off this node */ - - /* Parent window changes from a split window into the sibling window */ - /* The parent of any window is either a pair window or NULL */ - GNode *pair_node = win->window_node->parent; - g_node_destroy(win->window_node); - /* If win was not the root window: */ - if(pair_node != NULL) - { - gboolean new_child_on_left = ( pair_node == g_node_first_sibling(pair_node) ); - GNode *sibling_node = pair_node->children; /* only one child left */ - GNode *new_parent_node = pair_node->parent; - g_node_unlink(pair_node); - g_node_unlink(sibling_node); - /* pair_node and sibling_node should now be totally unconnected to the tree */ - - if(new_parent_node == NULL) - { - glk_data->root_window = sibling_node; - } - else - { - if(new_child_on_left) - g_node_prepend(new_parent_node, sibling_node); - else - g_node_append(new_parent_node, sibling_node); - } - - winid_t pair = (winid_t) pair_node->data; - g_node_destroy(pair_node); - - pair->magic = MAGIC_FREE; - g_free(pair); - } - else /* it was the root window */ - { - glk_data->root_window = NULL; - } - - win->magic = MAGIC_FREE; - g_free(win); - - /* Schedule a redraw */ - gdk_threads_enter(); - gtk_widget_queue_resize( GTK_WIDGET(glk_data->self) ); - gdk_window_process_all_updates(); - gdk_threads_leave(); -} - -/** - * glk_window_clear: - * @win: A window. - * - * Erases @win. The meaning of this depends on the window type. - * - * - * Text buffer - * - * This may do any number of things, such as delete all text in the window, or - * print enough blank lines to scroll all text beyond visibility, or insert a - * page-break marker which is treated specially by the display part of the - * library. - * - * - * - * Text grid - * - * This will clear the window, filling all positions with blanks. The window - * cursor is moved to the top left corner (position 0,0). - * - * - * - * Graphics - * - * Clears the entire window to its current background color. See Graphics Windows. - * - * - * - * Other window types - * No effect. - * - * - * - * It is illegal to erase a window which has line input pending. - */ -void -glk_window_clear(winid_t win) -{ - VALID_WINDOW(win, return); - g_return_if_fail(win->input_request_type != INPUT_REQUEST_LINE && win->input_request_type != INPUT_REQUEST_LINE_UNICODE); - - switch(win->type) - { - case wintype_Blank: - case wintype_Pair: - /* do nothing */ - break; - - case wintype_TextGrid: - /* fill the buffer with blanks */ - { - gdk_threads_enter(); - - /* Manually put newlines at the end of each row of characters in the buffer; manual newlines make resizing the window's grid easier. */ - gchar *blanks = g_strnfill(win->width, ' '); - gchar **blanklines = g_new0(gchar *, win->height + 1); - int count; - for(count = 0; count < win->height; count++) - blanklines[count] = blanks; - blanklines[win->height] = NULL; - gchar *text = g_strjoinv("\n", blanklines); - g_free(blanklines); /* not g_strfreev() */ - g_free(blanks); - - GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - gtk_text_buffer_set_text(textbuffer, text, -1); - g_free(text); - - GtkTextIter begin; - gtk_text_buffer_get_start_iter(textbuffer, &begin); - gtk_text_buffer_move_mark_by_name(textbuffer, "cursor_position", &begin); - - gdk_threads_leave(); - } - break; - - case wintype_TextBuffer: - /* delete all text in the window */ - { - gdk_threads_enter(); - - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - GtkTextIter start, end; - gtk_text_buffer_get_bounds(buffer, &start, &end); - gtk_text_buffer_delete(buffer, &start, &end); - - gdk_threads_leave(); - } - break; - - default: - ILLEGAL_PARAM("Unknown window type: %d", win->type); - } -} - -/** - * glk_set_window: - * @win: A window. - * - * Sets the current stream to @win's window stream. It is exactly equivalent to - * #glk_stream_set_current(#glk_window_get_stream(@win)). - */ -void -glk_set_window(winid_t win) -{ - VALID_WINDOW_OR_NULL(win, return); - glk_stream_set_current( glk_window_get_stream(win) ); -} - -/** - * glk_window_get_stream: - * @win: A window. - * - * Returns the stream which is associated with @win. (See Window Streams.) Every window has a - * stream which can be printed to, but this may not be useful, depending on the - * window type. - * - * - * For example, printing to a blank window's stream has no effect. - * - * - * Returns: A window stream. - */ -strid_t glk_window_get_stream(winid_t win) -{ - VALID_WINDOW(win, return NULL); - return win->window_stream; -} - -/** - * glk_window_set_echo_stream: - * @win: A window. - * @str: A stream to attach to the window, or %NULL. - * - * Sets @win's echo stream to @str, which can be any valid output stream. You - * can reset a window to stop echoing by calling - * #glk_window_set_echo_stream(@win, %NULL). - * - * It is illegal to set a window's echo stream to be its - * own window stream. That would create an infinite loop, - * and is nearly certain to crash the Glk library. It is similarly illegal to - * create a longer loop (two or more windows echoing to each other.) - */ -void -glk_window_set_echo_stream(winid_t win, strid_t str) -{ - VALID_WINDOW(win, return); - VALID_STREAM_OR_NULL(str, return); - - /* Test for an infinite loop */ - strid_t next = str; - for(; next && next->type == STREAM_TYPE_WINDOW; next = next->window->echo_stream) - { - if(next == win->window_stream) - { - ILLEGAL("Infinite loop detected"); - win->echo_stream = NULL; - return; - } - } - - win->echo_stream = str; -} - -/** - * glk_window_get_echo_stream: - * @win: A window. - * - * Returns the echo stream of window @win. Initially, a window has no echo - * stream, so #glk_window_get_echo_stream(@win) will return %NULL. - * - * Returns: A stream, or %NULL. - */ -strid_t -glk_window_get_echo_stream(winid_t win) -{ - VALID_WINDOW(win, return NULL); - return win->echo_stream; -} - -/** - * glk_window_get_size: - * @win: A window. - * @widthptr: Pointer to a location to store the window's width, or %NULL. - * @heightptr: Pointer to a location to store the window's height, or %NULL. - * - * Simply returns the actual size of the window, in its measurement system. - * As described in Other API - * Conventions, either @widthptr or @heightptr can be %NULL, if you - * only want one measurement. - * - * Or, in fact, both, if you want to waste time. - */ -void -glk_window_get_size(winid_t win, glui32 *widthptr, glui32 *heightptr) -{ - VALID_WINDOW(win, return); - - switch(win->type) - { - case wintype_Blank: - case wintype_Pair: - if(widthptr != NULL) - *widthptr = 0; - if(heightptr != NULL) - *heightptr = 0; - break; - - case wintype_TextGrid: - gdk_threads_enter(); - /* Wait for the window to be drawn, and then cache the width and height */ - gdk_window_process_all_updates(); - while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) - { - /* Release the GDK lock momentarily */ - gdk_threads_leave(); - gdk_threads_enter(); - while(gtk_events_pending()) - gtk_main_iteration(); - } - - win->width = (glui32)(win->widget->allocation.width / win->unit_width); - win->height = (glui32)(win->widget->allocation.height / win->unit_height); - gdk_threads_leave(); - - if(widthptr != NULL) - *widthptr = win->width; - if(heightptr != NULL) - *heightptr = win->height; - break; - - case wintype_TextBuffer: - /* TODO: Glk wants to be able to get its windows' sizes as soon as they are created, but GTK doesn't decide on their sizes until they are drawn. The drawing happens somewhere in an idle function. A good method would be to make an educated guess of the window's size using the ChimaraGlk widget's size. */ - gdk_threads_enter(); - /*if(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) - { - g_warning("glk_window_get_size: The Glk program requested the size of a window before it was allocated screen space by GTK. The window size is just an educated guess."); - guess the size from the parent window; - break; - } */ - - /* Instead, we wait for GTK to draw the widget. This is probably very slow and should be fixed. */ - gdk_window_process_all_updates(); - while(win->widget->allocation.width == 1 && win->widget->allocation.height == 1) - { - /* Release the GDK lock momentarily */ - gdk_threads_leave(); - gdk_threads_enter(); - while(gtk_events_pending()) - gtk_main_iteration(); - } - - if(widthptr != NULL) - *widthptr = (glui32)(win->widget->allocation.width / win->unit_width); - if(heightptr != NULL) - *heightptr = (glui32)(win->widget->allocation.height / win->unit_height); - gdk_threads_leave(); - - break; - - default: - ILLEGAL_PARAM("Unknown window type: %u", win->type); - } -} - -/** - * glk_window_move_cursor: - * @win: A text grid window. - * @xpos: Horizontal cursor position. - * @ypos: Vertical cursor position. - * - * Sets the cursor position. If you move the cursor right past the end of a - * line, it wraps; the next character which is printed will appear at the - * beginning of the next line. - * - * If you move the cursor below the last line, or when the cursor reaches the - * end of the last line, it goes off the screen and further - * output has no effect. You must call glk_window_move_cursor() or - * glk_window_clear() to move the cursor back into the visible region. - * - * - * Note that the arguments of glk_window_move_cursor() are unsigned - * ints. This is okay, since there are no negative positions. If you try - * to pass a negative value, Glk will interpret it as a huge positive value, - * and it will wrap or go off the last line. - * - * - * - * Also note that the output cursor is not necessarily visible. In particular, - * when you are requesting line or character input in a grid window, you cannot - * rely on the cursor position to prompt the player where input is indicated. - * You should print some character prompt at that spot — a - * > character, for example. - * - */ -void -glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos) -{ - VALID_WINDOW(win, return); - g_return_if_fail(win->type == wintype_TextGrid); - - /* Calculate actual position if cursor is moved past the right edge */ - if(xpos >= win->width) - { - ypos += xpos / win->width; - xpos %= win->width; - } - /* Go to the end if the cursor is moved off the bottom edge */ - if(ypos >= win->height) - { - xpos = win->width - 1; - ypos = win->height - 1; - } - - gdk_threads_enter(); - - GtkTextBuffer *buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); - GtkTextIter newpos; - /* There must actually be a character at xpos, or the following function will choke */ - gtk_text_buffer_get_iter_at_line_offset(buffer, &newpos, ypos, xpos); - gtk_text_buffer_move_mark_by_name(buffer, "cursor_position", &newpos); - - gdk_threads_leave(); -} - diff --git a/src/window.h b/src/window.h deleted file mode 100644 index b3b824a..0000000 --- a/src/window.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef WINDOW_H -#define WINDOW_H - -#include -#include "glk.h" - -#include "stream.h" -#include "error.h" -#include "callbacks.h" -#include "input.h" -#include "style.h" - - -enum InputRequestType -{ - INPUT_REQUEST_NONE, - INPUT_REQUEST_CHARACTER, - INPUT_REQUEST_CHARACTER_UNICODE, - INPUT_REQUEST_LINE, - INPUT_REQUEST_LINE_UNICODE -}; - -/** - * glk_window_struct: - * - * This is an opaque structure (see - * Opaque Structures and should not be accessed directly. - */ -struct glk_window_struct -{ - /*< private >*/ - glui32 magic, rock; - /* Pointer to the node in the global tree that contains this window */ - GNode *window_node; - /* Window parameters */ - glui32 type; - /* "widget" is the actual widget with the window's functionality */ - GtkWidget *widget; - /* "frame" is the widget that is the child of the ChimaraGlk container, such - as a scroll window. It may be the same as "widget". */ - GtkWidget *frame; - /* Width and height of the window's size units, in pixels */ - int unit_width; - int unit_height; - /* Streams associated with the window */ - strid_t window_stream; - strid_t echo_stream; - /* Width and height of the window, in characters (text grids only) */ - glui32 width; - glui32 height; - /* Window split data (pair windows only) */ - winid_t key_window; - glui32 split_method; - glui32 constraint_size; - /* Input request stuff */ - enum InputRequestType input_request_type; - gchar *line_input_buffer; - glui32 *line_input_buffer_unicode; - glui32 line_input_buffer_max_len; - gboolean mouse_input_requested; - /* Line input field (text grids only) */ - glui32 input_length; - GtkTextChildAnchor *input_anchor; - GtkWidget *input_entry; - /* Signal handlers */ - gulong keypress_handler; - gulong insert_text_handler; -}; - -#endif diff --git a/tests/callbacks.c b/tests/callbacks.c new file mode 100644 index 0000000..6067526 --- /dev/null +++ b/tests/callbacks.c @@ -0,0 +1,47 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * callbacks.c + * Copyright (C) Philip en Marijn 2008 <> + * + * callbacks.c is free software copyrighted by Philip en Marijn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name ``Philip en Marijn'' nor the name of any other + * contributor may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * callbacks.c IS PROVIDED BY Philip en Marijn ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Philip en Marijn OR ANY OTHER CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "callbacks.h" + +void on_save_tool_button_clicked(GtkToolButton *toolbutton, gpointer user_data) { + error_dialog( GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbutton))), NULL, "Not implemented yet" ); +} + +gboolean on_window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) { + gtk_main_quit(); + return TRUE; +} + +void on_file_quit_activate(GtkMenuItem *menuitem, gpointer user_data) { + gtk_main_quit(); +} + diff --git a/tests/callbacks.h b/tests/callbacks.h new file mode 100644 index 0000000..f14403c --- /dev/null +++ b/tests/callbacks.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * callbacks.h + * Copyright (C) Philip en Marijn 2008 <> + * + * callbacks.h is free software copyrighted by Philip en Marijn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name ``Philip en Marijn'' nor the name of any other + * contributor may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * callbacks.h IS PROVIDED BY Philip en Marijn ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Philip en Marijn OR ANY OTHER CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "error.h" + +void on_save_tool_button_clicked(GtkToolButton *toolbutton, gpointer user_data); +gboolean on_window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data); +void on_file_quit_activate(GtkMenuItem *menuitem, gpointer user_data); diff --git a/tests/chimara.glade b/tests/chimara.glade new file mode 100644 index 0000000..558f45b --- /dev/null +++ b/tests/chimara.glade @@ -0,0 +1,205 @@ + + + + + + Gargoyle GTK + 500 + 600 + + + + True + + + True + + + True + _File + True + + + True + + + True + Opens an interactive fiction game + gtk-open + True + True + + + + + True + + + + + True + gtk-quit + True + True + + + + + + + + + + True + _Edit + True + + + True + + + True + gtk-cut + True + True + + + + + True + gtk-copy + True + True + + + + + True + gtk-paste + True + True + + + + + True + gtk-delete + True + True + + + + + + + + + True + _Game + True + + + True + + + True + Saves your progress in the game + _Save + True + + + gtk-save + + + + + + + True + Restores a previously saved game + _Restore + True + + + gtk-open + + + + + + + True + Restart the game + Res_tart + True + + + True + gtk-refresh + + + + + + + + + + + True + _Help + True + + + True + + + True + gtk-about + True + True + + + + + + + + + False + + + + + True + + + True + Save + gtk-save + + + + True + + + + + True + Restore + gtk-open + + + True + + + + + False + 1 + + + + + + diff --git a/tests/error.c b/tests/error.c new file mode 100644 index 0000000..c9b6f08 --- /dev/null +++ b/tests/error.c @@ -0,0 +1,52 @@ +/* Copyright 2006 P.F. Chimento + * This file is part of GNOME Inform 7. + * + * GNOME Inform 7 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GNOME Inform 7 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNOME Inform 7; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include + +#include "error.h" + +/* Create and display an error dialog box, with parent window parent, and +message format string msg. If err is not NULL, tack the error message on to the +end of the format string. */ +void +error_dialog(GtkWindow *parent, GError *err, const gchar *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + gchar buffer[1024]; + g_vsnprintf(buffer, 1024, msg, ap); + va_end(ap); + + gchar *message; + if(err) { + message = g_strconcat(buffer, err->message, NULL); + g_error_free(err); + } else + message = g_strdup(buffer); + + GtkWidget *dialog = gtk_message_dialog_new(parent, + parent? GTK_DIALOG_DESTROY_WITH_PARENT : 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + message); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_free(message); +} diff --git a/tests/error.h b/tests/error.h new file mode 100644 index 0000000..bb05fbe --- /dev/null +++ b/tests/error.h @@ -0,0 +1,27 @@ +/* Copyright 2006 P.F. Chimento + * This file is part of GNOME Inform 7. + * + * GNOME Inform 7 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GNOME Inform 7 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNOME Inform 7; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef ERROR_H +#define ERROR_H + +#include +#include + +void error_dialog(GtkWindow *parent, GError *err, const gchar *msg, ...); + +#endif diff --git a/tests/first.c b/tests/first.c new file mode 100644 index 0000000..0189105 --- /dev/null +++ b/tests/first.c @@ -0,0 +1,611 @@ +#include + +/* model.c: Model program for Glk API, version 0.5. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + This program is in the public domain. +*/ + +/* This is a simple model of a text adventure which uses the Glk API. + It shows how to input a line of text, display results, maintain a + status window, write to a transcript file, and so on. */ + +/* This is the cleanest possible form of a Glk program. It includes only + "glk.h", and doesn't call any functions outside Glk at all. We even + define our own str_eq() and str_len(), rather than relying on the + standard libraries. */ + +/* We also define our own TRUE and FALSE and NULL. */ +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef NULL +#define NULL 0 +#endif + +/* The story, status, and quote windows. */ +static winid_t mainwin = NULL; +static winid_t statuswin = NULL; +static winid_t quotewin = NULL; + +/* A file reference for the transcript file. */ +static frefid_t scriptref = NULL; +/* A stream for the transcript file, when it's open. */ +static strid_t scriptstr = NULL; + +/* Your location. This determines what appears in the status line. */ +static int current_room; + +/* A flag indicating whether you should look around. */ +static int need_look; + +/* Forward declarations */ +void glk_main(void); + +static void draw_statuswin(void); +static int yes_or_no(void); + +static int str_eq(char *s1, char *s2); +static int str_len(char *s1); + +static void verb_help(void); +static void verb_jump(void); +static void verb_yada(void); +static void verb_quote(void); +static void verb_move(void); +static void verb_quit(void); +static void verb_script(void); +static void verb_unscript(void); +static void verb_save(void); +static void verb_restore(void); +static void verb_alert(void); +static void verb_notice(void); + +/* The glk_main() function is called by the Glk system; it's the main entry + point for your program. */ +void glk_main(void) +{ + /* Open the main window. */ + mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); + if (!mainwin) { + /* It's possible that the main window failed to open. There's + nothing we can do without it, so exit. */ + return; + } + + /* Set the current output stream to print to it. */ + glk_set_window(mainwin); + + /* Open a second window: a text grid, above the main window, three lines + high. It is possible that this will fail also, but we accept that. */ + statuswin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed, + 3, wintype_TextGrid, 0); + + /* The third window, quotewin, isn't opened immediately. We'll do + that in verb_quote(). */ + + glk_put_string("Model Glk Program\nAn Interactive Model Glk Program\n"); + glk_put_string("By Andrew Plotkin.\nRelease 7.\n"); + glk_put_string("Type \"help\" for a list of commands.\n"); + + current_room = 0; /* Set initial location. */ + need_look = TRUE; + + while (1) { + char commandbuf[256]; + char *cx, *cmd; + int gotline, len; + event_t ev; + + draw_statuswin(); + + if (need_look) { + need_look = FALSE; + glk_put_string("\n"); + glk_set_style(style_Subheader); + if (current_room == 0) + glk_put_string("The Room\n"); + else + glk_put_string("A Different Room\n"); + glk_set_style(style_Normal); + glk_put_string("You're in a room of some sort.\n"); + } + + glk_put_string("\n>"); + /* We request up to 255 characters. The buffer can hold 256, but we + are going to stick a null character at the end, so we have to + leave room for that. Note that the Glk library does *not* + put on that null character. */ + glk_request_line_event(mainwin, commandbuf, 255, 0); + + gotline = FALSE; + while (!gotline) { + + /* Grab an event. */ + glk_select(&ev); + + switch (ev.type) { + + case evtype_LineInput: + if (ev.win == mainwin) { + gotline = TRUE; + /* Really the event can *only* be from mainwin, + because we never request line input from the + status window. But we do a paranoia test, + because commandbuf is only filled if the line + event comes from the mainwin request. If the + line event comes from anywhere else, we ignore + it. */ + } + break; + + case evtype_Arrange: + /* Windows have changed size, so we have to redraw the + status window. */ + draw_statuswin(); + break; + } + } + + /* commandbuf now contains a line of input from the main window. + You would now run your parser and do something with it. */ + + /* First, if there's a blockquote window open, let's close it. + This ensures that quotes remain visible for exactly one + command. */ + if (quotewin) { + glk_window_close(quotewin, NULL); + quotewin = 0; + } + + /* The line we have received in commandbuf is not null-terminated. + We handle that first. */ + len = ev.val1; /* Will be between 0 and 255, inclusive. */ + commandbuf[len] = '\0'; + + /* Then squash to lower-case. */ + for (cx = commandbuf; *cx; cx++) { + *cx = glk_char_to_lower(*cx); + } + + /* Then trim whitespace before and after. */ + + for (cx = commandbuf; *cx == ' '; cx++) { }; + + cmd = cx; + + for (cx = commandbuf+len-1; cx >= cmd && *cx == ' '; cx--) { }; + *(cx+1) = '\0'; + + /* cmd now points to a nice null-terminated string. We'll do the + simplest possible parsing. */ + if (str_eq(cmd, "")) { + glk_put_string("Excuse me?\n"); + } + else if (str_eq(cmd, "help")) { + verb_help(); + } + else if (str_eq(cmd, "move")) { + verb_move(); + } + else if (str_eq(cmd, "jump")) { + verb_jump(); + } + else if (str_eq(cmd, "yada")) { + verb_yada(); + } + else if (str_eq(cmd, "quote")) { + verb_quote(); + } + else if (str_eq(cmd, "quit")) { + verb_quit(); + } + else if (str_eq(cmd, "save")) { + verb_save(); + } + else if (str_eq(cmd, "restore")) { + verb_restore(); + } + else if (str_eq(cmd, "script")) { + verb_script(); + } + else if (str_eq(cmd, "unscript")) { + verb_unscript(); + } + else if (str_eq(cmd, "alert")) { + verb_alert(); + } + else if (str_eq(cmd, "notice")) { + verb_notice(); + } + else { + glk_put_string("I don't understand the command \""); + glk_put_string(cmd); + glk_put_string("\".\n"); + } + } +} + +static void draw_statuswin(void) +{ + char *roomname; + glui32 width, height; + + if (!statuswin) { + /* It is possible that the window was not successfully + created. If that's the case, don't try to draw it. */ + return; + } + + if (current_room == 0) + roomname = "The Room"; + else + roomname = "A Different Room"; + + glk_set_window(statuswin); + glk_window_clear(statuswin); + + glk_window_get_size(statuswin, &width, &height); + + /* Print the room name, centered. */ + glk_window_move_cursor(statuswin, (width - str_len(roomname)) / 2, 1); + glk_put_string(roomname); + + /* Draw a decorative compass rose in the upper right. */ + glk_window_move_cursor(statuswin, width - 3, 0); + glk_put_string("\\|/"); + glk_window_move_cursor(statuswin, width - 3, 1); + glk_put_string("-*-"); + glk_window_move_cursor(statuswin, width - 3, 2); + glk_put_string("/|\\"); + + glk_set_window(mainwin); +} + +static int yes_or_no(void) +{ + char commandbuf[256]; + char *cx; + int gotline, len; + event_t ev; + + draw_statuswin(); + + /* This loop is identical to the main command loop in glk_main(). */ + + while (1) { + glk_request_line_event(mainwin, commandbuf, 255, 0); + + gotline = FALSE; + while (!gotline) { + + glk_select(&ev); + + switch (ev.type) { + case evtype_LineInput: + if (ev.win == mainwin) { + gotline = TRUE; + } + break; + + case evtype_Arrange: + draw_statuswin(); + break; + } + } + + len = ev.val1; + commandbuf[len] = '\0'; + for (cx = commandbuf; *cx == ' '; cx++) { }; + + if (*cx == 'y' || *cx == 'Y') + return TRUE; + if (*cx == 'n' || *cx == 'N') + return FALSE; + + glk_put_string("Please enter \"yes\" or \"no\": "); + } + +} + +static void verb_help(void) +{ + glk_put_string("This model only understands the following commands:\n"); + glk_put_string("HELP: Display this list.\n"); + glk_put_string("JUMP: A verb which just prints some text.\n"); + glk_put_string("YADA: A verb which prints a very long stream of text.\n"); + glk_put_string("MOVE: A verb which prints some text, and also changes the status line display.\n"); + glk_put_string("QUOTE: A verb which displays a block quote in a temporary third window.\n"); + glk_put_string("SCRIPT: Turn on transcripting, so that output will be echoed to a text file.\n"); + glk_put_string("UNSCRIPT: Turn off transcripting.\n"); + glk_put_string("SAVE: Write fake data to a save file.\n"); + glk_put_string("RESTORE: Read it back in.\n"); + glk_put_string("ALERT: Print a frightful message.\n"); + glk_put_string("NOTICE: Print an interesting message.\n"); + glk_put_string("QUIT: Quit and exit.\n"); +} + +static void verb_jump(void) +{ + glk_put_string("You jump on the fruit, spotlessly.\n"); +} + +static void verb_yada(void) +{ + /* This is a goofy (and overly ornate) way to print a long paragraph. + It just shows off line wrapping in the Glk implementation. */ + #define NUMWORDS (13) + static char *wordcaplist[NUMWORDS] = { + "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po", + "Ha", "Ni", "Na" + }; + static char *wordlist[NUMWORDS] = { + "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble", + "gazoon", "ting", "floo", "zonk", "loof", "lob", + }; + static int wcount1 = 0; + static int wcount2 = 0; + static int wstep = 1; + static int jx = 0; + int ix; + int first = TRUE; + + for (ix=0; ix<85; ix++) { + if (ix > 0) { + glk_put_string(" "); + } + + if (first) { + glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]); + first = FALSE; + } + + glk_put_string(wordlist[jx]); + jx = (jx + wstep) % NUMWORDS; + wcount1++; + if (wcount1 >= NUMWORDS) { + wcount1 = 0; + wstep++; + wcount2++; + if (wcount2 >= NUMWORDS-2) { + wcount2 = 0; + wstep = 1; + } + } + + if ((ix % 17) == 16) { + glk_put_string("."); + first = TRUE; + } + } + + glk_put_char('\n'); +} + +static void verb_quote(void) +{ + glk_put_string("Someone quotes some poetry.\n"); + + /* Open a third window, or clear it if it's already open. Actually, + since quotewin is closed right after line input, we know it + can't be open. But better safe, etc. */ + if (!quotewin) { + /* A five-line window above the main window, fixed size. */ + quotewin = glk_window_open(mainwin, winmethod_Above | winmethod_Fixed, + 5, wintype_TextBuffer, 0); + if (!quotewin) { + /* It's possible the quotewin couldn't be opened. In that + case, just give up. */ + return; + } + } + else { + glk_window_clear(quotewin); + } + + /* Print some quote. */ + glk_set_window(quotewin); + glk_set_style(style_BlockQuote); + glk_put_string("Tomorrow probably never rose "); + glk_put_string("or set\n Or went out and bought cheese, or anything like that\n" + "And anyway, what light through yonder quote box breaks\n" + "Handle to my hand?\n"); + glk_put_string(" -- Fred\n"); + + glk_set_window(mainwin); +} + +static void verb_move(void) +{ + current_room = (current_room+1) % 2; + need_look = TRUE; + + glk_put_string("You "); + glk_set_style(style_Emphasized); + glk_put_string("walk"); + glk_set_style(style_Normal); + glk_put_string(" for a while.\n"); +} + +static void verb_quit(void) +{ + glk_put_string("Are you sure you want to quit? "); + if (yes_or_no()) { + glk_put_string("Thanks for playing.\n"); + glk_exit(); + /* glk_exit() actually stops the process; it does not return. */ + } +} + +static void verb_script(void) +{ + if (scriptstr) { + glk_put_string("Scripting is already on.\n"); + return; + } + + /* If we've turned on scripting before, use the same file reference; + otherwise, prompt the player for a file. */ + if (!scriptref) { + scriptref = glk_fileref_create_by_prompt( + fileusage_Transcript | fileusage_TextMode, + filemode_WriteAppend, 0); + if (!scriptref) { + glk_put_string("Unable to place script file.\n"); + return; + } + } + + /* Open the file. */ + scriptstr = glk_stream_open_file(scriptref, filemode_WriteAppend, 0); + if (!scriptstr) { + glk_put_string("Unable to write to script file.\n"); + return; + } + glk_put_string("Scripting on.\n"); + glk_window_set_echo_stream(mainwin, scriptstr); + glk_put_string_stream(scriptstr, + "This is the beginning of a transcript.\n"); +} + +static void verb_unscript(void) +{ + if (!scriptstr) { + glk_put_string("Scripting is already off.\n"); + return; + } + + /* Close the file. */ + glk_put_string_stream(scriptstr, + "This is the end of a transcript.\n\n"); + glk_stream_close(scriptstr, NULL); + glk_put_string("Scripting off.\n"); + scriptstr = NULL; +} + +static void verb_save(void) +{ + int ix; + frefid_t saveref; + strid_t savestr; + + saveref = glk_fileref_create_by_prompt( + fileusage_SavedGame | fileusage_BinaryMode, + filemode_Write, 0); + if (!saveref) { + glk_put_string("Unable to place save file.\n"); + return; + } + + savestr = glk_stream_open_file(saveref, filemode_Write, 0); + if (!savestr) { + glk_put_string("Unable to write to save file.\n"); + glk_fileref_destroy(saveref); + return; + } + + glk_fileref_destroy(saveref); /* We're done with the file ref now. */ + + /* Write some binary data. */ + for (ix=0; ix<256; ix++) { + glk_put_char_stream(savestr, (unsigned char)ix); + } + + glk_stream_close(savestr, NULL); + + glk_put_string("Game saved.\n"); +} + +static void verb_restore(void) +{ + int ix; + int err; + glui32 ch; + frefid_t saveref; + strid_t savestr; + + saveref = glk_fileref_create_by_prompt( + fileusage_SavedGame | fileusage_BinaryMode, + filemode_Read, 0); + if (!saveref) { + glk_put_string("Unable to find save file.\n"); + return; + } + + savestr = glk_stream_open_file(saveref, filemode_Read, 0); + if (!savestr) { + glk_put_string("Unable to read from save file.\n"); + glk_fileref_destroy(saveref); + return; + } + + glk_fileref_destroy(saveref); /* We're done with the file ref now. */ + + /* Read some binary data. */ + err = FALSE; + + for (ix=0; ix<256; ix++) { + ch = glk_get_char_stream(savestr); + if (ch == (glui32)(-1)) { + glk_put_string("Unexpected end of file.\n"); + err = TRUE; + break; + } + if (ch != (glui32)ix) { + glk_put_string("This does not appear to be a valid saved game.\n"); + err = TRUE; + break; + } + } + + glk_stream_close(savestr, NULL); + + if (err) { + glk_put_string("Failed.\n"); + return; + } + + glk_put_string("Game restored.\n"); +} + +void verb_alert() +{ + glk_set_style(style_Alert); + glk_put_string("O noes!\n"); + glk_set_style(style_Normal); +} + +void verb_notice() +{ + glk_set_style(style_Note); + glk_put_string("The answer... is 42!\n"); + glk_set_style(style_Normal); +} + + +/* simple string length test */ +static int str_len(char *s1) +{ + int len; + for (len = 0; *s1; s1++) + len++; + return len; +} + +/* simple string comparison test */ +static int str_eq(char *s1, char *s2) +{ + for (; *s1 && *s2; s1++, s2++) { + if (*s1 != *s2) + return FALSE; + } + + if (*s1 || *s2) + return FALSE; + else + return TRUE; +} + diff --git a/tests/gridtest.c b/tests/gridtest.c new file mode 100644 index 0000000..d8922a1 --- /dev/null +++ b/tests/gridtest.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +void glk_main(void) +{ + event_t ev; + winid_t mainwin = glk_window_open(0, 0, 0, wintype_TextGrid, 0); + if(!mainwin) + return; + + glk_set_window(mainwin); + glk_put_string("Philip en Marijn zijn vet goed.\n"); + glk_put_string("A veeeeeeeeeeeeeeeeeeeeeeeeeeeery looooooooooooooooooooooooong striiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiing.\n"); + + int count; + for(count = 0; count < 30; count++) + glk_put_string("I want to write past the end of this text buffer! "); + + glui32 width, height; + glk_window_get_size(mainwin, &width, &height); + fprintf(stderr, "\nWidth: %d\nHeight: %d\nPress a key in the window, not in the terminal.\n", width, height); + glk_request_char_event(mainwin); + while(1) { + glk_select(&ev); + if(ev.type == evtype_CharInput) + break; + } + + glk_window_move_cursor(mainwin, 15, 15); + glk_put_string(". . "); + glk_window_move_cursor(mainwin, 15, 16); + glk_put_string(" . ."); + glk_window_move_cursor(mainwin, 15, 17); + glk_put_string(". . "); + glk_window_move_cursor(mainwin, 15, 18); + glk_put_string(" . ."); + fprintf(stderr, "Cursor location test.\nPress another key.\n"); + glk_request_char_event(mainwin); + while(1) { + glk_select(&ev); + if(ev.type == evtype_CharInput) + break; + } + + char *buffer = calloc(256, sizeof(char)); + assert(buffer); + + fprintf(stderr, "Line input field until end of line\n"); + glk_window_move_cursor(mainwin, 10, 20); + glk_request_line_event(mainwin, buffer, 256, 0); + while(1) { + glk_select(&ev); + if(ev.type == evtype_LineInput) + break; + } + + fprintf(stderr, "Now edit your previous line input\n"); + glk_window_move_cursor(mainwin, 10, 22); + glk_request_line_event(mainwin, buffer, 256, strlen(buffer)); + while(1) { + glk_select(&ev); + if(ev.type == evtype_LineInput) + break; + } + + char *text = calloc(ev.val1 + 1, sizeof(char)); + assert(text); + strncpy(text, buffer, ev.val1); + text[ev.val1] = '\0'; + fprintf(stderr, "Your string was: '%s'.\nPress another key to clear the window and exit.\n", text); + free(text); + glk_request_char_event(mainwin); + while(1) { + glk_select(&ev); + if(ev.type == evtype_CharInput) + break; + } + + glk_window_clear(mainwin); + free(buffer); +} diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..605a576 --- /dev/null +++ b/tests/main.c @@ -0,0 +1,134 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * main.c + * Copyright (C) Philip en Marijn 2008 <> + * + * main.c is free software copyrighted by Philip en Marijn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name ``Philip en Marijn'' nor the name of any other + * contributor may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * main.c IS PROVIDED BY Philip en Marijn ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Philip en Marijn OR ANY OTHER CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "callbacks.h" +#include "error.h" +#include + +/* Global pointers to widgets */ +GtkBuilder *builder = NULL; +GtkWidget *window = NULL; +GtkWidget *glk = NULL; + +static void +on_started(ChimaraGlk *glk) +{ + g_printerr("Started!\n"); +} + +static void +on_stopped(ChimaraGlk *glk) +{ + g_printerr("Stopped!\n"); +} + +static void +create_window(void) +{ + if( (window = GTK_WIDGET(gtk_builder_get_object(builder, "gargoyle-gtk"))) == NULL ) { + error_dialog(NULL, NULL, "Error while getting main window object"); + return; + } + + gtk_builder_connect_signals(builder, NULL); + + glk = chimara_glk_new(); + g_object_set(glk, "border-width", 6, "spacing", 6, NULL); + chimara_glk_set_default_font_string(CHIMARA_GLK(glk), "Sans 11"); + chimara_glk_set_monospace_font_string(CHIMARA_GLK(glk), "Monospace 10"); + g_signal_connect(glk, "started", G_CALLBACK(on_started), NULL); + g_signal_connect(glk, "stopped", G_CALLBACK(on_stopped), NULL); + + GtkBox *vbox = GTK_BOX( gtk_builder_get_object(builder, "vbox") ); + if(vbox == NULL) + { + error_dialog(NULL, NULL, "Could not find vbox"); + return; + } + + gtk_box_pack_end(vbox, glk, TRUE, TRUE, 0); +} + +int +main(int argc, char *argv[]) +{ + GError *error = NULL; + +#ifdef ENABLE_NLS + bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); +#endif + + if( !g_thread_supported() ) + g_thread_init(NULL); + + gdk_threads_init(); + + gtk_set_locale(); + gtk_init(&argc, &argv); + + builder = gtk_builder_new(); + if( !gtk_builder_add_from_file(builder, "chimara.ui", &error) ) { + error_dialog(NULL, error, "Error while building interface: "); + return 1; + } + + create_window(); + gtk_widget_show_all(window); + + g_object_unref( G_OBJECT(builder) ); + + if( !chimara_glk_run(CHIMARA_GLK(glk), ".libs/first.so", &error) ) { + error_dialog(GTK_WINDOW(window), error, "Error starting Glk library: "); + return 1; + } + + gdk_threads_enter(); + gtk_main(); + gdk_threads_leave(); + + chimara_glk_stop(CHIMARA_GLK(glk)); + chimara_glk_wait(CHIMARA_GLK(glk)); + + return 0; +} diff --git a/tests/model.c b/tests/model.c new file mode 100644 index 0000000..b7831df --- /dev/null +++ b/tests/model.c @@ -0,0 +1,81 @@ +#include +#include + +static winid_t mainwin = NULL; + +void sayit(void) +{ + fprintf(stderr, "I'm the interrupt handler!\n"); +} + +void glk_main(void) +{ + /* Open the main window. */ + mainwin = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); + if (!mainwin) { + /* It's possible that the main window failed to open. There's + nothing we can do without it, so exit. */ + return; + } + + glui32 buffer[1024]; + int i; + for(i = 0; i < 512; i++) { + buffer[i * 2] = i + 33; + buffer[i * 2 + 1] = 32; + } + +/* frefid_t f = glk_fileref_create_temp(fileusage_BinaryMode, 0); + if(f) + { + strid_t s = glk_stream_open_file(f, filemode_ReadWrite, 0);*/ + glui32 membuf[512]; + strid_t s = glk_stream_open_memory_uni(membuf, 512, filemode_ReadWrite, 0); + glk_stream_set_current(s); + + glk_put_char_uni('X'); + glk_put_string("Philip en Marijn zijn vet goed.\n"); + glk_put_buffer_uni(buffer, 1024); + + glk_stream_set_position(s, 0, seekmode_Start); + glk_set_window(mainwin); + glk_put_char_uni( glk_get_char_stream_uni(s) ); + glk_put_char('\n'); + g_printerr( "Line read: %d\n", glk_get_line_stream_uni(s, buffer, 1024) ); + g_printerr("string[5] = %X\n", buffer[5]); + glk_put_string_uni(buffer); + int count = glk_get_buffer_stream_uni(s, buffer, 1024); + g_printerr("Buffer read: %d\n", count); + glk_put_string("\n---SOME CHARACTERS---\n"); + glk_put_buffer_uni(buffer, count); + glk_put_string("\n---THE SAME CHARACTERS IN UPPERCASE---\n"); + int newcount = glk_buffer_to_upper_case_uni(buffer, 1024, 1024); + glk_put_buffer_uni(buffer, newcount); + + stream_result_t result; + glk_stream_close(s, &result); + + g_printerr("Read count: %d\nWrite count: %d\n", result.readcount, result.writecount); +/* glk_fileref_destroy(f); + }*/ + + glk_set_interrupt_handler(&sayit); + + event_t ev; + while(1) { + glk_put_string("\nprompt> "); + glk_request_line_event_uni(mainwin, buffer, 1024, 0); + glk_select(&ev); + switch(ev.type) { + default: + printf("Received event:\n"); + printf("Type: %d\n", ev.type); + printf("Win: %d\n", glk_window_get_rock(ev.win) ); + printf("Var1: %d\n", ev.val1); + printf("Var2: %d\n", ev.val2); + } + } + + /* Bye bye */ + glk_exit(); +} diff --git a/tests/multiwin.c b/tests/multiwin.c new file mode 100644 index 0000000..6aa7e1c --- /dev/null +++ b/tests/multiwin.c @@ -0,0 +1,843 @@ +#include + +/* multiwin.c: Sample program for Glk API, version 0.5. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + This program is in the public domain. +*/ + +/* This example demonstrates multiple windows and timed input in the + Glk API. */ + +/* This is the cleanest possible form of a Glk program. It includes only + "glk.h", and doesn't call any functions outside Glk at all. We even + define our own string functions, rather than relying on the + standard libraries. */ + +/* We also define our own TRUE and FALSE and NULL. */ +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef NULL +#define NULL 0 +#endif + +/* The story and status windows. */ +static winid_t mainwin1 = NULL; +static winid_t mainwin2 = NULL; +static winid_t statuswin = NULL; + +/* Key windows don't get stored in a global variable; we'll find them + by iterating over the list and looking for this rock value. */ +#define KEYWINROCK (97) + +/* For the two main windows, we keep a flag saying whether that window + has a line input request pending. (Because if it does, we need to + cancel the line input before printing to that window.) */ +static int inputpending1, inputpending2; +/* When we cancel line input, we should remember how many characters + had been typed. This lets us restart the input with those characters + already in place. */ +static int already1, already2; + +/* There's a three-second timer which can be on or off. */ +static int timer_on = FALSE; + +/* Forward declarations */ +void glk_main(void); + +static void draw_statuswin(void); +static void draw_keywins(void); +static void perform_key(winid_t win, glui32 key); +static void perform_timer(void); + +static int str_eq(char *s1, char *s2); +static int str_len(char *s1); +static char *str_cpy(char *s1, char *s2); +static char *str_cat(char *s1, char *s2); +static void num_to_str(char *buf, int num); + +static void verb_help(winid_t win); +static void verb_jump(winid_t win); +static void verb_yada(winid_t win); +static void verb_both(winid_t win); +static void verb_clear(winid_t win); +static void verb_page(winid_t win); +static void verb_pageboth(winid_t win); +static void verb_timer(winid_t win); +static void verb_untimer(winid_t win); +static void verb_chars(winid_t win); +static void verb_quit(winid_t win); + +/* The glk_main() function is called by the Glk system; it's the main entry + point for your program. */ +void glk_main(void) +{ + char commandbuf1[256]; /* For mainwin1 */ + char commandbuf2[256]; /* For mainwin2 */ + + /* Open the main windows. */ + mainwin1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 1); + if (!mainwin1) { + /* It's possible that the main window failed to open. There's + nothing we can do without it, so exit. */ + return; + } + + /* Open a second window: a text grid, above the main window, five + lines high. It is possible that this will fail also, but we accept + that. */ + statuswin = glk_window_open(mainwin1, + winmethod_Above | winmethod_Fixed, + 5, wintype_TextGrid, 0); + + /* And a third window, a second story window below the main one. */ + mainwin2 = glk_window_open(mainwin1, + winmethod_Below | winmethod_Proportional, + 50, wintype_TextBuffer, 0); + + /* We're going to be switching from one window to another all the + time. So we'll be setting the output stream on a case-by-case + basis. Every function that prints must set the output stream + first. (Contrast model.c, where the output stream is always the + main window, and every function that changes that must set it + back afterwards.) */ + + glk_set_window(mainwin1); + glk_put_string("Multiwin\nAn Interactive Sample Glk Program\n"); + glk_put_string("By Andrew Plotkin.\nRelease 3.\n"); + glk_put_string("Type \"help\" for a list of commands.\n"); + + glk_set_window(mainwin2); + glk_put_string("Note that the upper left-hand window accepts character"); + glk_put_string(" input. Hit 'h' to split the window horizontally, 'v' to"); + glk_put_string(" split the window vertically, 'c' to close a window,"); + glk_put_string(" and any other key (including special keys) to display"); + glk_put_string(" key codes. All new windows accept these same keys as"); + glk_put_string(" well.\n\n"); + glk_put_string("This bottom window accepts normal line input.\n"); + + if (statuswin) { + /* For fun, let's open a fourth window now, splitting the status + window. */ + winid_t keywin; + keywin = glk_window_open(statuswin, + winmethod_Left | winmethod_Proportional, + 66, wintype_TextGrid, KEYWINROCK); + if (keywin) { + glk_request_char_event(keywin); + } + } + + /* Draw the key window now, since we don't draw it every input (as + we do the status window. */ + draw_keywins(); + + inputpending1 = FALSE; + inputpending2 = FALSE; + already1 = 0; + already2 = 0; + + while (1) { + char *cx, *cmd; + int doneloop, len; + winid_t whichwin; + event_t ev; + + draw_statuswin(); + /* We're not redrawing the key windows every command. */ + + /* Either main window, or both, could already have line input + pending. If so, leave that window alone. If there is no + input pending on a window, set a line input request, but + keep around any characters that were in the buffer already. */ + + if (mainwin1 && !inputpending1) { + glk_set_window(mainwin1); + glk_put_string("\n>"); + /* We request up to 255 characters. The buffer can hold 256, + but we are going to stick a null character at the end, so + we have to leave room for that. Note that the Glk library + does *not* put on that null character. */ + glk_request_line_event(mainwin1, commandbuf1, 255, already1); + inputpending1 = TRUE; + } + + if (mainwin2 && !inputpending2) { + glk_set_window(mainwin2); + glk_put_string("\n>"); + /* See above. */ + glk_request_line_event(mainwin2, commandbuf2, 255, already2); + inputpending2 = TRUE; + } + + doneloop = FALSE; + while (!doneloop) { + + /* Grab an event. */ + glk_select(&ev); + + switch (ev.type) { + + case evtype_LineInput: + /* If the event comes from one main window or the other, + we mark that window as no longer having line input + pending. We also set commandbuf to point to the + appropriate buffer. Then we leave the event loop. */ + if (mainwin1 && ev.win == mainwin1) { + whichwin = mainwin1; + inputpending1 = FALSE; + cmd = commandbuf1; + doneloop = TRUE; + } + else if (mainwin2 && ev.win == mainwin2) { + whichwin = mainwin2; + inputpending2 = FALSE; + cmd = commandbuf2; + doneloop = TRUE; + } + break; + + case evtype_CharInput: + /* It's a key event, from one of the keywins. We + call a subroutine rather than exiting the + event loop (although I could have done it + that way too.) */ + perform_key(ev.win, ev.val1); + break; + + case evtype_Timer: + /* It's a timer event. This does exit from the event + loop, since we're going to interrupt input in + mainwin1 and then re-print the prompt. */ + whichwin = NULL; + cmd = NULL; + doneloop = TRUE; + break; + + case evtype_Arrange: + /* Windows have changed size, so we have to redraw the + status window and key window. But we stay in the + event loop. */ + draw_statuswin(); + draw_keywins(); + break; + } + } + + if (cmd == NULL) { + /* It was a timer event. */ + perform_timer(); + continue; + } + + /* It was a line input event. cmd now points at a line of input + from one of the main windows. */ + + /* The line we have received in commandbuf is not null-terminated. + We handle that first. */ + len = ev.val1; /* Will be between 0 and 255, inclusive. */ + cmd[len] = '\0'; + + /* Then squash to lower-case. */ + for (cx = cmd; *cx; cx++) { + *cx = glk_char_to_lower(*cx); + } + + /* Then trim whitespace before and after. */ + + for (cx = cmd; *cx == ' '; cx++, len--) { }; + + cmd = cx; + + for (cx = cmd+len-1; cx >= cmd && *cx == ' '; cx--) { }; + *(cx+1) = '\0'; + + /* cmd now points to a nice null-terminated string. We'll do the + simplest possible parsing. */ + if (str_eq(cmd, "")) { + glk_set_window(whichwin); + glk_put_string("Excuse me?\n"); + } + else if (str_eq(cmd, "help")) { + verb_help(whichwin); + } + else if (str_eq(cmd, "yada")) { + verb_yada(whichwin); + } + else if (str_eq(cmd, "both")) { + verb_both(whichwin); + } + else if (str_eq(cmd, "clear")) { + verb_clear(whichwin); + } + else if (str_eq(cmd, "page")) { + verb_page(whichwin); + } + else if (str_eq(cmd, "pageboth")) { + verb_pageboth(whichwin); + } + else if (str_eq(cmd, "timer")) { + verb_timer(whichwin); + } + else if (str_eq(cmd, "untimer")) { + verb_untimer(whichwin); + } + else if (str_eq(cmd, "chars")) { + verb_chars(whichwin); + } + else if (str_eq(cmd, "jump")) { + verb_jump(whichwin); + } + else if (str_eq(cmd, "quit")) { + verb_quit(whichwin); + } + else { + glk_set_window(whichwin); + glk_put_string("I don't understand the command \""); + glk_put_string(cmd); + glk_put_string("\".\n"); + } + + if (whichwin == mainwin1) + already1 = 0; + else if (whichwin == mainwin2) + already2 = 0; + } +} + +static void draw_statuswin(void) +{ + glui32 width, height; + + if (!statuswin) { + /* It is possible that the window was not successfully + created. If that's the case, don't try to draw it. */ + return; + } + + glk_set_window(statuswin); + glk_window_clear(statuswin); + + glk_window_get_size(statuswin, &width, &height); + + /* Draw a decorative compass rose in the center. */ + width = (width/2); + if (width > 0) + width--; + height = (height/2); + if (height > 0) + height--; + + glk_window_move_cursor(statuswin, width, height+0); + glk_put_string("\\|/"); + glk_window_move_cursor(statuswin, width, height+1); + glk_put_string("-*-"); + glk_window_move_cursor(statuswin, width, height+2); + glk_put_string("/|\\"); + +} + +/* This draws some corner decorations in *every* key window -- the + one created at startup, and any later ones. It finds them all + with glk_window_iterate. */ +static void draw_keywins(void) +{ + winid_t win; + glui32 rock; + glui32 width, height; + + for (win = glk_window_iterate(NULL, &rock); + win; + win = glk_window_iterate(win, &rock)) { + if (rock == KEYWINROCK) { + glk_set_window(win); + glk_window_clear(win); + glk_window_get_size(win, &width, &height); + glk_window_move_cursor(win, 0, 0); + glk_put_char('O'); + glk_window_move_cursor(win, width-1, 0); + glk_put_char('O'); + glk_window_move_cursor(win, 0, height-1); + glk_put_char('O'); + glk_window_move_cursor(win, width-1, height-1); + glk_put_char('O'); + } + } +} + +/* React to character input in a key window. */ +static void perform_key(winid_t win, glui32 key) +{ + glui32 width, height, len; + int ix; + char buf[128], keyname[64]; + + if (key == 'h' || key == 'v') { + winid_t newwin; + glui32 loc; + /* Open a new keywindow. */ + if (key == 'h') + loc = winmethod_Right | winmethod_Proportional; + else + loc = winmethod_Below | winmethod_Proportional; + newwin = glk_window_open(win, + loc, 50, wintype_TextGrid, KEYWINROCK); + /* Since the new window has rock value KEYWINROCK, the + draw_keywins() routine will redraw it. */ + if (newwin) { + /* Request character input. In this program, only keywins + get char input, so the CharInput events always call + perform_key() -- and so the new window will respond + to keys just as this one does. */ + glk_request_char_event(newwin); + /* We now have to redraw the keywins, because any or all of + them could have changed size when we opened newwin. + glk_window_open() does not generate Arrange events; we + have to do the redrawing manually. */ + draw_keywins(); + } + /* Re-request character input for this window, so that future + keys are accepted. */ + glk_request_char_event(win); + return; + } + else if (key == 'c') { + /* Close this keywindow. */ + glk_window_close(win, NULL); + /* Again, any key windows could have changed size. Also the + status window could have (if this was the last key window). */ + draw_keywins(); + draw_statuswin(); + return; + } + + /* Print a string naming the key that was just hit. */ + + switch (key) { + case ' ': + str_cpy(keyname, "space"); + break; + case keycode_Left: + str_cpy(keyname, "left"); + break; + case keycode_Right: + str_cpy(keyname, "right"); + break; + case keycode_Up: + str_cpy(keyname, "up"); + break; + case keycode_Down: + str_cpy(keyname, "down"); + break; + case keycode_Return: + str_cpy(keyname, "return"); + break; + case keycode_Delete: + str_cpy(keyname, "delete"); + break; + case keycode_Escape: + str_cpy(keyname, "escape"); + break; + case keycode_Tab: + str_cpy(keyname, "tab"); + break; + case keycode_PageUp: + str_cpy(keyname, "page up"); + break; + case keycode_PageDown: + str_cpy(keyname, "page down"); + break; + case keycode_Home: + str_cpy(keyname, "home"); + break; + case keycode_End: + str_cpy(keyname, "end"); + break; + default: + if (key >= keycode_Func1 && key < keycode_Func12) { + str_cpy(keyname, "function key"); + } + else if (key < 32) { + str_cpy(keyname, "ctrl-"); + keyname[5] = '@' + key; + keyname[6] = '\0'; + } + else if (key <= 255) { + keyname[0] = key; + keyname[1] = '\0'; + } + else { + str_cpy(keyname, "unknown key"); + } + break; + } + + str_cpy(buf, "Key: "); + str_cat(buf, keyname); + + len = str_len(buf); + + /* Print the string centered in this window. */ + glk_set_window(win); + glk_window_get_size(win, &width, &height); + glk_window_move_cursor(win, 0, height/2); + for (ix=0; ix len) + width = width-len; + else + width = 0; + + glk_window_move_cursor(win, width, height/2); + glk_put_string(buf); + + /* Re-request character input for this window, so that future + keys are accepted. */ + glk_request_char_event(win); +} + +/* React to a timer event. This just prints "Tick" in mainwin1, but it + first has to cancel line input if any is pending. */ +static void perform_timer() +{ + event_t ev; + + if (!mainwin1) + return; + + if (inputpending1) { + glk_cancel_line_event(mainwin1, &ev); + if (ev.type == evtype_LineInput) + already1 = ev.val1; + inputpending1 = FALSE; + } + + glk_set_window(mainwin1); + glk_put_string("Tick.\n"); +} + +/* This is a utility function. Given a main window, it finds the + "other" main window (if both actually exist) and cancels line + input in that other window (if input is pending.) It does not + set the output stream to point there, however. If there is only + one main window, this returns 0. */ +static winid_t print_to_otherwin(winid_t win) +{ + winid_t otherwin = NULL; + event_t ev; + + if (win == mainwin1) { + if (mainwin2) { + otherwin = mainwin2; + glk_cancel_line_event(mainwin2, &ev); + if (ev.type == evtype_LineInput) + already2 = ev.val1; + inputpending2 = FALSE; + } + } + else if (win == mainwin2) { + if (mainwin1) { + otherwin = mainwin1; + glk_cancel_line_event(mainwin1, &ev); + if (ev.type == evtype_LineInput) + already1 = ev.val1; + inputpending1 = FALSE; + } + } + + return otherwin; +} + +static void verb_help(winid_t win) +{ + glk_set_window(win); + + glk_put_string("This model only understands the following commands:\n"); + glk_put_string("HELP: Display this list.\n"); + glk_put_string("JUMP: Print a short message.\n"); + glk_put_string("YADA: Print a long paragraph.\n"); + glk_put_string("BOTH: Print a short message in both main windows.\n"); + glk_put_string("CLEAR: Clear one window.\n"); + glk_put_string("PAGE: Print thirty lines, demonstrating paging.\n"); + glk_put_string("PAGEBOTH: Print thirty lines in each window.\n"); + glk_put_string("TIMER: Turn on a timer, which ticks in the upper "); + glk_put_string("main window every three seconds.\n"); + glk_put_string("UNTIMER: Turns off the timer.\n"); + glk_put_string("CHARS: Prints the entire Latin-1 character set.\n"); + glk_put_string("QUIT: Quit and exit.\n"); +} + +static void verb_jump(winid_t win) +{ + glk_set_window(win); + + glk_put_string("You jump on the fruit, spotlessly.\n"); +} + +/* Print some text in both windows. This uses print_to_otherwin() to + find the other window and prepare it for printing. */ +static void verb_both(winid_t win) +{ + winid_t otherwin; + + glk_set_window(win); + glk_put_string("Something happens in this window.\n"); + + otherwin = print_to_otherwin(win); + + if (otherwin) { + glk_set_window(otherwin); + glk_put_string("Something happens in the other window.\n"); + } +} + +/* Clear a window. */ +static void verb_clear(winid_t win) +{ + glk_window_clear(win); +} + +/* Print thirty lines. */ +static void verb_page(winid_t win) +{ + int ix; + char buf[32]; + + glk_set_window(win); + for (ix=0; ix<30; ix++) { + num_to_str(buf, ix); + glk_put_string(buf); + glk_put_char('\n'); + } +} + +/* Print thirty lines in both windows. This gets fancy by printing + to each window alternately, without setting the output stream, + by using glk_put_string_stream() instead of glk_put_string(). + There's no particular difference; this is just a demonstration. */ +static void verb_pageboth(winid_t win) +{ + int ix; + winid_t otherwin; + strid_t str, otherstr; + char buf[32]; + + str = glk_window_get_stream(win); + otherwin = print_to_otherwin(win); + if (otherwin) + otherstr = glk_window_get_stream(otherwin); + else + otherstr = NULL; + + for (ix=0; ix<30; ix++) { + num_to_str(buf, ix); + str_cat(buf, "\n"); + glk_put_string_stream(str, buf); + if (otherstr) + glk_put_string_stream(otherstr, buf); + } +} + +/* Turn on the timer. The timer prints a tick in mainwin1 every three + seconds. */ +static void verb_timer(winid_t win) +{ + glk_set_window(win); + + if (timer_on) { + glk_put_string("The timer is already running.\n"); + return; + } + + if (glk_gestalt(gestalt_Timer, 0) == 0) { + glk_put_string("Your Glk library does not support timer events.\n"); + return; + } + + glk_put_string("A timer starts running in the upper window.\n"); + glk_request_timer_events(3000); /* Every three seconds. */ + timer_on = TRUE; +} + +/* Turn off the timer. */ +static void verb_untimer(winid_t win) +{ + glk_set_window(win); + + if (!timer_on) { + glk_put_string("The timer is not currently running.\n"); + return; + } + + glk_put_string("The timer stops running.\n"); + glk_request_timer_events(0); + timer_on = FALSE; +} + +/* Print every character, or rather try to. */ +static void verb_chars(winid_t win) +{ + int ix; + char buf[16]; + + glk_set_window(win); + + for (ix=0; ix<256; ix++) { + num_to_str(buf, ix); + glk_put_string(buf); + glk_put_string(": "); + glk_put_char(ix); + glk_put_char('\n'); + } +} + +static void verb_yada(winid_t win) +{ + /* This is a goofy (and overly ornate) way to print a long paragraph. + It just shows off line wrapping in the Glk implementation. */ + #define NUMWORDS (13) + static char *wordcaplist[NUMWORDS] = { + "Ga", "Bo", "Wa", "Mu", "Bi", "Fo", "Za", "Mo", "Ra", "Po", + "Ha", "Ni", "Na" + }; + static char *wordlist[NUMWORDS] = { + "figgle", "wob", "shim", "fleb", "moobosh", "fonk", "wabble", + "gazoon", "ting", "floo", "zonk", "loof", "lob", + }; + static int wcount1 = 0; + static int wcount2 = 0; + static int wstep = 1; + static int jx = 0; + int ix; + int first = TRUE; + + glk_set_window(win); + + for (ix=0; ix<85; ix++) { + if (ix > 0) { + glk_put_string(" "); + } + + if (first) { + glk_put_string(wordcaplist[(ix / 17) % NUMWORDS]); + first = FALSE; + } + + glk_put_string(wordlist[jx]); + jx = (jx + wstep) % NUMWORDS; + wcount1++; + if (wcount1 >= NUMWORDS) { + wcount1 = 0; + wstep++; + wcount2++; + if (wcount2 >= NUMWORDS-2) { + wcount2 = 0; + wstep = 1; + } + } + + if ((ix % 17) == 16) { + glk_put_string("."); + first = TRUE; + } + } + + glk_put_char('\n'); +} + +static void verb_quit(winid_t win) +{ + glk_set_window(win); + + glk_put_string("Thanks for playing.\n"); + glk_exit(); + /* glk_exit() actually stops the process; it does not return. */ +} + +/* simple string length test */ +static int str_len(char *s1) +{ + int len; + for (len = 0; *s1; s1++) + len++; + return len; +} + +/* simple string comparison test */ +static int str_eq(char *s1, char *s2) +{ + for (; *s1 && *s2; s1++, s2++) { + if (*s1 != *s2) + return FALSE; + } + + if (*s1 || *s2) + return FALSE; + else + return TRUE; +} + +/* simple string copy */ +static char *str_cpy(char *s1, char *s2) +{ + char *orig = s1; + + for (; *s2; s1++, s2++) + *s1 = *s2; + *s1 = '\0'; + + return orig; +} + +/* simple string concatenate */ +static char *str_cat(char *s1, char *s2) +{ + char *orig = s1; + + while (*s1) + s1++; + for (; *s2; s1++, s2++) + *s1 = *s2; + *s1 = '\0'; + + return orig; +} + +/* simple number printer */ +static void num_to_str(char *buf, int num) +{ + int ix; + int size = 0; + char tmpc; + + if (num == 0) { + str_cpy(buf, "0"); + return; + } + + if (num < 0) { + buf[0] = '-'; + buf++; + num = -num; + } + + while (num) { + buf[size] = '0' + (num % 10); + size++; + num /= 10; + } + for (ix=0; ix +#include +#include + +#define SPACE_FACTOR 1.8 + +void center_text(winid_t win, char *text) +{ + glui32 width, height; + glk_window_get_size(win, &width, &height); + + glk_set_window(win); + glk_window_clear(win); + + if(glk_window_get_type(win) == wintype_TextGrid) { + glk_window_move_cursor(win, width / 2 - strlen(text) / 2, height / 2); + glk_put_string(text); + } else if(glk_window_get_type(win) == wintype_TextBuffer) { + int count; + for(count = 0; count < height / 2; count++) + glk_put_char('\n'); + for(count = 0; + count < (int)(SPACE_FACTOR * (width / 2 - strlen(text) / 2)); + count++) + glk_put_char(' '); + glk_put_string(text); + } +} + +void print_two_rows(winid_t win) +{ + glui32 width, height; + glk_window_get_size(win, &width, &height); + + glk_set_window(win); + glk_window_clear(win); + + glui32 x = width / 2 - 3; + glui32 y = (height - 1) / 2; + if(x < 0) + x = 0; + if(y < 0) + y = 0; + + glk_window_move_cursor(win, x, y); + glk_put_string("C: 2"); + glk_window_move_cursor(win, x + 3, y + 1); + glk_put_string("rows"); +} + +void wait_for_key(winid_t win) +{ + event_t ev; + glk_request_char_event(win); + do + glk_select(&ev); + while(ev.type != evtype_CharInput); +} + +void glk_main(void) +{ + winid_t win_a = NULL, win_b = NULL, win_c = NULL, win_d = NULL; + + fprintf(stderr, "TEST CASES FROM GLK SPEC\n\n" + "(Press a key in window A to continue each time)\n\n" + "Say you do two splits, each a 50-50 percentage split. You start\n" + "with the original window A, and split that into A and B; then\n" + "you split B into B and C.\n\n"); + + win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0); + win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0); + if(!win_a || !win_b || !win_c) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + + wait_for_key(win_a); + glk_window_close(glk_window_get_root(), NULL); + + fprintf(stderr, "Or, you could split A into A and B, and then split A\n" + "again into A and C.\n\n"); + + win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0); + win_c = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0); + if(!win_a || !win_b || !win_c) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + + wait_for_key(win_a); + glk_window_close(glk_window_get_root(), NULL); + + fprintf(stderr, "Here are more ways to perform the first example; all of\n" + "them have the same tree structure, but look different on the\n" + "screen. Here, we turn the second split (B into B/C) upside down;\n" + "we put the new window (C) above the old window (B).\n\n"); + + win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0); + win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Above, + 50, wintype_TextBuffer, 0); + if(!win_a || !win_b || !win_c) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + + wait_for_key(win_a); + glk_window_close(glk_window_get_root(), NULL); + + fprintf(stderr, "Here, we mess with the percentages. The first split (A\n" + "into A/B) is a 25-75 split, which makes B three times the size\n" + "of A. The second (B into B/C) is a 33-66 split, which makes C\n" + "twice the size of B. This looks rather like the second example,\n" + "but has a different internal structure.\n\n"); + + win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, + 75, wintype_TextBuffer, 0); + win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Below, + 67, wintype_TextBuffer, 0); + if(!win_a || !win_b || !win_c) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + + wait_for_key(win_a); + glk_window_close(glk_window_get_root(), NULL); + + fprintf(stderr, "Here, the second split (B into B/C) is vertical instead\n" + "of horizontal, with the new window (C) on the left of the old\n" + "one.\n\n"); + + win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0); + win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Left, + 50, wintype_TextBuffer, 0); + if(!win_a || !win_b || !win_c) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + + wait_for_key(win_a); + glk_window_close(glk_window_get_root(), NULL); + + fprintf(stderr, "In the following two-split process, you can see that\n" + "when a window is split, it is replaced by a new pair window, and\n" + "moves down to become one of its two children.\n\n"); + + if(!(win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0))) + return; + center_text(win_a, "A"); + wait_for_key(win_a); + + if(!(win_b = glk_window_open(win_a, + winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0))) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + wait_for_key(win_a); + + if(!(win_c = glk_window_open(win_b, winmethod_Proportional | winmethod_Left, + 50, wintype_TextBuffer, 0))) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + wait_for_key(win_a); + + glk_window_close(glk_window_get_root(), NULL); + + fprintf(stderr, "What happens when there is a conflict? The rules are\n" + "simple. Size control always flows down the tree, and the player\n" + "is at the top. Let's bring out an example: first we split A into\n" + "A and B, with a 50%% proportional split. Then we split A into A\n" + "and C, with C above, being a text grid window, and C gets a\n" + "fixed size of two rows (as measured in its own font size). A\n" + "gets whatever remains of the 50%% it had before.\n\n"); + + win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0); + win_c = glk_window_open(win_a, winmethod_Fixed | winmethod_Above, + 2, wintype_TextGrid, 0); + if(!win_a || !win_b || !win_c) + return; + center_text(win_a, "A"); + center_text(win_b, "B: 50%"); + print_two_rows(win_c); + wait_for_key(win_a); + + fprintf(stderr, "(Stage 1) Now the player stretches the window\n" + "vertically.\n\n"); + + wait_for_key(win_a); + + fprintf(stderr, "(Stage 2) Then the user maliciously starts squeezing the\n" + "window down, in stages.\n\n"); + + center_text(win_a, "A"); + center_text(win_b, "B: 50%"); + print_two_rows(win_c); + wait_for_key(win_a); + + fprintf(stderr, "(Stage 3) The logic remains the same. At stage 3,\n" + "there's no room left for A, so it winds up with zero height.\n" + "Nothing displayed in A will be visible.\n\n"); + + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + wait_for_key(win_a); + + fprintf(stderr, "(Stage 4) At stage 4, there isn't even room in the upper\n" + "50%% to give C its two rows; so it only gets one.\n\n"); + + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + wait_for_key(win_a); + + fprintf(stderr, "(Stage 5) Finally, C is squashed out of existence as\n" + "well.\n\n"); + + center_text(win_a, "A"); + center_text(win_b, "B"); + wait_for_key(win_a); + + glk_window_close(glk_window_get_root(), NULL); + + fprintf(stderr, "What happens when you split a fixed-size window? The\n" + "resulting pair window retains the same size constraint as the\n" + "original window that was split. The key window for the original\n" + "split is still the key window for that split, even though it's\n" + "now a grandchild instead of a child.\n\n"); + + if(!(win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0))) + return; + center_text(win_a, "A"); + wait_for_key(win_a); + + fprintf(stderr, "After the first split, the new pair window (O1, which\n" + "covers the whole screen) knows that its first child (A) is above\n" + "the second, and gets 50%% of its own area. (A is the key window\n" + "for this split, but a proportional split doesn't care about key\n" + "windows.)\n\n"); + + if(!(win_b = glk_window_open(win_a, + winmethod_Proportional | winmethod_Below, + 50, wintype_TextBuffer, 0))) + return; + center_text(win_a, "A: 50%"); + center_text(win_b, "B"); + wait_for_key(win_a); + + fprintf(stderr, "After the second split, all this remains true; O1 knows\n" + "that its first child gets 50%% of its space, and A is O1's key\n" + "window. But now O1's first child is O2 instead of A. The newer\n" + "pair window (O2) knows that its first child (C) is above the\n" + "second, and gets a fixed size of two rows. (As measured in C's\n" + "font, because C is O2's key window.)\n\n"); + + if(!(win_c = glk_window_open(win_a, winmethod_Fixed | winmethod_Above, 2, + wintype_TextGrid, 0))) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + print_two_rows(win_c); + wait_for_key(win_a); + + fprintf(stderr, "If we split C, now, the resulting pair will still be two\n" + "C-font rows high -- that is, tall enough for two lines of\n" + "whatever font C displays. For the sake of example, we'll do this\n" + "vertically.\n\n"); + + if(!(win_d = glk_window_open(win_c, + winmethod_Proportional | winmethod_Right, + 50, wintype_TextBuffer, 0))) + return; + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + center_text(win_d, "D"); + wait_for_key(win_a); + + fprintf(stderr, "When you close a window (and it is not the root window),\n" + "the other window in its pair takes over all the freed-up area.\n" + "Let's close D, in the current example:\n\n"); + + glk_window_close(win_d, NULL); + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_c, "C"); + wait_for_key(win_a); + + fprintf(stderr, "But what if we had closed C instead of D? We would have\n" + "gotten this:\n\n"); + + glk_window_close(glk_window_get_root(), NULL); + win_a = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + win_b = glk_window_open(win_a, winmethod_Proportional | winmethod_Below, 50, + wintype_TextBuffer, 0); + win_c = glk_window_open(win_a, winmethod_Fixed | winmethod_Above, 2, + wintype_TextGrid, 0); + win_d = glk_window_open(win_c, winmethod_Proportional | winmethod_Right, 50, + wintype_TextBuffer, 0); + glk_window_close(win_c, NULL); + center_text(win_a, "A"); + center_text(win_b, "B"); + center_text(win_d, "D"); + wait_for_key(win_a); + + glk_window_close(win_d, NULL); + glk_window_close(win_b, NULL); + glk_window_clear(win_a); + glk_set_window(win_a); + glk_put_string("That's all, folks..."); +}