/*
 *						© Pierre Dak Baillargeon 1991,1992
 *
 *
 *  NAME
 *		KGB -- control the gadgets via the keyboard.
 *
 *  SYNOPSIS
 *		KGB
 *
 *  FUNCTION
 *		Let the user browse and select windows, screens and gadgets
 *		via the keyboard.
 *
 *  DESCRIPTION
 *		Process all events that go through the system and search for
 *		keyboard event with LALT and LCOMMAND qualifier.
 *
 *		When a LALT key down event is catched, arrows key let you browse
 *		through the list of windows and screens in the system.
 *
 *		While the LATL key is down, if the LCOMMAND key goes down, the
 *		arrow keys let you browse through the gadgets of the active
 *		window and select them with the return key.
 *
 *  NOTE
 *		If a window, screen or gadget goes away while the user is using
 *		them, anything can happen, from nothing at all to crash.
 *
 */


/*
 *	Data passed to the input handler.
 */

struct Box
{
	WORD					x,y,w,h;
};

struct Data
{
	struct IntuitionBase *	IntuitionBase;
	struct GfxBase *		GfxBase;
	UWORD 					Flags;
	struct Window *			Window;
	struct Gadget *			Gadget;
	struct Box				Box;
};



/*
 *	Function prototypes.
 */

void main(void);
struct InputEvent * __asm Handler (register __a0 struct InputEvent *, register __a1 struct Data *);

void UpGadget (struct Data *);
void DownGadget (struct Data *);
void LeftGadget (struct Data *);
void RightGadget (struct Data *);

BOOL InBox (struct Box *Box, WORD x, WORD y);
void GetGad (struct Data *Data);
void BoxMiddle (struct Box *);
void GetBox (struct Window *, struct Gadget *, struct Box *);

void PopScreen (struct Data *);
void PushScreen (struct Data *);
void PopWindow (struct Data *);
void PushWindow (struct Data *);


/*
 *	main
 *
 *		Open all the needed datas and ressources and wait for a ctrl-c.
 */

void main(void)
{
	struct Data				Data;
	struct MsgPort *		InputPort = NULL;
	struct Interrupt *		InputHandler = NULL;
	struct IOStdReq *		InputReq = NULL;

	if ((Data.IntuitionBase = (struct IntuitionBase *)OpenLibrary ("intuition.library", 0L)))
	{
		if ((Data.GfxBase = (struct GfxBase *)OpenLibrary ("graphics.library", 0L)))
		{
			if ((InputPort = CreatePort (0L, 0L)))
			{
				if ( (InputReq = (struct IOStdReq *)CreateExtIO (InputPort, sizeof (struct IOStdReq))))
				{
					if (!OpenDevice ("input.device", NULL, InputReq, NULL))
					{
						if ( (InputHandler = AllocMem (sizeof (struct Interrupt), MEMF_PUBLIC|MEMF_CLEAR)))
						{
							Data.Window = NULL;
							Data.Gadget = NULL;
							Data.Flags = 0;

							InputHandler->is_Code = (void (*__far)(void))Handler;
							InputHandler->is_Data = (APTR)&Data;
							InputHandler->is_Node.ln_Pri = 100;
							InputHandler->is_Node.ln_Name = "Keyboard Gadget";

							InputReq->io_Command = IND_ADDHANDLER;
							InputReq->io_Data = (APTR)InputHandler;
							DoIO (InputReq);

							if (Output ())
								Write (Output(), "KGB 2.0: Keyboard gadget control, © Pierre Dak Baillargeon 1992.\n", 65);

							Wait (SIGBREAKF_CTRL_C);

							InputReq->io_Command = IND_REMHANDLER;
							InputReq->io_Data = (APTR)InputHandler;
							DoIO (InputReq);

							FreeMem (InputHandler, sizeof (struct Interrupt));
						}
						CloseDevice (InputReq);
					}
					DeleteExtIO (InputReq);
				}
				DeletePort (InputPort);
			}
			CloseLibrary (Data.GfxBase);
		}
        CloseLibrary (Data.IntuitionBase);
	}
}


/*
 *	key and qualifer values.
 */

#define RETURN		0x44
#define UP			0x4C
#define DOWN		0x4D
#define RIGHT		0x4E
#define LEFT		0x4F
#define LEFT_ALT	0x64
#define LEFT_AMIGA	0x66

#define KEY_UP		0x80

#define LALT		IEQUALIFIER_LALT
#define LCOM		IEQUALIFIER_LCOMMAND

#define MOVE		0x01
#define RETURNED	0x02

#define IntuitionBase Data->IntuitionBase
#define GfxBase Data->GfxBase


struct InputEvent * __asm Handler (register __a0 struct InputEvent *chain, register __a1 struct Data *Data)
{
	struct InputEvent *ie = chain;

	while (ie)
	{
		if (ie->ie_Class == IECLASS_RAWKEY)
		{
			switch (ie->ie_Code)
			{
				case RETURN:
					if (Data->Flags & LCOM && Data->Gadget)
					{
						ie->ie_Class = IECLASS_RAWMOUSE;
						ie->ie_Code = IECODE_LBUTTON;
						ie->ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE;
						ie->ie_X = 0;
						ie->ie_Y = 0;
						if ((Data->Gadget->GadgetType & ~GADGETTYPE) == SIZING
							|| (Data->Gadget->GadgetType & ~GADGETTYPE) == WDRAGGING
							|| (Data->Gadget->GadgetType & ~GADGETTYPE) == PROPGADGET)
						{
							Data->Flags |= MOVE;
						}
						Data->Flags |= RETURNED;
					}
					break;
				case RETURN+KEY_UP:
					if (Data->Flags & LCOM && Data->Gadget)
					{
						ie->ie_Class = IECLASS_RAWMOUSE;
						ie->ie_Code = IECODE_LBUTTON + IECODE_UP_PREFIX;
						ie->ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE;
						ie->ie_X = 0;
						ie->ie_Y = 0;
						Data->Flags &= ~(MOVE+RETURNED);
						if ((Data->Gadget->GadgetType & ~GADGETTYPE) == CLOSE)
						{
							Data->Window = NULL;
							Data->Gadget = NULL;
						}
					}
					break;
				case UP:
					if (Data->Flags & MOVE)
					{
						/* nothing ! */
					}
					else if (Data->Flags & LCOM)
					{
						UpGadget (Data);
						GetBox (Data->Window, Data->Gadget, &Data->Box);
						BoxMiddle (&Data->Box);
						ie->ie_Class = IECLASS_POINTERPOS;
						ie->ie_Code = 0;
						ie->ie_Qualifier = 0;
						ie->ie_X = Data->Box.x;
						ie->ie_Y = Data->Box.y;
					}
					else if (Data->Flags & LALT)
					{
						PushScreen (Data);
						ie->ie_Class = IECLASS_NULL;
					}
					break;
				case DOWN:
					if (Data->Flags & MOVE)
					{
						/* nothing ! */
					}
					else if (Data->Flags & LCOM)
					{
						DownGadget (Data);
						GetBox (Data->Window, Data->Gadget, &Data->Box);
						BoxMiddle (&Data->Box);
						ie->ie_Class = IECLASS_POINTERPOS;
						ie->ie_Code = 0;
						ie->ie_Qualifier = 0;
						ie->ie_X = Data->Box.x;
						ie->ie_Y = Data->Box.y;
					}
					else if (Data->Flags & LALT)
					{
						PopScreen (Data);
						ie->ie_Class = IECLASS_NULL;
					}
					break;
				case RIGHT:
					if (Data->Flags & MOVE)
					{
						/* nothing ! */
					}
					else if (Data->Flags & LCOM)
					{
						RightGadget (Data);
						GetBox (Data->Window, Data->Gadget, &Data->Box);
						BoxMiddle (&Data->Box);
						ie->ie_Class = IECLASS_POINTERPOS;
						ie->ie_Code = 0;
						ie->ie_Qualifier = 0;
						ie->ie_X = Data->Box.x;
						ie->ie_Y = Data->Box.y;
					}
					else if (Data->Flags & LALT)
					{
						PushWindow (Data);
						ie->ie_Class = IECLASS_NULL;
					}
					break;
				case LEFT:
					if (Data->Flags & MOVE)
					{
						/* nothing ! */
					}
					else if (Data->Flags & LCOM)
					{
						LeftGadget (Data);
						GetBox (Data->Window, Data->Gadget, &Data->Box);
						BoxMiddle (&Data->Box);
						ie->ie_Class = IECLASS_POINTERPOS;
						ie->ie_Code = 0;
						ie->ie_Qualifier = 0;
						ie->ie_X = Data->Box.x;
						ie->ie_Y = Data->Box.y;
					}
					else if (Data->Flags & LALT)
					{
						PopWindow (Data);
						ie->ie_Class = IECLASS_NULL;
					}
					break;
				case LEFT_ALT:
					if (!(ie->ie_Qualifier & (0xff & ~LALT)))
					{
						Data->Flags |= LALT;
						ie->ie_Class = IECLASS_NULL;
					}
					break;
				case LEFT_ALT+KEY_UP:
					if (Data->Flags & LALT)
					{
						Data->Flags &= ~(LALT+LCOM);
						if (Data->Flags & (MOVE | RETURNED))
						{
							ie->ie_Class = IECLASS_RAWMOUSE;
							ie->ie_Code = IECODE_LBUTTON + IECODE_UP_PREFIX;
							ie->ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE;
							ie->ie_X = 0;
							ie->ie_Y = 0;
							Data->Flags &= ~(MOVE | RETURNED);
						}
						else
						{
							ie->ie_Class = IECLASS_NULL;
						}
					}
					break;
				case LEFT_AMIGA:
					if (Data->Flags & LALT)
					{
						Data->Window = IntuitionBase->ActiveWindow;
						GetGad (Data);
						Data->Flags  |= LCOM;
						ie->ie_Class = IECLASS_POINTERPOS;
						ie->ie_Code = 0;
						ie->ie_Qualifier = 0;
						ie->ie_X = Data->Box.x;
						ie->ie_Y = Data->Box.y;
					}
					break;
				case LEFT_AMIGA+KEY_UP:
					if (Data->Flags & LCOM)
					{
						Data->Flags &= ~LCOM;
						if (Data->Flags & (MOVE | RETURNED))
						{
							ie->ie_Class = IECLASS_RAWMOUSE;
							ie->ie_Code = IECODE_LBUTTON + IECODE_UP_PREFIX;
							ie->ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE;
							ie->ie_X = 0;
							ie->ie_Y = 0;
							Data->Flags &= ~(MOVE | RETURNED);
						}
						else
						{
							ie->ie_Class = IECLASS_NULL;
						}
					}
					break;
				default:
					break;
			}
		}
		ie = ie->ie_NextEvent;
	}
	return chain;
}



void GetGad (struct Data *Data)
{
	Data->Gadget = NULL;
	Data->Box.x = -20000;
	Data->Box.y = -20000;

	if (Data->Window)
	{
		struct Gadget *g;
		WORD dd, ndx, ndy;
		struct Box NewBox;

		g = Data->Window->FirstGadget;
		dd = 20000;

		while (g)
		{
			GetBox (Data->Window, g, &NewBox);
			if (InBox (&NewBox, IntuitionBase->MouseX, IntuitionBase->MouseY))
			{
				Data->Gadget = g;
				Data->Box.x = IntuitionBase->MouseX;
				Data->Box.y = IntuitionBase->MouseY;
				break;
			}
			else
			{
				BoxMiddle (&NewBox);
				ndx = NewBox.x - IntuitionBase->MouseX;
				ndy = NewBox.y - IntuitionBase->MouseY;
				if (abs (ndx) + abs (ndy) < dd)
				{
					dd = abs (ndx) + abs (ndy);
					Data->Gadget = g;
					Data->Box.x = ndx + IntuitionBase->MouseX;
					Data->Box.y = ndy + IntuitionBase->MouseY;
				}
			}
			g = g->NextGadget;
		}
	}
}



BOOL InBox (struct Box *Box, WORD x, WORD y)
{
	if (x >= Box->x && x < Box->x + Box->w && y >= Box->y && y < Box->y + Box->h)
		return 1;
	else
		return 0;
}


void BoxMiddle (struct Box *Box)
{
	Box->x += Box->w/2;
	Box->y += Box->h/2;
}


void GetBox (struct Window *Window, struct Gadget *Gadget, struct Box *Box)
{
	Box->x = Window->LeftEdge;
	Box->x += Gadget->LeftEdge;
	if (Gadget->Flags & GRELRIGHT)
		Box->x += Window->Width - 1;

	Box->w = Gadget->Width;
	if (Gadget->Flags & GRELWIDTH)
		Box->w += Window->Width;

	if (!(Window->WScreen->ViewPort.Modes & HIRES))
	{
		Box->x *= 2;
		Box->w *= 2;
	}

	Box->y = Window->TopEdge;
	Box->y += Gadget->TopEdge;
	if (Gadget->Flags & GRELBOTTOM)
		Box->y += Window->Height - 1;

	Box->h = Gadget->Height;
	if (Gadget->Flags & GRELHEIGHT)
		Box->h += Window->Height;

	if (!(Window->WScreen->ViewPort.Modes & LACE))
	{
		Box->y *= 2;
		Box->h *= 2;
	}
}



void UpGadget (struct Data *Data)
{
	struct Gadget *g;

	if (Data->Window)
	{
		g = Data->Window->FirstGadget;
		while (g && g->NextGadget && g->NextGadget != Data->Gadget)
		{
			g = g->NextGadget;
		}
		Data->Gadget = g;
	}
}



void DownGadget (struct Data *Data)
{
	if (Data->Gadget)
	{
		Data->Gadget = Data->Gadget->NextGadget;
	}

	if (Data->Gadget == NULL)
	{
		if (Data->Window)
		{
			Data->Gadget = Data->Window->FirstGadget;
		}
	}
}



void LeftGadget (struct Data *Data)
{
	struct Gadget *g;

	if (Data->Window)
	{
		g = Data->Window->FirstGadget;
		while (g && g->NextGadget && g->NextGadget != Data->Gadget)
		{
			g = g->NextGadget;
		}
		Data->Gadget = g;
	}
}



void RightGadget (struct Data *Data)
{
	if (Data->Gadget)
	{
		Data->Gadget = Data->Gadget->NextGadget;
	}

	if (Data->Gadget == NULL)
	{
		if (Data->Window)
		{
			Data->Gadget = Data->Window->FirstGadget;
		}
	}
}



void PopScreen (struct Data *Data)
{
	struct Screen *s = IntuitionBase->ActiveScreen;

	if (s != NULL)
	{
		while (s->NextScreen)
			s = s->NextScreen;
		ScreenToFront (s);
		if (s->FirstWindow)
			ActivateWindow (s->FirstWindow);
	}
}



void PushScreen (struct Data *Data)
{
	struct Screen *s = IntuitionBase->ActiveScreen;

	if (s)
	{
		ScreenToBack (s);
		s = IntuitionBase->FirstScreen;
		if (s)
		{
			if (s->FirstWindow)
			{
				ActivateWindow (s->FirstWindow);
			}
		}
	}
}



void PopWindow (struct Data *Data)
{
	struct Window *w = IntuitionBase->ActiveWindow;
	struct Window *nw;

	if (w)
	{
		nw = IntuitionBase->ActiveScreen->FirstWindow;
		while (nw->NextWindow != NULL && nw->NextWindow != w)
				nw = nw->NextWindow;
		if (nw != w)
		{
			WindowToFront (nw);
			ActivateWindow (nw);
		}
	}
}



void PushWindow (struct Data *Data)
{
	struct Window *w = IntuitionBase->ActiveWindow;
	struct Window *nw;

	if (w)
	{
		if ((nw = w->NextWindow) == NULL)
			nw = IntuitionBase->ActiveScreen->FirstWindow;
		if (nw != w)
		{
			WindowToBack (w);
			ActivateWindow (nw);
		}
	}
}
