
/*
 * KEYS.C
 * (c) 1992 J.Harper
 */

#include "jed.h"
#include "jed_protos.h"

Prototype   VALUE * cmd_bind	    (LONG, VALUE *);
Prototype   VALUE * cmd_unbind	    (LONG, VALUE *);
Prototype   VOID    killbindings    (VOID);
Local	    STRPTR  evalbinding	    (UBYTE, UWORD);
Prototype   VOID    usekey	    (struct IntuiMessage *);
Local	    ULONG   descrtoraw	    (STRPTR);
Local	    BOOL    cmpquals	    (UWORD, UWORD);

#define HARDCODE_CURSOR

#define UNPAIREDQUALS (IEQUALIFIER_CONTROL | IEQUALIFIER_NUMERICPAD | \
    IEQUALIFIER_MIDBUTTON | IEQUALIFIER_RBUTTON | IEQUALIFIER_LEFTBUTTON)
#define SHIFTQUALS (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)
#define ALTQUALS   (IEQUALIFIER_LALT | IEQUALIFIER_RALT)
#define CMDQUALS   (IEQUALIFIER_LCOMMAND | IEQUALIFIER_RCOMMAND)

typedef struct KEY
{
    struct KEY	 *ky_Next;
    UWORD	  ky_Qual;
    STRPTR	  ky_Cmd;
} KEY;

Local KEY *KeyHash[128];

typedef struct KEYDESC
{
    STRPTR	  kd_Name;
    BOOL	  kd_IsCode;
    UWORD	  kd_Value;
} KEYDESC;

Local const KEYDESC KeyDescr[] =
{
    "SHIFT",      FALSE, SHIFTQUALS,
    "ALT",        FALSE, ALTQUALS,
    "COMMAND",    FALSE, CMDQUALS,
    "AMIGA",      FALSE, CMDQUALS,
    "CONTROL",    FALSE, IEQUALIFIER_CONTROL,
    "CTRL",       FALSE, IEQUALIFIER_CONTROL,
    "NUMERICPAD", FALSE, IEQUALIFIER_NUMERICPAD,
    "LMB",        FALSE, IEQUALIFIER_LEFTBUTTON,
    "MMB",        FALSE, IEQUALIFIER_MIDBUTTON,
    "RMB",        FALSE, IEQUALIFIER_RBUTTON,

    "SPACE",      TRUE,  0x40,
    "BACKSPACE",  TRUE,  0x41,
    "TAB",        TRUE,  0x42,
    "ENTER",      TRUE,  0x43,
    "RETURN",     TRUE,  0x44,
    "ESC",        TRUE,  0x45,
    "ESCAPE",     TRUE,  0x45,
    "DEL",        TRUE,  0x46,
    "DELETE",     TRUE,  0x46,
    "HELP",       TRUE,  0x5f,
    "UP",         TRUE,  0x4c,
    "DOWN",       TRUE,  0x4d,
    "RIGHT",      TRUE,  0x4e,
    "LEFT",       TRUE,  0x4f,
    "F1",         TRUE,  0x50,
    "F2",         TRUE,  0x51,
    "F3",         TRUE,  0x52,
    "F4",         TRUE,  0x53,
    "F5",         TRUE,  0x54,
    "F6",         TRUE,  0x55,
    "F7",         TRUE,  0x56,
    "F8",         TRUE,  0x57,
    "F9",         TRUE,  0x58,
    "F10",        TRUE,  0x59,
    NULL, 0, 0
};

/*
 * Structure to fix problems with binding to numeric pad keys.
 * actually this may not work properly on some weird keymaps.
 */
typedef struct NUMDESC
{
    STRPTR	  nd_Name;	    /* char to map */
    UBYTE	  nd_TypewriterKey; /* rawkey for typewriter */
    UBYTE	  nd_TypewriterQual;
    UBYTE	  nd_NumericKey;    /* rawkey for numeric */
} NUMDESC;

Local const NUMDESC NumDescr[] =
{
    "(", 0x09, SHIFTQUALS, 0x5c,
    ")", 0x0a, SHIFTQUALS, 0x5b,
    "/", 0x3a, 0,          0x5c,
    "*", 0x08, SHIFTQUALS, 0x5d,
    "7", 0x07, 0,          0x3d,
    "8", 0x08, 0,          0x3e,
    "9", 0x09, 0,          0x3f,
    "-", 0x0b, 0,          0x4a,
    "4", 0x04, 0,          0x2d,
    "5", 0x05, 0,          0x2e,
    "6", 0x06, 0,          0x2f,
    "+", 0x0c, SHIFTQUALS, 0x5e,
    "1", 0x01, 0,          0x1d,
    "2", 0x02, 0,          0x1e,
    "3", 0x03, 0,          0x1f,
    "0", 0x0a, 0,          0x0f,
    ".", 0x39, 0,          0x3c,
    0, 0, 0, 0
};

/*
 * (bind {`keyDescr' `command'})
 */
VALUE *
cmd_bind(LONG argc, VALUE *argv)
{
    VALUE *res = &RES;
    BOOL rc = TRUE;
    while(rc && (argc >= 2))
    {
	if(TPLATE2(VTF_STRING, VTF_STRING))
	{
	    KEY *newkey;
	    LONG keyqual;

	    if(newkey = AllocVec(sizeof(KEY), MEMF_CLEAR))
	    {
		if(((keyqual = descrtoraw(ARG1.val_Value.String)) != -1) && ((UBYTE)keyqual < IECODE_UP_PREFIX))
		{
		    newkey->ky_Qual = (WORD)(keyqual >> 16);
		    if(newkey->ky_Cmd = savestring(ARG2.val_Value.String))
		    {
			newkey->ky_Next = KeyHash[(UBYTE)keyqual];
			KeyHash[(UBYTE)keyqual] = newkey;
		    }
		    else
		    {
			settitle(NoMemMsg);
			FreeVec(newkey);
			rc = FALSE;
		    }
		}
		else
		{
		    FreeVec(newkey);
		    rc = FALSE;
		}
	    }
	    else
	    {
		settitle(NoMemMsg);
		rc = FALSE;
	    }
	}
	argc -= 2;
	argv += 2;
    }
    res->val_Type = VTF_NUMBER;
    res->val_Value.Number = rc;
    return(res);
}

/*
 * (unbind {`keyDescr'})
 */
VALUE *
cmd_unbind(LONG argc, VALUE *argv)
{
    VALUE *res = &RES;
    BOOL rc = TRUE;
    while(rc && (argc >= 1))
    {
	if(TPLATE1(VTF_STRING))
	{
	    LONG keyqual;
	    if(keyqual = descrtoraw(ARG1.val_Value.String))
	    {
		KEY *thiskey, *prevkey = NULL;
		UBYTE index = (UBYTE)keyqual;
		if(thiskey = KeyHash[index])
		{
		    while(thiskey)
		    {
			if(thiskey->ky_Qual == (WORD)(keyqual >> 16))
			    break;
			prevkey = thiskey;
			thiskey = thiskey->ky_Next;
		    }
		    if(thiskey)
		    {
			if(prevkey)
			    prevkey->ky_Next = thiskey->ky_Next;
			else
			    KeyHash[index] = thiskey->ky_Next;
			freestring(thiskey->ky_Cmd);
			FreeVec(thiskey);
		    }
		}
		else
		{
		    settitle("error: key not bound to anything");
		    rc = FALSE;
		}
	    }
	}
	argc--;
	argv++;
    }
    res->val_Type = VTF_NUMBER;
    res->val_Value.Number = rc;
    return(res);
}

VOID
killbindings(VOID)
{
    LONG i;
    for(i = 0; i < 128; i++)
    {
	KEY *thiskey;
	thiskey = KeyHash[i];
	while(thiskey)
	{
	    KEY *nextkey = thiskey->ky_Next;
	    freestring(thiskey->ky_Cmd);
	    FreeVec(thiskey);
	    thiskey = nextkey;
	}
	KeyHash[i] = NULL;
    }
}

Local STRPTR
evalbinding(UBYTE key, UWORD qual)
{
    if(key < IECODE_UP_PREFIX)
    {
	KEY *thiskey;
	if(thiskey = KeyHash[key])
	{
	    while(thiskey)
	    {
		if(cmpquals(thiskey->ky_Qual, qual))
		    break;
		thiskey = thiskey->ky_Next;
	    }
	    if(thiskey)
		return(thiskey->ky_Cmd);
	}
    }
    return(FALSE);
}

VOID
usekey(struct IntuiMessage *im)
{
    UWORD key = im->Code;
    UWORD qual = im->Qualifier;
    STRPTR cmd;

    qual &= ~(IEQUALIFIER_REPEAT | IEQUALIFIER_INTERRUPT | IEQUALIFIER_MULTIBROADCAST | IEQUALIFIER_RELATIVEMOUSE);
    resettitle();
    cursor(OFF);
    if(cmd = evalbinding(key, qual))
    {
	VALUE result;
	execstring(cmd, &result);
	releasevalue(&result);
    }
    else
    {
#ifdef HARDCODE_CURSOR
	switch(key)
	{
	    case 0x4c:
		if(!(qual & ~IEQUALIFIER_CAPSLOCK))
		    moveuplines(1);
		else if(qual & SHIFTQUALS)
		    moveuppages(1);
		else if(qual & IEQUALIFIER_CONTROL)
		    movesof();
		break;
	    case 0x4d:
		if(!(qual & ~IEQUALIFIER_CAPSLOCK))
		    movedownlines(1);
		else if(qual & SHIFTQUALS)
		    movedownpages(1);
		else if(qual & IEQUALIFIER_CONTROL)
		    moveeof();
		break;
	    case 0x4e:
		if(!(qual & ~IEQUALIFIER_CAPSLOCK))
		    moveright(1);
		else if(qual & SHIFTQUALS)
		    moveeol();
		else if(qual & IEQUALIFIER_CONTROL)
		    movenextword(1);
		break;
	    case 0x4f:
		if(!(qual & ~IEQUALIFIER_CAPSLOCK))
		    moveleft(1);
		else if(qual & SHIFTQUALS)
		    movesol();
		else if(qual & IEQUALIFIER_CONTROL)
		    moveprevword(1);
		break;
	    default:
#endif
		UBYTE buff[256];
		LONG len;
		struct InputEvent ie;

		ie.ie_Class = IECLASS_RAWKEY;
		ie.ie_SubClass = 0;
		ie.ie_Code = key;
		ie.ie_Qualifier = qual;
		ie.ie_EventAddress = *((APTR *)im->IAddress);
		if((len = MapRawKey(&ie, buff, 255, NULL)) > 0)
		{
		    VW *vw = CurrVW;
		    setrefresh(RFF_LINEFROM, vw->vw_CursorPos.pos_Col, vw->vw_CursorPos.pos_Line);
		    buff[len] = 0;
		    padcursor();
		    if((vw->vw_Prefs.prf_WordWrap) && ((vw->vw_CursorPos.pos_Col + len) > vw->vw_Prefs.prf_RightMargin))
			wrapline();
		    insertstrn(buff, len);
		    resyncxy();
		    vw->vw_Tx->tx_Changes++;
		}
		else if(len < 0)
		    settitle("error: key buffer screwup");
#ifdef HARDCODE_CURSOR
		break;
	}
#endif
	refresh();
    }
    cursor(ON);
}

/*
 * Gets a packed set of rawkey and qualifier from an input description.
 * Qualifier is high 16 bits, rawkey is low 16. Returns -1 if an error.
 */
Local ULONG
descrtoraw(STRPTR desc)
{
    STRPTR str = desc;
    UWORD qual = 0;

    for(;;)
    {
	UBYTE buff[100];
	KEYDESC *kd = KeyDescr;

	str = stpblk(cpynotspc(buff, str));
	while(kd->kd_Name)
	{
	    if(!stricmp(kd->kd_Name, buff))
	    {
		if(!kd->kd_IsCode)
		{
		    qual |= kd->kd_Value;
		    break;
		}
		else
		    return(((ULONG)qual << 16) | ((ULONG)kd->kd_Value));
	    }
	    kd++;
	}
	if(!kd->kd_Name)
	{
	    NUMDESC *nd = NumDescr;
	    UBYTE coded[2];
	    while(nd->nd_Name)
	    {
		if(!strcmp(nd->nd_Name, buff))
		{
		    if(qual & IEQUALIFIER_NUMERICPAD)
			return(((ULONG)qual << 16) | ((ULONG)nd->nd_NumericKey));
		    else
		    {
			qual |= nd->nd_TypewriterQual;
			return(((ULONG)qual << 16) | ((ULONG)nd->nd_TypewriterKey));
		    }
		}
		nd++;
	    }
	    if(MapANSI(buff, strlen(buff), coded, 1, NULL) == 1)
	    {
		qual |= (UWORD)coded[1];
		return(((ULONG)qual << 16) | ((ULONG)coded[0]));
	    }
	    settitlefmt("error: invalid key description %s", (LONG)desc);
	    return(-1);
	}
    }
}

/*
 * I'm sure there's an easier way to compare qualifiers (treating the paired
 * keys (eg lshift and rshift) as equivalent), however, I can't think of it.
 */
Local BOOL
cmpquals(UWORD qual1, UWORD qual2)
{
    if((qual1 & UNPAIREDQUALS) == (qual2 & UNPAIREDQUALS))
    {
	UWORD res1, res2;
	res1 = qual1 & SHIFTQUALS;
	res2 = qual2 & SHIFTQUALS;
	if((!res1 && !res2) || (res1 && res2))
	{
	    res1 = qual1 & ALTQUALS;
	    res2 = qual2 & ALTQUALS;
	    if((!res1 && !res2) || (res1 && res2))
	    {
		res1 = qual1 & CMDQUALS;
		res2 = qual2 & CMDQUALS;
		if((!res1 && !res2) || (res1 && res2))
		    return(TRUE);
	    }
	}
    }
    return(FALSE);
}

