/*-*-C-*-
 *
 * Wimp veneer routines
 */

#include "resed.h"

#include "swicall.h"
#include "wimp.h"


int scalex, scaley;
int screenx, screeny;


/*
 * Determine stuff about the screen mode
 */

error * wimp_examine_mode ()
{
    /* read XEigFactor, YEigFactor */
    ER ( swi (OS_ReadModeVariable, R0, -1, R1,  4, OUT, R2, &scalex, END) );
    ER ( swi (OS_ReadModeVariable, R0, -1, R1,  5, OUT, R2, &scaley, END) );

    /* read XWindLimit, YWindLimit */
    ER ( swi (OS_ReadModeVariable, R0, -1, R1, 11, OUT, R2, &screenx, END) );
    ER ( swi (OS_ReadModeVariable, R0, -1, R1, 12, OUT, R2, &screeny, END) );

    screenx = (screenx + 1) << scalex;
    screeny = (screeny + 1) << scaley;

    scalex = 1 << scalex;
    scaley = 1 << scaley;

    return NULL;
}


/*
 * 's' addresses a string containing at most 'n' characters.
 * The first character < 32 - if any - is replaced by 0.
 */

static void ensure_null_terminator (char *s, int n)
{
    int i;

    if (s != NULL)
        for (i = 0; i < n; i++)
            if (s[i] < 32)
            {
                s[i] = 0;
                return;
            }

    return;
}


/* This function replaces any text terminator in the icon by a null value.
 *
 * This function is needed because FormEd terminates text strings with 0x0d,
 * whereas C programs expect to find a 0x00 terminator.
 */

static void check_null_terminator (int *icondata, unsigned int flags)
{
    if ((flags & (IF_TEXT | IF_SPRITE)) != 0)  /* IST != 000, 100 */
    {
        if ((flags & IF_INDIR) == 0)           /* IST = 001, 010, 011 */
            ensure_null_terminator ((char *) icondata, 12);
        else
        {
            if ((flags & IF_TEXT) != 0)        /* IST = 101, 111 */
            {
                ensure_null_terminator ((char *) icondata[0], icondata[2]);
                ensure_null_terminator ((char *) icondata[1], -1);
            }
            else                               /* IST = 110 */
            {
                if (icondata[2] != 0)
                    ensure_null_terminator ((char *) icondata[0],
                                                         icondata[2]);
            }
        }
    }

    return;
}


/*
 * Load the given window template into a malloced WindowRec.  The indirected
 * data is stuck right on the end of the WindowRec so that it can be
 * freed in one go.
 *
 * Note that this routine allocates enough space for the window handle
 * to sit at the top of the returned block.
 *
 * Any sprite area pointers are forced to 1 (Wimp Sprite area), since various
 * template editors don't always leave these fields set in a sensible way.
 *
 * Terminators for all indirected strings are forced to 0 (in case they were
 * created with an editor that set them to, say, 0x0d).
 */

error * wimp_load_template (char *name, WindowPtr *windowret)
{
    return wimp_load_template_returning_size (name, windowret, NULL);
}

error * wimp_load_template_returning_size (char *name, WindowPtr *windowret, int *size)
{
    int buflen, indirlen;
    char *buffer, *wbuf, *ibuf;

    ER( swi(Wimp_LoadTemplate,
            R1, -1, R4, -1,
            R5, name,
            R6, 0,
            OUT, R1, &buflen, R2, &indirlen, END) );

    buflen = buflen + 3 & ~3;           /* ensure indir on word boundary*/
    buffer = calloc(sizeof(int) + buflen + indirlen, sizeof(char));
    if (!buffer) return error_lookup("NoMem");
    if (size)
        *size = sizeof(int) + buflen + indirlen;
    wbuf = buffer + sizeof(int);
    
    ibuf = wbuf + buflen;

    ER( swi(Wimp_LoadTemplate,
            R1, wbuf,
            R2, ibuf,
            R3, ibuf + indirlen,
            R4, -1,                     /* No fonts */
            R5, name,
            R6, 0,
            END) );

    *windowret = (WindowPtr) buffer;

    /* set any sprite area pointers to +1 */
    {
        WindowPtr w = (WindowPtr) buffer;
        int i;

        w->spritearea = (void *) 1;
        for (i = 0; i < w->numicons; i++)
        {
            IconPtr p = &w->icons[i];

            /* if it's an indirected sprite-only icon - IST = 110 ... */
            if ( (p->flags & (IF_INDIR | IF_SPRITE | IF_TEXT)) ==
                                               (IF_INDIR | IF_SPRITE) )
                p->data[1] = 1;  /* force sprite area to be the Wimp's */
        }
    }

    /* make sure that all icon text strings are terminated with null chars */
    {
        WindowPtr w = (WindowPtr) buffer;
        int numicons = w->numicons;
        IconPtr icon = w->icons;
        int i;

        /* first, check the window's title string */
        check_null_terminator (w->titledata, w->titleflags);

        /* and now process each icon in turn */
        for (i = 0; i < numicons; i++)
        {
            check_null_terminator ((int *) icon->data, icon->flags);
            icon++;
        }
    }

    return NULL;
}


/*
 * Make a copy of a window template, and relocate the indirected icon data.
 * Requires the size of the original, as returned by wimp_load_template_returning_size().
 */

static int relocate_word (int value, WindowPtr from, WindowPtr to)
{
    if (value == -1 || value == 0 || value == 1)
        return value;
    else
        return value - (int) from + (int) to;
}

error * wimp_copy_template (WindowPtr pattern, WindowPtr *ret, int size)
{
    int i;
    WindowPtr new = (WindowPtr) calloc (size, sizeof(char));
    if (new == NULL)
        return error_lookup ("NoMem");
    *ret = new;
    
    memcpy ((void *) new, (void *) pattern, size);
    
    if (new->titleflags & IF_INDIR)
    {
        new->titledata[0] = relocate_word (new->titledata[0], pattern, new);
        new->titledata[1] = relocate_word (new->titledata[1], pattern, new);
    }

    for (i = 0; i < new->numicons; i++)
    {
        if (new->icons[i].flags & IF_INDIR)
        {
            new->icons[i].data[0] = relocate_word (new->icons[i].data[0], pattern, new);
            new->icons[i].data[1] = relocate_word (new->icons[i].data[1], pattern, new);
        }
    }
    return NULL;
}


/*
 * Is a point inside a rectangle?
 */

int wimp_point_inside (RectPtr rect, PointPtr point)
{
    return
        point->x >= rect->minx &&
            point->x < rect->maxx &&
                point->y >= rect->miny &&
                    point->y < rect->maxy;
}


/*
 * Do two half-open rectangles intersect?
 */

int wimp_rects_intersect (RectPtr one, RectPtr two)
{
    return
        one->maxx > two->minx &&
            one->minx < two->maxx &&
                one->maxy > two->miny &&
                    one->miny < two->maxy;
}


/*
 * Is small rectangle contained entirely within large rectangle?
 */

int wimp_rect_contained (RectPtr small, RectPtr large)
{
    return
        small->minx >= large->minx &&
            small->maxx <= large->maxx &&
                small->miny >= large->miny &&
                    small->maxy <= large->maxy;
}


/*
 * Convert screen-relative point to work-area-relative.
 * In and out may point to the same structure.
 */

void wimp_convert_point (ConvertType type, WindowPtr win, PointPtr in, PointPtr out)
{
    int offx = win->visarea.minx - win->scrolloffset.x;
    int offy = win->visarea.maxy - win->scrolloffset.y;
    out->x = in->x + (offx * type);
    out->y = in->y + (offy * type);
}


/*
 * Convert screen-relative area to work-area-relative.
 * In and out may point to the same structure.
 */

void wimp_convert_rect (ConvertType type, WindowPtr win, RectPtr in, RectPtr out)
{
    int offx = win->visarea.minx - win->scrolloffset.x;
    int offy = win->visarea.maxy - win->scrolloffset.y;
    out->minx = in->minx + (offx * type);
    out->miny = in->miny + (offy * type);
    out->maxx = in->maxx + (offx * type);
    out->maxy = in->maxy + (offy * type);
}


/*
 * Ensure that a rectangle's min/max are the normal way round.  If not,
 * swap them.   XXX is this correct resp. half-openness?
 */

void wimp_regularise_rect (RectPtr rect)
{
    int temp;
    if (rect->minx > rect->maxx)
    {
        temp = rect->minx;
        rect->minx = rect->maxx;
        rect->maxx = temp;
    }
    if (rect->miny > rect->maxy)
    {
        temp = rect->miny;
        rect->miny = rect->maxy;
        rect->maxy = temp;
    }
}


/*
 * Merge two bounding boxes.  'Out' may point to the
 * same place as one of the other parameters.
 */

void wimp_merge_bboxes (RectPtr out, RectPtr one, RectPtr two)
{
    out->minx = MIN(one->minx, two->minx);
    out->miny = MIN(one->miny, two->miny);
    out->maxx = MAX(one->maxx, two->maxx);
    out->maxy = MAX(one->maxy, two->maxy);
}


Bool wimp_bboxes_different (RectPtr one, RectPtr two)
{
    return memcmp ((void *) one, (void *) two, sizeof(RectRec)) != 0;
}


/*
 * Read values of the modifier keys
 */

unsigned int wimp_read_modifiers ()
{
    unsigned int ret = 0;
    int pressed;
    if (swi (OS_Byte,  R0, 129,  R1, KEYCODE_SHIFT ^ 0xFF,  R2, 0xFF,  OUT, R1, &pressed,  END) == NULL)
        if (pressed) ret |= MODIFIER_SHIFT;
    if (swi (OS_Byte,  R0, 129,  R1, KEYCODE_CTRL ^ 0xFF,  R2, 0xFF,  OUT, R1, &pressed,  END) == NULL)
        if (pressed) ret |= MODIFIER_CTRL;
    if (swi (OS_Byte,  R0, 129,  R1, KEYCODE_ALT ^ 0xFF,  R2, 0xFF,  OUT, R1, &pressed,  END) == NULL)
        if (pressed) ret |= MODIFIER_ALT;
    return ret;
}


/*
 * ForceRedraw a rectangle (work-area coords)
 */

error * wimp_invalidate (WindowPtr win, RectPtr area)
{
    return swi (Wimp_ForceRedraw,
                R0, win->handle,
                R1, area->minx,  R2, area->miny,
                R3, area->maxx,  R4, area->maxy,  END);
}


/*
 * Move caret from current writeable icon to another in the same
 * window.  It is placed at the same index in the string if possible.
 * Caret must already be in an indirected writeable icon of the window - so only
 * use this if you are sure of that e.g. in Key_Pressed handlers.
 */

error * wimp_move_caret (WindowPtr window, int i)
{
    int pos = strlen((char *) window->icons[i].data[0]);
    CaretPositionRec caret;

    if (swi (Wimp_GetCaretPosition,  R1, &caret,  END) == NULL && caret.index < pos)
        pos = caret.index;

    return swi (Wimp_SetCaretPosition,
                R0, window->handle,  R1, i,
                R4, -1,  R5, pos,  END);
}


/*
 * Move a window such that its top-left corner is at the specified
 * coordinates.  This call does not do a Wimp_OpenWindow for you,
 * it only updates the WindowRec.
 */

void wimp_move_window (WindowPtr win, int x, int y)
{
    RectPtr vis = &win->visarea;
    int w = vis->maxx - vis->minx;
    int h = vis->maxy - vis->miny;
    vis->minx = x;
    vis->maxy = y;
    vis->maxx = x + w;
    vis->miny = y - h;
}



/*
 * If the icon has a validation string, then return ptr to it.
 * If not, return NULL.
 */

static char * get_validation (IconPtr icon)
{
    if ((icon->flags & (IF_TEXT | IF_INDIR)) == (IF_TEXT | IF_INDIR))
        if (icon->data[1] != -1)
            return (char *)(icon->data[1]);
    return NULL;
}


/*
 * Locate the end of the validation command pointed to by 'start'.  Actually returns the
 * location of the 0 or semicolon.
 */

static char *validation_command_end (char *start)
{
    if (!start) return NULL;
    for (; *start > 31 && *start != ';'; start++)
    {
        if (*start == '\\' && *(start + 1) > 31)
        {
            start++;
            continue;
        }
    }
    return start;
}


/*
 * Read !WinEdit-style name icon name (only) into given buffer, which should
 *  be big enough.
 * This is only used for internal purposes (ie, interrogating our own
 *  windows).  Return TRUE for success, else FALSE if no name - in which case
 *  !buf = 0.
 *
 * Assumes that the name entry will be the first thing in the validation
 *  string.
 *
 * This function now handles "multi-icon" items - at least, the adjuster
 *  pair and labelled box items. Each of these consists of 2 icons with
 *  N commands as follows at the start of their validation strings:
 *
 *    Master icon:   N<type>/<name>/<slave icon number>;
 *    Slave icon:    N/<master icon numer>;
 *
 *  For the labelled box item , the master icon is the frame, and the slave
 *   icon contains the label; the type is 9.
 *
 *  For an adjuster pair, the master icon is the "down" arrow, and the slave
 *   icon is the "up" arrow; the type is 11.
 */

#define  WINEDIT_LABELLEDBOX   9
#define  WINEDIT_ADJUSTERPAIR 11

Bool wimp_read_icon_name (
    int w,       /* window handle */
    int i,       /* icon handle   */
    char *buf    /* icon name will be stored here */
)
{
    IconStateRec state;
    char *valid, *end;
    Bool slave = FALSE;
    int type;

    *buf = 0;

    state.windowhandle = w;

    while (TRUE)
    {
        state.iconhandle = i;
        if (swi (Wimp_GetIconState, R1, &state, END) != NULL)
            return FALSE;

        valid = get_validation (&state.icon);

        if (valid == NULL)                    /* no validation string */
            return FALSE;

        if (*valid != 'n' && *valid != 'N')   /* no N command */
            return FALSE;

        if (valid[1] != '/')                  /* looks ok */
            break;

        /* must be a slave icon */
        if (slave)                            /* don't expect two slaves */
            return FALSE;
        slave = TRUE;

        /* read master icon handle, and try again */
        sscanf (valid+2, "%d", &i);
    }

    end = validation_command_end (valid);
    {
        char endc = *end;
        *end = 0;
        sscanf(valid, "N%d/%[^/]", &type, buf);
        *end = endc;
    }

    if (*buf == 0)
        return FALSE;

    switch (type)
    {
    case WINEDIT_LABELLEDBOX:
        strcat (buf, slave ? "_LABEL" : "_FRAME");
        return TRUE;

    case WINEDIT_ADJUSTERPAIR:
        strcat (buf, slave ? "_UP" : "_DOWN");
        return TRUE;

    default:
        return (!slave);
    }
}


/*
 * Set the dot-dash pattern according to the eight
 * bytes passed in, or a default pattern if NULL passed.
 * Does not set the dot-dash repeat length.
 */

error * wimp_set_dotdash (char *eight)
{
    if (eight == NULL)
        eight = "\360\360\360\360\360\360\360\360";
    ER ( swi (OS_WriteI + 23,  END) );
    ER ( swi (OS_WriteI + 6,  END) );
    return swi (OS_WriteN,  R0, eight,  R1, 8,  END);
}


/*
 * These are the possible patterns used to plot the drag boxes.
 * The first patterns are used when plotting and erasing a drag box.
 * The second patterns are used when rotating a stationary drag box.
 */

static char patterns1[] = { 0xfc, 0xf9, 0xf3, 0xe7, 0xcf, 0x9f, 0x3f, 0x7e };
static char patterns2[] = { 0x82, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x41 };

/* contain the current patterns */
static int patternid = 7;  /* chooses the pattern */
static char pattern1[] = { 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e };
static char pattern2[] = { 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41 };

/*
 * This points to the pattern used by wimp_plot_eor_box(..);
 *  It addresses pattern1 whilst removing an existing drag box, or plotting
 *   a new one;
 *  It addresses pattern2 when rotating a stationary drag box.
 */

static char *pattern = pattern1;


/*
 * This function cycles the dot-dash patterns.
 */

void wimp_cycle_pattern (void)
{
    int i;

    patternid--;
    if (patternid < 0)
        patternid = 7;

    for (i=0; i<8; i++)
    {
        pattern1[i] = patterns1[patternid];
        pattern2[i] = patterns2[patternid];
    }

    return;
}


/*
 * Plot a dotted rubber box using EOR.  The coordinates are work-area
 * ones for the given window and are half-open.  Should only
 * be called in a Wimp_RedrawWindow or Wimp_UpdateWindow loop
 */

void wimp_plot_eor_box (WindowPtr win, RectPtr work)
{
    RectRec box;
    
    wimp_convert_rect (WorkToScreen, win, work, &box);
    box.maxx -= scalex;     /* convert from half open */
    box.maxy -= scaley;

    (void) wimp_set_dotdash (pattern);
    swi (Wimp_SetColour,  R0, 0x3F,  END);
    swi (OS_Plot, R0, 4, R1, box.minx, R2, box.miny,  END);
    swi (OS_Plot, R0, 5 | 24, R1, box.maxx, R2, box.miny,  END);
    swi (OS_Plot, R0, 5 | 48, R1, box.maxx, R2, box.maxy,  END);
    swi (OS_Plot, R0, 5 | 48, R1, box.minx, R2, box.maxy,  END);
    swi (OS_Plot, R0, 5 | 48, R1, box.minx, R2, box.miny,  END);
}


/*
 * Plot a dotted rubber box using EOR.  This may
 * be called outside a redraw loop because it uses
 * Wimp_UpdateWindow.
 */

void wimp_update_eor_box (WindowPtr win, RectPtr work)
{
    WindowRedrawRec temp;
    int more;

    temp.handle = win->handle;
    wimp_convert_rect (ScreenToWork, win, &win->visarea, &temp.visarea);
    swi (Wimp_UpdateWindow,  R1, &temp,  OUT,  R0, &more,  END);
    
    while (more)
    {
        wimp_plot_eor_box (win, work);
        swi (Wimp_GetRectangle,  R1,  &temp,  OUT,  R0, &more,  END);
    }
}


/*
 * Called before starting a "rotate" drag box plot.
 */

void wimp_start_rotate_box (void)
{
    pattern = pattern2;

    return;
}


/*
 * Called after completing a "rotate" drag box plot.
 */

void wimp_end_rotate_box (void)
{
    pattern = pattern1;
    wimp_cycle_pattern ();

    return;
}


/*
 * Determine the width of a string in Wimp system font.  This
 * measures the string using Wimp_TextOp if it is available,
 * or else uses the old formula of 16 OS Units per character.
 */

int wimp_string_width (char *string)
{
    int width;
    if (swi (Wimp_TextOp,  R0, 1,  R1, string,  R2, 0,  OUT,  R0, &width,  END) != NULL)
        width = strlen (string) * 16;
    return width;
}


/*
 * Ensures the rectangle's coordinates are multiples of 4
 */

void wimp_align_rect (RectPtr box)
{
    box->minx = WIMP_ALIGN_COORD(box->minx);
    box->maxx = WIMP_ALIGN_COORD(box->maxx);
    box->miny = WIMP_ALIGN_COORD(box->miny);
    box->maxy = WIMP_ALIGN_COORD(box->maxy);

    return;
}


/*
 * Ensures the point's coordinates are multiples of 4
 */

void wimp_align_point (PointPtr point)
{
    point->x = WIMP_ALIGN_COORD(point->x);
    point->y = WIMP_ALIGN_COORD(point->y);

    return;
}


/*
 * Reads CMOS byte to determine in what directions windows may be dragged
 *  off-screen.
 */

void wimp_read_window_constraints (Bool *topleft, Bool *bottomright)
{
    int flags;

    swi (OS_Byte, R0, 161, R1, 197, OUT, R2, &flags, END);

    *topleft = (flags & 0x40) != 0;
    *bottomright = (flags & 0x20) != 0;

    return;
}
