#include <intuition/intuition.h>
#include <graphics/gfxmacros.h>
#include <math.h>
/**********************************************************************/
/*								      */
/* Copyright 1990 by John Baez. 				      */
/*								      */
/* For private, non-commercial use only. May not be used in any other */
/* way without prior written consent from the author. This header must*/
/* be included in all use of this code. 			      */
/*								      */
/**********************************************************************/
#include "keys.h"
#include "getData.h"
/**********************************************************************/
/* NAME: getData()---perform i/o on formatted field		      */
/* SYNOPSIS: struct IntuiMessage *getData(window,datalist,datastart); */
/*								      */
/* FUNCTION: permits entry of field data as specified by datalist.    */
/*	     optionally positions itself to the field specified by    */
/*	     datastart if datastart is supplied.		      */
/**********************************************************************/
/* Secondary functions preceed the main (getData) entry point.	      */
/**********************************************************************/
struct IntuiMessage *input(w,flags)
struct Window *w;
long flags;
{  /*
    * This function handles input from the IDCMP. It waits for a message
    * when there are none.
    */
   static struct IntuiMessage m;
   struct IntuiMessage *message;
   if(flags==0)
      return(0);
   if(w->IDCMPFlags!=flags || !w->UserPort)
      ModifyIDCMP(w,flags);
   while(1)
   {  if(!(message=(struct IntuiMessage *)GetMsg(w->UserPort)))
      {  Wait(1<<w->UserPort->mp_SigBit);
	 message=(struct IntuiMessage *)GetMsg(w->UserPort);
      }
      if(!message) continue;
      m=*message;
      ReplyMsg(message);
      break;
   }
   return(&m);
}
/**********************************************************************/
displayIOField(w,fields)
struct Window *w;
struct IOField *fields;
{  unsigned long i;
   unsigned short c=0;
   char work[80], pena, penb, drawmode;

   static short xy_values[10]={0,0,0,0,0,0,0,0,0,0};
   static struct Border b={0,0,0,0,JAM1,5,xy_values};

   pena=w->RPort->FgPen;
   penb=w->RPort->BgPen;
   drawmode=w->RPort->DrawMode;
   while(fields)
   {  /*
       * reset FIELD_MODIFIED flag and display field.
       */
      fields->Flags=(fields->Flags|FIELD_MODIFIED)^FIELD_MODIFIED;
      i=fields->Flags&0xf000;
      if(fields->Flags&OUTIN|OUTONLY)
      {  convfield(work,fields);
	 Move(w->RPort,fields->LeftEdge,
		       fields->TopEdge+w->RPort->TxBaseline);
	 SetAPen(w->RPort,fields->FrontPen);
	 SetBPen(w->RPort,fields->BackPen);
	 SetDrMd(w->RPort,fields->DrawMode);
	 Text(w->RPort,work,fields->Length);
	 b.FrontPen=fields->FrontPen;
      }

      if(i&HILITE)
      {  xy_values[2]=xy_values[4]=fields->Length*w->RPort->TxWidth+3;
	 xy_values[5]=xy_values[7]=w->RPort->TxHeight+3;
	 b.FrontPen=fields->BackPen;
	 DrawBorder(w->RPort,&b,fields->LeftEdge-2,fields->TopEdge-2);
	 b.FrontPen=fields->FrontPen;
      }
      if(i&BORDERED)
      {  xy_values[2]=xy_values[4]=fields->Length*w->RPort->TxWidth+1;
	 xy_values[5]=xy_values[7]=w->RPort->TxHeight+1;
	 DrawBorder(w->RPort,&b,fields->LeftEdge-1,fields->TopEdge-1);
      }
      if(i&UNDERLINE)
      {  i=fields->TopEdge+w->RPort->TxHeight+1;
	 Move(w->RPort,fields->LeftEdge,i);
	 Draw(w->RPort,fields->LeftEdge+fields->Length*w->RPort->TxWidth,i);
      }
      c++;
      fields=fields->NextData;
   }
   SetAPen(w->RPort,pena);
   SetBPen(w->RPort,penb);
   SetDrMd(w->RPort,drawmode);
   return((int)c);
}
/**********************************************************************/
convfield(c,field)
char *c;
struct IOField *field;
{  /*
    * Convert fields from their internal representation into character
    * strings.
    */

   int i, j;
   double val;
   char work[80], *ecvt();

   switch(field->Flags&0xf0)
   {  case LONG_INT:
	stcl_d(c,*(long *)field->Data);
	break;
      case INTEGER:
	stci_d(c,*(short *)field->Data);
	break;
      case FLOAT:
	val=*(float *)field->Data;
      case DOUBLE:
	if((field->Flags&0xf0)==DOUBLE)
	   val=*(double *)field->Data;
	strcpy(c,ecvt(val,field->Length-2,&i,&j));
	strins(&c[i],".");
	if(j)
	   strins(c,"-");
	break;
      case DIGITS:
      case ANYCHAR:
	strncpy(c,(char *)field->Data,i=strlen(field->Data));
	setmem(c+i,field->Length,' ');
	c[field->Length]=0;
	break;
   }
   if(strlen(c)<field->Length)
   {  setmem(work,field->Length,' ');
      work[field->Length]=0;
      if((field->Flags&ANYCHAR)
	 || (field->Flags&DIGITS))
	 strncpy(work,c,strlen(c));
      else
	 strcpy(work+field->Length-strlen(c),c);
      strcpy(c,work);
   }
   return(strlen(c));
}
/**********************************************************************/
unconvfield(c,field)
char *c;
struct IOField *field;
{  /*
    * convert fields into internal representation from charater format.
    */
   char work[80];
   short i;

   strcpy(work,c);
   switch(field->Flags&0xf0)
   {  case LONG_INT:
	*(long *)field->Data=(long)atol(work);
	break;
      case INTEGER:
	*(short *)field->Data=(short)atoi(work);
	break;
      case FLOAT:
	sscanf(work,"%f",field->Data);
	break;
      case DOUBLE:
	sscanf(work,"%lf",field->Data);
	break;
      case DIGITS:
      case ANYCHAR:
	for(i=strlen(work)-1;i>=0;i--)
	{  if(work[i]!=' ')
	   {  work[i+1]=0;
	      break;
	   }
	}
	if(i<0)
	   *(char *)field->Data=0;
	else
	   strcpy(field->Data,work);
	break;
   }
}
/**********************************************************************/
check_runtime(w,flist,field)
struct Window *w;
struct IOField *flist, *field;
{  /*
    * execute user supplied runtime edit procedure.
    */
   if(field->Flags&FIELD_MODIFIED && field->EdFunc)
      return(field->EdFunc(w,flist,field));
   return(0);
}
/**********************************************************************/
struct IntuiMessage *getkey(w,fields,field,pos)
struct Window *w;
struct IOField *fields, *field;
short pos;
{  long key;
   struct IntuiMessage *msg, *return_msg=0;
   short cursor_pos;
   char pena, penb, drawmode, work[80];

   pena=w->RPort->FgPen;
   penb=w->RPort->BgPen;
   drawmode=w->RPort->DrawMode;
   SetAPen(w->RPort,field->FrontPen);
   SetBPen(w->RPort,field->BackPen);

   convfield(work,field);

   cursor_pos=pos;
   field->Flags=(field->Flags|FIELD_MODIFIED)^FIELD_MODIFIED;

   while(1)
   {  /* draw current data */
      if(!(field->Flags&INONLY))
      {  SetDrMd(w->RPort,field->DrawMode);
	 Move(w->RPort,field->LeftEdge,field->TopEdge+w->RPort->TxBaseline);
	 Text(w->RPort,work,field->Length);
      }

      if(cursor_pos>=field->Length) /* end of field? */
      {  /* if so, do edit checks */
	 unconvfield(work,field);
	 if(check_runtime(w,fields,field))
	 {  DisplayBeep(w->WScreen);
	    cursor_pos=0;
	    continue;
	 }
	 break;
      }

      /* draw cursor */
      cursor(w,field->LeftEdge+cursor_pos*w->RPort->TxWidth,
		field->TopEdge,w->RPort->TxWidth,w->RPort->TxHeight);

      msg=input(w,w->IDCMPFlags|MOUSEBUTTONS|RAWKEY|INACTIVEWINDOW);
      cursor(w,field->LeftEdge+cursor_pos*w->RPort->TxWidth,
		field->TopEdge,w->RPort->TxWidth,w->RPort->TxHeight);
      if(msg->Class!=RAWKEY) /* only RAWKEYs are accepted in this function */
      {  return_msg=msg;
	 cursor_pos=field->Length;
	 continue;
      }
      key=whichkey(msg); /* convert rawkey */
      if(key)
      {
	 switch(key)
	 {  case RK_LEFT: /* cursor left */
	      if(cursor_pos)
	      {  cursor_pos--;
	      }
	      break;
	    case RK_RIGHT: /* cursor right */
	      if(cursor_pos<field->Length-1)
	      {  cursor_pos++;
	      }
	      break;
	    case K_BKSP:
	      if(cursor_pos)
		 cursor_pos--;
	      else
		 break;
	    case K_DEL: /* delete key */
	      strcpy(&work[cursor_pos],&work[cursor_pos+1]);
	      work[field->Length-1]=' ';
	      field->Flags|=FIELD_MODIFIED;
	      break;
	    case K_INS: /* insert */
	      if(work[field->Length-1]==' ')
	      {  work[field->Length-1]=0;
		 strins(&work[cursor_pos]," ");
		 field->Flags|=FIELD_MODIFIED;
	      }
	      break;
	    case K_RETURN:
	      cursor_pos=field->Length;
	      break;
	    case K_ESC: /* clear the field contents */
	      {  short i;
		 for(i=0;i<field->Length;i++)
		    work[i]=' ';
		 cursor_pos=0;
		 field->Flags|=FIELD_MODIFIED;
	      }
	      break;
	    /******************************
	     * other keys may be cased here.
	     ******************************/
	    default:
	      if( (msg->Qualifier & 0x1f8) || /* ctl,alt,Akeys */
		  (key<32 || key>126))	    /* non-text keys */
	      {  cursor_pos=field->Length;
		 return_msg=msg;
		 break;
	      }
	      /* process data entry stroke */
	      if((field->Flags & 0xf0)==ANYCHAR
		 || (key>='0' && key<='9'))
	      {  work[cursor_pos++]=(char)key;
		 field->Flags|=FIELD_MODIFIED;
	      }
	      else
	      {  if(key=='-'
		    && (field->Flags & 0xf0)<DIGITS
		    && (!strchr(work,'-')))
		 {  strins(work,"-");
		    work[field->Length]=0;
		    field->Flags|=FIELD_MODIFIED;
		 }
		 else
		 {  if(key=='.'
		       && ((field->Flags & 0xf0)==FLOAT
			   || (field->Flags & 0xf0)==DOUBLE)
		       && (!strchr(work,'.')))
		    {  work[cursor_pos++]=(char)key;
		       field->Flags|=FIELD_MODIFIED;
		    }
		 }
	      }
	 }
      }
   }
   unconvfield(work,field);
   convfield(work,field);
   SetDrMd(w->RPort,field->DrawMode);
   Move(w->RPort,field->LeftEdge,field->TopEdge+w->RPort->TxBaseline);
   Text(w->RPort,work,field->Length);
   return(return_msg);
}
/**********************************************************************/
struct IOField *find_field(rp,x,y,fields)
struct RastPort *rp;
short x, y;
struct IOField *fields;
{  /*
    * find field based on an x, y (mouse) position.
    */
   while(fields)
   {  if(x>=fields->LeftEdge
	 && x<=fields->LeftEdge+(rp->TxWidth * fields->Length)
	 && y>=fields->TopEdge && y<=fields->TopEdge+rp->TxHeight)
	 return(fields);
      fields=fields->NextData;
   }
   return(0);
}
/********************************************************************/
whichkey(s)
struct IntuiMessage *s;
{  /*
    * Convert a RAWKEY to a key code.
    */
   long key;
   short l;
   static struct InputEvent event={0,IECLASS_RAWKEY,0,0,0};

   key=0;
   if(!(s->Code & IECODE_UP_PREFIX))
   {  event.ie_Code=s->Code;
      event.ie_Qualifier=s->Qualifier;
      if((l=RawKeyConvert(&event,&key,10,0)) > 0)
      {  if(key>0)
	 {  key>>=24;
	    if(s->Qualifier&0x71f8)
	       key|=((long)s->Qualifier&0x71f8)<<16;
	 }
      }
   }
   return(key);
}
/********************************************************************/
/********* main function ********************************************/
/********************************************************************/
struct IntuiMessage *getData(w,fields,start)
struct Window *w;
struct IOField *fields,
	      **start;
{  short num_fields, cursor_pos=0;
   struct IOField *cur_field, *tmp_field=0;
   struct IntuiMessage *msg;
   long key;

   num_fields=displayIOField(w,fields);
   if(start && *start)
      cur_field=*start;
   else
      cur_field=fields;

   if(!w->UserPort)
      ModifyIDCMP(w,MOUSEBUTTONS|RAWKEY|INACTIVEWINDOW);
   while(cur_field)
   {  if(cur_field->Flags&OUTONLY)
	 msg=0;
      else
      {  msg=getkey(w,fields,cur_field,cursor_pos);
	 /* If supplied, call field edit routine */
	 if(check_runtime(w,fields,cur_field))
	 {  DisplayBeep(w->WScreen);
	    continue;
	 }
      }
      if(msg)
      {  switch(msg->Class)
	 {  case MOUSEBUTTONS:
	      if(msg->IDCMPWindow==w &&
		(tmp_field=find_field(w->RPort,msg->MouseX,msg->MouseY,fields)))
	      {  if(tmp_field==cur_field)
		 {  cursor_pos=(msg->MouseX - cur_field->LeftEdge)/
					     w->RPort->TxWidth;
		 }
		 else
		    cur_field=tmp_field;
	      }
	      else
	      {  if(start)
		    *start=cur_field;
		 return(msg);
	      }
	      break;
	    case RAWKEY:
	      key=whichkey(msg);
	      switch(key)
	      {  case RK_UP:
		   {  struct IOField *work, *prev=0;
		      work=fields;
		      while(work)
		      {  if(work==cur_field)
			    break;
			 prev=work;
			 work=work->NextData;
		      }
		      if(prev)
			 cur_field=prev;
		   }
		   break;
		 case RK_DOWN:
		   if(cur_field->NextData)
		      cur_field=cur_field->NextData;
		   else
		      cur_field=fields;
		   break;
		 case K_HOME:
		 case K_PGUP:
		   cur_field=fields;
		   break;
		 case K_FTAB:
		   if(cur_field->NextData)
		      cur_field=cur_field->NextData;
		   else
		      cur_field=fields;
		   break;
		 case K_PGDOWN:
		   cur_field=fields;
		   while(cur_field->NextData)
		      cur_field=cur_field->NextData;
		   break;
		 case K_END:
		   if(start)
		      *start=cur_field;
		   return(0);
	      }
	      break;
	    case INACTIVEWINDOW:
	    default:
	      if(start)
		 *start=cur_field;
	      return(msg);
	 }
      }
      else
      {  cursor_pos=0;
	 cur_field=cur_field->NextData;
	 if(!cur_field)
	    cur_field=fields;
      }
   }
   return(0);
}
/********************************************************************/
cursor(wd,x,y,w,h)
struct Window *wd;
short x, y, w, h;
{  char chip mem[128];
   x+=wd->LeftEdge;
   y+=wd->TopEdge;
   BltBitMap(wd->RPort->BitMap,x,y,
	     wd->RPort->BitMap,x,y,  w,h,0x30,0xff,mem);
}
