/*
 *                 GRAPH, Version 1.00 - 4 August 1989
 *
 *            Copyright 1989, David Gay. All Rights Reserved.
 *            This software is freely redistrubatable.
 */

/* User interface routines */

#include <exec/types.h>
#include <exec/interrupts.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <exec/interrupts.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#define INTUITIONPRIVATE
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>
#include <graphics/text.h>
#include <libraries/diskfont.h>
#include "libraries/arpbase.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <dos.h>
#include <stdarg.h>
#include <ctype.h>

#include "uio.h"
#include "graph.h"
#include "grph.h"
#include "list.h"
#include "object.h"
#include "user/gadgets.h"
#include "tracker.h"

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/diskfont.h>
#define NODOS
#include "proto/arp.h"

#define DEFAVAILSIZE 2048 /* Default space reserved for AvailFonts */
#define ARROW 42 /* Identifier of arrow gadget */
#define CROSS 666 /* Identifer of cross gadget */

struct fnode /* font node */
{
    tnode node;
    char name[FONTLEN];
};

tlist flist;

extern struct IntuitionBase *IntuitionBase;

/* Default graph window */
static struct NewWindow graph_win = {
    0, 0,
    640, 200,
    -1, -1,
    RAWKEY | REFRESHWINDOW | CLOSEWINDOW | MENUPICK | GADGETDOWN | GADGETUP | R
EQCLEAR | REQSET | MOUSEBUTTONS | MOUSEMOVE,
    WINDOWSIZING | WINDOWDRAG | WINDOWDEPTH | WINDOWCLOSE | SIMPLE_REFRESH | AC
TIVATE,
    NULL,
    NULL,
    "Graph",
    NULL,
    NULL,
    85, 60,
    -1, -1,
    WBENCHSCREEN
};

/* The two icon images */
static UWORD chip arrow_data[] = {
    0x7fff, 0x7fff, 0x601f, 0x603f, 0x607f, 0x607f, 0x603f, 0x661f, 0x6f0f, 0x7
f87, 0x7fc3, 0x7fe3, 0x7fff,
    0x0000, 0x0000, 0x1fe0, 0x1fc0, 0x1f80, 0x1f80, 0x1fc0, 0x19e0, 0x10f0, 0x0
078, 0x003c, 0x001c, 0x0000
};

static struct Image arrow_image = {
    -1, 0, 16, 13, 2,
    arrow_data,
    3, 0
};

static struct Gadget arrow = {
    NULL,
    -18, 18, 15, 13,
    GADGHCOMP | GADGIMAGE | GRELRIGHT,
    RELVERIFY | RIGHTBORDER | TOGGLESELECT,
    BOOLGADGET,
    (APTR)&arrow_image, NULL, NULL,
    2,
    NULL,
    ARROW
};

static UWORD chip cross_data[] = {
    0x7fff, 0x7f7f, 0x7f7f, 0x7f7f, 0x7f7f, 0x7e3f, 0x4081, 0x7e3f, 0x7f7f, 0x7
f7f, 0x7f7f, 0x7f7f, 0x7fff,
    0x0000, 0x0080, 0x0080, 0x0080, 0x0080, 0x01c0, 0x3f7e, 0x01c0, 0x0080, 0x0
080, 0x0080, 0x0080, 0x0000
};

static struct Image cross_image = {
    -1, 0, 16, 13, 2,
    cross_data,
    3, 0
};

static struct Gadget cross = {
    NULL,
    -18, 36, 15, 13,
    GADGHCOMP | GADGIMAGE | GRELRIGHT | SELECTED,
    RELVERIFY | RIGHTBORDER | TOGGLESELECT,
    BOOLGADGET,
    (APTR)&cross_image, NULL, NULL,
    1,
    NULL,
    CROSS
};

static struct Memory *abort_mem; /* for abort requester */

static struct TextAttr alert_attr = { "topaz.font", 8 };
static struct TextFont *alert_font;

static struct Task *me;
static struct MsgPort *inputDevPort; /* for input device */
static struct IOStdReq *inputRequestBlock;
static struct Interrupt handlerStuff;
static int inputOpen;

static long window_sigs;  /* Combined sigs of all windows */

static struct TextAttr font = { "topaz.font", 8 }; /* font for menus, requester
s, etc */
static struct Requester *req;    /* Current requester */
static int ok;                   /* Value of last GadgetID */
static short reqdone;            /* Req must go away. Don't activate next str g
adget */
static gadgevent *gadgethandler; /* Handler for gadget events */

/* Add .font extension to fname (assumed of size FONTLEN) */
char *addfont(char *fname)
{
    return strncat(fname, ".font", FONTLEN - 1 - strlen(fname));
}

/* Removes .font extension if present */
char *remfont(char *fname)
{
    int l = strlen(fname);

    if (l >= 5 && strcmp(&fname[l - 5], ".font") == 0) fname[l - 5] = '\0';

    return fname;
}

/* Make a list of font names, one entry per font. Sizes, etc ignored. */
int make_font_list(void)
{
    int ok = FALSE;
    char *abuf;

    new_list(&flist);
    if (abuf = AllocMem(DEFAVAILSIZE, 0L)) /* Alloc default avail buffer */
    {
        int needs = AvailFonts(abuf, DEFAVAILSIZE, AFF_MEMORY | AFF_DISK);

        ok = TRUE;
        if (needs != 0) /* Need a bigger buffer */
        {
            FreeMem(abuf, DEFAVAILSIZE);
            if (abuf = AllocMem(DEFAVAILSIZE + needs, 0L))
                if (AvailFonts(abuf, DEFAVAILSIZE + needs, AFF_MEMORY | AFF_DIS
K))
                    ok = FALSE; /* Definite failure */
        }
        if (ok) /* Construct font list */
        {
            struct AvailFontsHeader *hdr = (struct AvailFontsHeader *)abuf;
            struct AvailFonts *fl = (struct AvailFonts *)(hdr + 1);
            int i;

            /* Add font entries to sorted list, by name. Duplicate entries remo
ved */
            for (i = hdr->afh_NumEntries; i > 0; i--, fl++)
            {
                struct fnode *scan;
                char *name = fl->af_Attr.ta_Name;
                int cmp = -1;

                remfont(name); /* remove extension */

                /* Find insertion position */
                for (scan = first(&flist); succ(scan) && (cmp = strcmp(name, sc
an->name)) > 0; scan = succ(scan))
                    ;
                if (cmp != 0) /* Not already present, add to list */
                {
                    struct fnode *n = alloc_node(sizeof(struct fnode));

                    if (!n)
                    {
                        ok = FALSE;
                        break;
                    }
                    n->node.ln_Name = n->name;
                    n->name[FONTLEN - 1] = '\0';
                    strncpy(n->name, name, FONTLEN - 1);

                    /* Add at correct position */
                    insert(&flist, n, scan->node.ln_Pred);
                }
            }
            if (!ok)
            {
                free_list((list *)&flist, sizeof(struct fnode));
                new_list(&flist);
            }
        }
        FreeMem(abuf, DEFAVAILSIZE + needs);
    }
    if (!ok) nomem(NULL);
    return ok;
}


/* Implement mutual exclude seeing Intuition is lazy */
/* (for border gadgets)                              */
/* ------------------------------------------------- */
static void MutualExclude(struct Gadget *us, struct Gadget *gadg, struct Window
 *win)
{
    register int i;
    register struct Gadget *gp;
    register LONG mutex = us->MutualExclude;
    UWORD pos;

    /* scan gadget list */
    for (i = 1, gp = gadg; gp && i != 0; i <<= 1, gp = gp->NextGadget)
        if (i & mutex)
        {
            pos = RemoveGadget(win, gp);
            gp->Flags &= ~SELECTED; /* unselect */
            AddGadget(win, gp, pos);
        }
    pos = RemoveGadget(win, us);
    us->Flags |= SELECTED;
    AddGadget(win, us, pos);

    RefreshWindowFrame(win); /* This works for border gadgets */
}

/* Implement mutual exclude seeing Intuition is lazy */
/* (for requester gadgets)                           */
static void MutEx(struct Gadget *us, struct Requester *req)
{
    register int nb = 0, doneus = FALSE;
    register struct Gadget *gp, *first = NULL;
    register LONG mutex = us->MutualExclude;
    UWORD pos;

    /* scan gadget list */
    for (gp = req->ReqGadget; gp && (mutex != 0 || !doneus); mutex >>= 1, gp =
gp->NextGadget)
    {
        if ((mutex & 1) || (doneus = gp == us))
        {
            if (!first) first = gp;
            pos = RemoveGList(req->RWindow, gp, 1);
            if (gp == us)
                gp->Flags |= SELECTED;  /* select */
            else
                gp->Flags &= ~SELECTED; /* unselect */
            AddGList(req->RWindow, gp, pos, 1, req);
        }
        if (first) nb++;
    }
    if (first) RefreshGList(first, req->RWindow, req, nb);
}

/* Display requester, & handle everything until it goes away */
int DoRequest(struct Requester *r, struct graph *g, gadgevent *handle)
{
    r->Flags |= NOISYREQ; /* We want keystrokes */

    ok = FALSE;
    if (Request(r, g->io.win))
    {
        /* setup vital info */
        gadgethandler = handle;
        req = r;
        reqdone = FALSE;
        while (next_command().command != reqgone) ; /* Wait till it leaves */
        req = NULL; /* No req. present */
        gadgethandler = NULL;
    }
    return ok;
}

/* Find first string gadget */
static struct Gadget *NextText(struct Gadget *look)
{
    while (look && (look->GadgetType & ~GADGETTYPE) != STRGADGET) look = look->
NextGadget;
    return look;
}

/* Default gadget handler, activates string gadgets in sequence & handles
   mutual exclude. Will normally be called by custom handlers if they don't
   have anything special to do.
   Returens the new value of ok */
int std_ghandler(struct Gadget *gg, ULONG class, struct Requester *req, struct
graph *g)
{
    if ((gg->GadgetType & ~GADGETTYPE) == STRGADGET && !reqdone) /* Activate ne
xt one */
    {
        struct Gadget *ng = NextText(gg->NextGadget);
        if (!ng) ng = NextText(req->ReqGadget);
        if (ng) ActivateGadget(ng, req->RWindow, req);
    }
    else if (gg->MutualExclude != 0) MutEx(gg, req);

    return gg->GadgetID != 0;
}

/* Insert ins in front of into, checking for string overflow
   ( sizeof(into)=maxlen ) */
static char *strinsert(char *into, char *ins, int maxlen)
{
    int delta = strlen(ins);
    int start = strlen(into);
    int i;

    if (start + delta >= maxlen) start = maxlen - delta - 1;

    for (i = start - 1; i >= 0; i--) into[i + delta] = into[i];
    into[start + delta] = '\0';
    memcpy(into, ins, delta);

    return into;
}

/* Convert a lock to a path, store in to (maxlen chars long) */
static char *pathstr(char *to, long l, int maxlen)
{
    long tl;
    int notfirst = FALSE;
    struct FileInfoBlock *fib = (struct FileInfoBlock *)AllocMem(sizeof(struct
FileInfoBlock), 0);

    if (!fib) return(NULL);
    to[0] = '\0';

    do {
        if (!Examine(l, fib))
        {
            to = NULL;
            goto error;
        }
        if (fib->fib_DirEntryType > 0) strinsert(to, "/", maxlen);
        /* Is this still necessary ? */
        if (fib->fib_FileName[0] == '\0') strinsert(to, "RAM", maxlen);
        else strinsert(to, fib->fib_FileName, maxlen);
        tl = l;
        l = ParentDir(l);
        if (notfirst) UnLock(tl); /* Release allocated locks */
        notfirst = TRUE;
    } while (l);

    *(strchr(to, '/')) = ':'; /* First name is disk name */

error:
    FreeMem((char *)fib, sizeof(struct FileInfoBlock));
    return(to);
}

/* Request a file from the user (save in file), return TRUE if OK,
   FALSE if cancelled or failed. Currently uses arp file requester. */
int getfile(char *file, char *msg)
{
    static char directory[DSIZE + 1];
    static struct FileRequester FR;
    char filename[FCHARS + 1];

    filename[0] = '\0';
    FR.fr_Hail = msg;
    FR.fr_File = filename;
    FR.fr_Dir = directory;

    if (FileRequest(&FR))
    {
        long lock = Lock(directory, SHARED_LOCK);

        if (lock)
        {
            if (!pathstr(file, lock, FILELEN)) /* get dir path */
            {
                UnLock(lock);
                return FALSE;
            }
            strncat(file, filename, FILELEN - 1 - strlen(file));

            UnLock(lock);
            return TRUE;
        }
        else
            alert(NULL, "Failed to lock directory", directory);
    }
    return FALSE;
}

/* Setup an "abort" requester, in graph g with text msg (must be as long as max
 message) */
struct Requester *abort_request(struct graph *g, char *msg)
{
    int len = strlen(msg);
    struct Requester *req;
    struct Gadget *gl = NULL;
    int height, width;
    static struct Gadget text = {
        NULL,
        10, 10, 1, 1,
        GADGHNONE, 0L, BOOLGADGET | REQGADGET
    };

    /* Construct requester */
    height = 8 * 1 + 10 + 12 + 25;
    width = 8 * len + 2 * 10;
    if (width < 85) width = 85;

    text.GadgetText = NULL;
    if ((abort_mem = NewMemory()) &&
        (req = InitReq(50, 15, width, height, abort_mem)) &&
        SetReqBorder(req, 1, abort_mem) &&
        AddBox(&gl, TRUE, "Stop!", 0, RELVERIFY, (width - 65) / 2, height - 25,
 65, 15, FALSE, abort_mem) &&
        AddIntuiText(&text.GadgetText, msg, 0, 0, abort_mem))
    {
        SetReqGadgets(req, gl);
        text.NextGadget = req->ReqGadget;
        req->ReqGadget = &text;
        if (!Request(req, g->io.win)) req = NULL; /* display req */
    }
    else
        req = NULL;

    if (!req) Free(abort_mem);

    return req;
}

/* Change abort requester message. msg must not be longer than the first msg */
     
void set_abort_msg(struct Requester *req, char *msg)
{
    req->ReqGadget->GadgetText->IText = msg;
    RefreshGList(req->ReqGadget, req->RWindow, req, 1);
}

/* Clear abort requester */
void end_abort_request(struct Requester *req)
{
    EndRequest(req, req->RWindow);
    Free(abort_mem);
}

/* Has the user asked for an abort ? */
int aborted(struct Requester *req)
{
    int abort = FALSE;
    struct IntuiMessage *msg;

    while (msg = (struct IntuiMessage *)GetMsg(req->RWindow->UserPort))
    {
        ULONG class = msg->Class;

        ReplyMsg((struct Message *)msg);

        if (class == REFRESHWINDOW) /* Ignore refreshes at this time */
        {
            BeginRefresh(req->RWindow);
            EndRefresh(req->RWindow, TRUE);
        }
        else if (class == GADGETUP) abort = TRUE;
    }
    return abort;
}

/* Display a message in graph g. You pass as many string as you want, followed
   by (char *)NULL. This will try very hard to actually display it, calling
   alert with the first two strings if it fails. */
void message(struct graph *g, ...)
{
    int nb, len;
    va_list msgs;
    char *scan;
    struct Memory *m;
    struct Requester *req;
    struct Gadget *gl = NULL;
    int height, width;
    int ok = FALSE;

    /* Find number of lines and maxmimum length */
    nb = 0; len = 0;
    va_start(msgs, g);
    while (scan = va_arg(msgs, char *))
    {
        int nl = strlen(scan);

        nb++;
        if (nl > len) len = nl;
    }
    va_end(msgs);

    /* Construct requester */
    height = 8 * nb + 10 + 12 + 25;
    width = 8 * len + 2 * 10;
    if (width < 85) width = 85;

    if ((m = NewMemory()) &&
        (req = InitReq(50, 15, width, height, m)) &&
        SetReqBorder(req, 1, m) &&
        AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, (width - 65) / 2, hei
ght - 25, 65, 15, FALSE, m))
    {
        int y = 10 - 8;

        ok = TRUE;
        SetReqGadgets(req, gl);

        /* Add message strings */
        va_start(msgs, g);
        while (ok && (scan = va_arg(msgs, char *)))
            ok = ok && AddIntuiText(&req->ReqText, scan, 10, (y += 8), m);
        va_end(msgs);

        /* You'll have a surprise if you press Amiga-B ... */
        if (ok && g) ok = DoRequest(req, g, std_ghandler);
    }
    Free(m);
    if (!ok || !g) /* call alert */
    {
        va_start(msgs, g);
        if (nb == 1)
            alert(g ? g->io.win : NULL, va_arg(msgs, char *), NULL);
        else if (nb >= 2)
        {
            char *m1 = va_arg(msgs, char *);
            char *m2 = va_arg(msgs, char *);

            alert(g ? g->io.win : NULL, m1, m2);
        }
    }
}

/* Display a two line auto request. Doesn't alloc any resources */
void alert(struct Window *win, char *msg1, char *msg2)
{
    struct IntuiText text1, text2, negative;
    const static struct IntuiText template = {
        0, 1, JAM1,
        8, 0,
        &alert_attr
    };
    int width, height;
    int ysize = alert_font ? alert_font->tf_YSize : 8;

    text1 = text2 = negative = template;
    text1.TopEdge = 8;
    text1.IText = msg1;
    width = IntuiTextLength(&text1) + 20;
    height = 37 + 2 * ysize;
    if (msg2 != NULL)
    {
        int w;

        text1.NextText = &text2;
        text2.TopEdge = text1.TopEdge + ysize;
        text2.IText = msg2;

        height += ysize;
        w = IntuiTextLength(&text2) + 20;
        if (w > width) width = w;
    }
    negative.LeftEdge = 6;
    negative.TopEdge = 4;
    negative.IText = "Ok";

    AutoRequest(win, &text1, NULL, &negative, 0L, 0L, width, height);
}

/* Easy no mem requester */
void nomem(struct Window *win)
{
    alert(win, "No memory !", NULL);
}

/* Return next menu selection that is a command (in graph g).
   *choice is the item number of the next menu selection */
static struct cmd process(UWORD *choice, struct graph *g)
{
    struct MenuItem *item;
    UWORD itemnb, subnb;
    struct cmd cmd;
    struct pos *rect = (struct pos *)g->s.current;

    /* Prepare command */
    cmd.command = none;
    cmd.g = g;

    /* Try & find a command */
    while (cmd.command == none && *choice != MENUNULL)
    {
        item = ItemAddress(g->io.menu, *choice);
        itemnb = ITEMNUM(*choice);
        subnb = SUBNUM(*choice);

        /* Rem: illegal choices are disabled ==> no checking here */
        switch (MENUNUM(*choice)) {
            case 0 : switch (itemnb) { /* Project */
                case 0 : /* New Graph */
                    cmd.command = _new_graph;
                    break;
                case 1 : /* Delete Graph */
                    cmd.command = close;
                    break;
                case 2 : /* Load Graph */
                    cmd.command = _load_graph;
                    break;
                case 3 : /* Save Graph */
                    cmd.command = _save_graph;
                    break;
                case 4 : /* Output Graph */
                    switch (subnb)
                    {
                        case 0 : /* To Printer */
                            cmd.command = print_graph;
                            break;
                        case 1 : /* To Disk */
                            cmd.command = iff_graph;
                            break;
                    }
                    break;
                case 6 : /* Load Variables */
                    cmd.command = load_vars;
                    break;
                case 7 : /* Save Variables */
                    cmd.command = save_vars;
                    break;
                case 9 : /* Quit */
                    cmd.command = quit;
                    break;
                } break;

            case 1 : switch (itemnb) { /* Graph */
                case 0 : /* Scale */
                    deselect(g);
                    cmd.command = limits;
                    break;
                case 1 : /* Axes */
                    deselect(g);
                    cmd.command = axes;
                    break;
                case 2 : /* Zoom */
                    cmd.command = zoom;
                    cmd.data.zoom_in.x0 = rect->x0;
                    cmd.data.zoom_in.y0 = rect->y0;
                    cmd.data.zoom_in.x1 = rect->x1;
                    cmd.data.zoom_in.y1 = rect->y1;
                    deselect(g);
                    break;
                case 3 : /* Zoom Out */
                    cmd.command = zoom_out;
                    cmd.data.zoom_out = 2.0;
                    break;
                case 4 : /* Center */
                    cmd.command = center;
                    cmd.data.pt.x = rect->x0;
                    cmd.data.pt.y = rect->y0;
                    deselect(g);
                    break;
                } break;

            case 2 : switch (itemnb) { /* Add */
                case 0 : /* Function */
                    deselect(g);
                    cmd.command = add_function;
                    break;
                case 1 : /* Label */
                    cmd.command = add_label;
                    cmd.data.pt.x = rect->x0;
                    cmd.data.pt.y = rect->y0;
                    deselect(g);
                    break;
                } break;

            case 3 : switch (itemnb) { /* Edit */
                case 0 : /* Variables */
                    cmd.command = edit_vars;
                    break;
                case 2 : /* Select function */
                    {   /* Processed locally */
                        struct object *o;

                        if (o = choose_object(g, "Select"))
                        {
                            deselect(g);
                            select_object(g, o);
                        }
                    }
                    break;
                case 3 : /* Deselect */
                    /* Processed locally */
                    deselect(g);
                    break;
                case 5 : /* Edit */
                    cmd.data.o = g->s.current;
                    cmd.command = edit;
                    break;
                case 6 : /* Improve */
                    cmd.data.f = g->s.current;
                    cmd.command = improve;
                    break;
                case 7 : /* Delete */
                    cmd.data.o = g->s.current;
                    cmd.command = del_object;
                    break;
                } break;
        }
        *choice = item->NextSelect;
        *choice = MENUNULL;
    }
    return cmd;
}

/* Return next command (in any graph) */
struct cmd next_command(void)
{
    struct cmd cmd;
    static int gone = FALSE;

    cmd.command = none;

    while (cmd.command == none) /* Wait for one */
    {
        struct graph *g;

        /* Scan all graphs, checking for commands */
        for (g = first(&graph_list); cmd.command == none && succ(g); g = succ(g
))
        {
            int moved = FALSE;
            struct IntuiMessage *msg;
            WORD sx, sy;

            cmd.g = g;
            /* Any pending menu selections ? */
            if (!req && g->io.nextmenu != MENUNULL) cmd = process(&g->io.nextme
nu, g);

            /* Check for messages on window */
            while (cmd.command == none && (msg = (struct IntuiMessage *)GetMsg(
g->io.win->UserPort)))
            {
                /* Save interesting info */
                ULONG class = msg->Class;
                UWORD code = msg->Code;
                UWORD qualifier = msg->Qualifier;
                struct Gadget *gg = (struct Gadget *)msg->IAddress, *gadg;

                sx = msg->MouseX; sy = msg->MouseY;
                ReplyMsg((struct Message *)msg);

                if (class == MOUSEMOVE) /* Accumulate moves */
                    moved = TRUE;
                else
                {
                    if (moved) mouse_move(g, sx, sy); /* process any mouse move
ment */
                    moved = FALSE;
                    /* Most messages imply that the mouse button is released. M
ake sure that the program thinks so */
                    if (class != MOUSEBUTTONS && class != REFRESHWINDOW) mouse_
up(g, sx, sy);

                    switch (class)
                    {
                        /* Note that most messages are ignored while a
                           requester is up in *any* window */

                        case RAWKEY:
                            /* Check for Amiga-V/B (Ok, Cancel shortcut) */
                            if (req && (qualifier & AMIGALEFT) && (code == KEYC
ODE_V || code == KEYCODE_B))
                            {
                                ok = code == KEYCODE_V;
                                /* Never let this happen while a string gadget
is active ... */
                                EndRequest(req, req->RWindow);
                            }
                            break;
                        case CLOSEWINDOW:
                            if (!req) cmd.command = close;
                            break;
                        case MENUPICK:
                            if (!req)
                            {
                                g->io.nextmenu = code;
                                cmd = process(&g->io.nextmenu, g);
                            }
                            break;
                        case REFRESHWINDOW:
                            {
                                int changed;

                                /* Refresh partially only if size hasn't change
d */
                                BeginRefresh(g->io.win);
                                changed = g->io.win->Width != g->io.oldwidth ||
 g->io.win->Height != g->io.oldheight;
                                if (!changed) draw_graph(g, FALSE); /* No messa
ges during refresh !!! */
                                EndRefresh(g->io.win, TRUE);
                                if (changed)
                                {
                                    set_scale(g);
                                    draw_graph(g, TRUE);
                                }
                                /* Wait for refresh after requester's disappear
ance
                                   before signaling it */
                                if (gone)
                                {
                                    gone = FALSE;
                                    cmd.command = reqgone;
                                }
                            }
                            break;
                        case REQSET: /* Activate first string gadget */
                            gadg = NextText(req->ReqGadget);
                            if (gadg && !reqdone) ActivateGadget(gadg, g->io.wi
n, req);
                            break;
                        case REQCLEAR:
                            /* Allow 1 refresh before signaling this. There is
                               probably a better way ! */
                            gone = TRUE;
                            break;
                        case GADGETUP: case GADGETDOWN:
                            /* Ack! Hack to avoid bug in string gadget activati
on (V1.2-1.3) */
                            if (class == GADGETUP && (gg->GadgetType & STRGADGE
T) != 0 && IntuitionBase->LibNode.lib_Version <= 34) IntuitionBase->ActiveGadget
 = NULL;

                            /* Handle requester hgadgets */
                            if (gadgethandler) ok = gadgethandler(gg, class, re
q, g);
                            else if (gg->GadgetID == ARROW || gg->GadgetID == C
ROSS)
                            {   /* Select new mode */
                                MutualExclude(gg, g->io.gadgets, g->io.win);
                                set_mode(g, gg->GadgetID == ARROW);
                            }
                            break;
                        case MOUSEBUTTONS: /* Pass on to graph */
                            if (code == SELECTDOWN)
                                mouse_down(g, sx, sy);
                            else
                                mouse_up(g, sx, sy);
                            break;
                    }
                }
            }
            /* Handle any pending moves */
            if (moved) mouse_move(g, sx, sy);
        }
        /* Wait for something to happen */
        if (cmd.command == none) Wait(window_sigs);
    }
    return cmd;
}


/* Enable/Disable various menus */

void disable_rect_menus(struct graph *g)
{
    struct Window *win = g->io.win;

    OffMenu(win, SHIFTMENU(1) | SHIFTITEM(2) | SHIFTSUB(NOSUB)); /* Zoom */
    OffMenu(win, SHIFTMENU(1) | SHIFTITEM(4) | SHIFTSUB(NOSUB)); /* Center */
    OffMenu(win, SHIFTMENU(2) | SHIFTITEM(1) | SHIFTSUB(NOSUB)); /* Label */
    OffMenu(win, SHIFTMENU(3) | SHIFTITEM(3) | SHIFTSUB(NOSUB)); /* Deselect */
     
}

void enable_rect_menus(struct graph *g)
{
    struct Window *win = g->io.win;

    OnMenu(win, SHIFTMENU(1) | SHIFTITEM(2) | SHIFTSUB(NOSUB)); /* Zoom */
    OnMenu(win, SHIFTMENU(1) | SHIFTITEM(4) | SHIFTSUB(NOSUB)); /* Center */
    OnMenu(win, SHIFTMENU(2) | SHIFTITEM(1) | SHIFTSUB(NOSUB)); /* Label */
    OnMenu(win, SHIFTMENU(3) | SHIFTITEM(3) | SHIFTSUB(NOSUB)); /* Deselect */
}

void disable_object_menus(struct graph *g)
{
    struct Window *win = g->io.win;

    OffMenu(win, SHIFTMENU(3) | SHIFTITEM(3) | SHIFTSUB(NOSUB)); /* Deselect */
     
    OffMenu(win, SHIFTMENU(3) | SHIFTITEM(5) | SHIFTSUB(NOSUB)); /* Edit */
    OffMenu(win, SHIFTMENU(3) | SHIFTITEM(6) | SHIFTSUB(NOSUB)); /* Improve */
    OffMenu(win, SHIFTMENU(3) | SHIFTITEM(7) | SHIFTSUB(NOSUB)); /* Delete */
}

void enable_object_menus(struct graph *g)
{
    struct Window *win = g->io.win;

    OnMenu(win, SHIFTMENU(3) | SHIFTITEM(3) | SHIFTSUB(NOSUB)); /* Deselect */
    OnMenu(win, SHIFTMENU(3) | SHIFTITEM(5) | SHIFTSUB(NOSUB)); /* Edit */
    OnMenu(win, SHIFTMENU(3) | SHIFTITEM(6) | SHIFTSUB(NOSUB)); /* Improve */
    OnMenu(win, SHIFTMENU(3) | SHIFTITEM(7) | SHIFTSUB(NOSUB)); /* Delete */
}

/* Convert double to nice string (must be of size NBLEN) */
char *double2str(char *to, double x)
{
    int l;

    if (x == NOVAL) to[0] = '\0';
    else sprintf(to, "%-5.3g", x);

    l = strlen(to);
    while (l > 0 && to[--l] == ' ') to[l] = '\0';

    return to;
}


/* Convert string to double */
double str2double(char *from)
{
    double x;

    if (sscanf(from, "%lf", &x) != 1) x = NOVAL;

    return x;
}

/* convert x to a string (must be of size INTLEN) */
char *int2str(char *to, int x)
{
    if (x == INOVAL) to[0] = '\0';
    else sprintf(to, "%d", x);

    return to;
}

/* convert string to integer */
int str2int(char *from)
{
    int x;

    if (sscanf(from, "%d", &x) != 1) x = INOVAL;

    return x;
}

/* Removes leading & trailing blanks fron name. Returns name */
char *strip(char *name)
{
    char *scan = name;

    while (isspace(*scan)) scan++;

    if (*scan)
    {
        char *copy = name;

        while (*scan) *(copy++) = *(scan++);

        while (isspace(*--copy))
            ;
        *(copy + 1) = '\0';
    }
    else
        *name = '\0';

    return name;
}

/* Hack to allow you to press Amiga-V/B even while entering a string in a
   string requester */
static long __saveds __asm handle_ok_cancel(register __a0 struct InputEvent *ev
, register __a1 void *dummy)
{
    struct InputEvent *ep, *laste;
    static struct InputEvent retkey;

    if (req)
        /* run down the list of events to see if they pressed the magic key */
        for (ep = ev, laste = NULL; ep != NULL; ep = ep->ie_NextEvent)
        {
            if (ep->ie_Class == IECLASS_RAWKEY && (ep->ie_Qualifier & AMIGALEFT
) && (ep->ie_Code == KEYCODE_V || ep->ie_Code == KEYCODE_B) && IntuitionBase->Ac
tiveWindow == req->RWindow)
            {
                reqdone = TRUE; /* The requester is going away */
                /* Add an extra "return key" event */
                retkey.ie_Class = IECLASS_RAWKEY;
                retkey.ie_SubClass = 0;
                retkey.ie_Code = 0x44; /* return key */
                retkey.ie_Qualifier = 0;
                retkey.ie_position = ep->ie_position;
                retkey.ie_TimeStamp = ep->ie_TimeStamp;
                retkey.ie_NextEvent = ep;
                /* we can handle this event so take it off the chain */
                if (laste == NULL)
                    ev = &retkey;
                else
                    laste->ie_NextEvent = &retkey;
                break;
            }
            else
                laste = ep;
        }

   /* pass on the pointer to the event */
   return (long)ev;
}

/* Create window, menus for a new graph */
int init_uio(struct graph *g)
{
    /* Create mode select gadgets */
    struct Gadget *a = AllocMem(sizeof(struct Gadget), 0L);
    struct Gadget *c = AllocMem(sizeof(struct Gadget), 0L);

    ModSys(0, 1, JAM2, &font);
    if (a && c)
    {
        struct Menu *ml = NULL, *project, *edit, *add, *graph;
        struct MenuItem *print;

        *a = arrow;
        *c = cross;
        a->NextGadget = c;
        g->io.gadgets = graph_win.FirstGadget = a;
        g->io.mem = NULL;

        /* Create window & menus */
        if (g->io.win = OpenWindow(&graph_win))
            if ((g->io.mem = NewMemory()) &&
                (project = AddMenu(&ml, NULL, "Project", MENUENABLED, g->io.mem
)) &&
                    AddItem(project, "New Graph", ITEMENABLED | HIGHCOMP, 0, 'N
', FALSE, g->io.mem) &&
                    AddItem(project, "Delete Graph", ITEMENABLED | HIGHCOMP, 0,
 0, FALSE, g->io.mem) &&
                    AddItem(project, "Load Graph...", ITEMENABLED | HIGHCOMP, 0
, 'O', FALSE, g->io.mem) &&
                    AddItem(project, "Save Graph...", ITEMENABLED | HIGHCOMP, 0
, 'S', FALSE, g->io.mem) &&
                    (print = AddItem(project, "Output Graph", ITEMENABLED | HIG
HCOMP, 0, 0, TRUE, g->io.mem)) &&
                        AddSub(print, "To Printer", ITEMENABLED | HIGHCOMP, 0,
'P', g->io.mem) &&
                        AddSub(print, "To Disk...", ITEMENABLED | HIGHCOMP, 0,
0, g->io.mem) &&
                    AddRule(project, g->io.mem) &&
                    AddItem(project, "Load Variables...", ITEMENABLED | HIGHCOM
P, 0, 0, FALSE, g->io.mem) &&
                    AddItem(project, "Save Variables...", ITEMENABLED | HIGHCOM
P, 0, 0, FALSE, g->io.mem) &&
                    AddRule(project, g->io.mem) &&
                    AddItem(project, "Quit", ITEMENABLED | HIGHCOMP, 0, 'Q', FA
LSE, g->io.mem) &&
                (graph = AddMenu(&ml, NULL, "Graph", MENUENABLED, g->io.mem)) &
&
                    AddItem(graph, "Scale...", ITEMENABLED | HIGHCOMP, 0, 'L',
FALSE, g->io.mem) &&
                    AddItem(graph, "Axes...", ITEMENABLED | HIGHCOMP, 0, 'X', F
ALSE, g->io.mem) &&
                    AddItem(graph, "Zoom", ITEMENABLED | HIGHCOMP, 0, 'Z', FALS
E, g->io.mem) &&
                    AddItem(graph, "Zoom out", ITEMENABLED | HIGHCOMP, 0, 0, FA
LSE, g->io.mem) &&
                    AddItem(graph, "Centre", ITEMENABLED | HIGHCOMP, 0, 'C', FA
LSE, g->io.mem) &&
                (add = AddMenu(&ml, NULL, "Add", MENUENABLED, g->io.mem)) &&
                    AddItem(add, "Function...", ITEMENABLED | HIGHCOMP, 0, 'F',
 FALSE, g->io.mem) &&
                    AddItem(add, "Label...", ITEMENABLED | HIGHCOMP, 0, 0, FALS
E, g->io.mem) &&
                (edit = AddMenu(&ml, NULL, "Edit", MENUENABLED, g->io.mem)) &&
                    AddItem(edit, "Variables...", ITEMENABLED | HIGHCOMP, 0, 'V
', FALSE, g->io.mem) &&
                    AddRule(edit, g->io.mem) &&
                    AddItem(edit, "Select function...", ITEMENABLED | HIGHCOMP,
 0, 0, FALSE, g->io.mem) &&
                    AddItem(edit, "Deselect", ITEMENABLED | HIGHCOMP, 0, 0, FAL
SE, g->io.mem) &&
                    AddRule(edit, g->io.mem) &&
                    AddItem(edit, "Edit...", ITEMENABLED | HIGHCOMP, 0, 'E', FA
LSE, g->io.mem) &&
                    AddItem(edit, "Improve", ITEMENABLED | HIGHCOMP, 0, 'I', FA
LSE, g->io.mem) &&
                    AddItem(edit, "Delete", ITEMENABLED | HIGHCOMP, 0, 0, FALSE
, g->io.mem))
            {
                SetMenuStrip(g->io.win, ml);
                g->io.menu = ml;
                disable_rect_menus(g);
                disable_object_menus(g);
                /* Add signal bit */
                window_sigs |= 1 << g->io.win->UserPort->mp_SigBit;

                return TRUE; /* all done ok */
            }
            else
            {
                Free(g->io.mem);
                CloseWindow(g->io.win);
                alert(NULL, "No memory !", NULL);
            }
        else alert(NULL, "Couldn't open window", NULL);
    }
    else
        alert(NULL, "No memory !", NULL);

    if (a) FreeMem(a, sizeof(struct Gadget));
    if (c) FreeMem(c, sizeof(struct Gadget));

    return FALSE;
}

/* Close window, etc */
void cleanup_uio(struct graph *g)
{
    struct Gadget *gg, *next;

    ClearMenuStrip(g->io.win);
    CloseWindow(g->io.win);
    Free(g->io.mem);
    for (gg = g->io.gadgets; gg; gg = next)
    {
        next = gg->NextGadget;
        FreeMem(gg, sizeof(struct Gadget));
    }
    /* Construct new signal list */
    window_sigs = 0;
    for (g = first(&graph_list); succ(g); g = succ(g))
        window_sigs |= 1 << g->io.win->UserPort->mp_SigBit;
}

/* Global initialisation */
int init_user()
{
    if (!make_font_list()) return FALSE;
    /* Open alert font */
    alert_font = OpenFont(&alert_attr);

    /* Install input handler */
    me = FindTask(0);
    inputDevPort = CreatePort(0,0);
    if (inputDevPort)
    {
        if (inputRequestBlock = CreateStdIO(inputDevPort))
        {

            handlerStuff.is_Data = NULL;
            handlerStuff.is_Code = (void *)handle_ok_cancel;
            handlerStuff.is_Node.ln_Pri = 51;

            if (OpenDevice("input.device", 0, inputRequestBlock, 0) == 0)
            {
                inputOpen = TRUE;
                inputRequestBlock->io_Command = IND_ADDHANDLER;
                inputRequestBlock->io_Data    = (APTR)&handlerStuff;

                DoIO(inputRequestBlock);
                return TRUE;
            }
        }
    }
    alert(NULL, "Couldn't install input handler", NULL);
    return FALSE;
}

/* Global cleanup */
void cleanup_user()
{
    if (inputOpen)
    {
        inputRequestBlock->io_Command = IND_REMHANDLER;
        inputRequestBlock->io_Data    = (APTR)&handlerStuff;
        DoIO(inputRequestBlock);
        CloseDevice(inputRequestBlock);
    }
    if (inputRequestBlock) DeleteStdIO(inputRequestBlock);
    if (inputDevPort) DeletePort(inputDevPort);
    free_list((list *)&flist, sizeof(struct fnode));
}

