/********************************************************
** Events.c: Handle user events coming from all sides. **
**           Written by T.Pierron                      **
**           10-Dec-1999                               **
********************************************************/

/* All the necessary includes: */
#include <intuition/intuition.h>				/* Std datatypes */
#include <intuition/intuitionbase.h>		/* To access ActiveScreen */
#include <intuition/screens.h>				/* Screen information */
#include <graphics/rastport.h>				/* Rastport info */
#include <graphics/gfxbase.h>					/* DefaultFont */
#include <graphics/text.h>						/* struct TextFont */
#include <libraries/gadtools.h>				/* NewMenus and NewGadget */
#include <libraries/commodities.h>			/* Commodity datatypes */
#include <libraries/dos.h>						/* CTRL/C signal */
#include <devices/inputevent.h>				/* For raw keymap conversion */

#include "cmap.h"
#define  CATCOMP_STRINGS						/* We only need strings */
#include "cmap_strings.h"						/* Support of locale.library */

/* External variables: */
extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;
extern struct Library *IconBase;
extern struct Window *window;
extern struct Screen *screen;
extern struct NewMenu newmenu[],*checked,*checkset;
extern struct Gadget *gad, *copy_gad, *paste_gad;
extern struct NewWindow new_window;
extern struct TextFont *font,*newfont;
extern struct Menu *menu;
extern WORD   xdeb,ydeb,xfin,yfin,X,Y;			/* ASCII Table position */
extern UBYTE  MaxWid,Font_height,Char[];		/* Font information */
extern ULONG  BoxTags[],sigwin;
extern UBYTE  CharsetNum, VertSort;


struct TTextAttr *aslfont;							/* Returned by the ASL font requester */
struct MsgPort *broker_mp = NULL;				/* Commodity message port */
CxObj  *broker, *filter;							/* Commodity and hot-key */
CxMsg  *msg;											/* To collect messages */
UBYTE  *StrBuf;										/* Shortcut to string gadget's buffer */
struct IntuiMessage message;

struct InputEvent ie = {0,IECLASS_RAWKEY};	/* Keyboard translation map */

BYTE	CloseQuit=TRUE;								/* True is close gadget quit the prog */
BYTE  IsDraw=0,PopWin=TRUE;						/* State of the recessed box */
WORD  NumChar=-1,CustDx=0;							/* Selected character number */
ULONG cxsigflag=0;									/* Signal bits of the commo. */

struct NewBroker newbroker = {
	NB_VERSION,
	"CharMap",							/* String to identify this commo. */
	MSG_DESCLINE1_STR,
	MSG_DESCLINE2_STR,
	NBU_UNIQUE | NBU_NOTIFY |		/* Don't want any new commodities starting with this name. */
	COF_SHOW_HIDE,						/* If someone tries it, let me know */
	0, 0, 0, 0
};

void Free_commodity();
void Handle_gadget(struct Gadget *);

/*** Initialize the message port of the commodity: ***/
BOOL Init_commodity(int argc, char *argv[])
{
	UBYTE *hotkey, **ttypes;

	if(broker_mp = (void *) CreateMsgPort())
	{
		newbroker.nb_Port = broker_mp;
		cxsigflag         = 1L << broker_mp->mp_SigBit;

		/* Get the tool-type or command line value: */
		ttypes            = (UBYTE **) ArgArrayInit(argc, argv);
		newbroker.nb_Pri  = (BYTE)ArgInt(ttypes, "CX_PRIORITY", 0);
		CharsetNum        = (BYTE)ArgInt(ttypes, "CHARSET", 2);
		hotkey            = (UBYTE *) ArgString(ttypes, "HOTKEY", "rawkey lalt control c");
		CloseQuit         = ArgString(ttypes, "CLOSEQUIT", NULL)==NULL ? FALSE:TRUE;
		VertSort          = ArgString(ttypes, "HORIZONTALSORT", NULL)==NULL ? TRUE:FALSE;
		Parse_geometry( (UBYTE *)ArgString(ttypes, "GEOMETRY", ",,,") );

		/* Whould the user show immediatly the window ? */
		if( !Stricmp(ArgString(ttypes, "CX_POPUP", Char), "NO") ) PopWin=FALSE;

		if(broker = (void *) CxBroker(&newbroker, NULL))
		{
			/* HotKey() is an amiga.lib function that creates a filter, sender */
			/* and translate CxObject and connects them to report a hot key    */
			/* press and delete its input event. */
			if(filter = (void *) HotKey(hotkey, broker_mp, EVT_HOTKEY))
			{
				AttachCxObj(broker, filter);		/* Add a CxObject to another's personal list */

				if(! CxObjError(filter))
					ActivateCxObj(broker, 1L);		/* All done! */
			}
		}
		ArgArrayDone();  /* this amiga.lib function cleans up after ArgArrayInit() */
	}
	return (BOOL)(broker && broker_mp);
}

void Free_commodity( void )
{
	/* DeleteCxObjAll() is a commodities.library function that not only    **
	** deletes the CxObject pointed to in its argument, but deletes all of **
	** the CxObjects attached to it.                                       */
	if(broker)
	{
		DeleteCxObjAll(broker);

		/* Empty the port of all CxMsgs */
		while(msg = (CxMsg *)GetMsg(broker_mp))
			ReplyMsg((struct Message *)msg);
	}

	if(broker_mp) DeletePort(broker_mp);
}

WORD OldX=0,OldY=0,Xc=0,Yc=0;
/*** Move the sunken box while dragging the mouse with LMB down: ***/
void Handle_box( void )
{
	extern WORD txtpen, fillpen, filltxt;
	extern struct RastPort *RP;
	extern UBYTE CharWid[], NumChars;
	WORD sunken=FALSE;

	/* Center mouse position on top-left corner of the box: */
	if(X<xdeb) X=0; else X -= (X-xdeb)%MaxWid;
	if(Y<ydeb) Y=0; else Y -= (Y-ydeb)%Font_height;

	/* If it isn't already drawn... */
	IsDraw=PRESSED;
	if(X==OldX && Y==OldY) return;
	BoxTags[2] = TAG_DONE;

	/* ...and inside area */
	if(OldX>=xdeb && OldX<xfin && OldY>=ydeb && OldY<yfin)
	{
		/* Draw the sunken / beveled box: */
		DR:SetAPen(RP,sunken?fillpen : 0);
		RectFill(RP,OldX+1,OldY+1,OldX+MaxWid-2,OldY+Font_height-2);
		SetAPen(RP,sunken?filltxt : txtpen);
		Move(RP,OldX+(MaxWid-CharWid[NumChar]>>1),OldY+font->tf_Baseline+2);
		*Char=(CharsetNum==3 && NumChar==127 ? 32:NumChar);
		Text(RP,Char,1);

		DrawBevelBoxA(RP,OldX,OldY,MaxWid,Font_height,BoxTags);
	}

	if(sunken) { Disp_charinfo(NumChar); return; } OldX=X; OldY=Y;

	if(X>=xdeb && X<xfin && Y>=ydeb && Y<yfin) {
		BoxTags[2] = GTBB_Recessed;
		NumChar = VertSort ? ((X-xdeb)/MaxWid<<3)+(Y-ydeb)/Font_height :
		                     ((Y-ydeb)/Font_height*NumChars)+(X-xdeb)/MaxWid;
		if(CharsetNum >1) NumChar+=32;
		if(CharsetNum==3 && NumChar>127) NumChar+=160-128;
		sunken=TRUE; Xc=X; Yc=Y; goto DR;
	} else {
		NumChar=-1;
		IsDraw=OUTSIDE_AREA;
	}
}

/*** Move the box using the keyboard: ***/
void Handle_keyboard(UWORD code)
{
	/* To prevent that keyboard doesn't disturb the mouse: */
	if(IsDraw > 0 || code<76 || code>79) return;
	if(Xc)
	{
		/* Rawkey code for arrow keys: */
		switch(code)
		{
			case 76: Yc -= Font_height; break;
			case 77: Yc += Font_height; break;
			case 78: Xc += MaxWid;      break;
			case 79: Xc -= MaxWid;      break;
		}
		/* Check if cursor overrun the map: */
		if(VertSort) {
			/* Increase/decrease x pos, if cursor reach bottom/top of table: */
			if(Yc< ydeb) { Yc=yfin-Font_height; Xc-=MaxWid; }
			if(Yc>=yfin) { Yc=ydeb; Xc+=MaxWid; }
			if(Xc< xdeb) Xc=xfin-MaxWid;
			if(Xc>=xfin) Xc=xdeb;
		}	else {
			if(Xc< xdeb) { Xc=xfin-MaxWid; Yc-=Font_height; }
			if(Xc>=xfin) { Xc=xdeb; Yc+=Font_height; }
			if(Yc< ydeb) Yc=yfin-Font_height;
			if(Yc>=yfin) Yc=ydeb;
		}
	} else {
		/* If cursor hasn't been moved yet: */
		Xc=xdeb; Yc=ydeb;
	}
	X=Xc; Y=Yc;
	Handle_box();
	/* To prevent mouse doesn't disturb the keyboard: */
	IsDraw=KEYB_CONTROL;
}

/*** Processes menus events: ***/
void Handle_menu( LONG MenuID )
{
	extern struct EasyStruct Request;
	extern ULONG  FontTags[];
	extern UBYTE *GadTxt[];

	/** I apologize for this very bad example of ***
	***      obfuscated C programming style.     **/
	switch( MenuID )
	{
		/**** Project Menu ****/

		case 111:/* Look if the first item isn't already selected: */
					if(checked == &newmenu[NM_FONTTYPE]) break;
					/* Take the font of the screen (Please, don't close it!): */
					font = screen->RastPort.Font; goto check;
		case 112:/* Ditto: */
					if(checked == &newmenu[NM_FONTTYPE+1]) break;
					font = GfxBase->DefaultFont;
		check:	/* Menus will be reallocated, change NewMenus struct. directly: */
					checked->nm_Flags &= ~CHECKED;
					checked = &newmenu[ MenuID-111+NM_FONTTYPE ];
					checked->nm_Flags |= CHECKED;
					/* Adjust cycle font-type entries: */
					if( CustDx = (FontTags[3] = MenuID-111)==2 ) FontTags[3]=0;
					FontTags[1] = (ULONG) &GadTxt[GT + (CustDx ? 0:1)];
					/* Change the charset depending of the font: */
					Init_charset(font);
					MenuID = CharsetNum+140;
					goto charset;
		REDOIT:	MenuID = 113;
		case 113:/* Open a ASL requester: */
					if( aslfont = (struct TTextAttr *) open_asl() )
					{
						if( newfont ) CloseFont(newfont);
						if( newfont=(void *)OpenDiskFont(aslfont) )
						{
							font = newfont;
							goto check;
						}
					}
					/* Cancel the menu selection (sorry, I don't want to use temporary variables) */
					((struct MenuItem *)ItemAddress(menu,MENUITEM(0,0,2)))->Flags &= ~CHECKED;
					((struct MenuItem *)ItemAddress(menu,MENUITEM(0,0,checked-&newmenu[NM_FONTTYPE])))->Flags |= CHECKED;

					if(message.Class==GADGETUP) GT_SetGadgetAttrsA(message.IAddress,window,NULL,FontTags);
					break;
					/* Changing the charset of ASCII table: */
		case 141:if(checkset == &newmenu[NM_CHARSET])   break; goto charset;
		case 142:if(checkset == &newmenu[NM_CHARSET+1]) break; goto charset;
		case 143:if(checkset == &newmenu[NM_CHARSET+2]) break;

		charset:	checkset->nm_Flags &= ~CHECKED;
					checkset   = &newmenu[ MenuID-141+NM_CHARSET ];
					CharsetNum = MenuID-140;
					checkset->nm_Flags |= CHECKED;
					/* Now, we can reopen our window: */
					goto new_win;

		case 21: Handle_gadget(copy_gad);
		case 24:	/* Clear the text contained in the string gadget: */
					StrBuf[0]=0; RefreshGList(gad, window, NULL, 1);
					break;
		case 25:	/* Jump to the next screen: */
					if( !screen->NextScreen ) break;
					screen = screen->NextScreen;
					/* Adjust the screen font, to what the screen uses: */
					if(checked == &newmenu[NM_FONTTYPE]) font = screen->RastPort.Font;
		new_win:	cleanup(NULL,-1);
					/* If init failed, try to see if we can changed the font again: */
					if( setup() ) goto REDOIT;
					/* Before to move the screen, wait all it's OK: */
					ScreenToFront(screen);
					break;
		case 15:	/* About menu entry: */
					EasyRequest(window, &Request, 0, CHARMAP_VERSION); break;
		case 16: /* Minimize/Normalize the window: */
					ZipWindow(window);
					break;
		case 17:	cleanup(NULL,-1); return;	/* Hide */
		case 18:	cleanup(NULL,0);				/* Quit */

		/**** Edit Menu ****/
		case 22:	Handle_gadget(copy_gad); break;
		case 23:	Handle_gadget(paste_gad); break;
 		case 26:	VertSort=((struct MenuItem *)ItemAddress(menu,MENUITEM(1,5,0)))->Flags & CHECKED ? 1:0;
					IsDraw = NOT_PRESSED;
					Draw_ASCIIChart();         /* Sorting */
	}
	if(window==NULL) cleanup(0,0);
}

/*** Handle all messages comming from a commo: ***/
void Handle_commo(void)
{
	ULONG msgid, msgtype;

	/* The signal is comming from a commodity: */
	while(msg = (CxMsg *)GetMsg(broker_mp))
	{
		msgid = CxMsgID(msg);
		msgtype = CxMsgType(msg);
		ReplyMsg((struct Message *)msg);

		switch(msgtype)
		{
			case CXM_IEVENT:
				/* Display or move the window, in the front-most screen: */
				if(msgid == EVT_HOTKEY)
				{
					front_most_screen:
					screen = IntuitionBase->ActiveScreen;
					/* If the window is already open, but not in the front-most screen, close it: */
					if(window)
						if(window->WScreen != screen) cleanup(NULL,-1);
						else {
							/* Activate and move it to front: */
							WindowToFront(window);
							ActivateWindow(window);
							/* If the window is minimize, normalize it: */
							if(window->Height<=window->BorderTop) ZipWindow(window);
							break;
						}

					/* The screen font can be changed: */
					if(checked == &newmenu[NM_FONTTYPE]) font = screen->RastPort.Font;
					/* If something goes wrong, change the font: */
					if( setup() ) Handle_menu(113);
					ScreenToFront(screen);
				}
				break;
			case CXM_COMMAND:
				switch(msgid)
				{
					case CXCMD_DISABLE:
						/* If user want to disable the commo, minimize the window: */
						if(window && window->Height>window->BorderTop) ZipWindow(window);
						ActivateCxObj(broker, 0L);
						break;
					case CXCMD_ENABLE:
						/* If user want to enable the commo, normalize it: */
						if(window && window->Height<=window->BorderTop) ZipWindow(window);
						ActivateCxObj(broker, 1L);
						break;

					case CXCMD_APPEAR:
						/* Screen can have changed: */
						goto front_most_screen;

					case CXCMD_DISAPPEAR:if(window) cleanup(NULL,-1);	break;
					case CXCMD_KILL:
					case CXCMD_UNIQUE:
						/* Commodities Exchange can be told not only to refuse to launch a   **
						** commodity with a name already in use but also can notify the      **
						** already running commodity that it happened.  It does this by      **
						** sending a CXM_COMMAND with the ID set to CXMCMD_UNIQUE.  If the   **
						** user tries to run a windowless commodity that is already running, **
						** the user wants the commodity to shut down.                        */
						cleanup(0,0);
				}
		}
	}
}

/*** Handle gadget messages ***/
void Handle_gadget( struct Gadget *G )
{
	extern void *clipdev;
	extern UBYTE GadDisable;

	/* If clipboard is unavailable: */
	if((G==copy_gad || G==paste_gad) && !clipdev && !(clipdev=(void *)CBOpen(0))) {
		/* Disable all related entries: */
		register int i;
		for(i=0; i<3; i++) {
			OffMenu(window, SHIFTMENU(1)+SHIFTITEM(i)+SHIFTSUB(NOSUB));
			newmenu[16+i].nm_Flags |= NM_ITEMDISABLED;
		}
		OffGadget(copy_gad,  window, NULL);
		OffGadget(paste_gad, window, NULL);
		GadDisable |= 6;
		return;
	}

	switch(G->GadgetID)
	{
		case 0: /* Clear everything in the string gadget */
			Handle_menu(24);
			break;
		case 1: /* Copy string into clipboard: */
			CBWriteFTXT(clipdev, StrBuf);
			break;
		case 2: /* Read string from clipboard: */
			if( CBQueryFTXT(clipdev) )
			{
				CBReadCHRS(clipdev, StrBuf, sti(gad)->MaxChars);
				RefreshGList(gad, window, NULL, 1);
			}
		case 3: /* Type of a font */
			Handle_menu(111+message.Code-CustDx);
			break;
		case 4: /* Choose a different Charset via the cycle gadget */
			Handle_menu(141+message.Code);
			break;
		case 10: /* String gadget realease: show info of character */
			if(sti(gad)->NumChars == 1)
				Disp_charinfo( StrBuf[0] );
	}
}

/**** Main loop, which collects and distributes all messages: ****/
void handle_input(void)
{
	static UBYTE buffer[8],drawit;
	ULONG  sigrcvd;

	FOREVER {
Deb:	sigrcvd = Wait(SIGBREAKF_CTRL_C | cxsigflag | sigwin);

		/* Look from where the signal comes: */
		if(sigrcvd & SIGBREAKF_CTRL_C) cleanup(0,0);

		if(sigrcvd & cxsigflag) Handle_commo();

		if((sigrcvd & sigwin) == 0) continue;

		drawit=FALSE;
		/* Use the new GadTools GT_GetIMsg() function to get input events */
		while (msg = (void *) GT_GetIMsg(window->UserPort))
		{
			/* Copy entire message: */
			CopyMem(msg,&message,sizeof(message));
			X=message.MouseX; Y=message.MouseY;

			if( message.Class == MENUVERIFY && message.Code == MENUHOT && IsDraw )
			{
				/* Just clears cursor */
				((struct IntuiMessage *)msg)->Code=MENUCANCEL;
				message.Class=MOUSEBUTTONS;
			}

			/* Reply to the message */
			GT_ReplyIMsg(msg);

			switch(message.Class)
			{
				/* The close-window gad. simply hide the main window: */
				case CLOSEWINDOW:	cleanup(NULL,CloseQuit ? 0:-1);
				case MOUSEBUTTONS:
					switch(message.Code)
					{
						/* Draw a recessed box below the mouse: */
						case SELECTDOWN: Handle_box(); break;
						/* Clear the box and add the char. in string gadget: */
						case SELECTUP:
							if(IsDraw) {
								INS:if(NumChar>=0 && IsDraw) {
									struct StringInfo *st = sti(gad);;

									if(st->NumChars>=st->MaxChars-1) strcpy(StrBuf, StrBuf+1);

									strcat(st->Buffer,Char);
									RefreshGList(gad, window, NULL, 1);
									/* If char has been keyed in, don't clear the cursor: */
									if(IsDraw==KEYB_CONTROL) break;
								}
								Xc=X; Yc=Y; CL:X=Y=0; Handle_box(); IsDraw=NOT_PRESSED; drawit=0;
							}	break;
						case MENUHOT: goto CL;
					}
					break;
						
				case MOUSEMOVE: if(IsDraw>0) drawit=1; break;

				case MENUPICK:
				{	struct MenuItem *menuitem;
					ULONG  MenuNum;

					while (message.Code != MENUNULL)
					{
						menuitem = (struct MenuItem *) ItemAddress(menu,message.Code);
						MenuNum = (ULONG) GTMENUITEM_USERDATA(menuitem);
						Handle_menu( MenuNum );
						message.Code = (MenuNum<100 ? menuitem->NextSelect:MENUNULL);
					}
				}	break;

				case RAWKEY:		/* Rawkey which will be Translated */
					/* If the rawkey code can be processed, don't translate it: */
					if(message.Code>127) break;
					Handle_keyboard(message.Code);

					ie.ie_Code = message.Code;

					/* Make sure deadkeys and qualifiers are take into account: */
					ie.ie_EventAddress = *((APTR *)message.IAddress);
					ie.ie_Qualifier = message.Qualifier;

					/* Map RAWKEY to ANSI */
					if(MapRawKey(&ie, buffer, sizeof(buffer), NULL) <= 0) break;
					{ register UBYTE code = *buffer;

					/* Space bar inserts the highlighted char in the string gadget: */
					if(code==' ' || code=='\r') goto INS;
 					if(code==27 && IsDraw) goto CL;

					/* ESC hide the interface, whereas SHIFT ESC, quit the programm: */
					if(code==27) cleanup(NULL,IconBase && !(ie.ie_Qualifier&IEQUALIFIER_LSHIFT) && !CloseQuit?-1:0);

					/* Tab, change the sorting method of chars: */
					else if(code=='\t')
						((struct MenuItem *)ItemAddress(menu,MENUITEM(1,5,0)))->Flags ^= CHECKED,
						Handle_menu(26);

					/* Remove the last inputed char or clear all the string gadget: */
					else if(code=='\b' || code==127) {
						register WORD n=(code=='\b'?sti(gad)->NumChars-1:0);
						if(n<0) break;
						StrBuf[n]=0;
						RefreshGList(gad, window, NULL, 1);
					}}
					break;
				case GADGETUP:
					Handle_gadget( (struct Gadget *)message.IAddress );
					break;

				case REFRESHWINDOW:
					/* A minimized window has been poped-up: */
					if(window->Height>window->BorderTop) {
						Init_helpwin(window);
						if(NumChar>=0) Disp_charinfo(NumChar);
						Draw_ASCIIChart();
					}
			}
			/* Has the user hiden the window ? */
			if(window==NULL) goto Deb;
		}
		/* This reduces number of IDCMP MOUSEMOVE msg to process */
		if(drawit) Handle_box();
	}
}
