/********************************************************
**                                                     **
**      $VER: Events.c 2.0 (20 feb 2002)               **
**      Handle user events coming from all sides.      **
**                                                     **
**      © T.Pierron, Free software under terms of      **
**      GNU public license.                            **
**                                                     **
********************************************************/

#include <libraries/gadtools.h>           /* NewMenus and NewGadget */
#include <intuition/intuitionbase.h>      /* To access ActiveScreen */
#include <intuition/screens.h>            /* Screen information */
#include <libraries/commodities.h>        /* Commodity datatypes */
#include <devices/inputevent.h>           /* For raw keymap conversion */
#include <dos/dos.h>                      /* CTRL/C signal */

#include "CharMap.h"
#include "Rawkey.h"
#include "Utility.h"
#define  CATCOMP_STRINGS                  /* We only need strings */
#include "CharMapStrs.h"                  /* Support of locale.library */

extern struct IntuitionBase *IntuitionBase;
extern struct Window *       window;
extern struct Screen *       screen;
extern struct Menu *         menu;
extern struct Gadget *       gads[];
extern struct TextFont *     cmapfont;
extern struct NewMenu *      nm_charset, *nm_fonttype, newmenu[];
extern struct RastPort *     RP;
extern struct IBox           Map;

extern ULONG  BoxTags[], FontTags[];
extern UBYTE  CharsetNum, VertSort, MaxWid, Fh, NumColumns;
static UBYTE  buffer[8];

struct EasyStruct Request = {
	sizeof(struct EasyStruct),0,
	MSG_ABOUT_STR,
	MSG_ABOUTMSG_STR,
	MSG_CONTINUE_STR
};

struct IntuiMessage msgbuf, *msg;

/** Public port of CharMap **/
static struct MsgPort *port = NULL;
static ULONG           sigp = 0;

UBYTE PortName[] = "CHARMAP";
BYTE  IsDraw  = NOT_PRESSED;             /* State of the recessed box */
WORD  NumChar = -1;                      /* Selected character number */
WORD  OldX=0,OldY=0,Xc=0,Yc=0;           /* Box position */

ULONG err_time = 0;

void Handle_gadget( struct Gadget * );

/*** Display an error message in the window's title ***/
void ThrowError( STRPTR Msg )
{
	if(Msg[0] != 127) DisplayBeep(NULL); else Msg++;
	if( window )
	{
		window->UserData = window->Title;
		SetWindowTitles(window, Msg, (STRPTR)-1);

		err_time = 0;
		/* To be sure that mesage will be disappear one day */
		ModifyIDCMP(window, window->IDCMPFlags | IDCMP_INTUITICKS);
	} else puts(Msg);
}

/*** Get options from command line ***/
void ParseCmdLine(int argc, char *argv[])
{
	struct RDArgs *ra;
	ULONG  Options[ MAX_OPTIONS ];

	if( argc > 1 )
	{
		extern UBYTE PrefPath[];

		memset(Options, 0, sizeof(Options));

		if( ra = (APTR) ReadArgs(TEMPLATE, Options, NULL) )
		{
			Parse_geometry( Options[0] ? (STRPTR) Options[0] : (STRPTR) ",,," );
			if(Options[1]) strcpy(PrefPath, (STRPTR) Options[1]);
			if(Options[2]) PEN_INFOWND = * (LONG *) Options[2];
			
			FreeArgs(ra);
		}
	}
}

/*** Move the sunken box while dragging the mouse with LMB down ***/
void Handle_box( WORD X, WORD Y )
{
	extern UBYTE CharWid[];

	/* Check if cusor is inside area */
	if( Map.Left <= X && X < Map.Width  &&
	    Map.Top  <= Y && Y < Map.Height )
	{
		/* Center mouse position on top-left corner of the box */
		X -= (X-Map.Left) % MaxWid;
		Y -= (Y-Map.Top)  % Fh;
	}
	else X = Y = 0;

	/* If it isn't already drawn at that positin */
	if(X != OldX || Y != OldY)
	{
		int i;
		BoxTags[2] = TAG_DONE; SetAPen(RP, 0);

		for( i = 0; i < 2; OldX=X, OldY=Y, i++ )
		{
			if( i == 1 && X )
			{
				BoxTags[2] = GTBB_Recessed;
				SetAPen(RP, PEN_BACKFILL);
				/* Get character ASCII num according to position selected */
				NumChar = VertSort ? ((X-Map.Left)/MaxWid*8)      + (Y-Map.Top)  / Fh :
				                     ((Y-Map.Top) /Fh*NumColumns) + (X-Map.Left) / MaxWid;

				if(CharsetNum > CHARSET_8BITS)                 NumChar += 32;
				if(CharsetNum == CHARSET_AMIGA && NumChar>127) NumChar += 160-128;
				IsDraw = PRESSED;
			}
			if( OldX != 0 )
			{
				UBYTE Char = (CharsetNum==CHARSET_AMIGA && NumChar==127 ? 32:NumChar);
				/* Draw the sunken / beveled box */
				RectFill(RP, OldX+1, OldY+1, OldX+MaxWid-2, OldY+Fh-2);
				SetAPen(RP, i == 1 ? PEN_TEXTFILL : PEN_TEXT);
				Move(RP, OldX+(MaxWid-CharWid[NumChar]>>1), OldY+cmapfont->tf_Baseline+2);
				Text(RP, &Char, 1);

				DrawBevelBoxA(RP,OldX,OldY,MaxWid,Fh,BoxTags);
			}
			else NumChar = -1, IsDraw = OUTSIDE_AREA;
		}
		if( NumChar >= 0 ) Disp_charinfo(NumChar);
	}
}

/*** Move the box using the keyboard ***/
BOOL Handle_rawkey(UWORD code)
{
	/* To prevent that keyboard doesn't disturb the mouse */
	if(IsDraw > 0 || code<UP_KEY || code>LEFT_KEY) return FALSE;
	if(Xc)
	{
		/* Rawkey code for arrow keys: */
		switch(code)
		{
			case    UP_KEY: Yc -= Fh;     break;
			case  DOWN_KEY: Yc += Fh;     break;
			case RIGHT_KEY: Xc += MaxWid; break;
			case  LEFT_KEY: Xc -= MaxWid; break;
		}
		/* Check if cursor is going outside map */
		if(VertSort) {
			/* Increase/decrease x pos, if cursor reach bottom/top of table */
			if(Yc <  Map.Top)    Yc=Map.Height-Fh,    Xc-=MaxWid;
			if(Yc >= Map.Height) Yc=Map.Top,          Xc+=MaxWid;
			if(Xc <  Map.Left)   Xc=Map.Width-MaxWid;
			if(Xc >= Map.Width)  Xc=Map.Left;
		}	else {
			if(Xc <  Map.Left)   Xc=Map.Width-MaxWid, Yc-=Fh;
			if(Xc >= Map.Width)  Xc=Map.Left,         Yc+=Fh;
			if(Yc <  Map.Top)    Yc=Map.Height-Fh;
			if(Yc >= Map.Height) Yc=Map.Top;
		}
	} else
		/* If cursor hasn't been moved yet */
		Xc=Map.Left, Yc=Map.Top;

	Handle_box(Xc, Yc);
	/* To prevent mouse doesn't disturb the keyboard */
	IsDraw = KEYB_CONTROL;
	return TRUE;
}

/*** Insert character into string gadget ***/
void InsertChar( WORD Char )
{
	if( Char > 0 )
	{
		struct StringInfo *st = sti(gads[ GAD_STRTEST ]);

		if( Char == '\b' || Char == 127 )
		{
			WORD n = (Char == '\b' ? st->NumChars-1 : 0);
			if(n < 0) return;
			st->Buffer[ n ] = 0;
		} else {
			/* Shifts buffer if not enough place */
			if(st->NumChars>=st->MaxChars-1) strcpy(st->Buffer, st->Buffer+1);

			buffer[0] = Char; buffer[1] = 0;
			strcat(st->Buffer, buffer);
		}
		RefreshGList(gads[ GAD_STRTEST ], window, NULL, 1);
	}
}

/*** Manage translated keycode ***/
BOOL Handle_vanillakey( void )
{
	static struct InputEvent ie = {0,IECLASS_RAWKEY};

	ie.ie_Code = msgbuf.Code;

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

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

		switch( code )
		{
			case 27: /* Quit the program */
				if( IsDraw ) Handle_box(0, 0), IsDraw=NOT_PRESSED;
				else         cleanup(NULL, 0);
				break;
			case '\t': /* Change the sorting method */
				MENUITEM(1,5,0)->Flags ^= CHECKED;
				Handle_menu(205);
				break;
			case '\b': case 127: /* Remove the last char or clear all */
				InsertChar( code );
				break;
			case ' ': case '\r':
				if( IsDraw ) InsertChar( NumChar );
			default:   /* Show character in the table */
				if( CharsetNum > CHARSET_8BITS )                code -= 32;
				if( CharsetNum > CHARSET_LATIN1 && code > 127 ) code -= 32;
				if( code < 0 ) break;
				if( VertSort ) Xc = Map.Left + (code / 8) * MaxWid,
				               Yc = Map.Top  + (code % 8) * Fh;
				else           Xc = Map.Left + (code % NumColumns) * MaxWid,
				               Yc = Map.Top  + (code / NumColumns) * Fh;
				Handle_box(Xc, Yc);
				IsDraw = KEYB_CONTROL;
				break;
		}
	}
	return TRUE;
}

/** Mark newmenu entries **/
BOOL Check_font_menu( UWORD type )
{
	struct NewMenu * new = &newmenu[NM_FONTTYPE-1];
	
	new += (type < PTYP_CUSTOM ? type : PTYP_CUSTOM);
	
	if( nm_fonttype != new )
	{
		/* Window will be closed, modify directly NewMenus */
		nm_fonttype->nm_Flags &= ~CHECKED; nm_fonttype = new;
		nm_fonttype->nm_Flags |=  CHECKED;
		return TRUE;
	}
	return FALSE;
}

BOOL Register_font( UWORD type )
{
	/* Keepp track of selected menu */
	extern WORD fontType; fontType = type;
	/* Already choose this menu? */
	if( Check_font_menu(type) || type >= PTYP_CUSTOM )
 	{
		struct TextFont *textfont;

		if( textfont = Get_pref_font( type ) )
		{
			/* Close old opened font */
			CloseFont( cmapfont );
			cmapfont = textfont;

			/* Rebuilds font cycle gadget list */
			Font_list_to_table();
		}
		else return FALSE;
	}
	return TRUE;
}

/*** Set NewMenus according to charset number ***/
BOOL Register_charset( UWORD type )
{
	struct NewMenu * new = &newmenu[ NM_CHARSET + type ];
	if( nm_charset != new )
	{
		nm_charset->nm_Flags &= ~CHECKED; nm_charset = new;
		nm_charset->nm_Flags |=  CHECKED; CharsetNum = type;
		return TRUE;
	}
	return FALSE;
}

/*** Processes menus events: ***/
BOOL Handle_menu( LONG MenuID )
{
	if( MenuID >= NMUD_FONT )
	{
		if( Register_font( MenuID - NMUD_FONT + 1 ) )
		{
			/* Change the charset depending of the font */
			Register_charset( CharsetNum );
			goto reset_window;
		}
		else /* Failed to open font / user cancel */
		{
			/* Cancel the menu selection */
			MENUITEM(0,0,2)          ->Flags &= ~CHECKED;
			MENUITEM(0,0,FontTags[3])->Flags |=  CHECKED;

			if(msgbuf.Class == IDCMP_GADGETUP)
				GT_SetGadgetAttrsA(gads[ GAD_FONTTYPE ], window, NULL, FontTags);
		}
	}
	else switch( MenuID )
	{
		/**** Project Menu ****/

		case 1020: goto reset_window;
		case 1021: /* Charset */
		case 1022:
		case 1023:
			if( Register_charset( MenuID - 1021 ) )
			{
				/* Reopen the window */
				reset_window: cleanup(NULL,-1); OldX = Xc = 0;
				/* If init failed, try to see if we can changed the font again */
				if( Setup() == FALSE )
				{
					/* Ask user for a new font */
					while( Register_font( PTYP_CUSTOM ) == TRUE && Setup() == FALSE );
					if( window == NULL ) cleanup(0,0);
				}
				return TRUE;
			}
			break;

		case 103: /* About */
			EasyRequest(window, &Request, 0, CHARMAP_VERSION);
			break;
		case 104: /* Minimize/Normalize the window */
			ZipWindow(window);
			break;
		case 105: /* Quit */
			cleanup(NULL,0);

		/**** Edit Menu ****/

		case 201: Handle_gadget( gads[ GAD_COPY ] );
		case 204: /* Clear text contained in the string gadget */
			GT_SetGadgetAttrs(gads[ GAD_STRTEST ], window, NULL, GTST_String, "", TAG_DONE);
			break;
		case 202: /* Copy */
			Handle_gadget( gads[ GAD_COPY ] );
			break;
		case 203: /* Paste */
			Handle_gadget( gads[ GAD_PASTE ] );
			break;
 		case 205: /* Vertical/horizontal sort */
			VertSort = (MENUITEM(1,5,0)->Flags & CHECKED) ? 1:0;
			IsDraw   = NOT_PRESSED; OldX = 0;
			Draw_ASCIIChart();
			break;
		case 206: /* Save settings */
			Save_pref();
	}
	return FALSE;
}

/*** Handle gadget messages ***/
void Handle_gadget( struct Gadget *G )
{
	switch(G->GadgetID)
	{
		case GAD_DEL: /* Clear everything in the string gadget */
			Handle_menu(204);
			break;
		case GAD_COPY: /* Copy string into clipboard */
			CBWriteFTXT( sti(gads[ GAD_STRTEST ])->Buffer );
			break;
		case GAD_PASTE: /* Read string from clipboard */
		{	struct StringInfo * st = sti(gads[ GAD_STRTEST ]);
			CBReadCHRS( st->Buffer, st->MaxChars-1 );
			RefreshGList(gads[ GAD_STRTEST ], window, NULL, 1);
		}	break;
		case GAD_FONTTYPE: /* Type of a font */
			Handle_menu( Translate_code( msgbuf.Code ) );
			break;
		case GAD_CHARSET: /* Choose a different Charset via the cycle gadget */
			Handle_menu(1021+msgbuf.Code);
			break;
	}
}

/*** Manage mouse movement while pressing lmb ***/
void Handle_mouse( void )
{
	switch( msgbuf.Code )
	{
		/* Draw a recessed box below the mouse */
		case SELECTDOWN:
			Handle_box(msgbuf.MouseX, msgbuf.MouseY);
			if( IsDraw == OUTSIDE_AREA ) IsDraw = NOT_PRESSED;
			break;
		/* Clear the box and add the char. in string gadget */
		case SELECTUP:
			if( IsDraw == NOT_PRESSED ) break;
			InsertChar(NumChar);
		case MENUHOT:
			Xc=OldX; Yc=OldY; Handle_box(0, 0); IsDraw=NOT_PRESSED;
	}
}

/** Look if CharMap is already running **/
BOOL find_charmap( void )
{
	struct MsgPort * reply;

	if( ( port = (struct MsgPort *) CreateMsgPort() ) )
	{
		sigp = 1 << port->mp_SigBit;
		if( (reply = (struct MsgPort *) FindPort(PortName)) )
		{
			struct Message * msg;
			if( ( msg = (APTR) CreateIORequest(port, sizeof (*msg)) ) )
			{
				/* Tell CharMap that someone tries to start it again */
				PutMsg(reply, msg);
				/* Packet is associated with "port", thus reply will be done here */
				Wait( sigp | SIGBREAKF_CTRL_C );
				/* Unqueue message */
				GetMsg( port );
				DeleteIORequest( (struct IORequest *)msg );
				return TRUE;
			}
		}
		else /* No public port created yet, so do it now */
		{
			/* Set this port public */
			port->mp_Node.ln_Name = PortName;
			port->mp_Node.ln_Pri  = 0;
			AddPort( port );
		}
	}
	return FALSE;
}

/** Shutdown port **/
void close_port( void )
{
	if( port )
	{
		if(port->mp_Node.ln_Name) RemPort(port);
		DeleteMsgPort(port);
	}
}

/*** Handle events coming from public port ***/
void Handle_port( void )
{
	while( msg = (APTR) GetMsg( port ) )
	{
		ReplyMsg( msg );
		if( screen != IntuitionBase->ActiveScreen )
		{
			screen = IntuitionBase->ActiveScreen;
			Add_pref_font(screen->RastPort.Font, PTYP_SCREENFONT, CHARSET_NOCARE);
			Handle_menu( 1020 );
			ScreenToFront( screen );
		}
		else cleanup(NULL, 0); /* Simply quit */
	}
}

/*** Main events dispatcher loop ***/
void Handle_input(void)
{
	extern ULONG sigwin;
	ULONG  sigrcvd;
	BOOL   drawit;

	for(;;)
	{
		sigrcvd = Wait(SIGBREAKF_CTRL_C | sigwin | sigp);

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

		else if( sigrcvd & sigp ) Handle_port();

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

		drawit=FALSE;
		/* Use the new GadTools GT_GetIMsg() function to get input events */
		while (msg = (APTR) GT_GetIMsg(window->UserPort))
		{
			CopyMem(msg, &msgbuf, sizeof(msgbuf));

			if( msgbuf.Class == IDCMP_MENUVERIFY && IsDraw >= PRESSED &&
			    msgbuf.Code  == MENUHOT )
			{
				/* Just cancel cursor movement */
				msg->Code    = MENUCANCEL;
				msgbuf.Class = IDCMP_MOUSEBUTTONS;
			}

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

			switch(msgbuf.Class)
			{
				case IDCMP_CLOSEWINDOW:  cleanup(0, 0);
				case IDCMP_MOUSEBUTTONS: Handle_mouse(); break;
				case IDCMP_MOUSEMOVE:    if(IsDraw > 0) drawit=TRUE; break;
				case IDCMP_MENUPICK:
				{	struct MenuItem *menuitem;

					while (msgbuf.Code != MENUNULL)
					{
						menuitem = (struct MenuItem *) ItemAddress(menu,msgbuf.Code);
						if( Handle_menu( (ULONG) GTMENUITEM_USERDATA(menuitem) ) )
							break;
						msgbuf.Code = menuitem->NextSelect;
					}
				}	break;
				case IDCMP_RAWKEY:
					/* If the rawkey can be directly processed, doesn't translate it */
					if(msgbuf.Code < 0x80 && !Handle_rawkey(msgbuf.Code))
						Handle_vanillakey();
					break;
				case IDCMP_GADGETUP:
					Handle_gadget( (struct Gadget *)msgbuf.IAddress );
					break;
				case IDCMP_INTUITICKS:
					/* An error message which needs to be removed? */
					if(err_time == 0) err_time = msgbuf.Seconds;
					if(err_time && msgbuf.Seconds-err_time>4)
					{
						SetWindowTitles(window, window->UserData, (STRPTR)-1);

						/* INTUITICKS aren't required anymore */
						ModifyIDCMP(window, window->IDCMPFlags & ~IDCMP_INTUITICKS);
					}
					break;

				case IDCMP_REFRESHWINDOW:
					/* A minimized window has been poped-up */
					GT_BeginRefresh( window );
					if(window->Height > window->BorderTop)
					{
						Init_helpwin(window);
						if(NumChar >= 0) Disp_charinfo(NumChar);
						Draw_ASCIIChart();
					}
					GT_EndRefresh( window, TRUE );
			}
		}
		/* This reduces number of IDCMP MOUSEMOVE msg to process */
		if(drawit) Handle_box(msgbuf.MouseX, msgbuf.MouseY);
	}
}
