/*-*-C-*-
 *
 * Minimal dbox manipulation
 */

#include "resed.h"

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

#include "dbox.h"


/*
 * Set a string field to the given entry.  If the size is too big
 * for the icon's buffer, shorten it.  Pass "" to clear the
 * field.  The icon must be indirected.
 *
 * Only call Wimp_SetIconState if the content of the icon has changed;
 *  if the string is the same, just check and reset the caret's position
 *  if necessary.
 */

error * dbox_setstring (WindowPtr win, int i, char *s)
{
    char *dst = (char *) win->icons[i].data[0];
    Bool nochange;

    if (s == NULL)
        s = "";

#if DEBUG
    if ((win->icons[i].flags & IF_INDIR) == 0)
        display("Icon %d is not indirect for '%s'", i, s);
#endif

    nochange = (strcmp (s, dst) == 0);

    if (!nochange)
    {
        int len = strlen(s);
        if (len > win->icons[i].data[2] - 1)
            len = win->icons[i].data[2] - 1;
        strncpy(dst, s, len);
        *(dst + len) = 0;
    }
    
    /* If the icon has the caret, set it to the start of the string */
    {
        CaretPositionRec caret;
        ER ( swi (Wimp_GetCaretPosition,  R1, &caret,  END) );
        if (caret.windowhandle == win->handle &&
            caret.iconhandle == i &&
            caret.height != -1)
        {
            ER ( swi (Wimp_SetCaretPosition,
                      R0, win->handle,  R1, i,
                      R2, caret.position.x,  R3, caret.position.y,  R4, -1,  R5, strlen(dst),  END ) );
        }
    }

    if (!nochange)
    {
        SetIconStateRec state;
        state.windowhandle = win->handle;
        state.iconhandle = i;
        state.clear = 0;
        state.eor = 0;
        ER ( swi (Wimp_SetIconState,  R1, &state,  END) );
    }
    return NULL;
}


/*
 * Obtain pointer to text of the given field.
 */

char *dbox_getstring (WindowPtr win, int i)
{
#if DEBUG
    if ((win->icons[i].flags & IF_INDIR) == 0)
        display("Icon %d is not indirect", i);
#endif

    return (char *) win->icons[i].data[0];
}


/*
 * Set an icon to be the specified number, expressed in decimal
 */

error * dbox_setint (WindowPtr win, int i, int v)
{
    char buf[30];
    sprintf (buf, "%d", v);
    if (strlen(buf) < win->icons[i].data[2])
        return dbox_setstring (win, i, buf);
    else
        return NULL;
}


/*
 * Set an icon to be the specified number, expressed in hex
 */

error * dbox_sethex (WindowPtr win, int i, int v)
{
    char buf[30];
    sprintf (buf, "&%x", v);
    if (strlen(buf) < win->icons[i].data[2])
        return dbox_setstring (win, i, buf);
    else
        return NULL;
}


/*
 * Read the number out of the specified icon taking account
 * of a leading & for hexadecimal
 */

int dbox_getint (WindowPtr win, int i)
{
    int result = 0;
    char *buf = dbox_getstring (win, i);
    if (*buf == '&')
        sscanf (buf + 1, "%x", &result);
    else
        sscanf (buf, "%d", &result);
    return result;
}


/*
 * Turn a button on or off without checking for radio behaviour.
 *
 * Only calls Wimp_SetIconState if the button state actually changes.
 */

error * dbox_setbutton (WindowPtr win, int i, Bool on)
{
    SetIconStateRec state;
    unsigned int oldflags = dbox_getflags (win, i);

    if (  on && (oldflags & IF_SELECTED) != 0 ||
         !on && (oldflags & IF_SELECTED)  == 0    )
        return NULL;

    state.windowhandle = win->handle;
    state.iconhandle = i;
    state.clear = IF_SELECTED;
    state.eor = on ? IF_SELECTED : 0;
    return swi (Wimp_SetIconState,  R1, &state,  END);
}

    
/*
 * Get the icon flags for an icon
 */

unsigned int dbox_getflags (WindowPtr win, int i)
{
    IconStateRec state;
    state.windowhandle = win->handle;
    state.iconhandle = i;
    swi (Wimp_GetIconState,  R1, &state,  END);
    return state.icon.flags;
}


/*
 * Get button status
 */

Bool dbox_getbutton (WindowPtr win, int i)
{
    return (dbox_getflags (win, i) & IF_SELECTED) != 0;
}


/*
 * Change icon flags for an icon.  Pass i == -1 to apply it to
 * all icons in the window.
 *
 * Only calls Wimp_SetIconState if the flags actually change.
 */

error * dbox_iconflag (WindowPtr win, int i, unsigned int clear, unsigned int eor)
{
    if (i == -1)
    {
        for (i = 0; i < win->numicons; i++)
            if ((dbox_getflags (win, i) & IF_DELETED) == 0)
            {
                ER ( dbox_iconflag (win, i, clear, eor) );
            }
    }
    else
    {
        SetIconStateRec state;
        unsigned int oldflags = dbox_getflags (win, i);

        if (((oldflags & ~clear) ^ eor) == oldflags)
            return NULL;

        state.windowhandle = win->handle;
        state.iconhandle = i;
        state.clear = clear;
        state.eor = eor;
        ER ( swi (Wimp_SetIconState,  R1, &state,  END) );
    }
    return NULL;
}


/*
 * Shade/unshade a single icon according to 'shaded'
 */

error * dbox_shade (WindowPtr win, int i, Bool shaded)
{
    unsigned int eor = shaded ? IF_SHADED : 0;
    return dbox_iconflag (win, i, IF_SHADED, eor);
}


/*
 * Alter the title of the given window.  The title must be INDIR TEXT
 * and if it is not, the call will be ignored.  If the text supplied
 * is too long, it will be truncated.
 *
 * The "redraw" parameter specifies whether the titlebar should be
 * redrawn; this should be FALSE for windows that are not currently
 * open.
 *
 * If the new title is NULL or seems to point to the current title,
 * just do the redraw part.
 */

error * dbox_settitle (WindowPtr win, char *s, Bool redraw)
{
    if ((win->titleflags & (IF_INDIR | IF_TEXT)) == (IF_INDIR | IF_TEXT))
    {
        char *dst = (char *) win->titledata[0];
        if (s != NULL && s != dst)
        {
            int len = strlen(s);
            if (len > win->titledata[2] - 1)
                len = win->titledata[2] - 1;
            strncpy(dst, s, len);
            *(dst + len) = 0;
        }
        
        if (redraw)
        {
            WindowRedrawRec outl, vis;
            outl.handle = vis.handle = win->handle;
            ER ( swi(Wimp_GetWindowOutline,  R1, &outl,  END) );
            ER ( swi(Wimp_GetWindowState,  R1, &vis,  END) );
            ER ( swi(Wimp_ForceRedraw,  R0, -1,
                     R1, outl.visarea.minx, R2, vis.visarea.maxy,
                     R3, outl.visarea.maxx, R4, outl.visarea.maxy,  END) );
        }
    }
    return NULL;
}


/*
 * Get the current title data from the specified window.
 * If the window is not INDIR TEXT, return NULL.
 */

char * dbox_gettitle (WindowPtr win)
{
    if ((win->titleflags & (IF_INDIR | IF_TEXT)) == (IF_INDIR | IF_TEXT))
        return (char *) win->titledata[0];
    else
        return NULL;
}


/*
 * Place the caret in the specified icon; no checks on the suitability of
 *  this icon are made.
 *
 * If i = -1, the caret is placed in the window invisibly.
 */

error * dbox_place_caret (WindowPtr win, int i)
{
#if DEBUG
    unsigned int flags = dbox_getflags (win, i);

    if (i >= 0 && (flags & ((IF_TYPE_MASK << IF_TYPE_SHFT) | IF_SHADED)) !=
                                                    (15 << IF_TYPE_SHFT)
               && (flags & ((IF_TYPE_MASK << IF_TYPE_SHFT) | IF_SHADED)) !=
                                                    (14 << IF_TYPE_SHFT))
        display("Icon %d is not (writable and unfaded)", i);
#endif

    /* set the caret's position */
    return swi (Wimp_SetCaretPosition,
                R0, win->handle,
                R1, i,
                R4, (i < 0) ? (1 << 25) : -1,    /* bit 25 => invisible */
                R5, (i < 0) ? 0 : strlen ((char *) win->icons[i].data[0]),
                    END);
}


/*
 * If the caret is not in the specified window:
 *   If the specified icon is not faded, put the caret into it; otherwise
 *    place the caret in the next suitable icon, if any.
 *
 * If the caret is already in the specified window:
 *   If the caret is inside an unfaded icon, leave it there; otherwise
 *    place the caret in the next suitable icon, if any.
 *
 * If i = -1 and the caret is not already inside the specified window, it
 *  is placed there invisibly; this makes it possible to give input focus to
 *  a dialogue box that contains no writable icons. 
 */

error * dbox_set_caret_to (WindowPtr win, int i)
{
    CaretPositionRec caret;
#if DEBUG
    if (i >= 0 && IF_GET_FIELD(TYPE, win->icons[i].flags) != 15
               && IF_GET_FIELD(TYPE, win->icons[i].flags) != 14)
        display("Icon %d is not writable", i);
#endif

    ER ( swi (Wimp_GetCaretPosition,  R1, &caret,  END) );
    if (caret.windowhandle == win->handle)
    {
        i = caret.iconhandle;
        if (i < 0 || (dbox_getflags (win, i) & IF_SHADED) == 0)
            return NULL;
    }

    if (i >= 0 && (dbox_getflags (win, i) & IF_SHADED) != 0)
    {
        int writables[100];  /* no more than 100 writables per dbox ... */
        int *ii = writables;

        /* make a list of all unfaded writables:
            (7 << 13) identifies button types 14 and 15 */
        ER ( swi (Wimp_WhichIcon,
                      R0, win->handle,
                      R1, (char *) writables,
                      R2, (7 << 13) | IF_SHADED,
                      R3, (7 << 13),  END) );

        /* find the one that follows i, if any */
        while (*ii >= 0 && *ii < i)
            ii++;
        if (*ii < 0)
            ii = writables;
        i = *ii;
    }

    return swi (Wimp_SetCaretPosition,
                R0, win->handle,
                R1, i,
                R4, (i < 0) ? (1 << 25) : -1,    /* bit 25 => invisible */
                R5, (i < 0) ? 0 : strlen ((char *) win->icons[i].data[0]),
                    END);
}
