/*
 * CKMKE3.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>

WindowPtr setfw;
#define setfwd ((DialogRecord *) setfw)	/* coerced to point to dlg */

BOOL dirty = FALSE;			/* if display need saving */
int oldtop = -1;			/* previous topline */

int fdisp[] = {SETFD_ST1,SETFD_ST2,SETFD_ST3,SETFD_ST4,SETFD_ST5,0};
int fvals[] = {SETFD_ET1,SETFD_ET2,SETFD_ET3,SETFD_ET4,SETFD_ET5,0};

#define NFDISPS 5			/* number of functions displayed */

char *fkeys[NFKEYS];			/* unparsed function defs */

char TEXT[] = {"TEXT"};			/* long aligned "text" */
char APPL[] = {"APPL"};			/* long aligned "appl" */


/* octout - translate number to backslash followed by 3 octal digits */

char *octout(s,n)
char *s;
{
 *s++ = '\\';				/* do backslash */
 *s++ = ((n >> 6) & 07) + '0';		/* first digit */
 *s++ = ((n >> 3) & 07) + '0';		/* second digit */
 *s++ = (n & 07) + '0';			/* yes, you guessed it, third digit */
 return(s);
 SYM(OCTOUT);
}

#define isnoctal(c) (c < '0' || c > '7')

char *octin(s,val)
int *val;
char *s;
{
  *val = ((s[0] - '0') << 6 | (s[1] - '0') << 3 | (s[2] - '0'));
  if (isnoctal(s[0]) || isnoctal(s[1]) || isnoctal(s[2]))
   *val = 0xff+1;			/* indicate error */
  return(&s[3]);			/* return updated ptr */
}       

/* updatefkey - given item number and function key index, update fkey
 *    	        array from edit text item.
 *
 */

updatefkey(rid,fkn)
{
  char etbuf[256];

  GetIText(gethdl(rid,setfw),etbuf);	/* get the text */
  DisposPtr(fkeys[fkn]);		/* dispose old ptr */
  fkeys[fkn] = NewPtr(strlen(etbuf)+1); /* assign storage */
  strcpy(fkeys[fkn],etbuf);		/* copy it in */
  return;
  SYM(GETFKEYS);
}

/* updatefdisp - Update the function key strings on the screen and in the
 *    	        fkeys array.
 *
 */

int oldself,oldsels,oldsele;
 
updatefdisp(newtop)
{
  char nambuf[10];
  int i,t;
  
/* check for the selection range... is it in our window? If so then */
/* update the values we remember - the function number, and the range. */

  if ((t = setfwd->editField+1) != SETFD_ETI) {
    for (i=0; fvals[i] != 0; i++)	/* locate by ID */
      if (fvals[i] == t)		/* found it? */
      	oldself = oldtop+i;		/* yes, set selected fcn number */
    oldsels = (*setfwd->textH)->selStart; /* set selection start */
    oldsele = (*setfwd->textH)->selEnd;	/* and selection end */
  }

  SelIText(setfw,SETFD_ETI,0,0);	/* make selection be invisible, */
					/*  prevents odd looking updates */
					/*  during scrolling */
 
/* if "dirty" is set then update fkeys array from edit text items */

  if (dirty)				/* if something may have changed */
    for (i=0; fvals[i] != 0; i++)	/* then loop saving edit text items */
     updatefkey(fvals[i],oldtop+i);	/*  into fkey array */

  dirty = FALSE;			/* nothing dirty any longer */
      
  if (oldtop != newtop)			/* if some work needs to be done */
    for (i=0, t = newtop; fdisp[i] != 0; i++)
    {
      nambuf[0] = 'F';
      NumToString(t,&nambuf[1]);
      SetIText(gethdl(fdisp[i],setfw),nambuf);
      SetIText(gethdl(fvals[i],setfw),fkeys[t++]);
    }    

/*
 * Manage the selection range: let the edit text selection range move
 * around, sticking with the function definition it was being used with.
 * If the function is not visible, then use already selected invisible item.
 */

  if (oldself >= newtop && oldself < newtop+NFDISPS)
    SelIText(setfw,fvals[oldself-newtop],oldsels,oldsele);
    
  oldtop = newtop;			/* remember for next time */
  return;
  SYM(UPDATENAMES)
}

pagescroll(part,ctl)
ControlHandle ctl;
{
  Point p;
  int amount = (part == inPageUp) ? -NFDISPS : NFDISPS;
  
  do {
    GetMouse(&p);
    if (TestControl(ctl,&p) != part)
      continue;
    SetCtlValue(ctl,GetCtlValue(ctl)+amount);
    updatefdisp(GetCtlValue(ctl));
  } while (StillDown());
  return;
  SYM(PAGESCROLL);
}
    
scrollbtn()
{
  struct {				/* args passed from Pascal */
    short part;				/*  TrackControl in stack order */
    ControlHandle ctlh;
  } args;
  int delta;
 
  getpargs(&args,sizeof args);
  
  switch (args.part) {
    case inUpButton: 
      delta = -1;
      break;
    case inDownButton:
      delta = 1;
      break;
    default: 
      return;
   }
   SetCtlValue(args.ctlh,GetCtlValue(args.ctlh)+delta);
   updatefdisp(GetCtlValue(args.ctlh));
   return;
   SYM(SCROLLBTN);
}


setfkfilter()
{
  struct {				/* pascal args for filter procedure */
    short *item;			/* (in stack order) */
    EventRecord *evt;    
    DialogPtr dlg;
  } args;
  BOOL *fcnret;
  Point lpt;
  ControlHandle ctlhdl;
  int part;

  fcnret = (BOOL *) getpargs(&args,sizeof args);
  *fcnret = FALSE;			/* assume we don't want to handle */

  if (args.evt->what == keyDown)	/* key down? */
  {
    if (((DialogRecord *) args.dlg)->editField == SETFD_ETI-1)
    {					/* typing to invisible selection? */
      SysBeep(1);			/* yes... beep them */
      *fcnret = TRUE;			/* do not let modal do anything */
      *args.item = SETFD_ETI;		/* a no-op */
      return;				/* and return now */
    }
    dirty = TRUE;			/* else things may change */
  }

  if (args.evt->what != mouseDown)	/* is this mouse down? */
    return;				/* no, we don't do anything */
  
  lpt = args.evt->where;		/* copy the point */
  GlobalToLocal(&lpt);			/* convert to local coords */

  part = FindControl(&lpt,args.dlg,&ctlhdl); /* find a control if any */
  
  switch (part) {
    case inUpButton:
    case inDownButton:
      TrackControl(ctlhdl,&lpt,scrollbtn);
      break;
    case inPageUp:
    case inPageDown:
      pagescroll(part,ctlhdl);
      break;
    case inThumb:
      TrackControl(ctlhdl,&lpt,NILPROC);
      updatefdisp(GetCtlValue(ctlhdl));
      break;
    default:
      return;
  }
  *fcnret = TRUE;			/* we handled it */
  return;
  SYM(SETFKFILTER)
}


/* getindstr - given an indirect (or is it indexed) string and integer
 *    	       n, return the pointer to the Nth substring.
 *
 * Indirect strings have the count of substrings in the first byte and
 * each string follows with a length byte and a body.
 *
 * Substrings are referenced by 1..N
 *
 */

char *getindstr(indstr,n)
char *indstr;
{
  register char *ip = indstr;
  int i;

  if (n > *ip++)			/* too large? */
   return(NILPTR);			/* yes, nothing there */

  for (i=1; i < n; i++)			/* scan until we hit the Nth */
   ip += (*ip)+1;			/* move to next substring */

  return(ip);				/* return ptr to it */
  SYM(GETINDSTR);
}

/* chkcodestr - checks a backslash coded string for correctness. */

chkcodestr(s,ber,eer)
char *s;
int *ber,*eer;				/* start and end of error */
{
  char *pi,*pix;
  int val;

  for (pi = s; *pi != NULL; ) 		/* check the string */
    if (*pi++ == '\\') {		/* found something? */
      pix = octin(pi,&val);		/* yes, fetch number */
      if (val > 0xff) {			/* error? */
	*ber = pi-s-1;			/* start of error */
	*eer = pi-s+3;			/* end of error */
	return(FALSE);			/* failed */
      }
      pi = pix;				/* update ptr */
    }					/* and loop */
  return(TRUE);
}

/* encodestr - encode backslash string, return NewPtr string. */

char *encodestr(s) 
char *s;				/* actually pascal string */
{
  char buf[256],			/* big buf */
       *dp = buf,			/* destination ptr */
       *sp = s,				/* source ptr */
       *newp;
  int len,olen;

  len = (sp != NILPTR) ? *sp++ : 0;	/* decide on length */
  for (dp = buf, olen = 0;		/* deposit into our buffer */
       len > 0 && olen < 64; len--) {	/* for each byte in string */ 
    olen++;				/* one more in destination */
    if (isprint(*sp) || *sp == ' ')	/* is it printable or space? */
      *dp++ = *sp++;			/* yes, just copy it */
    else {
      dp = octout(dp,*sp++);		/* else use backslash and digits */
      olen += 3;			/* account */
    }
  }
  *dp++ = 0;				/* tie off */
  newp = (char *) NewPtr(olen+2);	/* allocate storage */
  strcpy(newp,buf);			/* copy definition */
  return(newp);				/* return it */
}


/* decodestr - decode backslash string in place, make a pascal string
 *    	       returns size.
 */

decodestr(s) 
char *s;
{
  char *pi,*po;
  int li,lo,val;

  c2pstr(pi = s);			/* convert to pascal string */

  for (li = *pi++, lo=0, po=pi; 	/* init lengths, output ptr */
       li > 0; li--) {			/* do the entire string */
    if (*pi == '\\') {			/* special character? */
      pi = octin(++pi,&val);		/* yes fetch value (checked already) */
      *po++ = val;			/* store it */
      li -= 3;				/* account for digit bytes */
    } else *po++ = *pi++;		/* else just copy */
    lo++;				/* count up output chars */
  }    
  s[0] = lo;				/* set size */
  return(lo);				/* return size */
}


/* bldindstr - build an indexed string from fkeys array and return
 *    	       a relocatable handle to the new indexed string.
 *
 * An indexed string is contains the count of substrings in the first
 * byte and each string in pascal form (length byte then body) follows.
 *
 * This routine takes the contents of fkeys and translate the backslash
 * numbers to single bytes.  This conversion is done in place, as we
 * know the result will always be leq in size.  Since the final form
 * requires pascal form strings, and since the backslash might result 
 * in imbedded nulls, we first convert fkeys to pascal strings.
 *
 * The count of substrings in the indexed string will probably be less 
 * than NFKEYS (the number of definable function keys) since the last
 * batch of empty definitions need not be included.
 *
 * The resulting indexed string is allocated from the heap, it is
 * set non-purgeable, but it is relocatable.  A handle to this 
 * uber-string is returned.
 *
 * N.B. Don't try using fkeys after this routine has been called
 * since it now contains decoded strings, not backslash strings.
 *
 */

Handle bldindstr()
{
  int i,l,
      hifk = 0,				/* number of idx strings */
      tsize = 0;			/* total size of these */
  char *fki,*fko;
  Handle rp;

  for (i=0; i < NFKEYS; i++) {		/* modify strings in place */
    tsize += (l = decodestr(fkeys[i]));	/* decode the string in place */
					/*  accumulate size */
    if (l > 0) hifk = i;		/* remember highest with body */
  }
    
  tsize += hifk+1;			/* hifk is how many, add size bytes */

  rp = NewHandle(tsize+1);		/* get stg + count byte */
  HNoPurge(rp);				/* don't remove us */
  fko = *rp;				/* de-reference handle */
  *fko++ = hifk+1;			/* store count of substrings */
  
  for (i=0; i <= hifk; i++)		/* now store them */
  {
    fki = fkeys[i];			/* handle on input string */
    BlockMove(fki,fko,fki[0]+1);	/* move it to output */
    fko += fki[0]+1;			/* increment output ptr  */
  }
  
  return(rp);				/* return ptr */
  SYM(BLDINDSTR);			/* for debugging */
}


/* savefkeys - Check & save the fkeys array.
 *
 * The fkeys array is checked for correctnes (backslash badness).  If
 * something is wrong we will put the bad definition in a visible edit
 * text item in the display (if already visible no movement occurs)
 * select the bogus region, beep and return FALSE.
 *
 * If no badness is detected we release the current kset function def
 * handle, call buildidx to create a new one, store it, and return TRUE.
 *
 */

BOOL savefkeys(sctl)
ControlHandle sctl;
{
  int n,ctlv,sbeg,send;

  updatefdisp(GetCtlValue(sctl));	/* make sure fkeys updated */

  for (n=0; n < NFKEYS; n++) {		/* for all keys, check correctness */
    if (!chkcodestr(fkeys[n],&sbeg,&send)) {  /* check out? */
      ctlv = GetCtlValue(sctl);		/* no, get control setting */
      if (n > ctlv+NFDISPS || n < ctlv)	/* if N is not in our window */	
      	ctlv = n;			/*  need to reposition */
      SetCtlValue(sctl,ctlv);		/* set it */
      updatefdisp(ctlv);		/* update the function display */
      SelIText(setfw,			/* selects zeroth or 1st, 2nd */
      	       fvals[(n - ctlv)],	/*  edit item according to top */
	       sbeg,send);		/* region of badness */
      SysBeep(2);			/* beep them */
      return(FALSE);			/* and indicate error */
    }
  }    
  DisposHandle((*kshdl)->fcnshdl);	/* return old handle */
  (*kshdl)->fcnshdl = bldindstr();	/* build new one */
  return(TRUE);
  SYM(SAVEFKEYS);
}

/* initfkeys - given a ptr to the indexed string for function keys
 *    	       decode this string into the fkeys array.  
 *
 * Decoding is simply converting non-printable characters to backslash
 * followed by 3 octal digits.  Storage for encoded fkeys is allocated
 * in this routine, no modification is made to the indexed string.
 *
 */

initfkeys(indstr)
char *indstr;
{
  char *sp;
  int n;
  
  for (n=0; n < NFKEYS; n++) {		/* do all of functions */
    sp = getindstr(indstr,n+1);		/* get ptr to indexed string */
    fkeys[n] = encodestr(sp);		/* encode it */
  }
  return;
  SYM(INITFKEYS);
}


/* disposfkeys() - return storage used by fkeys array.
 *
 */

disposfkeys()
{
  int i;

  for (i=0; i < NFKEYS; i++)
    if (fkeys[i] != NILPTR)
    {
      DisposPtr((Ptr) fkeys[i]);	/* a comment: return it */
      fkeys[i] = NILPTR;
    }
}

setfkeydialog()
{
  int itemhit;
  ControlHandle scrollctl;
  Handle fkhdl;
  
  setfw = GetNewDialog(DLOG_SETF,NILPTR,(WindowPtr) -1);
  scrollctl = getctlhdl(SETFD_SCR,setfw);
  SetCtlMax(scrollctl,NFKEYS-NFDISPS);	/* make value */

  SetPort(setfw);			/* make globaltolocal work right */
  dirty = FALSE;			/* nothing is dirty */
  oldtop = -1;				/* previous topline */
  
  fkhdl = (*kshdl)->fcnshdl;		/* handle to function defs */
  HLock(fkhdl);				/* lock default key defs */
  initfkeys(*fkhdl);			/* de-ref and init fkeys array */
  HUnlock(fkhdl);			/* unlock the handle */

  updatefdisp(0);
  ShowWindow(setfw);
  for (;;) {
    ModalDialog(setfkfilter,&itemhit);
    switch (itemhit) {
      case SETFD_SCR:
      	break;
      case SETFD_SET:
      	if (!savefkeys(scrollctl))	/* try to save it... */
      	 break;				/* if fail, continue dialog */
	modified = TRUE;		/* things have changed */
      case SETFD_QUIT:			/* else fall into quit case */
      	DisposDialog(setfw);
	disposfkeys();			/* dispose fkeys array */
	return;
    }
  }
  SYM(SETFKDIALOG)
}

/* hexout - translate the byte to two octal digits. */

char hdigits[] = "0123456789abcdef";

hexout(dp,hc)
char hc,*dp;
{
  *dp++ = hdigits[(hc >> 4) & 0x0f];  
  *dp++ = hdigits[hc & 0x0f];
}

/* writehexhdl - given a file number, a handle, output the handle
 *    	      	 in hex to the file.
 *
 */

writehexhdl(okflg,f,hdl)
int *okflg;
Handle hdl;
{
  char *s,lb[100];			/* line buffer */
  PLONG oc,l;

  if (!*okflg)				/* error occured */
   return;				/*  just return */

  if ((l = GetHandleSize(hdl)) == 0)	/* find size, anything to do? */
   return;				/* nope... */

  HLock(hdl);				/* lock the handle */
  s = *hdl;				/* de-reference */

  oc = 0;				/* output count */
  
  for (; l > 0; l--) {			/* for each character */
    hexout(&lb[oc],*s++);		/* put in digits */
    oc += 2;				/* account */
    if (oc > 75 || l == 1) {		/* new line needed? */
      lb[oc++] = '\n';			/* yes, add nl character */
      if (!(*okflg = syserr(FSWrite(f,&oc,lb))))	/* output it */
      	return;				/* couldn't */
      oc = 0;				/* init count */
    } 
    else lb[oc++] = ' ';		/* else format */
  }
  HUnlock(hdl);				/* unlock... */
  return(TRUE);				/* done ok */
  SYM(WRITEHEXH);			/* for debugger */
}

writestr(okflg,f,s)
int *okflg;
char *s;
{
  PLONG l;
  int err;

  if (!*okflg)				/* error occured, so nop */
   return;
   
  l = strlen(s);
  *okflg = syserr(FSWrite(f,&l,s)); 	/* set to FALSE if error */
}
  
writetype(okflg,f,type)
int *okflg;
char *type;
{
  char numbuf[10];

  writestr(okflg,f,"\nType ");
  writestr(okflg,f,type);
  writestr(okflg,f," = HEXA\n ,");
  NumToString(KSVER,numbuf);
  writestr(okflg,f,numbuf);
  writestr(okflg,f,"\n");
}


/* Decompile the current file */

decomdialog(name)
char *name;
{
  SFReply sfr;
  char defnam[256];
  Point where;
  int fnum,err,okflg;

  strcpy(defnam,name);			/* copy name */
  strcat(defnam,"R");			/* here is new form... */

  SetPt(&where,75,100);			/* some place */
  SFPutFile(&where,"Save decompiled information in:",
      	    defnam,NILPROC,&sfr);

  if (!sfr.good)			/* really want to? */
   return;				/* nope... */

  err = FSDelete(isapstr(sfr.fName),sfr.vRefNum); /* delete old file */
  if (err != noErr && err != fnfErr)  {
    syserr(err);			/* do message */
    return;				/* and return */
  }

  if (syserr(Create(isapstr(sfr.fName),sfr.vRefNum,APPL,TEXT)))
    return;				/* forget it */
  
  if (syserr(FSOpen(isapstr(sfr.fName),sfr.vRefNum,&fnum)))
    return;				/* forget it */
  
  okflg = TRUE;				/* everything is ok... */
  writestr(&okflg,fnum,"* Decompiled information from ");
  writestr(&okflg,fnum,name);
  writestr(&okflg,fnum,"\n\n");

  writetype(&okflg,fnum,"FSET");
  writehexhdl(&okflg,fnum,(*kshdl)->fcnshdl);	/* write out functions */
  writestr(&okflg,fnum,"\n");
  
  writetype(&okflg,fnum,"MSET");
  writehexhdl(&okflg,fnum,(*kshdl)->metahdl);	/* write out meta string */
  writestr(&okflg,fnum,"\n");

  writetype(&okflg,fnum,"KSET");
  writehexhdl(&okflg,fnum,(Handle) kshdl);	/* write out key set */
  writestr(&okflg,fnum,"\n");

  if (syserr(FSClose(fnum)))		/* close it */
    return;				/* failed */
  return;
  SYM(DECOMDIALOG);
}
