/*
 * CKMKE2.C
 *
 * Key-Config is a keyboard configurator program for use with Columbia's
 * MacKermit.
 *
 * Bill Schilit, April 1985
 *
 *
 * Copyright (C) 1985, Trustees of Columbia University in the City of
 * New York.  Permission is granted to any individual or institution to
 * use, copy, or redistribute this software so long as it is not sold
 * for profit, provided this copyright notice is retained.
 *
 */

#include "mac/quickdraw.h"		/* order does matter */
#include "mac/osintf.h"			/*  on these... */
#include "mac/toolintf.h"
#include "mac/packintf.h"
#include "ckmkey.h"
#include "ckmkkc.h"
#include <ctype.h>

Rect kbbox,smallbox;

int keymods = 0,			/* current modifiers from keyboard  */
    butmods = 0,			/* current modifiers from button */
    keysel = KC_NUN,			/* selected key */
    kbselected = TRUE;			/* kb or text is active */

struct KEYCODE {
  int kcode;				/* key code */
  int krow;				/* row and column on keyboard */
  int kcol;
  int ksize;				/* horizontal key size */
  int khpos;				/* horizontal position */
  char *kname;				/* key name */
  Rect krect;
} keycodes[] = {
  {0x32,1,1,R1,0,"`"},{0x12,1,2,R1,R1,"1"},     	    /* `1 */
  {0x13,1,3,R1,2*R1,"2"},{0x14,1,4,R1,3*R1,"3"},	    /* 23 */
  {0x15,1,5,R1,4*R1,"4"},{0x17,1,6,R1,5*R1,"5"}, 	    /* 45 */
  {0x16,1,7,R1,6*R1,"6"},{0x1A,1,8,R1,7*R1,"7"},	    /* 67 */
  {0x1C,1,9,R1,8*R1,"8"},{0x19,1,10,R1,9*R1,"9"},     	    /* 89 */
  {0x1D,1,11,R1,10*R1,"0"},{0x1B,1,12,R1,11*R1,"-"}, 	    /* 0- */
  {0x18,1,13,R1,12*R1,"="},{0x33,1,14,R2,13*R1,"Backspace"}, /* =<BS> */

  {0x30,2,1,R2,0,"Tab"},{0x0C,2,2,R1,R2,"Q"},	    	    /* <TAB>Q */
  {0x0D,2,3,R1,R1+R2,"W"},{0x0E,2,4,R1,2*R1+R2,"E"},	    /* WE */
  {0x0F,2,5,R1,3*R1+R2,"R"},{0x11,2,6,R1,4*R1+R2,"T"},      /* RT */
  {0x10,2,7,R1,5*R1+R2,"Y"},{0x20,2,8,R1,6*R1+R2,"U"},      /* YU */
  {0x22,2,9,R1,7*R1+R2,"I"},{0x1F,2,10,R1,8*R1+R2,"O"},	    /* IO */
  {0x23,2,11,R1,9*R1+R2,"P"},{0x21,2,12,R1,10*R1+R2,"["},   /* P[ */
  {0x1E,2,13,R1,11*R1+R2,"]"},{0x2A,2,14,R1,12*R1+R2,"\\"},  /* ]\ */

  {KC_KLK,3,1,R3,0,"Caps lock"},			    /* <CAPS LOCK> */
  {0x00,3,2,R1,R3,"A"},{0x01,3,3,R1,R1+R3,"S"},		    /* AS */
  {0x02,3,4,R1,2*R1+R3,"D"},{0x03,3,5,R1,3*R1+R3,"F"},	    /* DF */
  {0x05,3,6,R1,4*R1+R3,"G"},{0x04,3,7,R1,5*R1+R3,"H"},      /* GH */
  {0x26,3,8,R1,6*R1+R3,"J"},{0x28,3,9,R1,7*R1+R3,"K"},      /* JK */
  {0x25,3,10,R1,8*R1+R3,"L"},{0x29,3,11,R1,9*R1+R3,";"},    /* L; */
  {0x27,3,12,R1,10*R1+R3,"'"},{0x24,3,13,R3,11*R1+R3,"Return"},  /* '<RET> */

  {KC_SHF,4,1,R4,0,"Shift"},				    /* <SHIFT> */
  {0x06,4,2,R1,R4,"Z"},{0x07,4,3,R1,R1+R4,"X"},  	    /* ZX */
  {0x08,4,4,R1,2*R1+R4,"C"},{0x09,4,5,R1,3*R1+R4,"V"},	    /* CV */
  {0x0B,4,6,R1,4*R1+R4,"B"},{0x2D,4,7,R1,5*R1+R4,"N"},      /* BN */
  {0x2E,4,8,R1,6*R1+R4,"M"},{0x2B,4,9,R1,7*R1+R4,","},      /* M, */
  {0x2F,4,10,R1,8*R1+R4,"."},{0x2C,4,11,R1,9*R1+R4,"/"},    /* ./ */
  {KC_SHF,4,12,R4,10*R1+R4,"Shift"},		 	    /* <SHIFT> */
  
  {KC_OPT,5,1,R1,R1,"Option"},				    /* <OPTION> */
  {KC_CMD,5,2,R2,2*R1,"Command"},			    /* <CMD> */
  {0x31,5,3,R5,2*R1+R2,"Space"},			    /* <SPACE> */
  {0x34,5,4,R2,2*R1+R2+R5,"Enter"},   			    /* <ENTER> */
  {KC_OPT,5,5,R1,2*R1+2*R2+R5,"Option"}			    /* <OPTION> */
};

#define NKEYS (sizeof(keycodes)/sizeof(struct KEYCODE))

Rect scoderects[MAX_SCODE+1];		/* rectanges for scan codes */


/* myfilter - filter events */

myfilter()
{
  struct {
    short *itemhit;			/* (in stack order) */
    EventRecord *theevent;    
    DialogPtr thedialog;
  } args;
  char *retval; 
  int newmods,
      newkey = KC_NUN;
  Point localpt;

  retval = (char *) getpargs(&args,sizeof(args));
  *retval = FALSE;			/* modal can deal with it */   
 
/* check for events which we do updates on */
 
  newmods = keymapmods();		/* set from modifiers from key map */

  switch (args.theevent->what) {
    
    case mouseDown:			/* check for mouse in KB */
      localpt = args.theevent->where;	/* copy the point */
      GlobalToLocal(&localpt);		/* make it local */  
      if (PtInRect(&localpt,&kbbox))	/* in our KB picture? */
	newkey = mousekb(&localpt,	/* yes set butmod and newkey */   
	      args.theevent->when); 	
      break;
 
    case keyDown:			/* key down event */
      if (kbselected)			/* do we want to handle it? */
      {					/* yes, KB is selected */
	*retval = TRUE;			/* so do not let modal do it */
	*args.itemhit = SETKD_KEYB;	/* ... */
	newmods = 			/* get key modifiers */       
	    args.theevent->modifiers & 
	    (alphaLock | cmdKey | optionKey | shiftKey);
	newkey = (args.theevent->message & keyCodeMask) >> 8;  /* and key */
      }
      break;
  }    

  newmods |= butmods;			/* include those set by button */

/* check for ambigous key stroke */
 
  if ((newmods & (*kshdl)->ctrlmods) &&	/* both ctrl and */
      (newmods & (*kshdl)->caplmods))	/*  caps lock? */
  { 
   SysBeep(1);				/* yes... */
   butmods = 0;				/* clear these out */
   *args.itemhit = 0;			/* ignore it... */
   return;				/* ugh */
  }
  
  if (keymods != newmods)		/* change in modifiers? */
  {
    keymods = newmods;			/* set new mod flags */
    drawkeychars(keymods);		/* do update on character set */
    hilitemods(keymods);		/* draw highlighted mod keys */
    if (keysel != KC_NUN)		/* something to keep hi-lited? */
     hilitekey(keysel);			/* yes... do it */
  }

  if (newkey != keysel && 		/* change in selected key? */
      newkey != KC_NUN)			/* and something selected? */
  {
    if (keysel != KC_NUN)		/* was there a previous? */
     hilitekey(keysel);			/* yes, so de-hilite it */
    keysel = newkey;			/* set current key */
    if (keysel != KC_NUN)		/* selected something good? */
     hilitekey(keysel);			/* yes, so hilite it */
  }
}


long lastwhen = 0;
int lastkeyc = KC_NUN;


/* mousekb - mouse down occured inside our KB item */

mousekb(pt,when)
Point *pt;
long when;
{
  int doubletime,
      twoclick,
      keyc = KC_NUN,
      modv;
  int k;
  
  doubletime = (SysPPtr->volClik >> 4) & 0x0f;
  doubletime *= 4;
  twoclick = ((when - lastwhen) <= doubletime);
  lastwhen = when;			/* remember last time */
  
  for (k = 0; k<NKEYS; k++)
    if (PtInRect(pt,&keycodes[k].krect)) /* found the KB key? */
    {
     keyc = keycodes[k].kcode;		/* here is the key code */
     break;				/* yes, it's in k */
    }

  if (keyc == KC_NUN)			/* not inside a key */
  {
    SysBeep(1);
    return(KC_NUN);
  }
  
  if (twoclick && (lastkeyc == keyc))	/* double click on it? */
   switch (keyc)			/* yes... maybe enter dlog */
   {
     case KC_CMD:
     case KC_SHF:
     case KC_KLK:
     case KC_OPT:
       dosetmkdialog();			/* enter the setup dialog */
       InvalRect(&kbbox);		/* cause entire update */
       return(KC_NUN);
     default:
       SysBeep(1);			/* can't open others */
       return(KC_NUN);
   }

   lastkeyc = keyc;			/* remember the key */   
   modv = keycodemods(keyc);		/* get modifier value if any */
   if (modv == 0)
    return(keyc);			/* return the key */
   butmods ^= modv;			/* toggle the button modifiers */
   return(KC_NUN);
}


initkeyrects()
{
  int k;
  Rect *kr;

  for (k=0; k < NKEYS; k++)
  {
    kr = &scoderects[keycodes[k].kcode];
    keyrect(&keycodes[k],kr,0);
    keyrect(&keycodes[k],&keycodes[k].krect,3);
  }
}
    
keyrect(k,kr,inset)
struct KEYCODE *k;
Rect *kr;
{
 SetRect(kr,0,0,k->ksize,K_HEIGHT);	    /* form key size */
 OffsetRect(kr,k->khpos,(k->krow-1)*K_HEIGHT); /* offset */
 InsetRect(kr,inset,inset);		/* make it fit per caller var */
 OffsetRect(kr,kbbox.left,kbbox.top); 	/* offset to our region */
} 


/* keymapmods */
 
#define KM_CMD 0x8000			/* KeyMap bit for cmdKey */
#define KM_SHF 0x1			/* KeyMap bit for shiftKey */
#define KM_KLK 0x2			/* KeyMap bit for alphaLock */
#define KM_OPT 0x4			/* KeyMap bit for optionKey */

int keymapmods() 
{
  KeyMap km;
  register int mods;
  register long kmw2;

  GetKeys(&km);
  mods = 0;  
  kmw2 = km.kmap[1];			/* all bits are in second word */
  if (kmw2 & KM_SHF) 
    mods |= shiftKey;
  if (kmw2 & KM_OPT) 
    mods |= optionKey;
  if (kmw2 & KM_CMD) 
    mods |= cmdKey;
  if (kmw2 & KM_KLK) 
    mods |= alphaLock;
  return(mods);
}


drawkeychars(mods)
{
  FontInfo fi;  
  char *kn,*namekey(),*namefkey();
  BOOL ismeta,isfkey;
  int k,vs,hs,kval;
  Rect *kr;

  TextFont(geneva);			/* has ok 9 pt system font */
  TextSize(9);				/* small font */
  GetFontInfo(&fi);			/* info for descent+ascent */
  
  vs = (fi.descent+fi.ascent-K_HEIGHT)/2; /* vertical space for centering */

  for (k=0; k<NKEYS; k++)
  {
    kr = &keycodes[k].krect;
    EraseRect(kr);			/* clean out old value */
    TextFace(boldStyle);      
    switch (keycodes[k].kcode)
    {
      case KC_CMD: kn = "Cmd"; break;	/* apple key */
      case KC_OPT: kn = "Opt"; break;
      case KC_SHF: kn = "Shift"; break;
      case KC_KLK: kn = "Cap Lock"; break;
      default:
      	kval = mapkey(keycodes[k].kcode,mods,&ismeta,&isfkey);
	if (!isfkey)			/* is it a function key? */
	 TextFace(0);			/* no, use normal font */
      	kn = namekey(kval,ismeta,isfkey,FALSE); /* give it a name */
	break;
    }
    hs = (keycodes[k].ksize - StringWidth(kn))/2; /* leading space */
    MoveTo(kr->left+hs-2,kr->bottom+vs); /* position the pen */
    DrawString(kn);				  /* draw it */
  }
  TextSize(0);				/* back to default */
  TextFace(0);
  TextFont(0);
}

PicHandle kbpic = NULL;

drawkeyboard(r)
Rect *r;
{
  int k;
  Rect kr;

  if (kbpic == NULL)
  {
    kbpic = OpenPicture(&kbbox);	/* open a picture */
    for (k=0; k<NKEYS; k++)		/* for all keys */
    {
      keyrect(&keycodes[k],&kr,1);
      FrameRoundRect(&kr,6,6);
    }
    ClosePicture();			/* all done */
  }
  HLock((Handle) kbpic);
  DrawPicture(kbpic,r);			/* draw the picture */
  HUnlock((Handle) kbpic);
}

hilitekey(kcode)
int kcode;
{
 int k;
 Rect kr;

 for (k=0; k<NKEYS; k++)
  if (keycodes[k].kcode == kcode)
  {
    keyrect(&keycodes[k],&kr,3);
    InvertRect(&kr);
    if (keycodes[k].kcode != KC_SHF &&	/* want to hilite another? */
      	keycodes[k].kcode != KC_OPT)	/* shift and option are two keys */
    return;
  }
}

hilitemods(mods)
{
 if (mods & optionKey)
   hilitekey(KC_OPT);
 if (mods & cmdKey)
   hilitekey(KC_CMD);
 if (mods & alphaLock)
   hilitekey(KC_KLK);
 if (mods & shiftKey)
   hilitekey(KC_SHF);
}

/* namekey - Given an 8 bit character value returns a string containing
 *    	     the name for the key. If lform is TRUE names are returned as:
 *
 *    	      Control-Meta-X, or Control-X, or X
 *
 *    	     if lform is FALSE, names are returned in the shorter form:
 *
 *    	      C-M-X, or C-X, or X.
 */

char *namekey(key,ismeta,isfkey,lform)
CHAR key;
BOOL ismeta,isfkey;
int lform;
{
  static char keyname[100];
  char *keyp = keyname,
       keyc[2];

  keyname[0] = keyc[1] = 0;		/* start with empty strings */

  if (isfkey) {				/* is this a function def? */
    strcat(keyp,(lform) ? "Function-" : "F");
    NumToString(key,keyp+strlen(keyp));
    return(keyname);
  }

  if (key < 040)			/* a control character? */
  {
   strcat(keyp,(lform) ? "Control-" : "^"); /* yes, indicate control */
   key += 0100;				/* normalize */
  }

  if (ismeta)				/* a meta character? */
   strcat(keyp,(lform) ? "Meta-" : "M-"); /* yes, indicate meta typed */
  
  keyc[0] = key;

  if (key == 0177)			/* delete? */
   strcat(keyp,(lform) ? "Delete" : "DEL"); /* yes, use this name */
  else strcat(keyp,keyc);		/* else put printable key in */
    
  return(keyname);			/* return the name */
}
 
/* namescode - given a scan code and modifier bits, return a string
 *    	       describing this set of depressed keys in Mac language.
 *
 */

char *namescode(scode,mod) 
{
 static char scodename[100];
 char *scodep;
 int k;

 scodep = scodename;
 scodename[0] = 0;
 
 if (mod & optionKey) strcat(scodep,"Option ");
 if (mod & cmdKey) strcat(scodep,"Command ");
 if (mod & alphaLock) strcat(scodep,"Caps Lock ");
 if (mod & shiftKey) strcat(scodep,"Shift "); 
 for (k=0; k < NKEYS; k++)
  if (keycodes[k].kcode == scode) 
  { strcat(scodep,keycodes[k].kname); break; }
 return(scodename);
}

setnum(hdl,val)
Handle hdl;
{
 char numbuf[20];

 NumToString(val,numbuf);
 SetIText((Handle) hdl,numbuf);
}
 
getnum(hdl,cell)
int *cell;
Handle hdl;
{
 char numbuf[20],c;
 int i;

 GetIText((Handle) hdl,numbuf);
 for (i=0; (c = numbuf[i]) != 0; i++)
 if (!isdigit(c))
 {
  printerr("Field contains a non numeric ",c);
  return(FALSE);
 }
 StringToNum(numbuf,cell);
 if (*cell > 0177 || *cell < 0)
 {
  printerr("Not in 7 bit range: ",*cell);
  return(FALSE);
 }
 return(TRUE);
}

/* drawkb - called by modal dialog to update KB.
 *
 * This is the only procedure that draws or updates the KB picture.  It
 * is called by ModalDialog for normal reasons (an overlapping window
 * has gone away) or the call can be caused by our code issuing an
 * InvalRect.  In order to do things quickly we control the amount to
 * be redrawn with 3 flags:
 *
 *    ourupdate - if FALSE then an entire redraw needs to be done.
 *    updatemods - if TRUE then characters within the keys need redrawing.
 *    updatekeys - if TRUE then selected (hi-lighted) keys have changed.
 *
 */

drawkb()
{
  struct {
    short item;
    WindowPtr w;
  } args;
 
  getpargs(&args,sizeof(args));
  EraseRect(&kbbox);			/* start with a clean slate */
  drawkeyboard(&kbbox);			/* draw the KB outline */
  drawkeychars(keymods);		/* do update on characters */
  hilitemods(keymods);			/* draw highlighted mod keys */  
  if (keysel != KC_NUN)			/* do the current key */
   hilitekey(keysel);			/* ... */  
}

int keycodemods(keyc)
{
  switch(keyc)
  {
    case KC_OPT: return(optionKey);
    case KC_CMD: return(cmdKey);
    case KC_KLK: return(alphaLock);
    case KC_SHF: return(shiftKey);
    default: return(0);
  }
}

setkeydialog()
{  
  WindowPtr w;
  BOOL ismeta,isfkey;
  int itype,itemhit,kval;
  ControlHandle setkeyhdl,setfkeyhdl;
  Handle ihdl,kvalhdl,mkeyhdl;
  char nambuf[256];

  w = GetNewDialog(DLOG_SETK,NILPTR,(WindowPtr) -1);
  SetPort(w);				/* set the port */

  GetDItem(w,SETKD_KEYB,&itype,&ihdl,&kbbox);
  SetDItem(w,SETKD_KEYB,itype,(Handle ) drawkb,&kbbox);
   
  initkeyrects();			/* now that kbbox is set */
  
  mkeyhdl = gethdl(SETKD_KEYT,w);
  kvalhdl = gethdl(SETKD_KVAL,w);
  setkeyhdl = getctlhdl(SETKD_SET,w);
  setfkeyhdl = getctlhdl(SETKD_SETFK,w);

  HiliteControl(setkeyhdl,dimHilite); 	/* these are not allowed */  
  HiliteControl(setfkeyhdl,dimHilite); 	/* these are not allowed */  
  SelIText(w,SETKD_ITXT,0,0); 		/* select invisible text */  

  keymods = 0;				/* current modifiers from keyboard  */
  butmods = 0;				/* current modifiers from button */
  keysel = KC_NUN;			/* selected key */
  kbselected = TRUE;			/* kb or text is active */  
  
  ShowWindow(w);  

  for (;;) {				/* begin dialog loop */
    ModalDialog(myfilter,&itemhit);	/* do modal dialog */
    
    switch (itemhit) {
      
      case SETKD_KEYB:			/* mouse down in keyboard */
	if (keysel == KC_NUN)
      	 break;				/* nothing to do */
      	kbselected = TRUE;		/* KB is selected */
	SelIText(w,SETKD_ITXT,0,0); 	/* select invisible text */
	kval = mapkey(keysel,keymods,&ismeta,&isfkey);
	setnum(kvalhdl,kval);
	strcpy(nambuf,namescode(keysel,keymods));
	strcat(nambuf," is mapped to ");
	strcat(nambuf,namekey((CHAR) kval,ismeta,isfkey,TRUE));
	strcat(nambuf,".   Enter new value: ");
	SetIText(mkeyhdl,nambuf);	
	HiliteControl(setfkeyhdl,noHilite);	/* these are now allowed */
	HiliteControl(setkeyhdl,noHilite);	/* these are now allowed */
	break;

      case SETKD_KVAL:
      	kbselected = FALSE;		/* deselect KB */
      	if (getnum(kvalhdl,&kval)) 	/* get the number */
	{
	  strcpy(nambuf,namescode(keysel,keymods));
	  strcat(nambuf," is mapped to ");
	  strcat(nambuf,namekey((CHAR) kval,ismeta,isfkey,TRUE));
	  strcat(nambuf,".   Enter new value: ");
	  SetIText(mkeyhdl,nambuf);	
	}
	else				/* badly formed -- so reset it */
	  setnum(kvalhdl,(int) mapkey(keysel,keymods,&ismeta,&isfkey));
	break;
	  
      case SETKD_SETFK:
      case SETKD_SET:
      	if (getnum(kvalhdl,&kval))
	{
	  modified = TRUE;		/* things have changed */
	  if (itemhit == SETKD_SETFK)
	    kval |= FKEYBIT;		/* turn on function key bit */
      	  setmapkey((CHAR) kval,keysel,keymods);
	  drawkeychars(keymods);	/* do update on characters */
	  hilitemods(keymods);		/* draw highlighted mod keys */
	  if (keysel != KC_NUN)		/* something to keep hi-lited? */
	    hilitekey(keysel);		/* yes... do it */
	  kbselected = TRUE;		/* keyboard is active */
	  SelIText(w,SETKD_ITXT,0,0); 	/* select invisible text */  	    
	}
	break;

      case SETKD_QUIT:
      	DisposDialog(w);
	return;
	break;
     }
  }
}
 


