#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <locale.h>
#include <ctype.h>
#include <signal.h>
#include <limits.h>

#include <kernel.h>

#include <flex.h>

#include <oslib/osfile.h>
#include <oslib/osgbpb.h>
#include <oslib/osbyte.h>
#include <oslib/colourtrans.h>
#include <oslib/territory.h>
#include <oslib/fileinfo.h>
#include <oslib/proginfo.h>
#include <oslib/saveas.h>
#include <oslib/menu.h>
#include <oslib/quit.h>
#include <oslib/fontmenu.h>
#include <oslib/colourdbox.h>
#include <oslib/popup.h>
#include <oslib/stringset.h>
#include <oslib/actionbutton.h>
#include <oslib/displayfield.h>
#include <oslib/numberrange.h>
#include <oslib/optionbutton.h>
#include <oslib/radiobutton.h>
#include <oslib/button.h>
#include <oslib/wimpspriteop.h>
#include <oslib/window.h>
#include <event.h>

#include "options.h"

#include "ztypes.h"
#include "riscosio.h"
#include "memory.h"
#include "control.h"
#include "fileio.h"
#include "quetzal.h"
#include "text.h"
#include "screen.h"
#include "osdepend.h"
#include "screenio.h"
#include "riscoswimp.h"
#include "v6.h"
#include "v6ro.h"
#include "graphics.h"
#include "pngro.h"
#include "rosound.h"

#include "user.h"

#ifdef INFIX
#include "debug.h"
#endif

void __rt_freeauto(void *x)
{
    free(x);
}

void *__rt_allocauto(size_t s)
{
    void *p=malloc(s);
    if (!p) { raise(SIGSTAK); abort(); }
    return p;
}

/* Toolbox events */
#define action_Quit 1
#define action_GrabFkeys 2
#define action_LineEdit 3
#define action_CopyScreen 4
#define action_Border 5
#define action_CmdRecall 6
#define action_Recording 7
#define action_Keypad 8
#define action_FullScreen 9
#define action_FileChoiceFlip 10
#define action_OpenSaveDir 11
#define action_MenuSave 12

#define Cmp_Fkeys 0
#define Cmp_LineEdit 1
#define Cmp_CmdRecall 2
#define Cmp_Recording 3
#define Cmp_Keypad 4

#define Cmp_Border 5
#define Cmp_FullScreen 7

#define Cmp_Save 3

#define Cmp_ExportTranscript 1
#define Cmp_ExportRecording 2
#define Cmp_ExportAuxiliary 3

#define Cmp_Release 0
#define Cmp_Version 2

#define Cmp_QuickLoad 2
#define Cmp_PauseExit 3
#define Cmp_ShowCaret 4
#define Cmp_ConfirmQ 5
#define Cmp_InterpNum 7
#define Cmp_InterpMenu 8
//#define Cmp_ForeMenu 11
//#define Cmp_BackMenu 12
#define Cmp_WarnOverwrite 13
#define Cmp_SmartQuotes 14
#define Cmp_UndoSlots 16
#define Cmp_ScreenGamma 18
#define Cmp_Dithering 20
#define Cmp_UpdateOften 21
#define Cmp_FGPatch 23
#define Cmp_FGPopUp 24
#define Cmp_BGPatch 25
#define Cmp_BGPopUp 26

#define Cmp_FileType 0
#define Cmp_Location 0x10
#define Cmp_LocationCentral 0x10
#define Cmp_LocationCentralDir 0x11
#define Cmp_LocationZipDir 0x12
#define Cmp_LocationGameDir 0x13
#define Cmp_LocationInDir 0x14
#define Cmp_LocationNearDir 0x15
#define Cmp_Prompt 0x20
#define Cmp_SaveAs 0x21

#define Cmp_StoryName 1
#define Cmp_StoryCopyright 3
#define Cmp_StoryAuthor 5
#define Cmp_StoryType 7
#define Cmp_StoryRelease 9
#define Cmp_StorySize 11
#define Cmp_StoryDate 13
#define Cmp_StoryAnno 15

unsigned h_file_size;
unsigned h_extension_table;
unsigned h_extension_length;

static messagetrans_control_block mfd;
static toolbox_block id_block;
static int wimpver;
int log2bpp;

#ifdef INFIX
wimp_t our_task;
#endif

os_colour true_default_fg=os_COLOUR_BLACK;
os_colour true_default_bg=os_COLOUR_WHITE;

toolbox_o mainmenu;
static toolbox_o Screen, StoryInfo, saveasbox, filemenu, exportmenu;
wimp_w ScreenW;

static char Release[5+1+6+1];
char StoryName[1024];
#define KeyQueueLen 16
static int KeyQueue[KeyQueueLen];
static int KeyIn=0, KeyOut=0;
int last_menu, last_item;

int caret_enabled;

static int scrwidth, scrheight;

static toolbox_o QuitObj;

bool hiresmode;
bool grab_fkeys;
static bool quickload;
bool pauseexit=true;
bool showcaret=true;
static bool confirmq=true;
bool warn_overwrite;
static bool separate_keypad=true;
#ifdef ALLOW_QUOTES
bool smartquotes=true;
#endif
#ifdef ALLOW_RECALL
bool command_recall;
#endif
#ifdef ALLOW_EDITING
bool line_editing=true;
#endif
int default_path[4] = { 0, 2, 2, 0 };
bool file_prompt[4] = { true, true, true, false };
bool file_savebox[4];
const char *const file_ext[4] = { "save", "script", "record", "aux" };

void *pollword;

//static bool mouse_in_window;

static const os_box *SetExtent(void);
static void savechoices(void);

static void gadget_fade(toolbox_o o, toolbox_c c, bool fade)
{
    gadget_flags want, flags=gadget_get_flags(NONE, o, c);

    if (fade)
        want = flags | gadget_FADED;
    else
        want = flags &~ gadget_FADED;

    if (flags != want)
        gadget_set_flags(NONE, o, c, want);
}

/*static int enter_handler(wimp_event_no event_code, wimp_block *event,
                         toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    mouse_in_window = true;
    return 1;
}

static int leave_handler(wimp_event_no event_code, wimp_block *event,
                         toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    mouse_in_window = false;
    return 1;
}*/

void setup_mouse(int use_mouse)
{
    NOT_USED(use_mouse);
}

static char msgs_buffer[256];

char *msgs_lookup(const char *token)
{
    int used;

    messagetrans_lookup(&mfd, token, msgs_buffer, sizeof msgs_buffer,
                        NULL, NULL, NULL, NULL, &used);

    msgs_buffer[used]='\0';

    return msgs_buffer;
}

char *msgs_lookup_1(const char *token, const char *param)
{
    int used;

    messagetrans_lookup(&mfd, token, msgs_buffer, sizeof msgs_buffer,
                        param, NULL, NULL, NULL, &used);

    msgs_buffer[used]='\0';

    return msgs_buffer;
}

char *msgs_lookup_2(const char *token, const char *param0, const char *param1)
{
    int used;

    messagetrans_lookup(&mfd, token, msgs_buffer, sizeof msgs_buffer,
                        param0, param1, NULL, NULL, &used);

    msgs_buffer[used]='\0';

    return msgs_buffer;
}

zword_t *msgs_lookup_u(const char *token)
{
    char buffer[sizeof msgs_buffer/2];
    int used;

    messagetrans_lookup(&mfd, token, buffer, sizeof buffer,
                        NULL, NULL, NULL, NULL, &used);

    buffer[used]='\0';

    return system_string_to_unicode((zword_t *) msgs_buffer, buffer);
}

zword_t *msgs_lookup_u_1(const char *token, const char *param)
{
    char buffer[sizeof msgs_buffer/2];
    int used;

    messagetrans_lookup(&mfd, token, buffer, sizeof buffer,
                        param, NULL, NULL, NULL, &used);

    msgs_buffer[used]='\0';

    return system_string_to_unicode((zword_t *) msgs_buffer, buffer);
}

#if 0 // Not used at present
static char *xmsgs_lookup(const char *token)
{
    int used;
    os_error *e;

    e=xmessagetrans_lookup(&mfd, token, msgs_buffer, sizeof msgs_buffer,
                           NULL, NULL, NULL, NULL, NULL, &used);

    if (e)
        return 0;

    msgs_buffer[used]='\0';

    return msgs_buffer;
}
#endif

void illegal_write(unsigned addr)
{
    char buffer[16];
    sprintf(buffer, "%X", addr);
    fatal(msgs_lookup_1("BadWrite", buffer));
}

unsigned illegal_read(unsigned addr)
{
    char buffer[16];
    sprintf(buffer, "%X", addr);
    fatal(msgs_lookup_1(addr >= (h_file_size << size_shift) ? "BadRead" : "BadRead2", buffer));
    return 0;
}

/*const char *last_os_error()
{
    return _kernel_last_oserror()->errmess;
}*/

void fatal_lookup(const char *token)
{
    fatal(msgs_lookup(token));
}

void fatal_lookup_1(const char *token, const char *param)
{
    fatal(msgs_lookup_1(token, param));
}

void warning_lookup(const char *token)
{
    warning(msgs_lookup(token));
}

void warning_lookup_1(const char *token, const char *param)
{
    warning(msgs_lookup_1(token, param));
}

void warning_lookup_2(const char *token, const char *param0, const char *param1)
{
    warning(msgs_lookup_2(token, param0, param1));
}

void info_lookup(const char *token)
{
    info(msgs_lookup(token));
}

void info_lookup_1(const char *token, const char *param0)
{
    info(msgs_lookup_1(token, param0));
}

const char *TaskName(void)
{
    static char *name;

    if (!name)
    {
        int len;
        len = toolboxgetsysinfo_task_name(NULL, 0) + 1;
        name = malloc(len);
        toolboxgetsysinfo_task_name(name, len);
    }

    return name;
}

void initialise_flex(void)
{
    flex_init((char *)TaskName(), mfd.cb, 0);
    flex_set_budge(1);
    flex_set_deferred_compaction(1);
}

static void backtrace(void)
{
    dump_backtrace("<Zip2000$Dir>.^.Backtrace");
    os_cli("Filer_Run <Zip2000$Dir>.^.Backtrace");
}

/*
 * fatal
 *
 * Display message and stop interpreter.
 *
 */

void fatal(const char *s)
{
    os_error err;
    wimp_error_box_selection sel;
    char *buttons = 0;

    if (h_type==V6)
    	switchoutput(-100);

    err.errnum=1;
    if (pc)
    {
    	sprintf(err.errmess, "Fatal error: %s (PC=&%05X)", s, pc-datap);
    	buttons = msgs_lookup("BtQuit");
    }
    else
    {
    	sprintf(err.errmess, "Fatal error: %s", s);
    	buttons = msgs_lookup("Quit");
    }

    if (wimpver >= 322)
        sel = wimp_report_error_by_category(&err,
                wimp_ERROR_BOX_CATEGORY_ERROR << wimp_ERROR_BOX_CATEGORY_SHIFT,
                TaskName(), "!zip2000", wimpspriteop_AREA, buttons);
    else
        sel = wimp_report_error(&err, wimp_ERROR_BOX_CANCEL_ICON, TaskName());

    if (sel == 4)
        backtrace();

    reset_screen();

    exit(1);
}

/*
 * warning
 *
 * Display warning message.
 *
 */
void warning(const char *s)
{
    os_error err;
    wimp_error_box_selection sel;
    char *buttons = 0;

    if (h_type==V6)
        switchoutput(-100);

    err.errnum=1;
    if (pc)
    {
    	sprintf(err.errmess, "Warning: %s (PC=&%05X)", s, pc-datap);
    	buttons = msgs_lookup("Backtrace");
    }
    else
    	sprintf(err.errmess, "Warning: %s", s);

    sel = wimp_report_error_by_category(&err,
                wimp_ERROR_BOX_OK_ICON |
                wimp_ERROR_BOX_CATEGORY_ERROR << wimp_ERROR_BOX_CATEGORY_SHIFT,
                TaskName(), "!zip2000", wimpspriteop_AREA, buttons);

    if (sel == 3)
        backtrace();

    if (h_type==V6)
    	switchoutput(+1);
}

/*
 * error
 *
 * Display error message.
 *
 */
void error(const os_error *e)
{
    if (h_type==V6)
        switchoutput(-100);

    wimp_report_error_by_category(e,
                wimp_ERROR_BOX_OK_ICON |
                wimp_ERROR_BOX_CATEGORY_ERROR << wimp_ERROR_BOX_CATEGORY_SHIFT,
                TaskName(), "!zip2000", wimpspriteop_AREA, NULL);

    if (h_type==V6)
    	switchoutput(+1);
}

/*
 * info
 *
 * Display warning message.
 *
 */
void info(const char *s)
{
    os_error err;

    if (h_type==V6)
        switchoutput(-100);

    err.errnum=1;
    strcpy(err.errmess, s);

    wimp_report_error_by_category(&err,
                wimp_ERROR_BOX_OK_ICON | wimp_ERROR_BOX_NO_BEEP |
                wimp_ERROR_BOX_CATEGORY_INFO << wimp_ERROR_BOX_CATEGORY_SHIFT,
                TaskName(), "!zip2000", wimpspriteop_AREA, 0);

    if (h_type==V6)
    	switchoutput(+1);
}


/*
 * warn_bad_use
 *
 * Warn about use of opcode in wrong version
 *
 */
void warn_bad_use(const char *s)
{
    char buffer[256];

    sprintf(buffer, msgs_lookup("BadUse"), s, h_type);

    warning(buffer);
}

const char *GameTitle(void)
{
    static char temp[256];
    char *guess;
    int used;
    os_error *e;

    if (blorb_name_s)
        return blorb_name_s;

    memcpy(temp, datap+H_SERIAL_NUMBER, 6);
    temp[6]='\0';

    sprintf(Release, "%d.%s", get_word_priv(H_RELEASE), temp);

    e=xmessagetrans_lookup(&mfd, Release, temp, sizeof temp,
                           NULL, NULL, NULL, NULL, &guess, &used);
    if (!e)
        temp[used]='\0';
    else
    {
        guess=strrchr(StoryName, '.');
        if (guess)
            guess++;
        else
            guess=StoryName;

        strcpy(temp, guess);
        *temp=toupper(*temp);
        guess=temp;
    }

    return guess;
}

static void sprintnum_formatted(char *out, unsigned num)
{
    struct lconv *l = localeconv();
    char buffer[64];
    char buffer2[64];
    char *p = buffer2 + sizeof buffer2;
    char *p2;
    const char *sep = l->thousands_sep;
    const char *group = l->grouping;
    int seplen = strlen(sep);
    int n;

    n = sprintf(buffer, "%u", num);
    p2 = buffer + n - 1;

    n = *group;
    *--p = '\0';

    while (p2 >= buffer)
    {
        n--;
        if (n < 0)
        {
            memcpy(p-seplen, sep, seplen);
            p-=seplen;
            n = *++group;
            if (n == 0)
                n = *--group;
            n--;
        }
        *--p = *p2--;
    }
    strcpy(out, p);
}

void SetTitle(void)
{
    const char *tit = GameTitle();
    char buffer[260];
    char buffer2[30];
    int i;
    os_date_and_time t;
    bits hi, lo;

    if (h_type==V6)
        switchoutput(-1);

    window_set_title(NONE, Screen, tit);
    button_set_value(NONE, StoryInfo, Cmp_StoryName, tit);

    window_set_title(NONE, StoryInfo, StoryName);
    if (blorb_copyright)
    {
        /*
         * Yes, we check whether the copyright symbol is available in the
         * current system alphabet...
         */
        i = unicode_to_system(0x00A9, 0);
        if (i)
            sprintf(buffer, "%c %s", i, blorb_copyright);
        else
            sprintf(buffer, "(c) %s", blorb_copyright);
        button_set_value(NONE, StoryInfo, Cmp_StoryCopyright, buffer);
    }
    if (blorb_author)
        button_set_value(NONE, StoryInfo, Cmp_StoryAuthor, blorb_author);
    if (blorb_anno)
        button_set_value(NONE, StoryInfo, Cmp_StoryAnno, blorb_anno);
    sprintf(buffer, "%d", h_type);
    button_set_value(NONE, StoryInfo, Cmp_StoryType,
                     msgs_lookup_1(zcode_in_blorb ? "BlorbV" :
                                   blorb_map ? "VersionB" : "Version", buffer));
    sprintf(buffer, "%d.%n", h_release, &i);
    memcpy(buffer + i, datap+H_SERIAL_NUMBER, 6);
    if (blorb_release)
        sprintf(buffer+i+6, " / %d", blorb_release);
    else if (GFile)
        sprintf(buffer+i+6, " / %d", GHeader.version);
    else
        buffer[i+6]='\0';
    button_set_value(NONE, StoryInfo, Cmp_StoryRelease, buffer);

    osfile_read_stamped(StoryName, &hi, &lo, NULL, NULL, NULL);
    *((int *)t)=lo; t[4]=hi;
    territory_convert_standard_date_and_time(territory_CURRENT, &t, buffer, sizeof buffer);
    button_set_value(NONE, StoryInfo, Cmp_StoryDate, buffer);

    sprintnum_formatted(buffer, h_file_size<<size_shift);
    if (blorb_map)
    {
        fseek(bfp, 4L, SEEK_SET);
        sprintnum_formatted(buffer2, fget32(bfp) + 8);
        button_set_value(NONE, StoryInfo, Cmp_StorySize,
                         msgs_lookup_2(zcode_in_blorb ? "SizeIn" : "SizePlus",
                                        buffer, buffer2));
    }
    else
        button_set_value(NONE, StoryInfo, Cmp_StorySize, buffer);

    if (h_type==V6)
        switchoutput(+1);
}

typedef struct
{
    os_box visible;
    int xscroll;
    int yscroll;
    wimp_w next;
    wimp_window_flags flags;
    wimp_w parent;
    bits alignment;
}
my_toolbox_full;

typedef union
{
    os_coord top_left;
    my_toolbox_full full;
}
my_toolbox_position;

#define toolbox_SHOW_AS_SUB_WINDOW ((toolbox_show_flags) 0x4u)

static void ExpandWindow(const os_box *extent)
{
    int width, height, scrwidth, scrheight, xeig, yeig;
    my_toolbox_position pos;
    wimp_window_state ws;

    ws.w = ScreenW;
    wimp_get_window_state(&ws);

    width=extent->x1-extent->x0;
    height=extent->y1-extent->y0;

    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT, &scrwidth);
    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT, &scrheight);
    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR, &xeig);
    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR, &yeig);

    scrwidth+=1; scrheight+=1;
    scrwidth<<=xeig; scrheight<<=yeig;

    pos.full.visible.x0=(scrwidth-width)/2;
    pos.full.visible.x1=pos.full.visible.x0+width;
    pos.full.visible.y0=(scrheight-height)/2;
    pos.full.visible.y1=pos.full.visible.y0+height;
    pos.full.xscroll=extent->x0;
    pos.full.yscroll=extent->y1;
    pos.full.next=wimp_TOP;
    if (fullscreen)
        pos.full.flags=(ws.flags | wimp_WINDOW_NO_BOUNDS) &~ wimp_WINDOW_BOUNDED_ONCE;
    else
        pos.full.flags=(ws.flags | wimp_WINDOW_BOUNDED_ONCE) &~ wimp_WINDOW_NO_BOUNDS;
    pos.full.parent=wimp_TOP;
    pos.full.alignment=1; /* Set flags */

    /* Keep clear of the icon bar */
    if (pos.full.visible.y0 < 128 && !fullscreen)
    {
        pos.full.visible.y1 += 128 - pos.full.visible.y0;
        pos.full.visible.y0 = 128;
    }

    toolbox_show_object(toolbox_SHOW_AS_SUB_WINDOW, Screen,
                        toolbox_POSITION_FULL, (toolbox_position *) &pos,
                        toolbox_NULL_OBJECT, toolbox_NULL_COMPONENT);
}

/*
 * This general-purpose routine will work for objects of class Window, DCS,
 * FileInfo, FontDbox, PrintDbox, ProgInfo, Quit, SaveAs, Scale, and indeed
 * any other object that supports Toolbox_MiscOp 0 as XXX_GetWindowId, and
 * for which Toolbox_ShowObject_FullSpec takes the same block as the Window
 * class.
 */

static void ShowCentred(toolbox_show_flags flags, toolbox_o obj,
                        toolbox_o parent_obj, toolbox_c parent_cmp)
{
    wimp_window_state state;
    int width, height;
    toolbox_o window;
    toolbox_class objclass;
    toolbox_position pos;

    objclass=toolbox_get_object_class(NONE, obj);
    if (objclass==class_WINDOW)
        window=obj;
    else
        window=fileinfo_get_window_id(NONE, obj);

    state.w=window_get_wimp_handle(NONE, window);

    wimp_get_window_state(&state);

    width=state.visible.x1-state.visible.x0;
    height=state.visible.y1-state.visible.y0;

    pos.top_left.x=(scrwidth-width)/2;
    pos.top_left.y=(scrheight+height)/2;

    /* Keep clear of the icon bar */
    if (pos.top_left.y - height < 128)
        pos.top_left.y = 128 + height;

    toolbox_show_object(flags, obj, toolbox_POSITION_TOP_LEFT, &pos,
                        parent_obj, parent_cmp);
}

static bool quitmenu_handler(bits event_code, toolbox_action *event,
                             toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    caret_enabled=false;
    place_caret();

    if (h_type==V6)
    	switchoutput(-1);

    reset_screen();

    exit(EXIT_SUCCESS);

    return 1;
}

static bool iconise_handler(wimp_message *message, void *handle)
{
    wimp_message_window_info *info = (wimp_message_window_info *) &message->data;
    char *p;

    NOT_USED(handle);

    strcpy(info->sprite_name, "zip2000");

    p=strrchr(StoryName, '.');

    strncpy(info->title, p ? p+1 : StoryName, sizeof info->title);

    message->size=48;
    message->your_ref=message->my_ref;

    wimp_send_message(wimp_USER_MESSAGE, message, message->sender);

    return 1;
}

static bool quit_handler(wimp_message *message, void *handle)
{
    NOT_USED(message); NOT_USED(handle);

    caret_enabled=false;
    place_caret();

    if (h_type==V6)
    	switchoutput(-1);

    reset_screen();

    exit(EXIT_SUCCESS);

    return 1;
}

static bool grabfkeys_handler(bits event_code, toolbox_action *event,
                              toolbox_block *id, void *handle)
{
    bool on;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    on=menu_get_tick(NONE, id->this_obj, id->this_cmp);

    menu_set_tick(NONE, id->this_obj, id->this_cmp, !on);

    grab_fkeys=!on;

    return 1;
}

#if defined(ALLOW_EDITING) || defined(ALLOW_RECALL)
static bool lineedit_handler(bits event_code, toolbox_action *event,
                             toolbox_block *id, void *handle)
{
    bool on;

    NOT_USED(event_code); NOT_USED(event);

    on=menu_get_tick(NONE, id->this_obj, id->this_cmp);

    menu_set_tick(NONE, id->this_obj, id->this_cmp, !on);

    *(bool *)handle=!on;

    return 1;
}
#endif

#ifdef HOT_RECORD
static bool recording_handler(bits event_code, toolbox_action *event,
                              toolbox_block *id, void *handle)
{
    bool on;

    on=menu_get_tick(NONE, id->this_obj, id->this_cmp);

    menu_set_tick(NONE, id->this_obj, id->this_cmp, !on);

    if (on)
    	z_output_stream(1, -4, 0, 0);
    else
        z_output_stream(1, 4, 0, 0);

    return 1;
}
#endif

static bool error_handler(bits event_code, toolbox_action *event,
                          toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    wimp_report_error_by_category((os_error *) &event->data.error,
        wimp_ERROR_BOX_OK_ICON |
        wimp_ERROR_BOX_CATEGORY_ERROR << wimp_ERROR_BOX_CATEGORY_SHIFT,
        TaskName(),
        "!zip2000",
        wimpspriteop_AREA,
        0);

    return 1;
}

void do_update_window(os_box *area)
{
    wimp_draw r;
    bool more;

    if (!ScreenW)
    	return;

    r.w=ScreenW;
    r.box=*area;

    more=wimp_update_window(&r);

    while (more)
    {
        redraw_window(&r);
        /*os_plot(os_MOVE_TO, r.clip.x0, r.clip.y0);
        os_plot(os_PLOT_SOLID_EX_END|os_PLOT_INVERSE_TO, r.clip.x1-1, r.clip.y0);
        os_plot(os_PLOT_SOLID_EX_END|os_PLOT_INVERSE_TO, r.clip.x1-1, r.clip.y1-1);
        os_plot(os_PLOT_SOLID_EX_END|os_PLOT_INVERSE_TO, r.clip.x0, r.clip.y1-1);
        os_plot(os_PLOT_SOLID_EX_END|os_PLOT_INVERSE_TO, r.clip.x0, r.clip.y0);*/
        more=wimp_get_rectangle(&r);
    }

    return;
}

static void InsertKey(int c)
{
    KeyQueue[KeyIn++]=c;

    if (KeyIn==KeyQueueLen)
    	KeyIn=0;
}

static void InsertUTF8(int c)
{
    static unsigned n;
    static unsigned expected;
    static unsigned u;

    if (c < 0x80)
    {
        u = c;
        n = 1;
        expected = 1;
    }
    else if (c < 0xC0)
    {
        if (!expected) return;
        u = (u << 6) | (c & 0x3F);
        n++;
    }
    else if (c < 0xE0)
    {
        u = c & 0x1F;
        n = 1;
        expected = 2;
    }
    else if (c < 0xF0)
    {
        u = c & 0x0F;
        n = 1;
        expected = 3;
    }
    else
        return;

    if (n < expected) return;
    expected = 0;
    if (n == 2 && u < 0x80) return;
    else if (n == 3 && u < 0x800) return;

    if (u < 0x80)
    {
        c = system_to_zscii_table[u]; /* to handle Ctrl-U etc */
        if (c)
            InsertKey(c);
    }
    else
    {
        c = unicode_to_zscii(u, 0x100);
        if (c < 0x100)
            InsertKey(c);
    }
}

int NextKey(void)
{
    int c;

    if (KeyIn==KeyOut)
    	return 0;

    c=KeyQueue[KeyOut++];

    if (KeyOut==KeyQueueLen)
    	KeyOut=0;

    return c;
}

static int update_pointer_pos(int x, int y, int buttons, bool click)
{
    static bool just_clicked;
    wimp_window_state state;

    /* We want the first READ_MOUSE after a click to give the click info,
     * so we ignore the first update request (from READ_MOUSE) after a click.
     */
    if (!click && just_clicked)
    {
        just_clicked = 0;
        return 0;
    }

    state.w=ScreenW;
    wimp_get_window_state(&state);

    x=x+state.xscroll-state.visible.x0;
    y=y+state.yscroll-state.visible.y1;
    y=(-y-1);

    if (click && (x<0 || y<0 || x>=SWidthOS || y>=SHeightOS))
        return 0;

    if (h_type!=V6)
    {
        x = 1 + x/CHAR_W;
        y = 1 + y/CHAR_H;
    }
    else
    {
        x = 1 + x/V6_PIX_X;
        y = 1 + y/V6_PIX_Y;

        if (mouse_window!=-1 && click)
        {
            if (x<wind_prop[mouse_window][WPROP_X] ||
                y<wind_prop[mouse_window][WPROP_Y] ||
                x>=wind_prop[mouse_window][WPROP_X]+wind_prop[mouse_window][WPROP_X_WIDTH] ||
                y>=wind_prop[mouse_window][WPROP_Y]+wind_prop[mouse_window][WPROP_Y_HEIGHT])
            	return 0;
        }
    }

    if (click)
    {
        just_clicked = true;
        if (hx_present(HX_CLICK_Y))
        {
            set_word(h_extension_table+HX_CLICK_X, x);
            set_word(h_extension_table+HX_CLICK_Y, y);
        }
    }

    mouse_x=x;
    mouse_y=y;
    mouse_butt=(buttons & wimp_CLICK_SELECT ? 1 : 0) |
               (buttons & wimp_CLICK_ADJUST ? 2 : 0);

    return 1;
}

void update_mouse_info(void)
{
    wimp_pointer p;

    wimp_get_pointer_info(&p);

    update_pointer_pos(p.pos.x, p.pos.y, p.buttons, false);
}

static bool handle_drag(wimp_pointer *p)
{
    wimp_window_state state;
    wimp_drag drag;
    int x,y;

    state.w=p->w;
    wimp_get_window_state(&state);

    x=p->pos.x+state.xscroll-state.visible.x0;
    y=p->pos.y+state.yscroll-state.visible.y1;

    if (x<0 || y>=0 || x>=SWidthOS || y<-SHeightOS)
        return 0;

    drag.w=state.w;
    drag.type=wimp_DRAG_USER_POINT;
    drag.initial.x0=drag.initial.x1=p->pos.x;
    drag.initial.y0=drag.initial.y1=p->pos.y;
    drag.bbox.x0=0-state.xscroll+state.visible.x0;
    drag.bbox.x1=SWidthOS-V6_PIX_X-state.xscroll+state.visible.x0;
    drag.bbox.y0=-SHeightOS-state.yscroll+state.visible.y1;
    drag.bbox.y1=-V6_PIX_Y-state.yscroll+state.visible.y1;
    wimp_drag_box(&drag);

    return 1;
}

static bool click_handler(wimp_event_no event_code, wimp_block *event,
                          toolbox_block *id, void *handle)
{
    wimp_pointer *p=&event->pointer;
    wimp_caret caret;
    int ok;
    int b;

    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    if (p->buttons == wimp_CLICK_MENU)
    	return 0;

    wimp_get_caret_position(&caret);
    if (caret.w != ScreenW)
    {
    	grab_caret(&caret);
    	if (h_type==V6)
    	    switchoutput(-1);
    }

    if (!using_mouse)
    	return 1;

    if (p->buttons==wimp_DRAG_ADJUST || p->buttons==wimp_DRAG_SELECT)
        return handle_drag(p);

    if (p->buttons!=wimp_SINGLE_SELECT && p->buttons!=wimp_DOUBLE_SELECT &&
        p->buttons!=wimp_SINGLE_ADJUST && p->buttons!=wimp_DOUBLE_ADJUST)
        return 1;

    /* Righty-ho - we have a mouse click to send to the interpreter! */

    if (p->buttons==wimp_SINGLE_SELECT || p->buttons==wimp_DOUBLE_SELECT)
    	b=wimp_CLICK_SELECT;
    else
    	b=wimp_CLICK_ADJUST;

    ok=update_pointer_pos(p->pos.x, p->pos.y, b, true);

    if (ok)
    {
    	if (h_type==V6)
    	    InsertKey((p->buttons == wimp_DOUBLE_SELECT ||
    	               p->buttons == wimp_DOUBLE_ADJUST) ? zscii_DBLCLICK : zscii_CLICK);
    	else
    	    InsertKey(zscii_CLICK);
    }

    return 1;
}

bool palettechange_handler(wimp_message *message, void *handle)
{
    NOT_USED(message); NOT_USED(handle);

    if (h_type==V6)
    {
        free(v6_trans_tab);
        v6_trans_tab=NULL;

        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_LOG2_BPP, &log2bpp);

        /*don't need a table if going into 16 or 32bpp*/

        if (log2bpp <= 3)
        {
            int bufsize=colourtrans_generate_table_for_sprite(v6_screen_area,
                               (osspriteop_id) v6_screen,
                               os_CURRENT_MODE,
                               colourtrans_CURRENT_PALETTE, NULL,
                               colourtrans_GIVEN_SPRITE, NULL, NULL);

            v6_trans_tab=malloc(bufsize);
            colourtrans_generate_table_for_sprite(v6_screen_area,
                               (osspriteop_id) v6_screen,
                               os_CURRENT_MODE,
                               colourtrans_CURRENT_PALETTE, v6_trans_tab,
                               colourtrans_GIVEN_SPRITE, NULL, NULL);

            if (log2bpp == 3 && bitmap_depth == 3)
            {
                /* Optimise for common 8bpp -> 8bpp, same palette case */
                for (int i=255; i>=0; i--)
                    if (v6_trans_tab->c[i] != i)
                        goto not_same;

                free(v6_trans_tab);
                v6_trans_tab = NULL;

              not_same: ;
            }
        }
    }

    return 0;
}

bool modechange_handler(wimp_message *message, void *handle)
{
    static
    const  os_VDU_VAR_LIST(5) vdulist={os_MODEVAR_XWIND_LIMIT,
                                       os_MODEVAR_YWIND_LIMIT,
                                       os_MODEVAR_XEIG_FACTOR,
                                       os_MODEVAR_YEIG_FACTOR,
                                       -1};
    struct
    {
        int scrw, scrh, xeig, yeig;
    } vdu;

    NOT_USED(message); NOT_USED(handle);

    if (h_type==V6)
    	wimp_read_pix_trans(osspriteop_PTR, v6_screen_area,
    	                    (osspriteop_id) v6_screen,
    	                    &factors,
    	                    NULL);
    else if (gfx_area)
        wimp_read_pix_trans(osspriteop_USER_AREA,
                            gfx_area,
                            (osspriteop_id) "20",
                            &factors,
                            NULL);

    os_read_vdu_variables((const os_vdu_var_list *) &vdulist, (int *) &vdu);

    scrwidth  = (vdu.scrw + 1) << vdu.xeig;
    scrheight = (vdu.scrh + 1) << vdu.yeig;

    if (h_type!=V6)
        find_fonts();

    palettechange_handler(NULL, NULL);

    if (fullscreen && message)
    {
        ExpandWindow(SetExtent());
    }

    return 0;
}

static int filetype_ok(bits type, int reason)
{
    switch (reason)
    {
        case GAME_RESTORE:  return type == osfile_TYPE_QUETZAL;
        case GAME_PLAYBACK: return type == osfile_TYPE_TEXT;
        case GAME_AUXLOAD:  return 1;
        default: return 0;
    }
}

static wimp_message *loaded_message;
static char saved_name[FILENAME_SIZE];
int awaiting_file = -1;
static char *awaiting_filename_default;
static int awaiting_savebox_only;

static bool dataopen_handler(wimp_message *message, void *handle)
{
    NOT_USED(handle);

    if (message->data.data_xfer.file_type == osfile_TYPE_QUETZAL &&
        awaiting_file == GAME_RESTORE)
    {
        loaded_message = message;
        InsertKey(key_FILE);
        return 1;
    }

    return 0;
}

static bool datasave_handler(wimp_message *message, void *handle)
{
    NOT_USED(handle);

    /* Accept only inter-application drags of Quetzal files to the main
     * window or icon bar.
     */

    if (!(message->data.data_xfer.w == ScreenW ||
          message->data.data_xfer.w == wimp_ICON_BAR))
        return 0;

    if (message->data.data_xfer.file_type != osfile_TYPE_QUETZAL)
        return 0;

    strcpy(message->data.data_xfer.file_name, "<Wimp$Scrap>");
    message->size = 60;
    message->your_ref = message->my_ref;
    message->action = message_DATA_SAVE_ACK;
    message->data.data_xfer.est_size = -1;
    wimp_send_message(wimp_USER_MESSAGE, message, message->sender);

    return 1;
}

static bool dataload_handler(wimp_message *message, void *handle)
{
    int ok;

    NOT_USED(handle);

    //if (message->your_ref)
    //    return 0;

    if (!(message->data.data_xfer.w == ScreenW ||
          message->data.data_xfer.w == wimp_ICON_BAR))
        return 0;

    if (awaiting_file == -1)
        ok = filetype_ok(message->data.data_xfer.file_type, GAME_RESTORE);
    else
        ok = filetype_ok(message->data.data_xfer.file_type, awaiting_file);

    if (!ok)
         return 0;

    loaded_message = message;
    if (awaiting_file == -1)
        restore_drag(message->data.data_xfer.file_name);
    else
        InsertKey(key_FILE);

    return 1;
}

static bool opensavedir_handler(bits event_code, toolbox_action *event,
                                toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    open_save_directory();

    return 1;
}

static bool menusave_handler(bits event_code, toolbox_action *event,
                             toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    if (awaiting_file)
    {
        strcpy(saved_name, awaiting_filename_default);
        InsertKey(key_FILE);
    }

    return 1;
}



const char *os_file_supplied(void)
{
    if (loaded_message)
        return loaded_message->data.data_xfer.file_name;
    else if (saved_name[0])
        return saved_name;
    else
        return NULL;
}

void os_file_loaded(void)
{
    if (loaded_message)
    {
        if (loaded_message->your_ref)
            remove(loaded_message->data.data_xfer.file_name);

        loaded_message->your_ref = loaded_message->my_ref;
        loaded_message->action = message_DATA_LOAD_ACK;
        wimp_send_message(wimp_USER_MESSAGE, loaded_message, loaded_message->sender);
        loaded_message = 0;
    }
}

void os_file_saved(int ok)
{
    if (saved_name[0])
    {
        saveas_file_save_completed(ok ? 1 : 0, saveasbox, saved_name);
        saved_name[0] = '\0';
        if (!awaiting_savebox_only) output_new_line();
        toolbox_hide_object(NONE, saveasbox);
    }
}

#ifdef ALLOW_FONTS
#if 0
static bool fontshow_handler(bits event_code, toolbox_action *event,
                             toolbox_block *id, void *handle)
{
    fontmenu_set_font(NONE, id->this_obj, handle);

    return 1;
}
#endif


static bool textfont_choose(bits event_code, toolbox_action *event,
                           toolbox_block *id, void *handle)

{
    fontmenu_action_selection *fontsel=(fontmenu_action_selection *)&event->data;
    os_box redraw;

    NOT_USED(event_code); NOT_USED(id);

    if (font_find_field(fontsel->font_identifier, 'E', NULL))
        font_apply_fields(fontsel->font_identifier, "\\E", (char *) handle, font_NAME_LIMIT);
    else
        strcpy((char *) handle, fontsel->font_identifier);

    if (h_type==V6)
    	switchoutput(+1);

    find_fonts();

    if (h_type==V6)
    	switchoutput(-1);

    if (h_type==V6)
        v6_signal_refresh();
    else
    {
        redraw.x0=0;
        redraw.x1=SWidthOS;
        redraw.y0=-SHeightOS;
        redraw.y1=0;

        window_force_redraw(NONE, Screen, &redraw);
    }

    return 1;
}

static int max_alphabet_cmp;

static void tick_alphabet_entry(toolbox_o o)
{
    int i;
    char buffer[14];

    for (i=0; i<=max_alphabet_cmp; i++)
    {
        menu_get_entry_text(NONE, o, i, buffer, sizeof buffer);
        menu_set_tick(NONE,o, i, strcmp(buffer, encoding_name+2)==0);
    }
}

static bool alphabet_choose(bits event_code, toolbox_action *event,
                            toolbox_block *id, void *handle)

{
    char buffer[14];

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    menu_get_entry_text(NONE, id->this_obj, id->this_cmp, buffer, sizeof buffer);

    if (strcmp(buffer, encoding_name+2))
    {
        os_box redraw;

        strcpy(encoding_name+2, buffer);

        if (h_type==V6)
        	switchoutput(+1);

        find_fonts();

        setup_mapping_tables();

        if (h_type==V6)
        	switchoutput(-1);

        if (h_type==V6)
            v6_signal_refresh();
        else
        {
            redraw.x0=0;
            redraw.x1=SWidthOS;
            redraw.y0=-SHeightOS;
            redraw.y1=0;

            window_force_redraw(NONE, Screen, &redraw);
        }

        tick_alphabet_entry(id->this_obj);
    }

    return 1;
}

static bool alphabet_show(bits event_code, toolbox_action *event,
                          toolbox_block *id, void *handle)

{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    tick_alphabet_entry(id->this_obj);

    return 1;
}

#endif

static bool savetofile_handler(bits event_code, toolbox_action *event,
                               toolbox_block *id, void *handle)
{
    saveas_action_save_to_file_block *s = (saveas_action_save_to_file_block *) event;
    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    if (awaiting_file)
    {
        strcpy(saved_name, s->file_name);
        InsertKey(key_FILE);
    }

    return 1;
}

static bool savecompleted_handler(bits event_code, toolbox_action *event,
                                  toolbox_block *id, void *handle)
{
    saveas_action_save_completed_block *s = (saveas_action_save_completed_block *) event;
    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    if (awaiting_filename_default)
    {
        if (s->flags & saveas_SAVE_SAFE)
            strcpy(awaiting_filename_default, s->file_name);
        awaiting_filename_default = NULL;
        /* Hmm. finish_save_box() seems to end up reshowing the menu. This should
         * dispatch it.
         */
        if (s->ref != 0) toolbox_hide_object(NONE, mainmenu);
    }

    return 1;
}

static bool hidesave_handler(bits event_code, toolbox_action *event,
                             toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    if (awaiting_file != -1)
        InsertKey(-1);

    event_deregister_toolbox_handler(id->this_obj, action_SAVE_AS_DIALOGUE_COMPLETED,
                                     hidesave_handler, 0);
    grab_caret(NULL);
    if (h_type==V6)
        switchoutput(-1);

    return true;
}

void setup_save_box(char *defname, int type, bool silent, bool persist)
{
    bits filetype;

    awaiting_file = type;
    awaiting_filename_default = defname;
    awaiting_savebox_only = persist;

    if (!silent)
    {
        if (h_type==V6)
    	    switchoutput(-1);

        saveas_set_file_name(NONE, saveasbox, defname);
        saveas_set_title(NONE, saveasbox, type == GAME_SAVE ? msgs_lookup("SaveAs") :
                                                              msgs_lookup("ExportAs"));
        switch (type)
        {
            case GAME_SAVE:    filetype = osfile_TYPE_QUETZAL; break;
            case GAME_AUXSAVE: filetype = osfile_TYPE_DATA; break;
            default:           filetype = osfile_TYPE_TEXT; break;
        }
        saveas_set_file_type(NONE, saveasbox, filetype);
        saveas_set_file_size(NONE, saveasbox, 8192);

        menu_set_fade(NONE, filemenu, Cmp_Save,
                                      persist || awaiting_file != GAME_SAVE);
        menu_set_fade(NONE, exportmenu, Cmp_ExportTranscript,
                                        persist || awaiting_file != GAME_SCRIPT);
        menu_set_fade(NONE, exportmenu, Cmp_ExportRecording,
                                        persist || awaiting_file != GAME_RECORD);
        menu_set_fade(NONE, exportmenu, Cmp_ExportAuxiliary,
                                        persist || awaiting_file != GAME_AUXSAVE);

        if (h_type==V6)
    	    switchoutput(+1);
    }
}

void finish_save_box(void)
{
    awaiting_file = -1;

    if (h_type==V6)
        switchoutput(-1);

    menu_set_fade(NONE, filemenu, Cmp_Save, true);
    menu_set_fade(NONE, exportmenu, Cmp_ExportTranscript, true);
    menu_set_fade(NONE, exportmenu, Cmp_ExportRecording, true);
    menu_set_fade(NONE, exportmenu, Cmp_ExportAuxiliary, true);

    if (h_type==V6)
        switchoutput(+1);
}

void open_save_box(bool persist)
{
    wimp_pointer p;
    toolbox_position pos;

    if (h_type==V6)
        switchoutput(-1);

    if (persist)
        event_register_toolbox_handler(saveasbox, action_SAVE_AS_DIALOGUE_COMPLETED,
                                       hidesave_handler, 0);
    wimp_get_pointer_info(&p);
    pos.top_left.x = p.pos.x - 160;
    pos.top_left.y = p.pos.y + 68;
    toolbox_show_object(persist ? NONE : toolbox_SHOW_AS_MENU,
                        saveasbox,
                        toolbox_POSITION_TOP_LEFT, &pos,
                        Screen, toolbox_NULL_COMPONENT);

    if (h_type==V6)
        switchoutput(+1);
}

static void open_parent(const char *file)
{
    char temp[FILENAME_SIZE+20];
    char *p;

    sprintf(temp, "Filer_OpenDir %s", file);

    p=strrchr(temp, '.');
    if (!p)
    	return;

    *p='\0';

    os_cli(temp);
}

static void initiate_quit(void)
{
    if (confirmq)
    	ShowCentred(toolbox_SHOW_AS_MENU, QuitObj, toolbox_NULL_OBJECT,
                                               	toolbox_NULL_COMPONENT);
    else
    	quit_handler(0,0);
}

static bool close_handler(wimp_event_no event_code, wimp_block *event,
                          toolbox_block *id, void *handle)
{
    wimp_pointer pointer;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    wimp_get_pointer_info(&pointer);

    if (pointer.buttons & wimp_CLICK_SELECT)
    	initiate_quit();
    else
    {
        open_parent(StoryName);
        if (!osbyte1(osbyte_IN_KEY, 0xFF, 0xFF))
            initiate_quit();
    }

    return 1;
}

static bool open_handler(wimp_event_no event_code, wimp_block *event,
                         toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    if (!fullscreen)
    {
        int w, h;
        os_box ext;
        window_get_extent(NONE, Screen, &ext);
        w = ext.x1 - ext.x0;
        h = ext.y1 - ext.y0;
        event->open.visible.x1 = event->open.visible.x0 + w;
        event->open.visible.y0 = event->open.visible.y1 - h;
        event->open.xscroll = ext.x0;
        event->open.yscroll = ext.y1;
        wimp_open_window(&event->open);
    }

    return 1;
}

static bool redraw_handler(wimp_event_no event_code, wimp_block *event,
                           toolbox_block *id, void *handle)
{
    wimp_draw *r=&event->redraw;
    bool more;

    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    more=wimp_redraw_window(r);

    while (more)
    {
        redraw_window(r);
        /*os_plot(os_MOVE_TO, r->clip.x0, r->clip.y0);
        os_plot(os_PLOT_DOTTED_EX_END|os_PLOT_INVERSE_TO, r->clip.x1-1, r->clip.y0);
        os_plot(os_PLOT_DOTTED_EX_END|os_PLOT_INVERSE_TO, r->clip.x1-1, r->clip.y1-1);
        os_plot(os_PLOT_DOTTED_EX_END|os_PLOT_INVERSE_TO, r->clip.x0, r->clip.y1-1);
        os_plot(os_PLOT_DOTTED_EX_END|os_PLOT_INVERSE_TO, r->clip.x0, r->clip.y0);*/
        more=wimp_get_rectangle(r);
    }

    return 1;
}

#define Cmp_Cols 0
#define Cmp_Rows 1

static bool showsize_handler(bits event_code, toolbox_action *event,
                             toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    numberrange_set_value(NONE, id->this_obj, Cmp_Cols, h_screen_cols);
    numberrange_set_value(NONE, id->this_obj, Cmp_Rows, h_screen_rows);
    numberrange_set_bounds(numberrange_BOUND_UPPER, id->this_obj, Cmp_Cols,
                            0, (scrwidth-((border&&!fullscreen)?64:0))*400/char_width, 0, 0);
    numberrange_set_bounds(numberrange_BOUND_UPPER, id->this_obj, Cmp_Rows,
                            0, (scrheight-((border&&!fullscreen)?128:0))*400/line_height, 0, 0);

    return 1;
}

static os_colour choice_fg, choice_bg;

static void update_gadget(toolbox_o o, toolbox_c c)
{
    os_box b;

    gadget_get_bbox(NONE, o, c, &b);
    window_force_redraw(NONE, o, &b);
}

static bool showchoice_handler(bits event_code, toolbox_action *event,
                               toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    optionbutton_set_state(NONE, id->this_obj, Cmp_QuickLoad, quickload);
    optionbutton_set_state(NONE, id->this_obj, Cmp_PauseExit, pauseexit);
    optionbutton_set_state(NONE, id->this_obj, Cmp_ShowCaret, showcaret);
    optionbutton_set_state(NONE, id->this_obj, Cmp_ConfirmQ, confirmq);
    optionbutton_set_state(NONE, id->this_obj, Cmp_WarnOverwrite, warn_overwrite);
    optionbutton_set_state(NONE, id->this_obj, Cmp_Dithering, dithering);
    optionbutton_set_state(NONE, id->this_obj, Cmp_UpdateOften, update_often);
    if (h_type != V6)
    {
        gadget_fade(id->this_obj, Cmp_Dithering, true);
        gadget_fade(id->this_obj, Cmp_UpdateOften, true);
    }
    numberrange_set_value(NONE, id->this_obj, Cmp_ScreenGamma, (int) (screen_gamma*10+0.5F));
    #ifdef ALLOW_QUOTES
    optionbutton_set_state(NONE, id->this_obj, Cmp_SmartQuotes, smartquotes);
    #endif
    #ifdef ALLOW_EXTRAS
    numberrange_set_value(NONE, id->this_obj, Cmp_UndoSlots, option_undo_slots);
    numberrange_set_value(NONE, id->this_obj, Cmp_InterpNum, h_interpreter);
    choice_fg = true_default_fg;
    choice_bg = true_default_bg;
    update_gadget(id->this_obj, Cmp_FGPatch);
    update_gadget(id->this_obj, Cmp_BGPatch);
    #endif

    return 1;
}

static bool setchoice_handler(bits event_code, toolbox_action *event,
                              toolbox_block *id, void *handle)
{
    if (event->flags & actionbutton_SELECTED_DEFAULT)
    {
        #ifdef ALLOW_EXTRAS
        int temp_slots;
        #endif

        quickload=optionbutton_get_state(NONE, id->this_obj, Cmp_QuickLoad);
        pauseexit=optionbutton_get_state(NONE, id->this_obj, Cmp_PauseExit);
        showcaret=optionbutton_get_state(NONE, id->this_obj, Cmp_ShowCaret);
        confirmq =optionbutton_get_state(NONE, id->this_obj, Cmp_ConfirmQ);
        warn_overwrite=optionbutton_get_state(NONE, id->this_obj, Cmp_WarnOverwrite);
        dithering=optionbutton_get_state(NONE, id->this_obj, Cmp_Dithering);
        update_often=optionbutton_get_state(NONE, id->this_obj, Cmp_UpdateOften);
        screen_gamma=numberrange_get_value(NONE, id->this_obj, Cmp_ScreenGamma) / 10.0F;
        if (h_type==V6)
            build_gamma_tables(screen_gamma);
        //update_text_gamma();
        #ifdef ALLOW_QUOTES
        smartquotes=optionbutton_get_state(NONE, id->this_obj, Cmp_SmartQuotes);
        #endif
        #ifdef ALLOW_EXTRAS
        temp_slots=numberrange_get_value(NONE, id->this_obj, Cmp_UndoSlots);
        if (option_undo_slots != temp_slots)
        {
            info_lookup("WUndoSlots");
            option_undo_slots = temp_slots;
        }
        h_interpreter=numberrange_get_value(NONE, id->this_obj, Cmp_InterpNum);
        true_default_fg=choice_fg;
        true_default_bg=choice_bg;
        h_default_bg_col=os_colour_to_z_colour(true_default_bg);
        h_default_fg_col=os_colour_to_z_colour(true_default_fg);
        #endif
        restart_header();
    	savechoices();
    }
    else if (event->flags & actionbutton_SELECTED_CANCEL)
    	showchoice_handler(event_code, event, id, handle);

    return 1;
}

static bool hidechoice_handler(bits event_code, toolbox_action *event,
                               toolbox_block *id, void *handle)
{
    wimp_caret c;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    wimp_get_caret_position(&c);

    /* The window has been closed by this stage, so if we had the caret
       it's now in the background window */
    if (c.w == wimp_BACKGROUND)
    {
        grab_caret(&c);
        if (h_type==V6)
            switchoutput(-1);
    }

    return 1;
}

static bool redrawchoice_handler(wimp_event_no event_code, wimp_block *event,
                                 toolbox_block *id, void *handle)
{
    wimp_draw *r=&event->redraw;
    int rx, ry;
    bool more;
    os_box fgbox, bgbox;

    NOT_USED(event_code); NOT_USED(handle);

    gadget_get_bbox(NONE, id->this_obj, Cmp_FGPatch, &fgbox);
    gadget_get_bbox(NONE, id->this_obj, Cmp_BGPatch, &bgbox);

    fgbox.x0+=4; fgbox.x1-=5; fgbox.y0+=4; fgbox.y1-=5;
    bgbox.x0+=4; bgbox.x1-=5; bgbox.y0+=4; bgbox.y1-=5;

    more=wimp_redraw_window(r);
    while (more)
    {
        colourtrans_set_gcol(choice_fg, os_GCOL_SET_FG, os_ACTION_OVERWRITE, SKIP);
        colourtrans_set_gcol(choice_bg, os_GCOL_SET_BG, os_ACTION_OVERWRITE, SKIP);

        rx=r->box.x0 - r->xscroll;
        ry=r->box.y1 - r->yscroll;

        os_plot(os_MOVE_TO, fgbox.x0+rx, fgbox.y0+ry);
        os_plot(os_PLOT_RECTANGLE|os_PLOT_TO, fgbox.x1+rx, fgbox.y1+ry);
        os_plot(os_MOVE_TO, bgbox.x0+rx, bgbox.y0+ry);
        os_plot(os_PLOT_RECTANGLE|os_PLOT_BG_TO, bgbox.x1+rx, bgbox.y1+ry);

        more = wimp_get_rectangle(r);
    }

    return 1;
}

static void update_col_menu(toolbox_o menu, os_colour c)
{
    int i, n;
    colourpicker_colour cp;

    n = os_colour_to_z_colour(c);

    for (i=2; i<=12; i++)
        menu_set_tick(NONE, menu, i, i==n);

    menu_set_tick(NONE, menu, 16, n>=16);

    cp.colour = c;
    cp.size = 0;
    colourdbox_set_colour(NONE, menu_get_sub_menu_show(NONE, menu, 16), &cp);
}

static bool showcolmenu_handler(bits event_code, toolbox_action *event,
                                toolbox_block *id, void *handle)
{
    toolbox_o menu = ((popup_action_about_to_be_shown *) &event->data)->menu;
    os_colour c;

    NOT_USED(event_code); NOT_USED(handle);

    if (id->this_cmp == Cmp_FGPopUp)
        c = choice_fg;
    else if (id->this_cmp == Cmp_BGPopUp)
        c = choice_bg;
    else
        return 0;

    menu_set_title(NONE, menu, msgs_lookup(id->this_cmp == Cmp_FGPopUp ? "FG" : "BG"));

    update_col_menu(menu, c);

    return 1;
}

static int colour_selected(toolbox_block *id, toolbox_o menu, os_colour c)
{
    if (id->ancestor_cmp == Cmp_FGPopUp)
        choice_fg = c;
    else
        choice_bg = c;

    update_col_menu(menu, c);

    update_gadget(id->ancestor_obj, id->ancestor_cmp-1);

    return 1;
}

static bool selcolmenu_handler(bits event_code, toolbox_action *event,
                               toolbox_block *id, void *handle)
{
    os_colour c;
    zword_t z;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    if (id->this_cmp < 2 || id->this_cmp > 12)
        return 0;

    z = z_colour_to_true_colour(id->this_cmp);
    c = z_true_colour_to_os_colour(z);

    return colour_selected(id, id->this_obj, c);
}

static bool selcoldbox_handler(bits event_code, toolbox_action *event,
                               toolbox_block *id, void *handle)
{
    colourdbox_action_colour_selected *ce =
          (colourdbox_action_colour_selected *) &event->data;

    NOT_USED(event_code); NOT_USED(handle);

    return colour_selected(id, id->parent_obj, ce->colour);
}


static int box_defpath[4];
static bool box_prompt[4];
static bool box_savebox[4];

static void filechoice_show_pane(toolbox_o o, int p)
{
    osbool fade;

    radiobutton_set_state(NONE, o, Cmp_Location + box_defpath[p], true);
    fade = p == GAME_IDX(GAME_SCRIPT) || p == GAME_IDX(GAME_RECORD);

    gadget_fade(o, Cmp_LocationCentral, fade);
    gadget_fade(o, Cmp_LocationCentralDir, fade);
    gadget_fade(o, Cmp_LocationGameDir, p == GAME_IDX(GAME_AUXSAVE));
    gadget_fade(o, Cmp_SaveAs, !box_prompt[p]);
    //gadget_fade(o, Cmp_Prompt, p != GAME_IDX(GAME_AUXSAVE));
    optionbutton_set_state(NONE, o, Cmp_Prompt, box_prompt[p]);
    optionbutton_set_state(NONE, o, Cmp_SaveAs, box_savebox[p]);

    radiobutton_set_label(NONE, o, Cmp_LocationInDir, msgs_lookup_1("InDir", file_ext[p]));
    radiobutton_set_label(NONE, o, Cmp_LocationNearDir, msgs_lookup_1("NearDir", file_ext[p]));
}

static void filechoice_note(toolbox_o o, toolbox_c c)
{
    toolbox_c l;
    radiobutton_get_state(NONE, o, Cmp_Location, &l);
    box_defpath[c] = l - Cmp_Location;
    box_prompt[c] = optionbutton_get_state(NONE, o, Cmp_Prompt);
    box_savebox[c] = optionbutton_get_state(NONE, o, Cmp_SaveAs);
}

static bool filechoice_prompt_handler(bits event_code, toolbox_action *event,
                                      toolbox_block *id, void *handle)
{
    optionbutton_action_state_changed_block *a =
                  (optionbutton_action_state_changed_block *) event;

    NOT_USED(event_code); NOT_USED(handle);

    if (id->this_cmp == Cmp_Prompt)
    {
        gadget_fade(id->this_obj, Cmp_SaveAs, !a->on);
        return 1;
    }

    return 0;
}

static bool showfilechoice_handler(bits event_code, toolbox_action *event,
                                   toolbox_block *id, void *handle)
{
    toolbox_c c;
    int i;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    for (i=0; i<4; i++)
    {
        box_defpath[i] = default_path[i];
        box_prompt[i] = file_prompt[i];
        box_savebox[i]= file_savebox[i];
    }

    radiobutton_get_state(NONE, id->this_obj, Cmp_FileType, &c);

    filechoice_show_pane(id->this_obj, c);

    radiobutton_set_state(NONE, id->this_obj, Cmp_Location + default_path[c], true);

    return 1;
}

static bool setfilechoice_handler(bits event_code, toolbox_action *event,
                                  toolbox_block *id, void *handle)
{
    if (event->flags & actionbutton_SELECTED_DEFAULT)
    {
        int i;
        toolbox_c c;
        radiobutton_get_state(NONE, id->this_obj, Cmp_FileType, &c);
        filechoice_note(id->this_obj, c);
        for (i=0; i<4; i++)
        {
            default_path[i] = box_defpath[i];
            file_prompt[i] = box_prompt[i];
            file_savebox[i] = box_savebox[i];
        }
    }
    else if (event->flags & actionbutton_SELECTED_CANCEL)
    	showfilechoice_handler(event_code, event, id, handle);

    return 1;
}

static bool flipfilechoice_handler(bits event_code, toolbox_action *event,
                                   toolbox_block *id, void *handle)
{
    radiobutton_action_state_changed_block *r =
        (radiobutton_action_state_changed_block *) event;

    NOT_USED(event_code); NOT_USED(handle);

    filechoice_note(id->this_obj, r->previous_on);
    filechoice_show_pane(id->this_obj, id->this_cmp);

    return 1;
}

static bool hidefilechoice_handler(bits event_code, toolbox_action *event,
                                   toolbox_block *id, void *handle)
{
    wimp_caret c;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    wimp_get_caret_position(&c);

    /* The window has been closed by this stage, so if we had the caret
       it's now in the background window */
    if (c.w == wimp_BACKGROUND)
    {
        toolbox_info i;
        i = toolbox_get_object_info(NONE, id->parent_obj);
        if (i & toolbox_INFO_SHOWING)
        {
            wimp_w w = window_get_wimp_handle(NONE, id->parent_obj);
            wimp_set_caret_position(w, wimp_ICON_WINDOW, 0, 0, 1<<25, 0);
        }
        else
        {
            grab_caret(&c);
            if (h_type==V6)
                switchoutput(-1);
        }
    }

    return 1;
}

void SetBorder(int fs)
{
    static int border_changed;
    static os_colour new_border;
    static os_colour old_border;
    os_colour c;
    os_PALETTE(20) pal;
    int i;

    wimp_read_true_palette((os_palette *) &pal);
    for (i=19; i>0; i--)
        pal.entries[i] &= ~0xFF;

    if (fs)
    {
        if (!border_changed)
        {
            old_border = pal.entries[16];
            border_changed = 1;
        }

        c = screen_bg;

        if (pal.entries[16] != c)
        {
            new_border = pal.entries[16] = c;
            wimp_set_palette((os_palette *) &pal);
        }
    }
    else
    {
        if (border_changed)
        {
            if (pal.entries[16] == new_border)
            {
                pal.entries[16] = old_border;
                wimp_set_palette((os_palette *) &pal);
            }
            border_changed = 0;
        }
    }
}

static const os_box *SetExtent(void)
{
    static os_box extent;
    int border_width, border_height;

    if (fullscreen)
    {
        int scrwidth, scrheight, xeig, yeig;
        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT, &scrwidth);
        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT, &scrheight);
        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR, &xeig);
        os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR, &yeig);

        scrwidth+=1; scrheight+=1;
        scrwidth<<=xeig; scrheight<<=yeig;
        border_width=(scrwidth-SWidthOS)/2;
        border_height=(scrheight-SHeightOS)/2;
        extent.x0=-border_width;
        extent.x1=extent.x0+scrwidth;
        extent.y0=-border_height-SHeightOS;
        extent.y1=extent.y0+scrheight;
    }
    else
    {
        border_width=border?32:0;
        extent.x0=-border_width;
        extent.x1=SWidthOS+border_width;
        extent.y0=-SHeightOS-border_width;
        extent.y1=border_width;
    }
    SetBorder(fullscreen);
    window_set_extent(NONE, Screen, &extent);

    return &extent;
}

static void ForceOnScreen(void)
{
    wimp_outline o;
    o.w=ScreenW;
    wimp_get_window_outline(&o);
    if (o.outline.x0 < 0 || o.outline.x1 > scrwidth ||
        o.outline.y0 < 0 || o.outline.y1 > scrheight)
    {
        wimp_window_state ws;
        ws.w=ScreenW;
        wimp_get_window_state(&ws);
        if (o.outline.x0 < 0)
        {
            ws.visible.x0 -= o.outline.x0;
            ws.visible.x1 -= o.outline.x0;
        }
        else
        {
            ws.visible.x0 -= (o.outline.x1 - scrwidth);
            ws.visible.x1 -= (o.outline.x1 - scrwidth);
        }
        if (o.outline.y0 < 0)
        {
            ws.visible.y0 -= o.outline.y0;
            ws.visible.y1 -= o.outline.y0;
        }
        else
        {
            ws.visible.y0 -= (o.outline.y1 - scrheight);
            ws.visible.y1 -= (o.outline.y1 - scrheight);
        }
        wimp_open_window((wimp_open *) &ws);
    }
}

static bool border_handler(bits event_code, toolbox_action *event,
                           toolbox_block *id, void *handle)
{
    const os_box *extent;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    border=!menu_get_tick(NONE, id->this_obj, id->this_cmp);

    menu_set_tick(NONE, id->this_obj, id->this_cmp, border);

    extent = SetExtent();
    place_caret();
    if (h_type==V6)
    	switchoutput(-1);

    ExpandWindow(extent);

    ForceOnScreen();

    return 1;
}

static bool fullscreen_handler(bits event_code, toolbox_action *event,
                               toolbox_block *id, void *handle)
{
    const os_box *extent;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    fullscreen=!menu_get_tick(NONE, id->this_obj, id->this_cmp);

    menu_set_tick(NONE, id->this_obj, id->this_cmp, fullscreen);
    menu_set_fade(NONE, id->this_obj, Cmp_Border, fullscreen);

    extent = SetExtent();
    place_caret();
    if (h_type==V6)
        switchoutput(-1);
    window_force_redraw(NONE, Screen, extent);

    ExpandWindow(extent);

    if (!fullscreen)
        ForceOnScreen();

    return 1;
}

static bool setsize_handler(bits event_code, toolbox_action *event,
                            toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(handle);

    if (event->flags & actionbutton_SELECTED_DEFAULT)
    {
        const os_box *extent;

        resize_window(numberrange_get_value(NONE, id->this_obj, Cmp_Cols),
                      numberrange_get_value(NONE, id->this_obj, Cmp_Rows));

        extent = SetExtent();
    	place_caret();
        if (h_type==V6)
            switchoutput(-1);
        window_force_redraw(NONE, Screen, extent);

        ExpandWindow(extent);
    }

    numberrange_set_value(NONE, id->this_obj, Cmp_Cols, h_screen_cols);
    numberrange_set_value(NONE, id->this_obj, Cmp_Rows, h_screen_rows);

    return 1;
}

static void ProcessDigit(int digit)
{
    static const char keycode[10]={149,148,131,147,133,132,229,228,213,212};

    if (separate_keypad && osbyte1(osbyte_IN_KEY, keycode[digit], 0xFF))
    	InsertKey(zscii_K0+digit);
    else
    	InsertKey('0'+digit);
}

static bool gamemenu_handler(bits event_code, toolbox_action *event,
                             toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    last_menu=id->parent_cmp;
    last_item=id->this_cmp;
    InsertKey(zscii_MENU);

    return 1;
}

static bool input_handler(wimp_event_no event_code, wimp_block *event,
                          toolbox_block *id, void *handle)
{
    wimp_key_no key=event->key.c;
    int alphabet;

    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    /* Spot alphabet changes */
    alphabet = osbyte1(osbyte_ALPHABET_NUMBER, 127, 0);
    if (alphabet != system_alphabet_no)
    	setup_mapping_tables();

    if (key>='0' && key <='9')
    	ProcessDigit(key-'0');
    else if (key <= 255)
    {
        if (system_alphabet_no == 111)
            InsertUTF8(key);
        else if (system_to_zscii_table[key])
            InsertKey(system_to_zscii_table[key]);
        else
            return 0;
    }
    else switch (key)
    {
        case wimp_KEY_UP: InsertKey(zscii_UP); break;
        case wimp_KEY_DOWN: InsertKey(zscii_DOWN); break;
        case wimp_KEY_LEFT: InsertKey(zscii_LEFT); break;
        case wimp_KEY_RIGHT: InsertKey(zscii_RIGHT); break;
        case wimp_KEY_SHIFT + wimp_KEY_LEFT: InsertKey(key_SHIFT_LEFT); break;
        case wimp_KEY_SHIFT + wimp_KEY_RIGHT: InsertKey(key_SHIFT_RIGHT); break;
        case wimp_KEY_CONTROL + wimp_KEY_LEFT: InsertKey(key_CTRL_LEFT); break;
        case wimp_KEY_CONTROL + wimp_KEY_RIGHT: InsertKey(key_CTRL_RIGHT); break;
        case wimp_KEY_COPY: InsertKey(key_COPY); break;
        case wimp_KEY_CONTROL + wimp_KEY_COPY: InsertKey(key_CTRL_COPY); break;
        case wimp_KEY_F1: InsertKey(zscii_F1); break;
        case wimp_KEY_F2: InsertKey(zscii_F2); break;
        case wimp_KEY_F3: InsertKey(zscii_F3); break;
        case wimp_KEY_F4: InsertKey(zscii_F4); break;
        case wimp_KEY_F5: InsertKey(zscii_F5); break;
        case wimp_KEY_F6: InsertKey(zscii_F6); break;
        case wimp_KEY_F7: InsertKey(zscii_F7); break;
        case wimp_KEY_F8: InsertKey(zscii_F8); break;
        case wimp_KEY_F9: InsertKey(zscii_F9); break;
        case wimp_KEY_F10: InsertKey(zscii_F10); break;
        case wimp_KEY_F11: InsertKey(zscii_F11); break;
        case wimp_KEY_F12: if (grab_fkeys) InsertKey(zscii_F12); else return 0; break;
        case wimp_KEY_CONTROL + wimp_KEY_F2: initiate_quit(); break;
        default: return 0;
    }

    return 1;
}

static bool key_handler(wimp_event_no event_code, wimp_block *event,
                        toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(id); NOT_USED(handle);

    wimp_process_key(event->key.c);

    return 1;
}

static bool gain_handler(wimp_event_no event_code, wimp_block *event,
                         toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    place_caret();

    return 1;
}

static bool savescreen_handler(bits event_code, toolbox_action *event,
                               toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    saveas_set_file_name(NONE, id->this_obj, "Screen");
    saveas_set_file_size(NONE, id->this_obj, v6_screen_area->size-4);
    saveas_set_data_address(NONE, id->this_obj, (byte *)v6_screen_area+4,
                                                v6_screen_area->size-4,
                                                NULL,
                                                0);
    return 1;
}

static bool memdump_saveas_handler(bits event_code, toolbox_action *event,
                                   toolbox_block *id, void *handle)
{
    int file_size=h_file_size<<size_shift;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    saveas_set_file_name(NONE, id->this_obj, "MemDump");
    saveas_set_file_size(NONE, id->this_obj, file_size);
    saveas_set_data_address(NONE, id->this_obj, (byte *) datap,
                                                file_size,
                                                NULL,
                                                0);
    return 1;
}

static void savechoices(void)
{
    FILE *f;
    int i;

    f=fopen("<Zip2000$ChoicesFile>", "w");

    if (!f)
    	return;

    fprintf(f, "grab_fkeys:%d\n"
               "screen_cols:%d\n"
               "screen_rows:%d\n"
               "pause_exit:%d\n"
               "quick_load:%d\n"
               "show_caret:%d\n"
               "confirm_quit:%d\n"
               "warn_overwrite:%d\n"
               "border:%d\n"
               "full_screen:%d\n"
               "separate_keypad:%d\n"
               "screen_gamma:%d\n"
               "dithering:%d\n"
               "update_often:%d\n",
               grab_fkeys,
               h_screen_cols,
               h_screen_rows,
               pauseexit, quickload, showcaret, confirmq,
               warn_overwrite, border, fullscreen, separate_keypad,
               (int) (screen_gamma*10+0.5F), dithering, update_often);
    #ifdef ALLOW_EXTRAS
    fprintf(f, "undo_slots:%d\n"
    	       "interpreter_no:%d\n"
    	       "true_default_fg:&%08X\n"
    	       "true_default_bg:&%08X\n",
    	       option_undo_slots, h_interpreter, true_default_fg, true_default_bg);
    #endif
    #ifdef ALLOW_RECALL
    fprintf(f, "command_recall:%d\n", command_recall);
    #endif
    #ifdef ALLOW_EDITING
    fprintf(f, "line_editing:%d\n", line_editing);
    #endif
    #ifdef ALLOW_QUOTES
    fprintf(f, "smart_quotes:%d\n", smartquotes);
    #endif
    #ifdef ALLOW_FONTS
    fprintf(f, "text_font:%s\n"
    	       "fixed_font:%s\n"
    	       "alphabet:%s\n",
    	       textfont_name, fixedfont_name, encoding_name);
    #endif
    for (i=0; i<4; i++)
    {
        fprintf(f, "default_path_%s:%d\n"
                   "file_prompt_%s:%d\n"
                   "file_savebox_%s:%d\n",
                   file_ext[i], default_path[i],
                   file_ext[i], file_prompt[i],
                   file_ext[i], file_savebox[i]);
    }

    fclose(f);
}

void gasp(void)
{
    wimp_poll_flags flags;
    static os_t last_gasped;

    if (os_read_monotonic_time() - last_gasped >= 10)
    {
        event_get_mask(&flags);
        event_set_mask(flags&~wimp_MASK_NULL);
        poll();
        event_set_mask(flags);
        last_gasped = os_read_monotonic_time();
    }
}

static int get_choice(messagetrans_control_block *cb, const char *tok, int dflt)
{
    char buf[32];
    int used;

    if (xmessagetrans_lookup(cb, tok, buf, sizeof buf, 0, 0, 0, 0, NULL, &used))
    	return dflt;

    buf[used]='\0';

    return os_read_unsigned(os_READ_CONTROL_TERMINATED, buf, NONE, SKIP);
}

#ifdef ALLOW_FONTS
static void get_choice_s(messagetrans_control_block *cb, const char *tok, char *v)
{
    int used;

    if (xmessagetrans_lookup(cb, tok, v, 216, 0, 0, 0, 0, NULL, &used))
    	return;

    v[used]='\0';
}
#endif

void load_choices(void)
{
    messagetrans_control_block cb;
    os_error *e;
    #ifdef ALLOW_EXTRAS
    int temp;
    #endif
    int dim, eig;

    e=xmessagetrans_open_file(&cb, "<Zip2000$ChoicesFile>", 0);

    if (!e)
    {
        int i;
        grab_fkeys =      get_choice(&cb, "grab_fkeys", grab_fkeys);
        h_screen_cols =   get_choice(&cb, "screen_cols", h_screen_cols);
        h_screen_rows =   get_choice(&cb, "screen_rows", h_screen_rows);
        pauseexit =       get_choice(&cb, "pause_exit", pauseexit);
        quickload =       get_choice(&cb, "quick_load", quickload);
        showcaret =       get_choice(&cb, "show_caret", showcaret);
        warn_overwrite =  get_choice(&cb, "warn_overwrite", warn_overwrite);
        confirmq =        get_choice(&cb, "confirm_quit", confirmq);
        border =          get_choice(&cb, "border", border);
        fullscreen =      get_choice(&cb, "full_screen", fullscreen);
        separate_keypad = get_choice(&cb, "separate_keypad", separate_keypad);
        temp =            get_choice(&cb, "screen_gamma", -1);
        if (temp!=-1) screen_gamma=temp/10.0F;
        dithering =       get_choice(&cb, "dithering", dithering);
        update_often =    get_choice(&cb, "update_often", update_often);
        #ifdef ALLOW_QUOTES
        smartquotes =     get_choice(&cb, "smart_quotes", smartquotes);
        #endif
        #ifdef ALLOW_EXTRAS
        option_undo_slots=get_choice(&cb, "undo_slots", option_undo_slots);
        temp =            get_choice(&cb, "interpreter_no", -1);
        if (temp!=-1) h_interpreter=temp;
        temp =            get_choice(&cb, "default_fg", -1);
        if (temp!=-1)
            true_default_fg=z_true_colour_to_os_colour(z_colour_to_true_colour(temp));
        temp =            get_choice(&cb, "default_bg", -1);
        if (temp!=-1)
            true_default_bg=z_true_colour_to_os_colour(z_colour_to_true_colour(temp));
        true_default_fg = get_choice(&cb, "true_default_fg", true_default_fg);
        true_default_bg = get_choice(&cb, "true_default_bg", true_default_bg);
        #endif
        #ifdef ALLOW_RECALL
        command_recall =  get_choice(&cb, "command_recall", command_recall);
        #endif
        #ifdef ALLOW_EDITING
        line_editing =    get_choice(&cb, "line_editing", line_editing);
        #endif
        #ifdef ALLOW_FONTS
        get_choice_s(&cb, "text_font", textfont_name);
        get_choice_s(&cb, "fixed_font", fixedfont_name);
        get_choice_s(&cb, "alphabet", encoding_name);
        #endif
        for (i=0; i<4; i++)
        {
            char buf[40];
            sprintf(buf, "default_path_%s", file_ext[i]);
            default_path[i] = get_choice(&cb, buf, default_path[i]);
            sprintf(buf, "file_prompt_%s", file_ext[i]);
            file_prompt[i]  = get_choice(&cb, buf, file_prompt[i]);
            sprintf(buf, "file_savebox_%s", file_ext[i]);
            file_savebox[i] = get_choice(&cb, buf, file_savebox[i]);
        }

        xmessagetrans_close_file(&cb);
    }

    //update_text_gamma();
    if (h_type==V6)
        build_gamma_tables(screen_gamma);

    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT, &dim);
    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR, &eig);
    dim+=1;
    dim<<=eig;
    if (h_screen_cols > (dim-((border&&!fullscreen)?64:0))/16)
    	h_screen_cols = (dim-((border&&!fullscreen)?64:0))/16;

    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT, &dim);
    os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR, &eig);
    dim+=1;
    dim<<=eig;
    if (h_screen_rows > (dim-((border&&!fullscreen)?64:0))/32)
    	h_screen_rows = (dim-((border&&!fullscreen)?64:0))/32;
}

#if 0
static bool showwin_handler(bits event_code, toolbox_action *event,
                            toolbox_block *id, void *handle)
{
    remove_caret();

    event_deregister_toolbox_handler(id->this_obj, action_WINDOW_ABOUT_TO_BE_SHOWN,
    	                            showwin_handler, 0);
    return 1;
}
#endif

static bool confirmed_quit_handler(bits event_code, toolbox_action *event,
                                   toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(id); NOT_USED(handle);

    quit_handler(0, 0);

    return 1;
}

#ifdef ALLOW_EXTRAS
static bool interpmenu_handler(bits event_code, toolbox_action *event,
                                  toolbox_block *id, void *handle)
{
    toolbox_c i;
    int n;
    popup_action_about_to_be_shown *popup=(popup_action_about_to_be_shown *) &event->data;

    NOT_USED(event_code); NOT_USED(handle);

    if (id->this_cmp != Cmp_InterpMenu)
    	return 0;

    n=numberrange_get_value(NONE, id->this_obj, Cmp_InterpNum);

    for (i=1; i<=11; i++)
    	menu_set_tick(NONE, popup->menu, i, i==n);

    return 1;
}

static bool interpmenu_selection(bits event_code, toolbox_action *event,
                                 toolbox_block *id, void *handle)
{
    toolbox_c i;

    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    numberrange_set_value(NONE, id->parent_obj, Cmp_InterpNum, id->this_cmp);

    for (i=1; i<=11; i++)
    	menu_set_tick(NONE, id->this_obj, i, i==id->this_cmp);

    return 1;
}
#endif

static bool quit_cancelled_handler(bits event_code, toolbox_action *event,
                                   toolbox_block *id, void *handle)
{
    NOT_USED(event_code); NOT_USED(event); NOT_USED(handle);

    if (!(toolbox_get_object_info(NONE, id->this_obj) & toolbox_INFO_SHOWING))
    {
    	grab_caret(NULL);
    }

    if (h_type==V6)
    	switchoutput(-1);

    return 1;
}

static bool create_handler(bits event_code, toolbox_action *event,
                           toolbox_block *id, void *handle)
{
    char *name=event->data.created.name;

    NOT_USED(event_code); NOT_USED(handle);

    if (strcmp(name, "Screen")==0)
    {
        Screen=id->this_obj;
        ScreenW=window_get_wimp_handle(NONE, Screen);
        event_register_wimp_handler(id->this_obj, wimp_REDRAW_WINDOW_REQUEST,
                                    redraw_handler, 0);
        event_register_wimp_handler(id->this_obj, wimp_MOUSE_CLICK,
                                    click_handler, 0);
        event_register_wimp_handler(id->this_obj, wimp_KEY_PRESSED,
                                    input_handler, 0);
        event_register_wimp_handler(id->this_obj, wimp_OPEN_WINDOW_REQUEST,
                                    open_handler, 0);
        event_register_wimp_handler(id->this_obj, wimp_CLOSE_WINDOW_REQUEST,
                                    close_handler, 0);
        event_register_wimp_handler(id->this_obj, wimp_GAIN_CARET,
                                    gain_handler, 0);
/*    	event_register_wimp_handler(id->this_obj, wimp_POINTER_ENTERING_WINDOW,
    	                            enter_handler, 0);
    	event_register_wimp_handler(id->this_obj, wimp_POINTER_LEAVING_WINDOW,
    	                            leave_handler, 0);*/

    	/*event_register_toolbox_handler(id->this_obj, action_WINDOW_ABOUT_TO_BE_SHOWN,
    	                            showwin_handler, 0);*/
        ExpandWindow(SetExtent());
        grab_caret(NULL);
        if (h_type==V6)
            switchoutput(-1);
    }
    else if (strcmp(name, "UtilsMenu")==0)
    {
        menu_set_tick(NONE, id->this_obj, Cmp_Fkeys, grab_fkeys);
        #ifdef ALLOW_EDITING
        menu_set_tick(NONE, id->this_obj, Cmp_LineEdit, line_editing);
        #endif
        #ifdef ALLOW_RECALL
        menu_set_tick(NONE, id->this_obj, Cmp_CmdRecall, command_recall);
        #endif
        menu_set_tick(NONE, id->this_obj, Cmp_Keypad, separate_keypad);
    }
    else if (strcmp(name, "StyleMenu")==0)
    {
        //menu_set_fade(NONE, id->this_obj, 2, h_type==V6);
    	menu_set_tick(NONE, id->this_obj, Cmp_Border, border);
        menu_set_tick(NONE, id->this_obj, Cmp_FullScreen, fullscreen);
        menu_set_fade(NONE, id->this_obj, Cmp_Border, fullscreen);
    }
    else if (strcmp(name, "EditMenu")==0)
    {
        menu_set_fade(NONE, id->this_obj, 0, h_type!=V6);
    }
    else if (strcmp(name, "ExportMenu")==0)
    {
        exportmenu=id->this_obj;
        menu_set_fade(NONE, id->this_obj, 0, h_type!=V6);
    }
    else if (strcmp(name, "ScreenSize")==0)
    {
    	event_register_toolbox_handler(id->this_obj, action_ACTION_BUTTON_SELECTED,
    	                                        setsize_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_WINDOW_ABOUT_TO_BE_SHOWN,
    	                                        showsize_handler, 0);
    }
    else if (strcmp(name, "Choices")==0)
    {
    	event_register_toolbox_handler(id->this_obj, action_ACTION_BUTTON_SELECTED,
    	                                        setchoice_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_WINDOW_ABOUT_TO_BE_SHOWN,
    	                                        showchoice_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_WINDOW_DIALOGUE_COMPLETED,
    	                                        hidechoice_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_POP_UP_ABOUT_TO_BE_SHOWN,
    	                                        showcolmenu_handler, 0);
        event_register_wimp_handler(id->this_obj, wimp_REDRAW_WINDOW_REQUEST,
                                                redrawchoice_handler, 0);
    	#ifdef ALLOW_EXTRAS
    	event_register_toolbox_handler(id->this_obj, action_POP_UP_ABOUT_TO_BE_SHOWN,
    	                                        interpmenu_handler, 0);
    	#endif
    	if (h_type==V6)
    	{
    	    gadget_fade(id->this_obj, Cmp_ShowCaret, true);
    	}
    }
    else if (strcmp(name, "ColourMenu")==0)
    {
    	event_register_toolbox_handler(id->this_obj, action_MENU_SELECTION,
    	                                        selcolmenu_handler, 0);
    }
    else if (strcmp(name, "FileChoices")==0)
    {
    	event_register_toolbox_handler(id->this_obj, action_ACTION_BUTTON_SELECTED,
    	                                        setfilechoice_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_WINDOW_ABOUT_TO_BE_SHOWN,
    	                                        showfilechoice_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_WINDOW_DIALOGUE_COMPLETED,
    	                                        hidefilechoice_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_FileChoiceFlip,
    	                                        flipfilechoice_handler, 0);
        event_register_toolbox_handler(id->this_obj, action_OPTION_BUTTON_STATE_CHANGED,
                                                filechoice_prompt_handler, 0);
    }
    #ifdef ALLOW_EXTRAS
    else if (strcmp(name, "Interpreter")==0)
    {
        event_register_toolbox_handler(id->this_obj, action_MENU_SELECTION,
                                                interpmenu_selection, 0);
    }
    #endif
    else if (strcmp(name, "MainMenu")==0)
        mainmenu=id->this_obj;
    else if (strcmp(name, "FileMenu")==0)
        filemenu=id->this_obj;
    else if (strcmp(name, "Quit")==0)
    {
    	event_register_toolbox_handler(id->this_obj, action_QUIT_QUIT,
    	                                        confirmed_quit_handler, 0);
    	event_register_toolbox_handler(id->this_obj, action_QUIT_DIALOGUE_COMPLETED,
    	                                        quit_cancelled_handler, 0);
    	QuitObj=id->this_obj;
    }
    #ifdef ALLOW_FONTS
    else if (strcmp(name, "TextFont")==0)
    {
        fontmenu_set_font(NONE, id->this_obj, textfont_name);
    	/*event_register_toolbox_handler(id->this_obj, action_FONT_MENU_ABOUT_TO_BE_SHOWN,
    	                                         fontshow_handler, textfont_name);*/
    	event_register_toolbox_handler(id->this_obj, action_FONT_MENU_SELECTION,
    	                                         textfont_choose, textfont_name);
    }
    else if (strcmp(name, "FixedFont")==0)
    {
        fontmenu_set_font(NONE, id->this_obj, fixedfont_name);
    	/*event_register_toolbox_handler(id->this_obj, action_FONT_MENU_ABOUT_TO_BE_SHOWN,
    	                                         fontshow_handler, fixedfont_name);*/
    	event_register_toolbox_handler(id->this_obj, action_FONT_MENU_SELECTION,
    	                                         textfont_choose, fixedfont_name);
    }
    #endif
    else if (strcmp(name, "SaveScreen")==0)
    {
        event_register_toolbox_handler(id->this_obj, action_SAVE_AS_ABOUT_TO_BE_SHOWN,
                                       savescreen_handler, 0);
    }
    else if (strcmp(name, "SaveAs")==0)
    {
        saveasbox = id->this_obj;
        event_register_toolbox_handler(id->this_obj, action_SAVE_AS_SAVE_TO_FILE,
                                       savetofile_handler, 0);
        event_register_toolbox_handler(id->this_obj, action_SAVE_AS_SAVE_COMPLETED,
                                       savecompleted_handler, 0);
        //event_register_toolbox_handler(id->this_obj, action_SAVE_AS_ABOUT_TO_BE_SHOWN,
        //                               saveas_handler, 0);
    }
    else if (strcmp(name, "SaveMemDump")==0)
    {
        event_register_toolbox_handler(id->this_obj, action_SAVE_AS_ABOUT_TO_BE_SHOWN,
                                       memdump_saveas_handler, 0);
    }
    else if (strcmp(name, "Alphabet")==0)
    {
        char name_list[256];
        int index = 0, read;
        toolbox_c cmp=-1;
        menu_entry_object entry;

        entry.flags = 0;
        entry.click_object_name = NULL;
        entry.sub_menu_object_name = NULL;
        entry.sub_menu_action = 0;
        entry.click_action = 0;
        entry.help = msgs_lookup("AlphHelp");
        entry.help_limit = strlen(entry.help)+1;

        do
        {
            index = osgbpb_dir_entries("<Zip2000$Dir>.Resources.Encodings",
    	                               (osgbpb_string_list *) name_list,
                                       1, index, 256, "*", &read);
            if (read)
            {
                entry.cmp = max_alphabet_cmp = cmp + 1;
                entry.text = name_list;
                entry.text_limit = strlen(name_list)+1;
                menu_add_entry(0, id->this_obj, cmp++, &entry);
            }
        }
        while (index != -1);

        event_register_toolbox_handler(id->this_obj, action_MENU_SELECTION,
                                       alphabet_choose, 0);
        event_register_toolbox_handler(id->this_obj, action_MENU_ABOUT_TO_BE_SHOWN,
                                       alphabet_show, 0);
    }
    else if (strcmp(name, "StoryInfo")==0)
    {
        StoryInfo = id->this_obj;
    }

    return 1;
}

#pragma no_check_stack

static void sighandler(int sig)
{
    char Quit[32];
    messagetrans_control_block cb;

    NOT_USED(sig);

    if (h_type==V6)
    	switchoutput(-100);

    if (sig == SIGILL || sig == SIGSEGV || sig == SIGFPE || sig == SIGOSERROR)
    {
        unsigned int *regdump, *os_regdump;

        os_change_environment(os_HANDLER_CALL_BACK, 0, 0, 0,
                              0, (byte **) &regdump);
        os_regdump = (unsigned int *)
                     os_change_environment(os_HANDLER_EXCEPTION_REGISTERS, 0, 0, 0,
                                           0, 0);
        memcpy(os_regdump, regdump, 4*16);
    }

    messagetrans_open_file(&cb, "WindowManager:Messages", 0);
    messagetrans_lookup(&cb, "Quit:Quit", Quit, sizeof Quit, 0, 0, 0, 0, 0);
    messagetrans_close_file(&cb);

    if (wimpver >= 322)
    	wimp_report_error_by_category((os_error *) _kernel_last_oserror(),
                wimp_ERROR_BOX_CATEGORY_ERROR << wimp_ERROR_BOX_CATEGORY_SHIFT,
                TaskName(), "!zip2000", wimpspriteop_AREA, Quit);
    else
    	wimp_report_error((os_error *) _kernel_last_oserror(),
    	    	    	      wimp_ERROR_BOX_CANCEL_ICON,
    	    	    	      TaskName());

    reset_screen();
    exit(1);
}

#pragma check_stack

static void close_menu(void)
{
    wimp_create_menu(wimp_CLOSE_MENU, 0, 0);
    SetBorder(0);
}

void pre_initialise_wimp(void)
{
    static
    const  wimp_MESSAGE_LIST(9) messages={message_PALETTE_CHANGE,
                                          message_MODE_CHANGE,
    	    	    	    	    	  message_WINDOW_INFO,
    	    	    	    	    	  message_DEVICE_CLAIM,
    	    	    	    	    	  message_DEVICE_IN_USE,
    	    	    	    	    	  message_DATA_SAVE,
    	    	    	    	    	  message_DATA_LOAD,
    	    	    	    	    	  message_DATA_OPEN,
    	    	    	    	    	  #ifdef INFIX
    	    	    	    	    	  message_INFIX_TRANSFER,
    	    	    	    	    	  #endif
                                          0};

    static
    const  toolbox_ACTION_LIST(31) action_nos={action_ERROR,
    	    	    	    	    	       action_OBJECT_AUTO_CREATED,
    	    	    	    	    	       action_WINDOW_ABOUT_TO_BE_SHOWN,
    	    	    	    	    	       action_WINDOW_DIALOGUE_COMPLETED,
    	    	    	    	    	       action_ACTION_BUTTON_SELECTED,
    	    	    	    	    	       action_OPTION_BUTTON_STATE_CHANGED,
    	    	    	    	    	       action_PROG_INFO_ABOUT_TO_BE_SHOWN,
    	    	    	    	    	       action_QUIT_QUIT,
    	    	    	    	    	       action_QUIT_DIALOGUE_COMPLETED,
    	    	    	    	    	       action_SAVE_AS_ABOUT_TO_BE_SHOWN,
    	    	    	    	    	       action_SAVE_AS_SAVE_TO_FILE,
    	    	    	    	    	       action_SAVE_AS_SAVE_COMPLETED,
    	    	    	    	    	       action_SAVE_AS_DIALOGUE_COMPLETED,
    	    	    	    	    	       action_POP_UP_ABOUT_TO_BE_SHOWN,
    	    	    	    	    	       action_MENU_SELECTION,
    	    	    	    	    	       action_MENU_ABOUT_TO_BE_SHOWN,
    	    	    	    	    	       action_FONT_MENU_SELECTION,
    	    	    	    	    	       action_FONT_MENU_ABOUT_TO_BE_SHOWN,
    	    	    	    	    	       action_COLOUR_DBOX_COLOUR_SELECTED,
                                               action_Quit,
                                               action_GrabFkeys,
                                               action_LineEdit,
                                               action_CmdRecall,
                                               action_Border,
                                               action_GameMenu,
                                               /*action_Recording,*/
                                               action_Keypad,
                                               action_FullScreen,
                                               action_FileChoiceFlip,
                                               action_OpenSaveDir,
                                               action_MenuSave,
                                               0};

    int i;

    setlocale(LC_ALL, "");

    for (i=os_HANDLER_UNDEFINED_INSTRUCTION; i<=os_HANDLER_ADDRESS_EXCEPTION; i++)
    {
        void *h;
        byte *a, *b;

    	h=os_read_default_handler(i, &a, &b);
    	os_change_environment(i, h, a, b, 0, 0);
    }

    signal(SIGFPE, sighandler);
    signal(SIGILL, sighandler);
    signal(SIGSEGV, sighandler);
    signal(SIGSTAK, sighandler);
    signal(SIGOSERROR, sighandler);

    toolbox_initialise(NONE, wimp_VERSION_RO3, (wimp_message_list *) &messages,
                       (toolbox_action_list *) &action_nos,
                       getenv("Zip2000$Dir"), &mfd, &id_block, &wimpver, 0);

    #ifdef INFIX
    our_task=toolboxgetsysinfo_task();
    #endif

    // Nasty Toolbox 1.36 bug - if the game quits while one of our menus is up
    // the whole of RISC OS will crash
    atexit(close_menu);

    #if SIGABRT != 1 || SIGSTAK != 7
    #error "Signals have been pissed around with!"
    #endif

    for (i=SIGABRT; i<=SIGSTAK; i++)
    	signal(i, sighandler);

    signal(SIGOSERROR, sighandler);

}

void initialise_wimp(void)
{
    wimp_event_no code=-1;

    event_initialise(&id_block);

    event_register_toolbox_handler(event_ANY, action_ERROR,
                                                    error_handler, 0);

    event_register_toolbox_handler(event_ANY, action_Quit,
                                                    quitmenu_handler, 0);

    event_register_toolbox_handler(event_ANY, action_GrabFkeys,
                                                    grabfkeys_handler, 0);

    #ifdef ALLOW_EDITING
    event_register_toolbox_handler(event_ANY, action_LineEdit,
                                                    lineedit_handler, &line_editing);
    #endif
    #ifdef ALLOW_RECALL
    event_register_toolbox_handler(event_ANY, action_CmdRecall,
                                                    lineedit_handler, &command_recall);
    #endif
    #ifdef HOT_RECORD
    event_register_toolbox_handler(event_ANY, action_Recording,
                                                    recording_handler, 0);
    #endif
    event_register_toolbox_handler(event_ANY, action_Keypad,
                                                    lineedit_handler, &separate_keypad);

    event_register_message_handler(message_QUIT, quit_handler, 0);

    event_register_message_handler(message_WINDOW_INFO, iconise_handler, 0);

    #ifdef INFIX
    event_register_message_handler(message_INFIX_TRANSFER, debug_transfer, 0);
    #endif

    event_register_toolbox_handler(event_ANY, action_OBJECT_AUTO_CREATED,
                                                   create_handler, 0);

    event_register_toolbox_handler(event_ANY, action_PROG_INFO_ABOUT_TO_BE_SHOWN,
                                                   proginfo_handler, 0);

    event_register_toolbox_handler(event_ANY, action_COLOUR_DBOX_COLOUR_SELECTED,
                                                   selcoldbox_handler, 0);

    event_register_toolbox_handler(event_ANY, action_Border,
                                                   border_handler, 0);

    event_register_toolbox_handler(event_ANY, action_FullScreen,
                                                   fullscreen_handler, 0);

    event_register_toolbox_handler(event_ANY, action_GameMenu,
                                                   gamemenu_handler, 0);

    event_register_toolbox_handler(event_ANY, action_OpenSaveDir,
                                                   opensavedir_handler, 0);

    event_register_toolbox_handler(event_ANY, action_MenuSave,
                                                   menusave_handler, 0);

    event_register_message_handler(message_MODE_CHANGE, modechange_handler, 0);

    event_register_message_handler(message_PALETTE_CHANGE, palettechange_handler, 0);

    event_register_message_handler(message_DATA_OPEN, dataopen_handler, 0);

    event_register_message_handler(message_DATA_SAVE, datasave_handler, 0);

    event_register_message_handler(message_DATA_LOAD, dataload_handler, 0);

    event_register_wimp_handler(event_ANY, wimp_KEY_PRESSED, key_handler, 0);

    event_set_mask(0);

    if (quickload)
    {
        while (!Screen)
            poll();
    }
    else
    {
        while (code != wimp_NULL_REASON_CODE)
            code = poll();
    }
    event_set_mask(wimp_MASK_NULL | wimp_MASK_LOSE |
                   wimp_MASK_LEAVING | wimp_MASK_ENTERING);
}

wimp_event_no poll(void)
{
    wimp_block block;
    wimp_event_no event_code;

    os_update_window();

    if (h_type==V6)
    {
        flex_compact();
        switchoutput(-1);
    }

    event_poll(&event_code, &block, pollword);

    if (h_type==V6)
    	switchoutput(+1);

    return event_code;
}

wimp_event_no poll_noswitch(void)
{
    wimp_block block;
    wimp_event_no event_code;

    os_update_window();

    event_poll(&event_code, &block, pollword);

    return event_code;
}

wimp_event_no poll_idle(os_t earliest)
{
    wimp_block block;
    wimp_event_no event_code;

    os_update_window();

    if (h_type==V6)
    {
        flex_compact();
        switchoutput(-1);
    }

    event_poll_idle(&event_code, &block, earliest, pollword);

    if (h_type==V6)
    	switchoutput(+1);

    return event_code;
}

static int null_count;

int claim_null_events(void)
{
    wimp_poll_flags oldmask;

    if (null_count == 0)
    {
        event_get_mask(&oldmask);
        event_set_mask(oldmask &~ wimp_MASK_NULL);
    }

    return null_count++;
}

int release_null_events(void)
{
    wimp_poll_flags oldmask;

    if (null_count == 1)
    {
        event_get_mask(&oldmask);
        event_set_mask(oldmask | wimp_MASK_NULL);
    }

    return null_count--;
}

int release_memory(void)
{
    return graphics_release_memory() ||
           sound_release_memory();
}
