/*
 * CKMKEY.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"

KSHDL kshdl;
SFReply sfr;				/* global... */

OsType kermtype = {"KERM"};
OsType texttype = {"TEXT"};
char ttype[] = "TEXT";			/* want's to be long aligned */
int quit;

MenuHandle menus[MAX_MENU+1];

char MSET_TYPE[] = {"MSET"},
     FSET_TYPE[] = {"FSET"},
     KSET_TYPE[] = {"KSET"},
     *FSET_NAME = {"Kermit function keys"},
     *MSET_NAME = {"Kermit meta key prefix"},
     *KSET_NAME = {"Kermit Keys"};

BOOL modified;				/* if changes have been made */


/* gethdl - return a control handle given a resource ID */

Handle gethdl(item,dp)
DialogPtr dp;
{
 int itype;
 Rect ibox;
 Handle ihdl;

 GetDItem(dp,item,&itype,&ihdl,&ibox);
 return(ihdl);
}

 

/* mapkey - accepts a scan code plus modifier bits and translate this
 *    	    into an 8 bit ascii character using the KSET in ourkey.
 */

#define LC_IDX 0			/* use lower case map */
#define UC_IDX 1			/* use upper case map */

CHAR mapkey(scode,mods,ismeta,isfkey)
BOOL *ismeta,*isfkey;
{
 CHAR c;				/* returned char (unsigned) */
 int casidx = LC_IDX;			/* assume lower case */

 if (scode < 0 || scode > MAX_SCODE)
  printerr("Scan code not in range: ",scode);

 if (mods & shiftKey)			/* shift depressed? */
  casidx = UC_IDX;			/* yes, select an upper case map */

 if (mods & (*kshdl)->ctrlmods)		/* control? */
   c = (*kshdl)->ctrlmap[casidx][scode];	/* yes, use control table */
 else 
   if (mods & (*kshdl)->caplmods)		/* else is it caps lock? */
     c = (*kshdl)->caplmap[casidx][scode]; /*  yes, use caps lock table */
   else
     c = (*kshdl)->normmap[casidx][scode]; /*  else it is a normal character */

 *ismeta = (mods & (*kshdl)->metamods) ? 	/* tell about meta */
      	    TRUE : FALSE;		
 *isfkey = (c & FKEYBIT) ? 		/* tell about function keys */
      	    TRUE : FALSE;		
 return(c & ~FKEYBIT);			/* and return the character */
}
 

/* setmapkey - given a scan code, modifiers, and an 8 bit value, store
 *    	       the value in the appropriate cell of our KSET (ourkset).
 */

setmapkey(val,scode,mods)
CHAR val;				/* 8-bit unsigned char */
{
 int casidx = LC_IDX;

 if (scode < 0 || scode > MAX_SCODE)
  printerr("Scan code not in range: ",scode);

 if (mods & shiftKey)			/* shift depressed? */
  casidx = UC_IDX;			/* yes, select an upper case map */

 if (mods & (*kshdl)->ctrlmods)		/* control? */
   (*kshdl)->ctrlmap[casidx][scode] = val; /* yes, use control table */
 else 
   if (mods & (*kshdl)->caplmods)		/* else is it caps lock? */
     (*kshdl)->caplmap[casidx][scode] = val; /*  yes, use caps lock table */
   else
     (*kshdl)->normmap[casidx][scode] = val; /*  else it is a normal character */
}

printerr(s,n)
char *s;
{
 char num[10];

 if (n != 0)
 {
  NumToString(n,num);
  ParamText(s,num,"","");
 }
 else
  ParamText(s,"","","");
 CautionAlert(OUR_ALRT,NILPROC);
}
     
fatal(s,n)
char *s;
{
  printerr(s,n);
  ExitToShell();
}


/* support routines for modifier key dialog */

#define NILCLS 0			/* classes...  */
#define OPTCLS 1			/*  optionKey */
#define CMDCLS 2			/*  cmdKey */
#define KLKCLS 3			/*  alphaLock */

int normjunk,
    caplbits,
    ctrlbits,
    metabits;

/* restb - table for table driven dialog */

struct restb {
  int rid,
      rclass,				/* class */
      *rcell,				/* location of "home" */
      rbitv;				/* bit value (alphaKey, etc.) */
  ControlHandle rhdl;			/* handle to this control */
} rsmktb[] = {
  {RSMK_OPTNORM,OPTCLS,&normjunk,0,NILCTRL},
  {RSMK_OPTCTRL,OPTCLS,&ctrlbits,optionKey,NILCTRL},
  {RSMK_OPTCAPS,OPTCLS,&caplbits,optionKey,NILCTRL},
  {RSMK_OPTMETA,NILCLS,&metabits,optionKey,NILCTRL},
  {RSMK_CMDNORM,CMDCLS,&normjunk,0,NILCTRL},
  {RSMK_CMDCTRL,CMDCLS,&ctrlbits,cmdKey,NILCTRL},
  {RSMK_CMDCAPS,CMDCLS,&caplbits,cmdKey,NILCTRL},
  {RSMK_CMDMETA,NILCLS,&metabits,cmdKey,NILCTRL},
  {RSMK_KLKNORM,KLKCLS,&normjunk,0,NILCTRL},
  {RSMK_KLKCTRL,KLKCLS,&ctrlbits,alphaLock,NILCTRL},
  {RSMK_KLKCAPS,KLKCLS,&caplbits,alphaLock,NILCTRL},
  {RSMK_KLKMETA,NILCLS,&metabits,alphaLock,NILCTRL}
};

#define RSMKZ (sizeof (rsmktb)/sizeof(struct restb))

Handle getdlghdl(dp,rid)
DialogPtr dp;
{
 int itemtype;
 Rect itembox;
 Handle itemhdl;  

 GetDItem(dp,rid,&itemtype,&itemhdl,&itembox);
 return(itemhdl);
}


/* rmskinit - initialize "set modifier key" dialog, find control
 *    	      handles for each item, hilite "normal" for each
 *    	      class and then re-hilite any actual settings.
 */

rsmkinit(dp)
DialogPtr dp;
{
 int r;
  
 caplbits = (*kshdl)->caplmods;
 metabits = (*kshdl)->metamods;
 ctrlbits = (*kshdl)->ctrlmods;
 
 for (r=0; r < RSMKZ; r++)
  rsmktb[r].rhdl = (ControlHandle) getdlghdl(dp,rsmktb[r].rid);

 rsmksetcls(RSMK_OPTNORM);
 rsmksetcls(RSMK_KLKNORM);
 rsmksetcls(RSMK_CMDNORM);

 for (r=0; r < RSMKZ; r++)
  if (rsmktb[r].rbitv & *rsmktb[r].rcell)
   rsmksetcls(rsmktb[r].rid);
}

/* rsmkfinish - finish up "set modifier key" dialog.  Store results
 *    	      	into cells
 */

rsmkfinish()
{
 int r;

 metabits = caplbits = ctrlbits = 0;

 for (r=0; r < RSMKZ; r++)
  if (GetCtlValue(rsmktb[r].rhdl))
   *rsmktb[r].rcell |= rsmktb[r].rbitv;	/* turn on the bit */

 (*kshdl)->metamods = metabits;
 (*kshdl)->caplmods = caplbits;
 (*kshdl)->ctrlmods = ctrlbits;
}
  
rsmksetcls(item)
{
 int r,cls;

 for (r=0; r < RSMKZ; r++)		/* find the class */
  if (rsmktb[r].rid == item)
  {
   cls = rsmktb[r].rclass;    
   if (cls == NILCLS)			/* toggle? */
    SetCtlValue(rsmktb[r].rhdl,!GetCtlValue(rsmktb[r].rhdl));
   else
    SetCtlValue(rsmktb[r].rhdl,btnOn);
   break;
  }   
 
 if (cls != NILCLS)			/* if there is a class (skip meta) */
  for (r=0; r < RSMKZ; r++)
   if (rsmktb[r].rid != item && rsmktb[r].rclass == cls)
    SetCtlValue(rsmktb[r].rhdl,btnOff);
}

metaradios(use8,dlg)
DialogPtr dlg;
{
  SetCtlValue(getctlhdl(RSMK_M8BIT,dlg),(use8) ? btnOn : btnOff);
  SetCtlValue(getctlhdl(RSMK_MPREF,dlg),(use8) ? btnOff : btnOn);
}

BOOL metafinish(use8,dlg)
DialogPtr dlg;
{
  char mtxt[256];
  Handle mhdl;
  int l,sb,se;

  GetIText(gethdl(RSMK_METXT,dlg),mtxt); /* get meta text itself */
  
  if (use8)				/* want to use 8 bit? */
    mtxt[0] = l = 0;			/* yes, use zero length string */
  else {				/* else check out meta text */
    if (!chkcodestr(mtxt,&sb,&se)) {  	/* error in backslash? */
      SelIText(dlg,RSMK_METXT,sb,se); 	/* yes, set selection range */
      SysBeep(2);			/* tell them */
      return(FALSE);			/* and indicate failure */
    }
    l = decodestr(mtxt);		/* decode in place, get length */
  }
  
  (*kshdl)->meta8bit = use8;		/* update boolean */        
  mhdl = (*kshdl)->metahdl;		/* get old meta text handle */
  DisposHandle(mhdl);			/* rid old handle */      
  mhdl = NewHandle(l+1);		/* string size plus size byte */
  BlockMove(mtxt,*mhdl,l+1);		/* copy the string */
  return(TRUE);				/* and return ok */
}
  
dosetmkdialog()
{
 int itemhit,m8bit;
 DialogPtr dlg;
 Handle mhdl;
 char *mstr,*encodestr();

 dlg = GetNewDialog(DLOG_RSMK,NILPTR,(WindowPtr) -1);

 mhdl = (*kshdl)->metahdl;		/* get meta string */
 HLock(mhdl);				/* prevent movement */
 mstr = encodestr(*mhdl);		/* encode it */
 HUnlock(mhdl);				/* unlock */ 
 SetIText(gethdl(RSMK_METXT,dlg),mstr); /* set it */
 DisposPtr(mstr);			/* no longer needed */ 

 rsmkinit(dlg);				/* init settings */
 
 m8bit = (*kshdl)->meta8bit;		/* get 8 bit quote value */
 metaradios(m8bit,dlg);			/* init radio items */
 
 ShowWindow(dlg);
  for (;;) {
    ModalDialog(NILPROC,&itemhit);
    switch (itemhit) {
     case RSMK_OK:
       if (!metafinish(m8bit,dlg))	/* do meta stuff */
         break;				/* format error... */
       modified = TRUE;			/* things have changed */
       rsmkfinish();			/* store values */

     case RSMK_CANCEL:
       DisposDialog(dlg);
       return;
       
     case RSMK_M8BIT:
     case RSMK_MPREF:
      m8bit = (itemhit == RSMK_M8BIT);
      metaradios(m8bit,dlg);
      break;

     default:
       rsmksetcls(itemhit);		/* set buttons */
       break;
    }
  }
}


/* privrsrc - return a private copy of a resource.  The handle
 *    	      returned is relocatable but non purgeable.
 *
 */

Handle privrsrc(type,id,name)
char *type,*name;
{
  Handle rhdl,phdl;
  char errbuf[256];
  int l;

  rhdl = GetResource(type,id);		/* load the resource */
  if (rhdl == (Handle) NIL)		/* nothing? */
  {
    strcpy(errbuf,"There are no ");
    strcat(errbuf,name);
    strcat(errbuf," of the known version in this file.");
    printerr(errbuf,0);
    return((Handle) NIL);		/* then return now with NIL */
  }

  HLock(rhdl);				/* make sure rsrc stays around */
  phdl = NewHandle(l = SizeResource(rhdl)); /* get new relocatable block */
  HLock(phdl);				/* lock new block also */
  BlockMove(*rhdl,*phdl,l);		/* copy from resource to new block */
  HUnlock(rhdl);			/* rsrc lock no longer needed */
  ReleaseResource(rhdl);		/* rsrc completely gone now */
  HNoPurge(phdl);			/* keep new block in memory */
  HUnlock(phdl);			/* but can move around */
  return(phdl);				/* all done */
  SYM(PRIVRESOURCE);
}

KSHDL loadkset()
{
 Handle fhdl,mhdl;
 KSHDL khdl;

 khdl = (KSHDL) privrsrc(KSET_TYPE,KSVER,KSET_NAME);
 if (khdl == (KSHDL) NIL)
  return((KSHDL) NIL);

 fhdl = privrsrc(FSET_TYPE,KSVER,FSET_NAME);
 if (fhdl == (Handle) NIL)
  return((KSHDL) NIL);

 (*khdl)->fcnshdl = fhdl;		/* save handle to relocatable block */
 
 mhdl = privrsrc(MSET_TYPE,KSVER,MSET_NAME);
 if (mhdl == (Handle) NIL)
  return((KSHDL) NIL);
  
 (*khdl)->metahdl = mhdl;		/* save handle */

 return(khdl);				/* return the KSET handle */
 SYM(LOADKSET);
}

/*
 * initmenus - create the menu bar.
 * 
 */

initmenus()
{
  int i;

  for (i=MIN_MENU; i<=MAX_MENU; i++)  	/* For all menus */
  {
    menus[i] = GetMenu(i);		/* Fetch it from resource file */
    InsertMenu(menus[i],0);		/* Put it on menu line */
  }
  DrawMenuBar();			/* Finish up by displaying the bar */
  return;
  SYM(INITMENUS);
}


addrsrc(type,id,hdl,str)
char *type,*str;
Handle hdl;
{
  Handle rhdl;

  rhdl = GetResource(type,id);		/* check for old value */
  if (rhdl != NULL) {			/* something there? */
    RmveResource(rhdl);			/* yes, then delete it */
    if (ResError() != noErr)
      return(!syserr(ResError()));
  }
  
  AddResource(hdl,type,id,str);
  return(!syserr(ResError()));		/* return TRUE if ok */
}
  

/* kfilefilter - match creator "KERM" from sfgetfile call */

kfilefilter()
{
 FileParam *pb;				/* args from SFGetFile... */
 char *retval;
 
 retval = (char *) getpargs(&pb,sizeof(FileParam *));
 *retval = (strncmp(			/* compare creator's... and return */
      pb->ioFlFndrInfo.fdCreator.s, 	/* TRUE if not a match */
      kermtype.s,4) != 0);		/* FALSE otherwise for inclusion */
 return;
 SYM(KFILEFILTER);      
}

savevals(fname,fvol)
char *fname;
{
  int rfnum;

  SetVol(NILPTR,fvol);			/* set volume */

  rfnum = OpenResFile(isapstr(fname));	/* open it up */
  if (rfnum == -1)			/* could not? */
  {
    syserr(ResError());			/* handle error */
    return;				/* and return */
  }

  if (addrsrc(MSET_TYPE,KSVER,(*kshdl)->metahdl,MSET_NAME))
    if (addrsrc(FSET_TYPE,KSVER,(*kshdl)->fcnshdl,FSET_NAME))
      (addrsrc(KSET_TYPE,KSVER,(Handle) kshdl,KSET_NAME));
  
  CloseResFile(rfnum);
  if (syserr(ResError()))		/* error occured */
   return;
   
  FlushVol(NILPTR,fvol);		/* flush the volume */
  
  rfnum = OpenResFile(isapstr(fname)); 	/* reopen the file */
  if (rfnum == -1)			/* unable? */
  {
    syserr(ResError());
    return;
  }
  kshdl = loadkset();			/* load in files KSET */
  CloseResFile(rfnum);			/* close the file */
  if (kshdl == NIL)			/* problems */
   fatal("Unable to reload key defs!",0);
  
  modified = FALSE;			/* no longer modified */
  return;
  SYM(SAVEVALS);
}



loadvals()
{
  Point where;
  int rfnum;

  SetPt(&where,75,115);
  SFGetFile(&where,"Load variables from:",
      	    kfilefilter,1,ttype,NILPROC,&sfr);
  if (!sfr.good)			/* did they hit cancel? */
   return(FALSE);			/* yes, so return now */
  
  SetVol(NILPTR,sfr.vRefNum);		/* setup volume */
  rfnum = OpenResFile(isapstr(sfr.fName)); /* open up the file */
  if (rfnum == -1)			/* unable? */
    return(!syserr(ResError()));

  kshdl = loadkset();			/* load in files KSET */
  CloseResFile(rfnum);			/* close the file */
  if (kshdl == (KSHDL) NIL)
   return(FALSE);
  else
   return(TRUE);
  SYM(LOADVALS);
}


/* 
 * aboutme - Display the about KCONFIG dialog box, wait for user to
 *     	     hit the OK button (maybe this should be a mouse click).
 *
 * The KCONFIG version string is stored in the resource fork as type 
 * "KERK" ID 0 -- load this resource into memory, and lock it.  The
 * resource is locked so that we won't go off the deep end if the
 * heap gets compacted during the dialog display.  This would cause
 * problems because we are using the de-referenced handle, the actual
 * location of the string and not a pointer (handle) to it.
 *
 */

aboutme()
{
  Handle kversion;
  static char ktype[] = "KERK";
  DialogPtr kdialog;			/* our dialog */

  kversion = GetResource(ktype,0);	/* get version information */
  LoadResource(kversion);		/* make sure loaded */
  HLock(kversion);			/* prevent movement */
  ParamText(isapstr(*kversion),"","",""); /* set it up for display via ^0 */
					/*  de-reference the handle */
  kdialog = GetNewDialog(ABOUTID,NILPTR,(WindowPtr) -1);
  ShowWindow(kdialog);			/* in case hidden or something */
  DrawDialog(kdialog);			/* draw the dialog */
  while (!Button());			/* wait for button */
  DisposDialog(kdialog);		/* dispose dialog box */
  HUnlock(kversion);			/* undo previous HLock */
  ReleaseResource(kversion);		/* no longer needed */
}

/* maybesave - enter maybe save dialog.
 *
 * Pass the name of the operation which will zap things, for example
 * "quitting" or "opening" and we'll tell the user about the pending
 * doom of changes if they've occured.  We return TRUE if user wants
 * to proceed, FALSE otherwise.
 *
 */

BOOL maybesave(op)
char *op;
{
 if (!modified) 
  return;				/* no changes made */

  ParamText(isapstr(sfr.fName),op,"","");  /* set name, operation */
  switch (Alert(MAYBE_ALRT,NILPROC)) {
    case YES_MAYBE: 
      savevals(sfr.fName,sfr.vRefNum); /* YES: do it */
    case NO_MAYBE:
      return(TRUE);			/* NO: proceed */
  }
  return(FALSE);			/* Cancel or other... */
}


parser()
{
  EventRecord ev;
  WindowPtr evw;

  for (quit = FALSE; !quit;) {
    SystemTask();
    GetNextEvent(everyEvent,&ev);
    if (ev.what == mouseDown) {
      
      switch (FindWindow(&ev.where,&evw)) {
	
	case inMenuBar:
	  menuparser(MenuSelect(&ev.where));
	  HiliteMenu(0);		/* done so un-hilite */
	  break;
	  
	case inSysWindow:
	  SystemClick(&ev,evw);
	  break;

      }
    }
  }    
}


alterofferings(savok)
{
  int (*fcn)(),EnableItem(),DisableItem();
  char savbuf[256];
  
  fcn = (savok) ? EnableItem : DisableItem;
	    
  (*fcn)(menus[SETK_MENU],0);		/* enable/disable SET menu */
  (*fcn)(menus[FILE_MENU],SAVE_FILE);	/* enable/disable SAVE item */
  (*fcn)(menus[FILE_MENU],DECOM_FILE);	/* enable/disable DECOMPILE... */
  strcpy(savbuf,"Save ");		/* initial text of save */
  if (savok)				/* ok to save? */
    strncat(savbuf,&sfr.fName[1],sfr.fName[0]);	/* yes... put in name */
    
  SetItem(menus[FILE_MENU],SAVE_FILE,savbuf); /* set item */
  HiliteMenu(0);			/* de-hilite, prevents oddness */
  DrawMenuBar();			/* disable/enable menu bar items */
}

menuparser(mitem)
{
  int menu = HiWord(mitem),
      item = LoWord(mitem);

  switch (menu) {
    case APPL_MENU:
      switch (item) {
	case ABOU_APPL:
	  aboutme();
	  break;
	default:
	  break;
      }
      break;

    case FILE_MENU:
      switch (item) {
	case OPEN_FILE:
	  if (maybesave("opening"))	/* if they want... */
	    alterofferings(loadvals());	/* do it and alter menu items */
	  break;

        case DECOM_FILE:
	  p2cstr(sfr.fName);
	  decomdialog(sfr.fName);
	  c2pstr(sfr.fName);
	  break;

	case SAVE_FILE:	    
	  savevals(sfr.fName,sfr.vRefNum); /* do it */
	  break;
	  
	case QUIT_FILE:
	  if (maybesave("quitting"))	/* if they really want to */
	    quit = TRUE;		/* then let them */
	  break;
      }
      break;

    case SETK_MENU:
      switch (item) {
	case SKEY_SETK:
	  ScrDmpEnb = scrdmpdisabled;	/* disable special keys */
	  setkeydialog();
	  ScrDmpEnb = scrdmpenabled;	/* enable special keys */	  
	  break;
	case SMOD_SETK:
	  dosetmkdialog();
	  break;
	case FCNK_SETK:
	  setfkeydialog();
	  break;
      }
      break;
   }
}


/* checks for system error, returns FALSE if noErr, else puts up an error
 * message with an understandable text string.
 *
 */

struct {
  int errnum;
  char *errstr;
  } ioerrs[] = {
    {resNotFound,"Resource not found"},
    {resFNotFound,"Resource file not found"},
    {addResFailed,"AddResource failed"},
    {dirFulErr,"Directory is full"},
    {dskFulErr,"Disk is full"},
    {wPrErr,"Diskette is write protected"},
    {fLckdErr,"File is software locked"},
    {vLckdErr,"Volume is software locked"},
    {fBsyErr,"File is busy"},
    {opWrErr,"File is already open with write permission"},
    {0,NILPTR}
    };

syserr(err)
{
  int e;

  if (err == noErr)
   return(FALSE);

  for (e = 0; ioerrs[e].errnum != 0 && 
      	      ioerrs[e].errnum != err; e++);

  if (ioerrs[e].errstr == NILPTR)	/* anything there? */
   printerr("Unknown IO error: ",err);
  else
   printerr(ioerrs[e].errstr,0);
   
  return(TRUE);   
  SYM(SYSERR);
}


main() 
{
  QD = &QDVar;
 
  InitGraf(&thePort);
  InitWindows();
  InitFonts();
  InitDialogs(NILPROC);
  InitMenus();
  TEInit();
  InitCursor();

  initmenus();
  alterofferings(FALSE);		/* disable menu items */
  modified = FALSE;			/* not modified */
  parser();
  ExitToShell();
  SYM(MAIN);
}

