/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
 
/*
 *   Portions Copyright 1996 Warm Silence Software Ltd.
 */

/******************************************************/
/*                                                    */
/* Name: PGwin.c                                      */
/* Author: Paul Gardiner.                             */
/* Function: Provides a library of functions for      */
/*           manipulating windows.                    */
/*                                                    */
/******************************************************/

#include "kernel.h"
#include "swis.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "PGerr.h"
#include "PGgr.h"
#include "PGptr.h"
#include "PGfs.h"
#include "PGicn.h"
#include "PGicnH.h"
#include "PGdevH.h"
#include "PGmnu.h"
#include "PGmnuH.h"
#include "PGspr.h"
#include "PGsprH.h"
#include "PGwin.h"
#include "PGwinH.h"

#define MAX_TPLT_SIZE (1024*4)
#define MAX_TITLE_SIZE (80)


static _kernel_swi_regs regs;
static int task;
static struct win_window_s icon_bar = {-2};
static win_window window_list = &icon_bar;
static void *win_data = NULL;
static int double_clicked = 0;
static win_window rubber_band_window = NULL;
static win_new_mode mode_change_handler = NULL;
static win_data_open data_open_handler = NULL;
static char task_name[21];
struct redraw_area_s xyzREDRAW_AREAxyz;

static void *scratch = NULL;
static int scratch_size = 0;

static char font_ref[256] = {0};

struct callback_list {
  win_callback		 cb;
  void 			*data;
  struct callback_list	*next;
};

static struct callback_list *pending_callbacks;

static void lose_fonts(void)
{
    int i;

    for(i = 1; i < 256; i++)
    {
        regs.r[0] = i;
        while(font_ref[i] > 0)
        {
            _kernel_swi(Font_LoseFont, &regs, &regs);
            font_ref[i]--;
        }
    }
}

static void skip(void) {}

static win_window add(int handle, char *title, int n_icon)
{
    win_window win;

    win = (win_window) malloc(sizeof(struct win_window_s));
    if(win == NULL)
    {
        ErrorFatal("Insufficient memory to create window");
        return NULL;
    }
    win->handle  = handle;
    win->title   = title;
    win->draw    = NULL;
    win->click_s = NULL;
    win->click_m = NULL;
    win->click_a = NULL;
    win->click_c = NULL;
    win->key     = NULL;
    win->caret   = NULL;
    win->select  = NULL;
    win->load    = NULL;
    win->data    = NULL;
    win->n_icon  = n_icon;
    win->icon_data = NULL;
    win->next    = window_list;
    window_list = win;
    return win;
}

static void remv(int handle)
{
    win_window *ptr;

    ptr = &window_list;
    while(*ptr != NULL && (*ptr)->handle != handle)
        ptr = &(*ptr)->next;
    if(*ptr != NULL)
    {
        void *del;

        del = (void *) *ptr;
        *ptr = (*ptr)->next;
        free(del);
    }
}

win_window xyzWINFINDxyz(int handle)
{
    win_window win;

    win = window_list;
    while(win != NULL && win->handle != handle)
        win = win->next;
    if(win != NULL)
        win_data = win->data;
    return win;
}


void WinInit(char *name)
{
    static int messages[] = {1,2,3,4,5,11,12,0x400C1, 0};

    strncpy(task_name, name, 21);
    task_name[20] = '\0';
    regs.r[0] = 310;
    regs.r[1] = 0x4B534154;
    regs.r[2] = (int) task_name;
    regs.r[3] = (int) messages;
    if (_kernel_swi(Wimp_Initialise, &regs, &regs))
    {
        ErrorFatal("Wimp_Initialise failed");
    }
    task = regs.r[1];
    atexit(lose_fonts);
}

void WinFin(void)
{
    regs.r[0] = task;
    regs.r[1] = 0x4B534154;
    _kernel_swi(Wimp_CloseDown, &regs, &regs);
    exit(0);
}

char *WinTaskName(void)
{
    return task_name;
}

static void do_rubber_band(win_window win, int x, int y)
{
    int drag_buf[10], win_buf[9];

    rubber_band_window = win;
    win_buf[0] = win->handle;
    regs.r[1] = (int) win_buf;
    _kernel_swi(Wimp_GetWindowState, &regs, &regs);
    drag_buf[1] = 7;
    drag_buf[2] = x;
    drag_buf[3] = y;
    drag_buf[4] = x;
    drag_buf[5] = y;
    drag_buf[6] = win_buf[1];
    drag_buf[7] = win_buf[2];
    drag_buf[8] = win_buf[3];
    drag_buf[9] = win_buf[4];
    regs.r[1] = (int) drag_buf;
    _kernel_swi(Wimp_DragBox, &regs, &regs);
}

static void window_click(int *poll)
{
    win_window win;

    win = xyzWINFINDxyz(poll[3]);
    if(win != NULL)
    {
        if((poll[2] & 0x40 || poll[2] & 0x10) && win->select != NULL)
        {
            do_rubber_band(win, poll[0], poll[1]);
        }
        else
        {
            int blk[9];

            blk[0] = poll[3];
            regs.r[1] = (int) blk;
            _kernel_swi(Wimp_GetWindowState, &regs, &regs);
            double_clicked = ((poll[2] & 0x7) != 0);
            if(poll[2] & 0x101 && win->click_a != NULL)
                win->click_a(poll[0] + blk[5] - blk[1], poll[1] + blk[6] - blk[4]);
            if(poll[2] & 0x202 && win->click_m != NULL)
                win->click_m(poll[0] + blk[5] - blk[1], poll[1] + blk[6] - blk[4]);
            if(poll[2] & 0x404 && win->click_s != NULL)
                win->click_s(poll[0] + blk[5] - blk[1], poll[1] + blk[6] - blk[4]);
        }
    }
}

static void respond_to_drag(int *poll)
{
    int blk[9];

    blk[0] = rubber_band_window->handle;
    regs.r[1] = (int) blk;
    _kernel_swi(Wimp_GetWindowState, &regs, &regs);
    rubber_band_window->select(poll[0] + blk[5] - blk[1],
                                    poll[3] + blk[6] - blk[4],
                                    poll[2] + blk[5] - blk[1],
                                    poll[1] + blk[6] - blk[4]);
    rubber_band_window = NULL;
}

static void message(int reason, int *poll)
{
    win_window win;

    switch(poll[4])
    {
        case  0: WinFin();
                 break;
        case  1:
            win = xyzWINFINDxyz(poll[5]);
            if(win != NULL && win->load != NULL)
            {
                poll[3] = poll[2];
                poll[4] = 2;
                poll[9] = -1;
                strcpy((char *)(poll + 11), "<Wimp$Scrap>");
                regs.r[0] = 17;
                regs.r[1] = (int) poll;
                regs.r[2] = poll[1];
                _kernel_swi(Wimp_SendMessage, &regs, &regs);
            }
            break;
        case  2: xyzICNxyz(reason, poll);
            break;
        case  3:
            win = xyzWINFINDxyz(poll[5]);
            if(win != NULL && win->load != NULL)
            {
                win->load((char *) (poll + 11));
                if(poll[3] != 0)
                {
                    FsDelete((char *)(poll + 11));
                }
                poll[3] = poll[2];
                poll[4] = 4;
                regs.r[0] = 17;
                regs.r[1] = (int) poll;
                regs.r[2] = poll[1];
                _kernel_swi(Wimp_SendMessage, &regs, &regs);
            }
            break;
        case  4: xyzICNxyz(reason, poll);
            break;
        case  5:
            if(data_open_handler)
                if(data_open_handler(poll[10], (char *) (poll + 11)))
                {
                    poll[3] = poll[2];
                    poll[4] = 4;
                    regs.r[0] = 17;
                    regs.r[1] = (int) poll;
                    regs.r[2] = poll[1];
                    _kernel_swi(Wimp_SendMessage, &regs, &regs);
                 }
            break;
        case 11:
        case 12:
            xyzDEVxyz(reason, poll);
            break;
        case 0x400c1:
            if(mode_change_handler != NULL)
                mode_change_handler();
            break;
    }
}

static int Time(void)
{
    _kernel_swi_regs regs;

    _kernel_swi(OS_ReadMonotonicTime, &regs, &regs);
    return regs.r[0];
}

static void win_coop(int time)
{
    int poll[64];
    int reason = -1;
    static int entered = 0;
    static int entered_cb = 0;
    struct callback_list *callback, *callback2;
    int swi_num;

    if(entered)
        return;
    else
        entered = 1;
    while(reason != 0)
    {
	/* Changes here from RJW:                                                                  */
	/* 1) If we have pending callbacks, then we mustn't do either a pollidle or a poll         */
	/* with null events disabled, as callbacks want to be processed asap.                      */
	/* 2) If we do a pollidle for 'time', and then get our sleep interrupted by an event       */
	/* we want to go back to sleep only for the remaining time, not for the full 'time' again. */
        regs.r[0] = ((time < 0) && (pending_callbacks == NULL)) ? 1 : 0;
        regs.r[1] = (int) poll;
        swi_num = (((time > 0) && (pending_callbacks == NULL)) ? Wimp_PollIdle : Wimp_Poll);
        /* time = Desired Finish Time */
        time += Time();
        regs.r[2] = time;
        _kernel_swi(swi_num, &regs, &regs);
        reason = regs.r[0];
        switch(reason)
        {
            case 1: _kernel_swi(Wimp_RedrawWindow, &regs, &regs);
                    if(regs.r[0] != 0)
                    {
                        win_window win;
                        win_draw draw;
                        int xorig, yorig;

                        win = xyzWINFINDxyz(poll[0]);
                        if(win != NULL && win->draw != NULL)
                            draw = win->draw;
                        else
                            draw = skip;
                        do
                        {
                            xorig = poll[1] - poll[5];
                            yorig = poll[4] - poll[6];
                            GrOrigin(xorig, yorig);
                            xyzREDRAW_AREAxyz.xmin = poll[7] - xorig;
                            xyzREDRAW_AREAxyz.ymin = poll[8] - yorig;
                            xyzREDRAW_AREAxyz.xmax = poll[9] - xorig;
                            xyzREDRAW_AREAxyz.ymax = poll[10] - yorig;
                            GrMove(0, 0);
                            draw();
                            GrOrigin(0, 0);
                        }
                        while(_kernel_swi(Wimp_GetRectangle, &regs, &regs), regs.r[0] != 0);
                    }
                    break;
            case 2: _kernel_swi(Wimp_OpenWindow, &regs, &regs);
                    break;
            case 3: {
                        win_window win;

                        win = xyzWINFINDxyz(poll[0]);
                        if(win == NULL || win->click_c == NULL)
                            _kernel_swi(Wimp_CloseWindow, &regs, &regs);
                        else
                            win->click_c();
                    }
                    break;
            case 6: if(poll[3] != -1 && poll[3] != -2 && poll[4] == -1)
                        window_click(poll);
                    else
                        xyzICNxyz(reason, poll);
                    break;
            case 7: if(rubber_band_window != NULL)
                        respond_to_drag(poll);
                    else
                        xyzICNxyz(reason, poll);
                    break;
            case 8: {
                        win_window win;

                        win = xyzWINFINDxyz(poll[0]);
                        if(win == NULL || win->key == NULL || !win->key(poll[6]))
                        {
                            regs.r[0] = poll[6];
                            _kernel_swi(Wimp_ProcessKey, &regs, &regs);
                        }
                    }
                    break;
            case 9: xyzMNUxyz(poll);
                    break;
           case 11:
           case 12: {
                        win_window win;

                        win = xyzWINFINDxyz(poll[0]);
                        if(win != NULL && win->caret != NULL)
                            win->caret(reason == 12);
                    }
                    break;
           case 17:
           case 18:
           case 19: message(reason, poll);
                    break;
        }
        /* How much longer do we have to sleep for? */
        time -= Time();
        if (time < 0)
          time = 0;
    }
    entered = 0;
    if (entered_cb == 0) {
      entered_cb = 1;
      /* Now we process any callbacks that may be outstanding */
      callback = pending_callbacks;
      pending_callbacks = NULL;
      while (callback != NULL) {
        callback->cb(callback->data);
        callback2 = callback->next;
        free(callback);
        callback = callback2;
      }
      entered_cb = 0;
    }
}

void WinCoOp(void)
{
    win_coop(0);
}

void WinSleep(int t)
{
    win_coop(t);
}

win_template WinStdTplt(void)
{
    static struct win_template_s tplt;
    static unsigned int data[] =
    {
        0x0000014A, 0x000001E4, 0x00000582, 0x00000448, 0x0000006C,
        0x00000334, 0xFFFFFFFF, 0xFF000002, 0xFF070207, 0x000C0103,
        0x00000000, 0x00000000, 0x00000500, 0x00000400, 0x0000003D,
        0x00006000, 0x00000001, 0x00000001, 0x6C0D700D, 0x65572021,
        0x0D216C6C, 0x00000000
    };

    tplt.handle   = (int) data;
    return &tplt;
}

win_template WinLdTplt(char *file_name, char *tplt_name)
{
    win_template tplt;
    int ind_buf, win_buf;
    int ind_size, win_size;
    char cbuf[12];

    regs.r[1] = (int) file_name;
    if (_kernel_swi(Wimp_OpenTemplate, &regs, &regs))
    {
        ErrorFatal("Cannot open template file");
        return NULL;
    }

    strncpy(cbuf, tplt_name, 12);
    cbuf[11] = '\0';
    regs.r[1] = 0;
    regs.r[2] = 0;
    regs.r[3] = 0;
    regs.r[4] = -1;
    regs.r[5] = (int) cbuf;
    regs.r[6] = 0;
    if (_kernel_swi(Wimp_LoadTemplate, &regs, &regs))
    {
        ErrorFatal("Cannot load template");
        return NULL;
    }
    win_size = regs.r[1];
    ind_size = regs.r[2];

    win_buf = (int) malloc(win_size);
    ind_buf = (int) malloc(ind_size);
    tplt = (win_template) malloc(sizeof(struct win_template_s));
    if(tplt == NULL || win_buf == 0 || ind_buf == 0)
    {

        if(win_buf) free((void *) win_buf);
        if(ind_buf) free((void *) ind_buf);
        if(tplt) free((void *) tplt);
        ErrorFatal("Insufficient memory to load template");
        return NULL;
    }

    strncpy(cbuf, tplt_name, 12);
    cbuf[11] = '\0';
    regs.r[1] = win_buf;
    regs.r[2] = ind_buf;
    regs.r[3] = ind_buf + ind_size;
    regs.r[4] = (int) font_ref;
    regs.r[5] = (int) cbuf;
    regs.r[6] = 0;
    _kernel_swi(Wimp_LoadTemplate, &regs, &regs);

    tplt->handle = win_buf;

    _kernel_swi(Wimp_CloseTemplate, &regs, &regs);

    win_size += 4;
    if(win_size > scratch_size)
    {
        scratch_size = win_size;
        if(scratch) free(scratch);
        scratch = malloc(scratch_size);
        if(scratch == NULL)
        {
            ErrorFatal("Insufficient memory for scratch buffer");
            return NULL;
        }
    }
    return tplt;
}

void WinSprites(win_template tplt, spr_sprite *sp)
{
    ((int *) tplt->handle)[16] = (*sp)->area;
}

win_window WinIconBar(void)
{
    return &icon_bar;
}

win_window WinCreate(win_template tplt)
{
    char *title = NULL;
    int *blk;

    if(tplt == NULL)
        return NULL;
    blk = (int *) tplt->handle;
    if((blk[7] & 0x4000000) && (blk[14] & 0x3) == 1)
    {
        title = (char *) malloc(MAX_TITLE_SIZE);
        if(title == NULL)
        {
            ErrorFatal("Insufficient memory to create window");
            return NULL;
        }
        if(blk[14] & 0x100)
        {
            strncpy(title, (char *) blk[18], MAX_TITLE_SIZE);
        }
        else
        {
            strncpy(title, (char *) (blk + 18), 12);
            title[12] = '\0';
        }
        blk[14] |= 0x100;
        blk[18] = (int) title;
        blk[19] = -1;
        blk[20] = MAX_TITLE_SIZE;
    }
    regs.r[1] = (int) blk;
    _kernel_swi(Wimp_CreateWindow, &regs, &regs);
    return add(regs.r[0], title, blk[21]);
}

void WinTitle(win_window win, char *title)
{
    int win_st[9], win_ol[5];

    if(win->title != NULL)
    {
        strncpy(win->title, title, MAX_TITLE_SIZE);
        win_st[0] = win->handle;
        regs.r[1] = (int) win_st;
        _kernel_swi(Wimp_GetWindowState, &regs, &regs);
        if(win_st[8] & 0x10000)
        {
            win_ol[0] = win->handle;
            regs.r[1] = (int) win_ol;
            _kernel_swi(Wimp_GetWindowOutline, &regs, &regs);
            regs.r[0] = -1;
            regs.r[1] = win_st[1];
            regs.r[2] = win_st[4];
            regs.r[3] = win_st[3];
            regs.r[4] = win_ol[4];
            _kernel_swi(Wimp_ForceRedraw, &regs, &regs);
        }
    }
}

void WinKill(win_window win)
{
    int block[1];

    if(win == NULL)
        return;
    block[0] = win->handle;
    regs.r[1] = (int) block;
    _kernel_swi(Wimp_DeleteWindow, &regs, &regs);
    remv(win->handle);
}

void WinOpen(win_window win)
{
    int blk[9];

    if(win == NULL)
        return;
    blk[0] = win->handle;
    regs.r[1] = (int) blk;
    _kernel_swi(Wimp_GetWindowState, &regs, &regs);
    _kernel_swi(Wimp_OpenWindow, &regs, &regs);
}

void WinClose(win_window win)
{
    int blk[1];


    if(win != NULL)
    {
        blk[0] = win->handle;
        regs.r[1] = (int) blk;
        _kernel_swi(Wimp_CloseWindow, &regs, &regs);
    }
}

void WinExtent(win_window win, int *xmin, int *ymin, int *xmax, int *ymax)
{
    int *buf;

    if(win == NULL)
        return;
    buf = scratch;
    buf[0] = win->handle;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
    *xmin = buf[11];
    *ymin = buf[12];
    *xmax = buf[13];
    *ymax = buf[14];
}

void WinSetExtent(win_window win, int xmin, int ymin, int xmax, int ymax)
{
    int buf[9];

    if(win != NULL)
    {
        buf[0] = xmin;
        buf[1] = ymin;
        buf[2] = xmax;
        buf[3] = ymax;
        regs.r[0] = win->handle;
        regs.r[1] = (int) buf;
        _kernel_swi(Wimp_SetExtent, &regs, &regs);
        buf[0] = win->handle;
        regs.r[1] = (int) buf;
        _kernel_swi(Wimp_GetWindowState, &regs, &regs);
        if(buf[8] & 0x10000)
        {
            _kernel_swi(Wimp_OpenWindow, &regs, &regs);
        }
    }
}

void WinGetPlace(win_window win, int *x, int *y)
{
    int *buf;

    if(win == NULL)
        return;
    buf = scratch;
    buf[0] = win->handle;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
    *x = buf[1];
    *y = buf[4];
}

void WinPlace(win_window win, int x, int y)
{
    int *buf;

    if(win == NULL)
        return;
    buf = scratch;
    buf[0] = win->handle;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
    buf[2] += (y - buf[4]);
    buf[3] += (x - buf[1]);
    buf[4] = y;
    buf[1] = x;
    _kernel_swi(Wimp_OpenWindow, &regs, &regs);
    if(!(buf[8] & 0x10000))
    {
        _kernel_swi(Wimp_CloseWindow, &regs, &regs);
    }
}

void WinNudge(win_window win, int x, int y)
{
    int *buf;

    if(win == NULL)
        return;
    buf = scratch;
    buf[0] = win->handle;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
    buf[2] += y;
    buf[3] += x;
    buf[4] += y;
    buf[1] += x;
    _kernel_swi(Wimp_OpenWindow, &regs, &regs);
    if(!(buf[8] & 0x10000))
    {
        _kernel_swi(Wimp_CloseWindow, &regs, &regs);
    }
}

void WinFullSize(win_window win)
{
    int *buf;

    if(win == NULL)
        return;
    buf = scratch;
    buf[0] = win->handle;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
    buf[2] = buf[4] - (buf[14] - buf[12]);
    buf[3] = buf[1] + (buf[13] - buf[11]);
    buf[5] = buf[6] = 0;
    _kernel_swi(Wimp_OpenWindow, &regs, &regs);
    if(!(buf[8] & 0x10000))
    {
        _kernel_swi(Wimp_CloseWindow, &regs, &regs);
    }
}

void WinShowBottom(win_window win)
{
    int *buf;

    if(win == NULL)
        return;
    buf = scratch;
    buf[0] = win->handle;
    regs.r[1] = (int) buf;
    _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
    buf[6] = buf[12] + (buf[4] - buf[2]);
    _kernel_swi(Wimp_OpenWindow, &regs, &regs);
}

void WinNewMode(win_new_mode mch)
{
    mode_change_handler = mch;
}

void WinContent(win_window win, win_draw draw)
{
    if(win != NULL)
        win->draw = draw;
}

void WinRedrawArea(int *xmin, int *ymin, int *xmax, int *ymax)
{
    *xmin = xyzREDRAW_AREAxyz.xmin;
    *ymin = xyzREDRAW_AREAxyz.ymin;
    *xmax = xyzREDRAW_AREAxyz.xmax;
    *ymax = xyzREDRAW_AREAxyz.ymax;
}

void WinPatch(win_window win, int minx, int miny, int maxx, int maxy)
{
    if(win != NULL)
    {
        int buf[11];

        buf[0] = win->handle;
        buf[1] = minx;
        buf[2] = miny;
        buf[3] = maxx;
        buf[4] = maxy;
        regs.r[1] = (int) buf;
        _kernel_swi(Wimp_UpdateWindow, &regs, &regs);
        if(regs.r[0] != 0)
        {
            win_draw draw;
            int xorig, yorig;

            if(win->draw != NULL)
                draw = win->draw;
            else
                draw = skip;
            win_data = win->data;
            do
            {
                xorig = buf[1] - buf[5];
                yorig = buf[4] - buf[6];
                GrOrigin(xorig, yorig);
                xyzREDRAW_AREAxyz.xmin = buf[7] - xorig;
                xyzREDRAW_AREAxyz.ymin = buf[8] - yorig;
                xyzREDRAW_AREAxyz.xmax = buf[9] - xorig;
                xyzREDRAW_AREAxyz.ymax = buf[10] - yorig;
                GrMove(0, 0);
                draw();
                GrOrigin(0, 0);
            }
            while(_kernel_swi(Wimp_GetRectangle, &regs, &regs), regs.r[0] != 0);
        }
    }
}

void WinClickS(win_window win, win_action click)
{
    if(win != NULL)
        win->click_s = click;
}

void WinClickM(win_window win, win_action click)
{
    if(win != NULL)
        win->click_m = click;
}

void WinClickA(win_window win, win_action click)
{
    if(win != NULL)
        win->click_a = click;
}

void WinClickC(win_window win, win_closing click)
{
    if(win != NULL)
        win->click_c = click;
}

int WinDoubled(void)
{
    return double_clicked;
}

void WinPress(win_window win, win_use_char key)
{
    if(win != NULL)
    {
        win->key = key;
        if(key != NULL)
        {
            regs.r[0] = win->handle;
        }
        else
        {
            regs.r[0] = -1;
        }
        regs.r[1] = -1;
        regs.r[2] = 0;
        regs.r[3] = 0;
        regs.r[4] = 0x2000000;
        regs.r[5] = 0;
        _kernel_swi(Wimp_SetCaretPosition, &regs, &regs);
    }
}

void WinFocus(win_window win, win_caret caret)
{
    if(win != NULL)
        win->caret = caret;
}

void WinDrag(win_window win, win_select select)
{
    if(win != NULL)
        win->select = select;
}

void WinDataOpen(win_data_open doh)
{
    data_open_handler = doh;
}

void WinLoad(win_window win, win_load load)
{
    if(win != NULL)
        win->load = load;
}

void WinSetData(win_window win, void *data)
{
    if(win != NULL)
        win->data = data;
}

void *WinGetData(win_window win)
{
    return win ? win->data : NULL;
}

void *WinData(void)
{
    return win_data;
}

typedef struct {int ft;
                icn_icon drag;
                icn_icon writeable;
                icn_icon ok;
                win_save save;} *save_data;

static void ssave(void)
{
    save_data data;

    data = (save_data) WinData();
    data->save(IcnText(data->writeable));
    FsSetType(IcnText(data->writeable), data->ft);
    MnuOpen(NULL);
}

static void asave(void)
{
    save_data data;

    data = (save_data) WinData();
    data->save(IcnText(data->writeable));
    FsSetType(IcnText(data->writeable), data->ft);
}

static char *dsave_seed(void)
{
    save_data data;

    data = (save_data) WinData();
    return IcnText(data->writeable);
}

static void dsave_save(char *path)
{
    save_data data;

    data = (save_data) WinData();
    data->save(path);
    FsSetType(path, data->ft);
    if(IcnSafe())
        IcnSetText(data->writeable, path);
    MnuOpen(NULL);
}

win_window WinSaveAs(char *tplt, win_save save)
{
    win_window win;
    icn_icon *button;
    save_data data;
    char *spr_name;
    icn_drag dsave;

    win = WinCreate(WinLdTplt(tplt, "saveas"));
    button = IcnButtons(win);
    data = (save_data) malloc(sizeof(*data));
    if(data == NULL)
    {
        ErrorFatal("Insufficient memory to create window");
        return NULL;
    }
    data->drag = button[0];
    data->writeable = button[1];
    data->ok = button[2];
    data->save = save;
    WinSetData(win, (void *) data);
    spr_name = IcnSprName(data->drag);
    if(spr_name == NULL || sscanf(spr_name, "file_%3x", &data->ft) != 1)
        data->ft = 0xfff;
    dsave.file_type = data->ft;
    dsave.seed = dsave_seed;
    dsave.save = dsave_save;
    IcnDrag(data->drag, dsave);
    IcnClickS(data->ok, ssave);
    IcnClickA(data->ok, asave);
    return win;
}

void WinHourGlass(int onoff)
{
    if(onoff)
        _kernel_swi(Hourglass_On, &regs, &regs);
    else
        _kernel_swi(Hourglass_Off, &regs, &regs);
}

void WinHourGlassPercentage(int percent)
{
  regs.r[0] = percent;
  _kernel_swi(Hourglass_Percentage, &regs, &regs);
}

void WinRedraw(int xmin, int ymin, int xmax, int ymax)
{
    regs.r[0] = -1;
    regs.r[1] = xmin;
    regs.r[2] = ymin;
    regs.r[3] = xmax;
    regs.r[4] = ymax;
    _kernel_swi(Wimp_ForceRedraw, &regs, &regs);
}

void WinError(_kernel_oserror *err)
{
    PtrOn();
    PtrAnimate(NULL, 0, 0, 0, 0);
    regs.r[0] = (int) err;
    regs.r[1] = 1;
    regs.r[2] = (int) WinTaskName();
    _kernel_swi(Wimp_ReportError, &regs, &regs);
}

void WinMessage(_kernel_oserror *err)
{
    PtrOn();
    PtrAnimate(NULL, 0, 0, 0, 0);
    regs.r[0] = (int) err;
    regs.r[1] = 1+(1<<4);
    regs.r[2] = (int) WinTaskName();
    _kernel_swi(Wimp_ReportError, &regs, &regs);
}

int WinCallBack(win_callback cb, void *data)
{
  struct callback_list *entry;

  entry = (struct callback_list *)malloc(sizeof(struct callback_list));
  if (entry == NULL)
    return 1;
  entry->cb         = cb;
  entry->data       = data;
  entry->next       = pending_callbacks;
  pending_callbacks = entry;

  return 0;
}
