/*
** AltMenu - the keyboard menu replacement doohicky
**
** Version 1.0		10/14/89
** Authors: E. A. Hutchins and L. J. Rittle
**
** Credits: Many basic input event handling routines and ideas blatantly
**	    lifted from:
**
** NewPOP which is based upon POPCLI,
**
** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
** |_o_o|\\ Copyright (c) 1986 The Software Distillery.  All Rights Reserved *
** |. o.| || This program may not be distributed without the permission of   *
** | .  | || the authors.                                                    *
** | o  | ||    Dave Baker     Ed Burnette  Stan Chow    Jay Denebeim        *
** |  . |//     Gordon Keener  Jack Rouse   John Toebes  Doug Walker         *
** ======          BBS:(919)-471-6436      VOICE:(919)-469-4210              * 
** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
**
** which is VERY loosely based on the input.device example by Rob Peck, 12/1/85
**
** NewPOP additions and modifications Copyright © 1989 Loren J. Rittle. 
**
** As stated in the original POPCLI documentation file about POPCLI:
**
** Permission is hereby granted to distribute this program provided 
** both the documentation accompanies the executable and that no 
** charge is made for its distribution.
**
**
**
** History:
**	EAH&LJR	10/14	While chatting about Amiga problems, Ed points out that
**			it would sure be nice if one could navigate through
**			menus with out picking up the mouse. I finally see the
**			light and agree, thus AltMenu is born!
**	EAH&LJR 10/14	Started version 1.0 with the intent of getting it done
**			in one flurry of Amiga hacking.
**	EAH	10/14	Finished version 1.0! 
**			(Loren sleeping at the great momment)
**	LJR	10/14	Changed the priority to run us at from -20 to 19 in
**			order to clean up the order in which output is displayed
**			on the screen. (No more prompt then our output!)
**	LJR	10/14	Added nice HELLO and START messages.
**	LJR	10/14	Fixed problem with DMouse by just making sure that
**			DMouse's input handler has a higher priority than
**			ours.
**	EAH	10/21	Added direct key jumps.
*/

/*
** Includes
*/

#include <exec/types.h>
#include <exec/exec.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/console.h>
#include <string.h>

/*
** Defines
*/

#define ALTMENU_PRIORITY 55

#define PORTNAME     	"AltMenu.port"
#define HELLOMSG	"\x9B" "0;33mAltMenu\x9B" "0m by E. A. Hutchins and L. J. Rittle\n" \
		     	"Version 1.0  Copyright \xA9 1989  All Rights Reserved.\n"
#define STARTMSG	"AltMenu is starting.\n"
#define KILLMSG		"AltMenu is leaving.\n"

#define LAST_KEY	(0x4a)

#define UP(x)		((x) | 0x80)
#define RAW_ESC		(0x45)
#define RAW_RETURN	(0x44)
#define RAW_SPACE	(0x40)
#define RAW_UP		(0x4c)
#define RAW_DOWN	(0x4d)
#define RAW_RIGHT	(0x4e)
#define RAW_LEFT	(0x4f)
#define RAW_LALT	(0x64)
#define RAW_RALT	(0x65)
#define EXTQUAL		(0x70cf)

#define SCRN		(gptr->Scrn)
#define ROOT		(gptr->Root)
#define MENU		(gptr->Menu)
#define menu		(gptr->_menu)
#define MID		(gptr->MidItem)
#define SUB		(gptr->SubItem)
#define item		(gptr->_item)
#define mtmp		((struct Menu *)(gptr->_mtmp))
#define itmp		((struct MenuItem *)(gptr->_mtmp))
#define WHERE		(gptr->where)
#define MENUX		(gptr->MenuX)
#define MENUY		(gptr->MenuY)
#define EVT		(gptr->Evt)
#define GETKEY(x)	((gptr->Key)=((gptr->RawKeyMap)[(UBYTE)(x)]))
#define KEY		(gptr->Key)
#define NAME		(gptr->Name)
#define UPPER(x)	(((x) >= 'a' && (x) <= '~')?((x)-'a'+'A'):(x))
#define MX		(ep->ie_X)
#define MY		(ep->ie_Y)
#define GLOBALMOUSE	((gptr->MouseX=MX),(gptr->MouseY=MY),(MX=((MX*gptr->XMul)-(gptr->IBase->MouseX))),(MY=((MY*gptr->YMul)-(gptr->IBase->MouseY))))
#define ITEXT_OF(x)	(((struct IntuiText *)((x)->ItemFill))->IText)

/*
** Type definitions
*/

typedef enum { Idle, AltDown, InMenu } States;
typedef enum { Top, Mid, Sub } MenuPos;

typedef struct 
{
	struct Task		*buddy;
	struct IntuitionBase	*IBase;
	States			state;
	WORD			OldMouseX, OldMouseY;
	WORD			MouseX, MouseY;
	struct Screen		*Scrn;
	int			XMul, YMul;
	MenuPos			where;
	struct Menu		*Root, *Menu, *_menu;
	struct MenuItem		*MidItem, *SubItem, *_item;
	void			*_mtmp;
	int			MenuX, MenuY;
	struct InputEvent	Evt;
	BYTE			*Name;
	BYTE			Key;
	BYTE			RawKeyMap[LAST_KEY];
} GLOBAL_DATA;

/*
** Global variables
*/

struct IntuitionBase	*IntuitionBase = NULL;
struct Library		*ConsoleDevice = NULL;

extern BPTR _Backstdout;          /* standard output when run in background */
long  _BackGroundIO = 1;          /* Flag to tell it we want to do I/O      */
long  _stack = 4000;              /* Amount of stack space our task needs   */
char *_procname = "AltMenu";      /* The name of the task to create         */
long  _priority = 19;             /* The priority to run us at              */

/*
** Prototypes
*/

struct MsgPort 		*FindPort();
struct MsgPort 		*CreatePort();
void 			DeletePort();
struct IOStdReq		*CreateIOReq(struct MsgPort *, int);
void 			DeleteIOReq(struct IOStdReq *);

/*
** myhandler - our awesome input event handler
*/

__regargs struct InputEvent *myhandler( struct InputEvent *ev, register GLOBAL_DATA *gptr )
{
	register struct InputEvent *ep;

   	for (ep = ev; ep != NULL; ep = ep->ie_NextEvent)
   	{
		if (ep->ie_Class == IECLASS_TIMER ||
		    ep->ie_Class == IECLASS_NULL)
			continue;

		if (gptr->state == InMenu)
		{
			if (ep->ie_Class == IECLASS_RAWKEY)
			{
				switch (ep->ie_Code)
				{
				case UP(RAW_ESC):
				case RAW_LALT:
				case RAW_RALT:
					gptr->MouseX = SCRN->LeftEdge;
					gptr->MouseY = SCRN->TopEdge;
					/* fall through */
				case UP(RAW_RETURN):
					MX = gptr->MouseX;
					MY = gptr->MouseY;
					ep->ie_Class = IECLASS_RAWMOUSE;
					ep->ie_Code = UP(IECODE_RBUTTON);
					ep->ie_Qualifier = 0x8000; /* relmouse */
					GLOBALMOUSE;
					gptr->state = Idle;
					continue;
				case RAW_SPACE:
					MX = gptr->MouseX;
					MY = gptr->MouseY;
					ep->ie_Class = IECLASS_RAWMOUSE;
					ep->ie_Code = IECODE_LBUTTON;
					ep->ie_Qualifier = 0xc000; /* relmouse | lbutton */
					GLOBALMOUSE;
					continue;
				case UP(RAW_SPACE):
					MX = gptr->MouseX;
					MY = gptr->MouseY;
					ep->ie_Class = IECLASS_RAWMOUSE;
					ep->ie_Code = UP(IECODE_LBUTTON);
					ep->ie_Qualifier = 0x8000; /* relmouse */
					GLOBALMOUSE;
					continue;
				case RAW_UP:
					switch (WHERE)
					{
					case Top:
						if ((MID = MENU->FirstItem) == NULL) goto EatMe;
						WHERE = Mid;
						while (MID->NextItem != NULL)
							MID = MID->NextItem;
						break;
					case Mid:
						if ((item = MENU->FirstItem) == MID)
						{
							WHERE = Top;
							break;
						}
						while (item->NextItem != NULL && item->NextItem != MID)
							item = item->NextItem;
						MID = item;
						break;
					case Sub:
						if ((item = MID->SubItem) == SUB)
						{
							while (SUB->NextItem != NULL)
								SUB = SUB->NextItem;
							break;
						}
						while (item->NextItem != NULL && item->NextItem != SUB)
							item = item->NextItem;
						SUB = item;
						break;
					}
					break;
				case RAW_DOWN:
					switch (WHERE)
					{
					case Top:
						if ((MID = MENU->FirstItem) == NULL) goto EatMe;
						WHERE = Mid;
						break;
					case Mid:
						if (MID->NextItem != NULL)
							MID = MID->NextItem;
						else
							MID = MENU->FirstItem;
						break;
					case Sub:
						if (SUB->NextItem != NULL)
							SUB = SUB->NextItem;
						else
							SUB = MID->SubItem;
						break;
					}
					break;
				case RAW_LEFT:
					switch (WHERE)
					{
					case Mid:
						WHERE = Top;
						/* fall through */
					case Top:
						if (MENU == (menu = ROOT))
							while (MENU->NextMenu != NULL) MENU = MENU->NextMenu;
						else
						{
							while (menu->NextMenu != NULL && menu->NextMenu != MENU) menu = menu->NextMenu;
							MENU = menu;
						}
						break;
					case Sub:
						WHERE = Mid;
						break;
					}
					break;
				case RAW_RIGHT:
					switch (WHERE)
					{
					case Mid:
						if (MID->SubItem != NULL)
						{
							WHERE = Sub;
							SUB = MID->SubItem;
							break;
						}
						/* fall through */
					case Sub:
						WHERE = Top;
						/* fall through */
					case Top:
						if (MENU->NextMenu != NULL)
							MENU = MENU->NextMenu;
						else
							MENU = ROOT;
						break;
					}
					break;
				default:
					if (((UBYTE)(ep->ie_Code)) > LAST_KEY) goto EatMe;
					GETKEY( ep->ie_Code );
					if (KEY == 0) goto EatMe;
					/* find the menu name */
					switch (WHERE)
					{
					case Top:
						mtmp = NULL;
						menu = MENU;
						do {
							NAME = menu->MenuName;
							while (*NAME == ' ') ++NAME;
							if (KEY == UPPER( *NAME ))
							{
								if (mtmp != NULL)
								{
									if (mtmp == MENU) mtmp = menu;
									break;
								}
								mtmp = menu;
							}
							menu = menu->NextMenu;
							if (menu == NULL) menu = ROOT;
						} while (menu != MENU);
						/* no matches? */
						if (mtmp == NULL) goto EatMe;
						/* only one match? */
						if (menu == MENU)
						{
							MID = mtmp->FirstItem;
							if (MENU == mtmp && MID != NULL)
								WHERE = Mid;
						}
						MENU = mtmp;
						break;
					case Mid:
						itmp = NULL;
						item = MID;
						do {
							if (item->Flags & ITEMTEXT)
							{
								NAME = ITEXT_OF(item);
								while (*NAME == ' ') ++NAME;
								if (KEY == UPPER( *NAME ))
								{
									if (itmp != NULL)
									{
										if (itmp == MID) itmp = item;
										break;
									}
									itmp = item;
								}
							}
							item = item->NextItem;
							if (item == NULL) item = MENU->FirstItem;
						} while (item != MID);
						/* no matches? */
						if (itmp == NULL) goto EatMe;
						/* only one match? */
						if (item == MID)
						{
							SUB = itmp->SubItem;
							if (MID == itmp)
							{
								if (SUB != NULL)
									WHERE = Sub;
								else
								{
									EVT.ie_NextEvent = ep->ie_NextEvent;
									EVT.ie_Class = IECLASS_RAWKEY;
									EVT.ie_SubClass = 0;
									EVT.ie_Qualifier = 0;
									EVT.ie_Code = UP(RAW_RETURN);
									ep->ie_NextEvent = &EVT;
								}
							}
						}
						MID = itmp;
						break;
					case Sub:
						itmp = NULL;
						item = SUB;
						do {
							if (item->Flags & ITEMTEXT)
							{
								NAME = ITEXT_OF(item);
								while (*NAME == ' ') ++NAME;
								if (KEY == UPPER( *NAME ))
								{
									if (itmp != NULL)
									{
										if (itmp == SUB) itmp = item;
										break;
									}
									itmp = item;
								}
							}
							item = item->NextItem;
							if (item == NULL) item = MID->SubItem;
						} while (item != SUB);
						/* no matches? */
						if (itmp == NULL) goto EatMe;
						/* only one match? */
						if (item == SUB && SUB == itmp)
						{
							EVT.ie_NextEvent = ep->ie_NextEvent;
							EVT.ie_Class = IECLASS_RAWKEY;
							EVT.ie_SubClass = 0;
							EVT.ie_Qualifier = 0;
							EVT.ie_Code = UP(RAW_RETURN);
							ep->ie_NextEvent = &EVT;
						}
						SUB = itmp;
						break;
					}
					break;
				EatMe:
					ep->ie_Class = IECLASS_NULL;
					continue;
				}

				/* move the mouse cursor on top of the item */

				switch (WHERE)
				{
				case Top:
					MX = SCRN->LeftEdge + MENU->LeftEdge + MENU->Width - 2;
					MY = SCRN->TopEdge + MENU->Height/2;
					break;
				case Mid:
					MX = MENUX + MENU->LeftEdge + MID->LeftEdge + 2;
					MY = MENUY + MENU->Height + MID->TopEdge + MID->Height/2;
					break;
				case Sub:
					MX = MENUX + MENU->LeftEdge + MID->LeftEdge + SUB->LeftEdge + SUB->Width - 8;
					MY = MENUY + MENU->Height + MID->TopEdge + SUB->TopEdge + SUB->Height/2;
					break;
				}
				ep->ie_Class = IECLASS_RAWMOUSE;
				ep->ie_Code = IECODE_NOBUTTON;
				ep->ie_Qualifier = 0x8000; /* relmouse */
				GLOBALMOUSE;
			}
			else if (ep->ie_Class == IECLASS_RAWMOUSE)
			{
				/* if this is a button up, quit */
				if (ep->ie_Code != IECODE_NOBUTTON)
					gptr->state = Idle;
				else /* otherwise eat 'em for now */
					ep->ie_Class = IECLASS_NULL;
			}
		}
      		else if ((ep->ie_Class == IECLASS_RAWKEY) &&
          		(gptr->state == Idle) &&
          		((ep->ie_Code == RAW_LALT) || (ep->ie_Code == RAW_RALT)) &&
          		((ep->ie_Qualifier & EXTQUAL) == 0))
         	{
			gptr->state = AltDown;
		}
		else if ((ep->ie_Class == IECLASS_RAWKEY) &&
			(gptr->state == AltDown) &&
          		((ep->ie_Code == UP(RAW_LALT)) || (ep->ie_Code == UP(RAW_RALT))) &&
          		((ep->ie_Qualifier & EXTQUAL) == 0))
		{
			/* point at the menu */
			gptr->state = InMenu;
			ROOT = MENU = gptr->IBase->ActiveWindow->MenuStrip;
			SCRN = gptr->IBase->ActiveWindow->WScreen;
			if (ROOT == NULL || SCRN == NULL)
			{
				gptr->state = Idle;
				continue;
			}
			gptr->OldMouseX = gptr->IBase->MouseX;
			gptr->OldMouseY = gptr->IBase->MouseY;
			gptr->XMul = (gptr->Scrn->ViewPort.Modes & HIRES) ? 1 : 2;
			gptr->YMul = (gptr->Scrn->ViewPort.Modes & LACE) ? 1 : 2;
			MENUX = SCRN->LeftEdge + SCRN->MenuHBorder;
			MENUY = SCRN->TopEdge + SCRN->MenuVBorder;
			/* fix for non-standard menus */
			if (MENU->Height == 0) MENUY += SCRN->BarHeight;
			WHERE = Top;
			MX = SCRN->LeftEdge + MENU->LeftEdge + MENU->Width - 2;
			MY = SCRN->TopEdge + MENU->Height/2;
			ep->ie_Class = IECLASS_RAWMOUSE;
			ep->ie_Code = IECODE_RBUTTON;
			ep->ie_Qualifier = 0xa000; /* relmouse | rbutton */
			GLOBALMOUSE;
		}
		else gptr->state = Idle;
	}
	return(ev);
}

/*
** GetRawKeyMap - get the raw key conversion lookup table
*/

void GetRawKeyMap( GLOBAL_DATA *gptr )
{
	BYTE			key;
	BYTE			Ascii[16];
	struct InputEvent	ev;

	for (key = 0; key <= LAST_KEY; ++key)
	{
		ev.ie_NextEvent = NULL;
		ev.ie_Class = IECLASS_RAWKEY;
		ev.ie_SubClass = 0;
		ev.ie_Qualifier = 0;

		ev.ie_Code = key;
		RawKeyConvert( &ev, Ascii, sizeof( Ascii ), NULL );
		(gptr->RawKeyMap)[key] = UPPER( Ascii[0] );
	}
}

/*
** Main entry point
*/

void _main( char *cmd )
{
	int			stay = 0;
	struct MsgPort		*port;
	struct MsgPort		*DevPort = NULL;
	struct Message 		*msg = NULL;
	struct IOStdReq		*inputRequestBlock = NULL;
	struct IOStdReq		*conRequestBlock = NULL;
	struct Interrupt	handlerStuff;
	GLOBAL_DATA		global;

	if (_Backstdout) Write( _Backstdout, HELLOMSG, sizeof(HELLOMSG) );

	/* now see if we are already installed */
	if ((port = FindPort( PORTNAME )) == NULL) 
	{
		/* remember to hang around when we are done */
		stay = 1;
		/* not installed, we need to install our own port */
		if ((port = CreatePort( PORTNAME, 0 )) == NULL) goto abort;
		if (_Backstdout)
			Write( _Backstdout, STARTMSG, sizeof(STARTMSG) );
	}

	/* if we were run from CLI then process parameters */
	if (cmd && *cmd)
	{
		/* skip over any leading spaces in the command line */
		while (*cmd && *cmd != '-') cmd++;

		/* see if they are trying to kill us. */
		if (*cmd == '-' && cmd[1] == 'q')
		{
			if ((msg = (struct Message *)
				AllocMem( sizeof(struct Message), MEMF_CLEAR|MEMF_PUBLIC)) == NULL )
				goto abort;
			msg->mn_Length = sizeof(struct Message);
			PutMsg( port, msg );
			msg = NULL;

			if (_Backstdout)
				Write( _Backstdout, KILLMSG, sizeof(KILLMSG) );
		}
	}

	if (!stay) goto abort;

	/* Let the original window go away */
	if (_Backstdout) Close( _Backstdout );
	_Backstdout = NULL;

	if (((DevPort = CreatePort( NULL, 0 )) == NULL)	||
	    ((inputRequestBlock = CreateIOReq( DevPort, sizeof(struct IOStdReq) )) == NULL)     ||
	    ((conRequestBlock = CreateIOReq( DevPort, sizeof(struct IOStdReq) )) == NULL)     ||
	    ((IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library", 0 )) == NULL)      ||
	      OpenDevice( "input.device", 0, (struct IORequest *)inputRequestBlock, 0 )	||
	      OpenDevice( "console.device", -1, (struct IORequest *)conRequestBlock, 0 ))
		goto abort;

	/* for using RawKeyConvert */
	ConsoleDevice = (struct Library *)conRequestBlock->io_Device;
	if (ConsoleDevice == NULL) goto abort;

	GetRawKeyMap( &global );
	global.buddy = FindTask( NULL );
	global.IBase = IntuitionBase;
	global.state = Idle;

	handlerStuff.is_Data = (APTR)&global;
   	handlerStuff.is_Code = (VOID (*)()) myhandler;
   	handlerStuff.is_Node.ln_Pri = ALTMENU_PRIORITY;

	inputRequestBlock->io_Command = IND_ADDHANDLER;
   	inputRequestBlock->io_Data    = (APTR)&handlerStuff;
	DoIO( inputRequestBlock );

	WaitPort( port );
	msg = GetMsg( port );

abort:
	if (msg != NULL) FreeMem( (char *)msg, msg->mn_Length );

   	if (conRequestBlock != NULL)
      	{
      		if (conRequestBlock->io_Device != NULL)
         		CloseDevice( (struct IORequest *)conRequestBlock );
      		DeleteIOReq( conRequestBlock );
      	}

   	if (inputRequestBlock != NULL)
      	{
      		if (inputRequestBlock->io_Device != NULL)
         	{
         		inputRequestBlock->io_Command = IND_REMHANDLER;
         		inputRequestBlock->io_Data = (APTR)&handlerStuff;
         		DoIO( (struct IORequest *)inputRequestBlock );
         		CloseDevice( (struct IORequest *)inputRequestBlock );
         	}
      		DeleteIOReq( inputRequestBlock );
      	}

   	if (IntuitionBase != NULL)      CloseLibrary( (struct Library *)IntuitionBase );
   	if (DevPort != NULL)       	DeletePort( DevPort );
   	if (_Backstdout)                Close( _Backstdout );
   	if (stay && (port != NULL))     DeletePort( port );
}

/*
** No nutty memory cleanup needed!
*/

void MemCleanup( void )
{
}

/*
** CreatePort - create a message port
*/

struct MsgPort *CreatePort( name, pri )
char *name;
int pri;
{
   	UBYTE 			sigbit;
   	register struct MsgPort *port;

   	if ((sigbit = AllocSignal(-1)) == -1) return( NULL );
	if ((port = (struct MsgPort *)AllocMem( sizeof( struct MsgPort ), MEMF_CLEAR|MEMF_PUBLIC)) == NULL)
      	{
      		FreeSignal( sigbit );
      		return( NULL );
      	}

   	port->mp_Node.ln_Name = name;
   	port->mp_Node.ln_Pri = pri;
   	port->mp_Node.ln_Type = NT_MSGPORT;
   	port->mp_Flags = PA_SIGNAL;
   	port->mp_SigBit = sigbit;
   	port->mp_SigTask = (struct Task *)FindTask( NULL );
   	AddPort( port );
   	return( port );
}

/*
** DeletePort - delete an allocated message port
*/

void DeletePort( struct MsgPort *port )
{
	RemPort( port );
	FreeSignal( port->mp_SigBit );
	FreeMem( (char *)port,sizeof(struct MsgPort) );
}

/*
** CreateIOReq - create an i/o request block
*/

struct IOStdReq *CreateIOReq( struct MsgPort *port, int size )
{
   	struct IOStdReq *ioReq;

   	if ((ioReq = (struct IOStdReq *)AllocMem( size, MEMF_CLEAR | MEMF_PUBLIC )) != NULL)
      	{
      		ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
      		ioReq->io_Message.mn_Node.ln_Pri  = 0;
      		ioReq->io_Message.mn_Length       = size;
      		ioReq->io_Message.mn_ReplyPort    = port;
      	}
   	return( ioReq );
}

/*
** DeleteIOReq - delete an allocated i/o block
*/

void DeleteIOReq( struct IOStdReq *ioReq )
{
	ioReq->io_Message.mn_Node.ln_Type = 0xff;
	ioReq->io_Device = (struct Device *) -1;
	ioReq->io_Unit = (struct Unit *) -1;
	FreeMem( (char *)ioReq, ioReq->io_Message.mn_Length );
}
