/* mod_idcmp.c
 * Copyright (C) 1990 Commodore-Amiga, Inc.
 * written by David N. Junod
 *
 * IDCMP gadget/menu/keyboard message handling routines
 *
 */

#include "mod.h"

/* normal low-overhead IDCMP messages */
#define	IDCMP_flagF (CLOSEWINDOW | RAWKEY | MOUSEBUTTONS | GADGETDOWN \
		     | GADGETUP | MENUPICK)

/* IDCMP messages used when a hold or drag gadget is active */
#define	IDCMP_flagS (RAWKEY | MOUSEMOVE | MOUSEBUTTONS | INTUITICKS \
		     | GADGETUP)

/* variables required for IDCMP message handling */
struct IDCMPInfo
{
    struct MsgPort *msgport;	/* Message port */
    struct NewWindow *nw;	/* NewWindow pointer */
    struct Menu *menu;		/* Menu strip pointer */
    WORD *KeyFunctions;		/* Keyboard function map */
    struct Window *win;		/* Window */
    struct EGadget *ActiveGad;	/* Currrently active gadget */
    struct IOStdReq ioreq;	/* Rawkey conversion IO request */
};

/* IDCMP message handler function prototypes */
BOOL open_idcmp (struct AppInfo *, struct MsgHandler *);
BOOL handle_idcmp (struct AppInfo *, struct MsgHandler *);
BOOL close_idcmp (struct AppInfo *, struct MsgHandler *);
BOOL shutdown_idcmp (struct AppInfo *, struct MsgHandler *);
BOOL setup_key_array (struct IDCMPInfo * mh, struct KeyboardCMD * KeyArray);
VOID shutdown_key_array (struct IDCMPInfo *);
LONG DeadKeyConvert(struct IntuiMessage * msg, UBYTE * kbuffer, LONG kbsize, struct KeyMap * kmap);
VOID CloseWindowSafely (struct Window * win);
VOID HandleKeyEvent(struct AppInfo *,struct MsgHandler *,struct IntuiMessage * msg);
VOID HandleGadgEvent(struct AppInfo *,struct MsgHandler *,struct IntuiMessage * msg);
VOID HandleMenuEvent(struct AppInfo *,struct MsgHandler *,struct IntuiMessage * msg);

/* Required for DeadKeyConvert processing */
struct ConsoleDevice *ConsoleDevice = NULL;

/* setup the resources required for IDCMP processing */
struct MsgHandler *setup_idcmp (struct AppInfo * ai,
				struct NewWindow * nw,
				struct KeyboardCMD * kba,
				struct Menu * menu, BOOL immed)
{
    struct MsgHandler *mh = NULL;
    struct IDCMPInfo *md = NULL;

    if (md = (struct IDCMPInfo *)
	AllocMem (sizeof (struct IDCMPInfo), MEMF_CLEAR|MEMF_PUBLIC))
    {
	md->nw = nw;
	md->menu = menu;
	if (setup_key_array (md, kba))
	{
	    if (md->msgport = CreatePort (NULL, 0))
	    {
		if (mh = (struct MsgHandler *)
		    AllocMem (sizeof (struct MsgHandler), MEMF_CLEAR|MEMF_PUBLIC))
		{
		    mh->mh_Node.ln_Type = MH_HANDLER_T;
		    mh->mh_Node.ln_Pri  = MH_HANDLER_P;
		    mh->mh_Node.ln_Name = "IDCMP";
		    mh->mh_SigBits = (1L << md->msgport->mp_SigBit);
		    mh->mh_Func[MH_OPEN]     = open_idcmp;
		    mh->mh_Func[MH_HANDLE]   = handle_idcmp;
		    mh->mh_Func[MH_CLOSE]    = close_idcmp;
		    mh->mh_Func[MH_SHUTDOWN] = shutdown_idcmp;
		    mh->mh_Data = md;

		    if (immed)
		    {
			if (open_idcmp (ai, mh))
			    return (mh);
		    }
		    else
			return (mh);

		    FreeMem (mh, sizeof (struct MsgHandler));
		}
		else
		    NotifyUser (NULL, "Not enough memory");
	    }
	    else
		NotifyUser (NULL, "Could not create IDCMP port");

	}
	FreeMem (md, sizeof (struct IDCMPInfo));
    }

    return (mh);
}

/* activate IDCMP handler */
BOOL open_idcmp (struct AppInfo * ai, struct MsgHandler * mh)
{
    struct IDCMPInfo *md = mh->mh_Data;

    if (!(md->win))
    {
	/* Open an Intuition window */
	md->nw->IDCMPFlags = NULL;
	if (md->win = OpenWindow (md->nw))
	{
	    /* Attach the menu strip. You should pass it thru the AdjustMenu
	     * function that is shown in the 1.3 RKM Libraries & Devices menu
	     * example. */
	    SetMenuStrip (md->win, md->menu);

	    /* set up the message port information */
	    md->win->UserPort = md->msgport;
	    ModifyIDCMP (md->win, IDCMP_flagF);

	    return (TRUE);
	}
	else
	    NotifyUser (NULL, "Could not open window");
    }
    return (FALSE);
}

/* Intuition message processing */
BOOL handle_idcmp (struct AppInfo * ai, struct MsgHandler * mh)
{
    struct IDCMPInfo *md = mh->mh_Data;
    struct IntuiMessage *imsg;	/* incoming Intuition messages */
    struct IntuiMessage *fmsg, *tmsg; /* for filtering messages */
    struct Window * win; /* only used for filtering messages */
    struct IntuiMessage nmsg;	/* copy of the message */

    while (imsg = (struct IntuiMessage *) GetMsg (md->msgport))
    {
	/* Filter out excessive mouse-moves (oldest first) */
	if (imsg->Class == MOUSEMOVE)
	{
	    win = imsg->IDCMPWindow;
	    Forbid ();
	    fmsg = (struct IntuiMessage *)
	      (md->msgport->mp_MsgList.lh_Head);
	    while (tmsg = (struct IntuiMessage *)
		   (fmsg->ExecMessage.mn_Node.ln_Succ))
	    {
		if (fmsg->IDCMPWindow == win &&
		    fmsg->Class == MOUSEMOVE)
		{
		    Remove ((struct Node *) fmsg);
		    ReplyMsg ((struct Message *) imsg);
		    imsg = fmsg;
		}
		fmsg = tmsg;
	    }
	    Permit ();
	}
	/* Filter out excessive key repeats here. Amiga Mail article by
	 * Michael Sinz IV-37 Paced Repeat Key */

	/* copy the message so that we can reply to it */
	CopyMem ( (APTR)imsg, (APTR)&nmsg, sizeof (struct IntuiMessage) );

	/* Reply to the message now that we're done with it */
	ReplyMsg ((struct Message *) imsg);

	/* Process Intuition events */
	switch (nmsg.Class)
	{
	    /* Handle the close window gadget */
	    case CLOSEWINDOW:
		if (nmsg.Qualifier & SHIFTED)
		    /* hide Intuition interface */
		    close_idcmp (ai, mh);
		else
		    /* close down the application */
		    QuitFunc (ai, (struct Message *) &nmsg, NULL);
		break;

	    /* Handle keyboard events */
	    case RAWKEY:
		HandleKeyEvent (ai, mh, &nmsg);
		break;

	    /* Handle events that pertain to gadgets */
	    case INTUITICKS:
	    case MOUSEMOVE:
	    case MOUSEBUTTONS:
	    case GADGETDOWN:
	    case GADGETUP:
		HandleGadgEvent (ai, mh, &nmsg);
		break;

	    /* Handle menu events */
	    case MENUPICK:
		HandleMenuEvent (ai, mh, &nmsg);
		break;
	}
    }
    return (TRUE);
}

/* hide IDCMP message handler */
BOOL close_idcmp (struct AppInfo * ai, struct MsgHandler * mh)
{
    struct IDCMPInfo *md = mh->mh_Data;

    if (mh)
    {
	if (md->win)
	{
	    /* save current settings */
	    md->nw->Width = md->win->Width;
	    md->nw->Height = md->win->Height;
	    md->nw->LeftEdge = md->win->LeftEdge;
	    md->nw->TopEdge = md->win->TopEdge;

	    /* clear the menu strip if there is one */
	    if (md->win->MenuStrip)
		ClearMenuStrip (md->win);

	    /* close the window */
	    CloseWindowSafely (md->win);
	    md->win = NULL;
	}
    }
    return (TRUE);
}

/* free resources for IDCMP message handler */
BOOL shutdown_idcmp (struct AppInfo * ai, struct MsgHandler * mh)
{
    struct IDCMPInfo *md = mh->mh_Data;

    if (mh)
    {
	close_idcmp (ai, mh);		/* close window */
	shutdown_key_array (md);	/* free keyboard array */

	DeletePort (md->msgport);	/* delete IDCMP port */
	md->msgport = NULL;
    }
    return (TRUE);
}

/* safely close a window that shares a message port with another window */
VOID CloseWindowSafely (struct Window * win)
{
    struct IntuiMessage * msg, * succ;

    Forbid ();

    msg = (struct IntuiMessage *)win->UserPort->mp_MsgList.lh_Head;
    while (succ = (struct IntuiMessage *)msg->ExecMessage.mn_Node.ln_Succ)
    {
	if (msg->IDCMPWindow == win)
	{
	    Remove ((struct Node *)msg);
	    ReplyMsg ((struct Message *)msg);
	}
	msg = succ;
    }
    win->UserPort = NULL;
    ModifyIDCMP (win, NULL);

    Permit ();
    CloseWindow (win);
}

/* Allocate resources for handling keyboard input */
BOOL setup_key_array (struct IDCMPInfo * md, struct KeyboardCMD * KeyArray)
{
    register WORD cntr;

    /* Allow for NULL specification of a keyboard command array */
    if (!KeyArray)
	return (TRUE);

    /* Prepare for DeadKeyConvert */
    if (!(OpenDevice ("console.device", -1L,
		      (struct IORequest *) & (md->ioreq), 0L)))
    {
	ConsoleDevice = (struct ConsoleDevice *) md->ioreq.io_Device;

	/* Allocate memory for the keyboard function map */
	if (md->KeyFunctions =
	    AllocMem ((sizeof (WORD) * MAXKEYS), MEMF_CLEAR|MEMF_PUBLIC))
	{
	    /* read in the key assignments */
	    for (cntr = 0; KeyArray[cntr].key != NULL; cntr++)
		md->KeyFunctions[KeyArray[cntr].key] = KeyArray[cntr].funcID;
	    return (TRUE);
	}
	else
	    NotifyUser (NULL, "Not enough memory");

	CloseDevice ((struct IORequest *) & (md->ioreq));
    }
    else
	NotifyUser (NULL, "Could not open console.device");

    return (FALSE);
}

/* Release resources for handling keyboard input */
VOID shutdown_key_array (struct IDCMPInfo * md)
{
    if (md)
    {
	if (md->KeyFunctions)
	    FreeMem (md->KeyFunctions, (sizeof (WORD) * MAXKEYS));
	md->KeyFunctions = NULL;

	if (ConsoleDevice)
	    CloseDevice ((struct IORequest *) & (md->ioreq));
    }
}

/* Keyboard handling routines */
VOID HandleKeyEvent (struct AppInfo * ai, struct MsgHandler * mh,
		     struct IntuiMessage * msg)
{
    struct IDCMPInfo *md = mh->mh_Data;
    WORD FuncID = NO_FUNCTION;
    WORD key, cur;
    UBYTE buffer[17];

    strcpy (buffer, "               ");
    key = (WORD) DeadKeyConvert (msg, buffer, 15L, 0L);
    if (key > 0)
    {
	/* Get the ASCII value of the key that was pressed */
	cur = buffer[0];

	/* Check to see if it was a special key; like a Function key,
	 * Help, or the Arrow keys.  Could also do additional checking for
	 * ALT, AMIGA and CTRL qualifiers by adding in a proportional
	 *weight for each one. */
	if (key > 1 && cur == 155)
	    cur = SPECIAL + buffer[1];

	/* Get the function number assigned to this key */
	FuncID = md->KeyFunctions[cur];

	if (FuncID != NO_FUNCTION)
	    /* Perform the function assigned to this key. */
	    PerfFunc (ai, (struct Message *)msg, ai->FuncTable[FuncID].name);
    }
}

/* Convert Raw keys to Vanilla keys */
LONG DeadKeyConvert (msg, kbuffer, kbsize, kmap)
    struct IntuiMessage *msg;
    UBYTE *kbuffer;
    LONG kbsize;
    struct KeyMap *kmap;
{
    static struct InputEvent ievent =
    {NULL, IECLASS_RAWKEY, 0, 0, 0};

    if (msg->Class != RAWKEY)
	return (-2);
    ievent.ie_Code = msg->Code;
    ievent.ie_Qualifier = msg->Qualifier;
    ievent.ie_position.ie_addr = *((APTR *) msg->IAddress);

    return (RawKeyConvert (&ievent, kbuffer, kbsize, kmap));
}

/* Gadget handling routine */
VOID HandleGadgEvent (struct AppInfo * ai, struct MsgHandler * mh,
		      struct IntuiMessage * msg)
{
    struct IDCMPInfo *md = mh->mh_Data;
    struct EGadget *egad = (struct EGadget *) msg->IAddress;
    WORD FuncID = NO_FUNCTION;
    static ULONG glsecs = 0L, glmics = 0L;	/* For Gadget DoubleClick */

    switch (msg->Class)
    {
	/* Check to see if gadget is being held down and if there is a
	 * function to perform while it is being held. */
	case MOUSEMOVE:
	case INTUITICKS:
	    if (md->ActiveGad &&
		md->ActiveGad->eg_Gadget.Flags & SELECTED)
	    {
		/* gadget being held or dragged */
		FuncID = md->ActiveGad->eg_Funcs[EG_HOLD];
	    }

	    if (msg->Qualifier & IEQUALIFIER_RBUTTON)
	    {
		/* Right mouse button pressed, abort operation */
		FuncID = md->ActiveGad->eg_Funcs[EG_ABORT];

		/* clear the active gadget */
		md->ActiveGad = NULL;

		/* only watch for necessary messages */
		ModifyIDCMP (md->win, IDCMP_flagF);
	    }
	    break;

	/* Indicate that there is no active gadget now. */
	case MOUSEBUTTONS:
	    if (msg->Code == SELECTUP)
	    {
		/* clear the active gadget */
		md->ActiveGad = NULL;

		/* only watch for necessary messages */
		ModifyIDCMP (md->win, IDCMP_flagF);
	    }
	    break;

	/* Check to see if there is a function to perform on downpress or
	 * double-click of the gadget. */
	case GADGETDOWN:

	    /* Set the active gadget.  Only one gadget can EVER be active
	     * at a time (Intuition rule). */
	    md->ActiveGad = egad;

	    /* tell the system that we need to watch for INTUITICKS
	     * and MOUSEMOVES if there is a hold function for this gadget */
	    if (egad->eg_Funcs[EG_HOLD] != NO_FUNCTION)
		ModifyIDCMP (md->win, IDCMP_flagS);

	    FuncID = egad->eg_Funcs[EG_DOWNPRESS];
	    if (egad->eg_Funcs[EG_DBLCLICK] != NO_FUNCTION)
	    {
		/* If the gadget has a double-click function, then check to
		 * see if it has been double-clicked.  Notice that if there
		 * is a double-click function, then it over-rules the
		 *downpress function. */
		if (DoubleClick (glsecs, glmics, msg->Seconds, msg->Micros))
		    FuncID = egad->eg_Funcs[EG_DBLCLICK];
		else
		{
		    glsecs = msg->Seconds;
		    glmics = msg->Micros;
		}
	    }
	    break;

	/* Check to see if there is a function to perform on release of the
	 * gadget. */
	case GADGETUP:
	    /* Clear the active gadget variable */
	    md->ActiveGad = NULL;

	    /* only watch for necessary messages */
	    ModifyIDCMP (md->win, IDCMP_flagF);

	    FuncID = egad->eg_Funcs[EG_RELEASE];
	    break;

    }

    /* Perform the function if there is one. */
    if (FuncID != NO_FUNCTION)
	PerfFunc (ai, (struct Message *) msg, ai->FuncTable[FuncID].name);
}

/* Menu handling routine */
VOID HandleMenuEvent (struct AppInfo * ai, struct MsgHandler * mh,
		      struct IntuiMessage * msg)
{
    struct EMenuItem *item;
    UWORD selection = msg->Code;

    /* Shut down menu events while we're processing these. Any function that
     * opens its own window and ignores events from the main window, should
     * also set a busy pointer in the main window. */
    msg->IDCMPWindow->Flags |= RMBTRAP;

    /* Process all menu events */
    while ((selection != MENUNULL) && (!ai->Done))
    {
	/* Get the Extended MenuItem structure address */
	item = (struct EMenuItem *)
	  ItemAddress (msg->IDCMPWindow->MenuStrip, (LONG) selection);

	/* Mutual Excluded items should all call the same function.  And that
	 * function should do any processing based on the currently selected
	 * item. */
	PerfFunc (ai, (struct Message *)msg,
		  ai->FuncTable[item->emi_MenuID].name);

	/* Get the next selection */
	selection = item->emi_Item.NextSelect;
    }

    /* Turn menu events back on. */
    msg->IDCMPWindow->Flags &= ~RMBTRAP;
}
