/*
 *  DLGBOX.C
 *
 *  Written on 10-Jul-94 by John Dennis and released to the public domain.
 *
 *  DialogBox Code.  Also includes the system HotSpot code.
 *
 *
 *  A dialog box consists of the main dlgbox structure, which defines
 *  colours and window position and the like, plus the dialog controls
 *  themselves (buttons, fields etc).  Each control has a corresponding
 *  HotSpot and ID, which allows any mouse press/release/movement to be
 *  related to a hotspot and corresponding control ID.  Other hotspots
 *  may be in the system, but note that if a hotspot is associated with
 *  a window, it will only be looked at if that window is current (on top).
 *
 *  This windowing system is NOT event based in the same manner as
 *  Windows.  It's done this way to make non-event based programs easily
 *  portable to this system; and as a result flexibility suffers.  If you
 *  want to add new types, you will have to recompile the affected
 *  modules.
 *
 *  Caveats: If a new ctrl type is wanted, it MUST be added here if it
 *    is to be processed by/for the DoDialog function.  You must also
 *    add the display function somwhere & link it in.
 *
 *  Functions to lookout for (that need mods):
 *    DoDialog(), ShowCtrl(), DoCursor(), GetYCoord(), BuildDialogHot().
 */

#define DLGBOX

#include <stdio.h>
#include <string.h>
#include "winsys.h"
#include "menu.h"
#include "keys.h"
#include "unused.h"

/* Global vars used by all functions */

static int Focus = -1;
static int OldFocus = -1;

int ValidCtrl(int type)
{
    switch (type)
    {
    case D_WBX:
    case D_TXT:
        return 0;
    default:
        return 1;
    }
}

void BuildDialogHot(dlgbox * db, HotGroup * hot)
{
    WND *hCurr;
    int i, k, modx, mody;

    if (!db)
    {
        return;
    }

    hCurr = WndTop();
    if (hCurr == NULL)
    {
        return;
    }

    /*
     *  The dialog items are on a window, so we have to get the
     *  window's position relative to the screen.
     */

    if (hCurr->flags & SBDR)
    {
        modx = 1;
        mody = 1;
    }
    else
    {
        if (hCurr->flags & INSBDR)
        {
            modx = 3;
            mody = 2;
        }
        else
        {
            modx = 0;
            mody = 0;
        }
    }

    modx += hCurr->x1;
    mody += hCurr->y1;

    /* fill out the HotGroup */

    k = i = 0;

    hot->wid = hCurr->wid;
    while (i < db->num)
    {
        if (ValidCtrl(db->ctrls[i].type))
        {
            switch (db->ctrls[i].type)
            {
            case D_CHK:
                db->ctrls[i].id = ((ckbutton *) db->ctrls[i].ctl)->id;
                hot->harr[k].id = ((ckbutton *) db->ctrls[i].ctl)->id;
                hot->harr[k].x1 = ((ckbutton *) db->ctrls[i].ctl)->px + modx;
                hot->harr[k].y1 = ((ckbutton *) db->ctrls[i].ctl)->y + mody;
                hot->harr[k].x2 = ((ckbutton *) db->ctrls[i].ctl)->x + modx + 3;
                hot->harr[k].y2 = ((ckbutton *) db->ctrls[i].ctl)->y + mody;
                break;

            case D_BUT:
                db->ctrls[i].id = ((button *) db->ctrls[i].ctl)->id;
                hot->harr[k].id = ((button *) db->ctrls[i].ctl)->id;
                hot->harr[k].x1 = ((button *) db->ctrls[i].ctl)->x + modx;
                hot->harr[k].x2 = ((button *) db->ctrls[i].ctl)->x + modx +
                  strlen(((button *) db->ctrls[i].ctl)->btext) + 4;
                hot->harr[k].y1 = ((button *) db->ctrls[i].ctl)->y + mody;
                hot->harr[k].y2 = ((button *) db->ctrls[i].ctl)->y + mody;
                break;

            case D_EDT:
                db->ctrls[i].id = ((editf *) db->ctrls[i].ctl)->id;
                hot->harr[k].id = ((editf *) db->ctrls[i].ctl)->id;
                hot->harr[k].x1 = ((editf *) db->ctrls[i].ctl)->x + modx;
                hot->harr[k].x2 = ((editf *) db->ctrls[i].ctl)->x + modx +
                  ((editf *) db->ctrls[i].ctl)->len;
                hot->harr[k].y1 = ((editf *) db->ctrls[i].ctl)->y + mody;
                hot->harr[k].y2 = ((editf *) db->ctrls[i].ctl)->y + mody;

            default:
                break;
            }
            k++;
        }
        else
        {
            db->ctrls[i].id = 0;
        }
        i++;
    }
    hot->num = k;
}

/*
 *  A dummy MnuGetMsg used when an edit feild has focus, it handles all
 *  the msgs it understands and passes others back to the DoDialog()
 *  procedure.
 */

int DoEditField(EVT * event, editf * i, unsigned long wid)
{
    int ret;
    int pos = i->curpos;
    unused(wid);
    ret = WndGetLine(i->x, i->y, i->len, i->buf, i->sattr, &pos, 1, 0, 0, event);
    i->curpos = pos;
    return ret;
}

/*
 *  Shows a dialog box controls.  Also used to show whether a control has
 *  been selected or not.
 */

static void ShowCtrl(ctrl * i, unsigned char sel)
{
    switch (i->type)
    {
    case D_EDT:
        ((editf *) i->ctl)->select = sel;
        ShowEditField((editf *) i->ctl);
        break;

    case D_BUT:
        ((button *) i->ctl)->select = sel;
        ShowButton((button *) i->ctl);
        break;

    case D_CHK:
        ((ckbutton *) i->ctl)->select = sel;
        ShowCkbutton((ckbutton *) i->ctl);
        break;

    case D_TXT:
        D_ShowTxt((textl *) i->ctl);
        break;

    case D_WBX:
        D_ShowWBox((wbox *) i->ctl);
        break;

    default:
        break;
    }
}

/*
 *  Handles the placement of the cursor, if needed.
 */

void DoCursor(dlgbox * db, int foc)
{
    if (!db)
    {
        TTCurSet(0);
        return;
    }
    switch (db->ctrls[foc].type)
    {
    case D_CHK:
        WndGotoXY(((ckbutton *) db->ctrls[foc].ctl)->x + 2, ((ckbutton *) db->ctrls[foc].ctl)->y);
        TTCurSet(1);
        break;

    default:
        TTCurSet(0);
        break;
    }
}

int GetPrevCtrl(dlgbox * db, int cur)
{
    int foc = cur;

    foc--;
    if (foc < 0)
    {
        foc = db->num - 1;
    }

    while (foc != cur && !ValidCtrl(db->ctrls[foc].type))
    {
        foc--;
        if (foc < 0)
        {
            foc = db->num - 1;
        }
    }

    return foc;
}

int GetNextCtrl(dlgbox * db, int cur)
{
    int foc = cur;

    foc++;
    if (foc == db->num)
    {
        foc = 0;
    }

    while (foc != cur && !ValidCtrl(db->ctrls[foc].type))
    {
        foc++;
        if (foc == db->num)
        {
            foc = 0;
        }
    }

    return foc;
}

int GetYCoord(ctrl * i)
{
    switch (i->type)
    {
    case D_BUT:
        return ((button *) i->ctl)->y;
    case D_CHK:
        return ((ckbutton *) i->ctl)->y;
    case D_EDT:
        return ((editf *) i->ctl)->y;
    default:
        return 0;
    }
}

int GetRightCtrl(dlgbox * db, int cur, int num)
{
    int y = GetYCoord(&db->ctrls[cur]);
    int i;

    for (i = cur + 1; i < num; i++)
    {
        if (GetYCoord(&db->ctrls[i]) == y && ValidCtrl(db->ctrls[i].type))
        {
            return i;
        }
    }
    for (i = 0; i < cur; i++)
    {
        if (GetYCoord(&db->ctrls[i]) == y && ValidCtrl(db->ctrls[i].type))
        {
            return i;
        }
    }
    return cur;
}

int GetLeftCtrl(dlgbox * db, int cur, int num)
{
    int y = GetYCoord(&db->ctrls[cur]);
    int i;

    for (i = cur - 1; i >= 0; i--)
    {
        if (GetYCoord(&db->ctrls[i]) == y && ValidCtrl(db->ctrls[i].type))
        {
            return i;
        }
    }
    for (i = num; i > cur; i--)
    {
        if (GetYCoord(&db->ctrls[i]) == y && ValidCtrl(db->ctrls[i].type))
        {
            return i;
        }
    }
    return cur;
}

int GetFocus(dlgbox * db, int id)
{
    int i;
    for (i = 0; i < db->num; i++)
    {
        if (db->ctrls[i].id == id)
        {
            return i;
        }
    }
    return -1;
}

/*
 *  Process a dialogbox structure (dlgbox), incorporating mouse and
 *  keyboard support.  If there are buttons, it returns when one is
 *  pressed and returns the ID of the button.
 *
 *      db  - dialogbox structure to process
 *      wnd - use already open window?
 */

int DoDialog(dlgbox * db, int wnd)
{
    WND *hWnd, *hCurr = NULL;
    HotGroup Hot;
    int WAttr = db->fattr;
    int BAttr = db->battr;
    int done = 0;               /* terminate condition */
    int i;                      /* loop var */
    int select = 0;
    int ret = 0;
    int NewFoc;
    EVT event;
    int Msg;                    /* Key/Mou press */

    if (!wnd)
    {
        hCurr = WndTop();
        hWnd = WndOpen(db->x1, db->y1, db->x2, db->y2, db->btype, BAttr, WAttr);
        if (hWnd == NULL)
        {
            WndCurr(hCurr);
            return WND_ERROR;
        }
    }
    else
    {
        hWnd = WndTop();
    }

    if (db->title)
    {
        WndTitle(db->title, BAttr);
    }

    for (i = 0; i < db->num; i++)
    {
        ShowCtrl(&db->ctrls[i], 0);
    }

    BuildDialogHot(db, &Hot);
    PushHotGroup(&Hot);

    Focus = 0;

    ShowCtrl(&db->ctrls[Focus], 1);
    DoCursor(db, Focus);

    TTClearQue();  /* clear input queue */

    while (!done)
    {
        OldFocus = Focus;

        if (db->ctrls[Focus].type == D_EDT)
        {
            Msg = DoEditField(&event, (editf *) db->ctrls[Focus].ctl, hWnd->wid);
        }
        else
        {
            Msg = MnuGetMsg(&event, hWnd->wid);
        }

        switch (event.msgtype)
        {
        case WND_WM_COMMAND:  /* item pressed */
            NewFoc = GetFocus(db, event.id);
            if (NewFoc != -1)
            {
                switch (Msg)
                {
                case LMOU_CLCK:
                    Focus = NewFoc;
                    if (db->ctrls[Focus].type != D_EDT)
                    {
                        select = TRUE;
                    }
                    break;

                case MOU_LBTDN:
                    Focus = NewFoc;
                    if (db->ctrls[Focus].type == D_BUT)
                    {
                        if (!((button *) db->ctrls[Focus].ctl)->down)
                        {
                            ((button *) db->ctrls[Focus].ctl)->down = 1;
                            ShowCtrl(&db->ctrls[Focus], 1);
                        }
                    }
                    break;

                case MOU_LBTUP:
                    if (db->ctrls[Focus].type == D_BUT)
                    {
                        if (NewFoc == Focus)
                        {
                            select = TRUE;
                        }
                    }
                    break;

                case LMOU_RPT:
                case MOUSE_EVT:
                    if (NewFoc != Focus)
                    {
                        if (db->ctrls[Focus].type == D_BUT)
                        {
                            if (((button *) db->ctrls[Focus].ctl)->down)
                            {
                                ((button *) db->ctrls[Focus].ctl)->down = 0;
                                ShowCtrl(&db->ctrls[Focus], 1);
                            }
                        }
                    }
                    else
                    {
                        if (db->ctrls[Focus].type == D_BUT)
                        {
                            if (!((button *) db->ctrls[Focus].ctl)->down)
                            {
                                ((button *) db->ctrls[Focus].ctl)->down = 1;
                                ShowCtrl(&db->ctrls[Focus], 1);
                            }
                        }
                    }
                    break;
                }
            }
            break;

        case WND_WM_MOUSE:  /* action in non-recognized area */
            switch (Msg)    /* actual message */
            {
            case MOU_LBTUP:
                if (db->ctrls[Focus].type == D_BUT)
                {
                    if (((button *) db->ctrls[Focus].ctl)->down)
                    {
                        ((button *) db->ctrls[Focus].ctl)->down = 0;
                        ShowCtrl(&db->ctrls[Focus], 1);
                    }
                }
                break;
            case MOUSE_EVT:
            case LMOU_RPT:
                if (db->ctrls[Focus].type == D_BUT)
                {
                    if (((button *) db->ctrls[Focus].ctl)->down)
                    {
                        ((button *) db->ctrls[Focus].ctl)->down = 0;
                        ShowCtrl(&db->ctrls[Focus], 1);
                    }
                }
                break;
            }
            break;

        case WND_WM_CHAR:   /* keyboard in use */
            switch (Msg)
            {
            case Key_Up:
            case Key_S_Tab:
                Focus = GetPrevCtrl(db, Focus);
                break;

            case Key_Dwn:
            case Key_Tab:
                Focus = GetNextCtrl(db, Focus);
                break;

            case Key_Rgt:
                Focus = GetRightCtrl(db, Focus, db->num);
                break;

            case Key_Lft:
                Focus = GetLeftCtrl(db, Focus, db->num);
                break;

            case Key_Spc:
            case Key_Ent:
                select = TRUE;
                break;

            case Key_Esc:
                done = TRUE;
                ret = Key_Esc;
                break;

            default:
                break;
            }
            break;

        default:
            break;
        }

        if (OldFocus != Focus)
        {
            ShowCtrl(&db->ctrls[OldFocus], 0);
            ShowCtrl(&db->ctrls[Focus], 1);
        }
        DoCursor(db, Focus);

        if (select)
        {
            switch (db->ctrls[Focus].type)
            {
            case D_BUT:
                if (event.msgtype == WND_WM_CHAR)
                {
                    ((button *) db->ctrls[Focus].ctl)->down = 1;
                    ShowCtrl(&db->ctrls[Focus], 1);
                    TTdelay(100);
                }
                ((button *) db->ctrls[Focus].ctl)->down = 0;
                ret = db->ctrls[Focus].id;
                done = TRUE;
                ShowCtrl(&db->ctrls[Focus], 1);
                break;

            case D_CHK:
                ((ckbutton *) db->ctrls[Focus].ctl)->down =
                  (unsigned char)!((ckbutton *) db->ctrls[Focus].ctl)->down;
                ShowCtrl(&db->ctrls[Focus], 1);
                break;

            case D_EDT:
                done = 1;
                ret = Key_Ent;
                break;

            default:
                break;
            }
        }
        select = 0;
    }

    DoCursor(NULL, 0);
    PopHotGroup();

    if (!wnd)
    {
        WndClose(hWnd);
        WndCurr(hCurr);
    }
    return ret;
}
