/*
	Miscellaneous C routines used only in DOS Fractint.
*/

#include <string.h>
#include <stdio.h>
#ifndef XFRACT
#include <dos.h>
#include <io.h>
#include <process.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#include <stdlib.h>
#include <ctype.h>
#include "fractint.h"
#include "fractype.h"
#include "helpdefs.h"
#include "prototyp.h"

/* routines in this module	*/

static int menu_checkkey(int curkey,int choice);

/* uncomment following for book version */
/* #define WAITE */

int release=1820; /* this has 2 implied decimals; increment it every synch */
int patchlevel=0; /* patchlevel for DOS version */
int xrelease=202;

/* fullscreen_choice options */
#define CHOICERETURNKEY 1
#define CHOICEMENU	2
#define CHOICEHELP	4

extern char diskfilename[];
extern int using_jiim;
extern int  xdots, ydots, sxdots, sydots, sxoffs, syoffs, vxdots;
extern int  colors;
extern int  dotmode;
extern int  oktoprint;
extern int  textrow, textcol, textrbase, textcbase;
extern int  debugflag;
extern int  fractype;
extern int  calc_status;
extern double param[];
extern int  tabmode;
extern int  color_dark,color_medium,color_bright;
extern int  lookatmouse;
extern int  gotrealdac;
extern int  reallyega;
extern SEGTYPE  extraseg;
extern int  active_system;
extern int  first_init;
extern int initbatch;		/* 1 if batch run (no kbd)  */


/* int stopmsg(flags,message) displays message and waits for a key:
     message should be a max of 9 lines with \n's separating them;
       no leading or trailing \n's in message;
       no line longer than 76 chars for best appearance;
     flag options:
       &1 if already in text display mode, stackscreen is not called
	  and message is displayed at (12,0) instead of (4,0)
       &2 if continue/cancel indication is to be returned;
	  when not set, "Any key to continue..." is displayed
	  when set, "Escape to cancel, any other key to continue..."
	  -1 is returned for cancel, 0 for continue
       &4 set to suppress buzzer
       &8 for Fractint for Windows & parser - use a fixed pitch font
      &16 for info only message (green box instead of red in DOS vsn)
   */
int stopmsg (int flags, CHAR far *msg)
{
   int ret,toprow,color,savelookatmouse;
   if (active_system == 0 /* DOS */
     && first_init) {	  /* & cmdfiles hasn't finished 1st try */
      setvideotext();
      buzzer(2);
      putstring(0,0,15,"*** Error during startup:");
      putstring(2,0,15,msg);
      movecursor(8,0);
#ifdef XFRACT
      sleep(1);
      UnixDone();
#endif
      exit(1);
      }
   if (initbatch == 1) { /* in batch mode */
      initbatch = 4; /* used to set errorlevel */
      return (-1);
      }
   ret = 0;
   savelookatmouse = lookatmouse;
   lookatmouse = -13;
   if ((flags & 1))
      blankrows(toprow=12,10,7);
   else {
      stackscreen();
      toprow = 4;
      movecursor(4,0);
      }
   textcbase = 2; /* left margin is 2 */
   putstring(toprow,0,7,msg);
   if (flags & 2)
      putstring(textrow+2,0,7,"Escape to cancel, any other key to continue...");
   else
      putstring(textrow+2,0,7,"Any key to continue...");
   textcbase = 0; /* back to full line */
   color = (flags & 16) ? C_STOP_INFO : C_STOP_ERR;
   setattr(toprow,0,color,(textrow+1-toprow)*80);
   movecursor(25,80);	/* cursor off */
   if ((flags & 4) == 0)
      buzzer((flags & 16) ? 0 : 2);
   while (keypressed()) /* flush any keyahead */
      getakey();
   if (getakeynohelp() == ESC)
      ret = -1;
   if ((flags & 1))
      blankrows(toprow,10,7);
   else
      unstackscreen();
   lookatmouse = savelookatmouse;
   return ret;
}


static char far *temptextsave = NULL;
static int  textxdots,textydots;

/* texttempmsg(msg) displays a text message of up to 40 characters, waits
      for a key press, restores the prior display, and returns (without
      eating the key).
      It works in almost any video mode - does nothing in some very odd cases
      (HCGA hi-res with old bios), or when there isn't 10k of temp mem free. */
int texttempmsg(char *msgparm)
{
   if (showtempmsg(msgparm))
      return(-1);
#ifndef XFRACT
   while (!keypressed()) {} /* wait for a keystroke but don't eat it */
#else
   waitkeypressed(0); /* wait for a keystroke but don't eat it */
#endif
   cleartempmsg();
   return(0);
}

void freetempmsg()
{
   if(temptextsave != NULL)
      farmemfree(temptextsave);
   temptextsave = NULL;
}

int showtempmsg(char *msgparm)
{
   static long size = 0;
   extern int color_dark,color_medium;
   CHAR msg[41];
   BYTE buffer[640];
   char far *fartmp;
   BYTE far *fontptr;
   BYTE *bufptr;
   int i,j,k,xrepeat,yrepeat,fontchar,charnum;
   int save_sxoffs,save_syoffs;
   strncpy(msg,msgparm,40);
   msg[40] = 0; /* ensure max message len of 40 chars */
   if (dotmode == 11) { /* disk video, screen in text mode, easy */
      dvid_status(0,msg);
      return(0);
      }
   if ((fontptr = findfont(0)) == NULL) { /* old bios, no font table? */
      if (oktoprint == 0	       /* can't printf */
	|| sxdots > 640 || sydots > 200) /* not willing to trust char cell size */
	 return(-1); /* sorry, message not displayed */
      textydots = 8;
      textxdots = sxdots;
      }
   else {
      xrepeat = (sxdots >= 640) ? 2 : 1;
      yrepeat = (sydots >= 300) ? 2 : 1;
      textxdots = strlen(msg) * xrepeat * 8;
      textydots = yrepeat * 8;
      }
   /* worst case needs 10k */
   if(temptextsave !=NULL)
      if(size != (long)textxdots * (long)textydots)
         freetempmsg();      
   size = (long)textxdots * (long)textydots;
   save_sxoffs = sxoffs;
   save_syoffs = syoffs;
   sxoffs = syoffs = 0;
   if(temptextsave == NULL) /* only save screen first time called */
   {
      if ((temptextsave = farmemalloc(size)) == NULL)
         return(-1); /* sorry, message not displayed */
      fartmp = temptextsave;
      for (i = 0; i < textydots; ++i) {
         get_line(i,0,textxdots-1,buffer);
         for (j = 0; j < textxdots; ++j) /* copy it out to far memory */
		 *(fartmp++) = buffer[j];
         }
      }
   if (fontptr == NULL) { /* bios must do it for us */
      home();
      printf(msg);
      }
   else { /* generate the characters */
      find_special_colors(); /* get color_dark & color_medium set */
      for (i = 0; i < 8; ++i) {
	 memset(buffer,color_dark,640);
	 bufptr = buffer;
	 charnum = -1;
	 while (msg[++charnum] != 0) {
	    fontchar = *(fontptr + msg[charnum]*8 + i);
	    for (j = 0; j < 8; ++j) {
	       for (k = 0; k < xrepeat; ++k) {
		  if ((fontchar & 0x80) != 0)
		     *bufptr = color_medium;
		  ++bufptr;
		  }
	       fontchar <<= 1;
	       }
	    }
	 for (j = 0; j < yrepeat; ++j)
	    put_line(i*yrepeat+j,0,textxdots-1,buffer);
	 }
      }
   sxoffs = save_sxoffs;
   syoffs = save_syoffs;
   return(0);
}

void cleartempmsg()
{
   BYTE buffer[640];
   char far *fartmp;
   int i,j;
   int save_sxoffs,save_syoffs;
   if (dotmode == 11) /* disk video, easy */
      dvid_status(0,"");
   else if (temptextsave != NULL) {
      save_sxoffs = sxoffs;
      save_syoffs = syoffs;
      sxoffs = syoffs = 0;
      fartmp = temptextsave;
      for (i = 0; i < textydots; ++i) {
	 for (j = 0; j < textxdots; ++j) /* copy back from far memory */
	    buffer[j] = *(fartmp++);
	 put_line(i,0,textxdots-1,buffer);
	 }
     if(using_jiim == 0)  /* jiim frees memory with freetempmsg() */
     {
         farmemfree(temptextsave);
         temptextsave = NULL;
      }
      sxoffs = save_sxoffs;
      syoffs = save_syoffs;
      }
}


void blankrows(int row,int rows,int attr)
{
   char buf[81];
   memset(buf,' ',80);
   buf[80] = 0;
   while (--rows >= 0)
      putstring(row++,0,attr,buf);
   }


void helptitle()
{
   char msg[80],buf[80];
   setclear(); /* clear the screen */
#ifdef XFRACT
   sprintf(msg,"XFRACTINT  Version %d.%02d (FRACTINT Version %d.%02d)",
           xrelease/100,xrelease%100, release/100,release%100);
   putstringcenter(0,0,80,C_TITLE,msg);
#else
#ifdef WAITE
   release=1821;
   patchlevel = 0;
   sprintf(msg,"Special FRACTINT Version %d.%01d",release/100,(release%100)/10);
#else
   sprintf(msg,"FRACTINT Version %d.%01d",release/100,(release%100)/10);
#endif
   if (release%10) {
      sprintf(buf,"%01d",release%10);
      strcat(msg,buf);
      }
#ifndef XFRACT
   if (patchlevel) {
      sprintf(buf," Patch %d",patchlevel);
      strcat(msg,buf);
      }
#endif
#ifdef WAITE /* realdos.c */
   strcat(msg," for the Waite Group's Fractal Creations 2nd Ed.");
#endif /* WAITE - realdos.c */
   putstringcenter(0,0,80,C_TITLE,msg);
#ifdef WAITE
   return;
#endif
#endif
/* uncomment next for production executable: */
   /* return;  */
   /*NOTREACHED*/
   if (debugflag == 3002) return;
/* putstring(0,2,C_TITLE_DEV,"Development Version"); */
/* replace above by next after creating production release, for release source */
   putstring(0,3,C_TITLE_DEV, "Customized Version"); 
   putstring(0,55,C_TITLE_DEV,"Not for Public Release");
}


int putstringcenter(int row, int col, int width, int attr, char far *msg)
{
   char buf[81];
   int i,j,k;
   i = 0;
#ifdef XFRACT
   if (width==80) width=79; /* Some systems choke in column 80 */
#endif
   while (msg[i]) ++i; /* strlen for a far */
   if (i == 0) return(-1);
   j = (width - i) / 2;
   j -= (width + 10 - i) / 20; /* when wide a bit left of center looks better */
   memset(buf,' ',width);
   buf[width] = 0;
   i = 0;
   k = j;
   while (msg[i]) buf[k++] = msg[i++]; /* strcpy for a far */
   putstring(row,col,attr,buf);
   return j;
}


#ifndef XFRACT
static int screenctr=-1;
#else
static int screenctr=0;
#endif
#define MAXSCREENS 3
static BYTE far *savescreen[MAXSCREENS];
static int saverc[MAXSCREENS+1];
static FILE *savescf=NULL;
static char scsvfile[]="fractscr.tmp";
extern int text_type;
extern int textaddr;

void stackscreen()
{
#ifndef XFRACT
   BYTE far *vidmem;
   int savebytes;
   int i;
   BYTE far *ptr;
   char buf[100];
   saverc[screenctr+1] = textrow*80 + textcol;
   if (++screenctr) { /* already have some stacked */
	 static char far msg[]={"stackscreen overflow"};
      if ((i = screenctr - 1) >= MAXSCREENS) { /* bug, missing unstack? */
	 stopmsg(1,msg);
	 exit(1);
	 }
      vidmem = MK_FP(textaddr,0);
      savebytes = (text_type == 0) ? 4000 : 16384;
      if ((ptr = savescreen[i] = farmemalloc((long)savebytes)))
	 far_memcpy(ptr,vidmem,savebytes);
      else {
	 if (savescf == NULL) { /* create file just once */
	    if ((savescf = fopen(scsvfile,"wb")) == NULL)
	       goto fileproblem;
	    if (fwrite(buf,MAXSCREENS,16384,savescf) != 16384)
	       goto fileproblem;
	    fclose(savescf);
	    if ((savescf = fopen(scsvfile,"r+b")) == NULL) {
	    static char far msg[]={"insufficient memory, aborting"};
fileproblem:   stopmsg(1,msg);
	       exit(1);
	       }
	    }
	 fseek(savescf,(long)(savebytes*i),SEEK_SET);
	 while (--savebytes >= 0)
	    putc(*(vidmem++),savescf);
	 }
      setclear();
      }
   else
      setfortext();
#else
   int i;
   BYTE far *ptr;
   saverc[screenctr+1] = textrow*80 + textcol;
   if (++screenctr) { /* already have some stacked */
         static char far msg[]={"stackscreen overflow"};
      if ((i = screenctr - 1) >= MAXSCREENS) { /* bug, missing unstack? */
         stopmsg(1,msg);
         exit(1);
         }
      if (ptr = savescreen[i] = farmemalloc(sizeof(int *)))
         savecurses(ptr);
      else {
         stopmsg(1,msg);
         exit(1);
        }
      setclear();
      }
   else
      setfortext();
#endif
}

void unstackscreen()
{
#ifndef XFRACT
   char far *vidmem;
   int savebytes;
   BYTE far *ptr;
   textrow = saverc[screenctr] / 80;
   textcol = saverc[screenctr] % 80;
   if (--screenctr >= 0) { /* unstack */
      vidmem = MK_FP(textaddr,0);
      savebytes = (text_type == 0) ? 4000 : 16384;
      if ((ptr = savescreen[screenctr])) {
	 far_memcpy(vidmem,ptr,savebytes);
	 farmemfree(ptr);
	 }
      else {
	 fseek(savescf,(long)(savebytes*screenctr),SEEK_SET);
	 while (--savebytes >= 0)
	    *(vidmem++) = getc(savescf);
	 }
      }
   else
      setforgraphics();
   movecursor(-1,-1);
#else
   BYTE far *ptr;
   textrow = saverc[screenctr] / 80;
   textcol = saverc[screenctr] % 80;
   if (--screenctr >= 0) { /* unstack */
      ptr = savescreen[screenctr];
      restorecurses(ptr);
      farmemfree(ptr);
      }
   else
      setforgraphics();
   movecursor(-1,-1);
#endif
}

void discardscreen()
{
   if (--screenctr >= 0) { /* unstack */
      if (savescreen[screenctr])
	 farmemfree(savescreen[screenctr]);
      }
   else
      discardgraphics();
}


/* --------------------------------------------------------------------- */

char speed_prompt[]="Speed key string";

int fullscreen_choice(
	int options,	     /* &2 use menu coloring scheme	       */
			     /* &4 include F1 for help in instructions */
			     /* &8 add caller's instr after normal set */
	char far *hdg,	     /* heading info, \n delimited	       */
	char far *hdg2,	     /* column heading or NULL		       */
	char far *instr,     /* instructions, \n delimited, or NULL    */
	int numchoices,      /* How many choices in list	       */
	char **choices,      /* array of choice strings 	       */
	int *attributes,     /* &3: 0 normal color, 1,3 highlight      */
			     /* &256 marks a dummy entry	       */
	int boxwidth,	     /* box width, 0 for calc (in items)       */
	int boxdepth,	     /* box depth, 0 for calc, 99 for max      */
	int colwidth,	     /* data width of a column, 0 for calc     */
	int current,	     /* start with this item		       */
	void (*formatitem)(),/* routine to display an item or NULL     */
	char *speedstring,   /* returned speed key value, or NULL >[30]*/
	int (*speedprompt)(),/* routine to display prompt or NULL      */
	int (*checkkey)()    /* routine to check keystroke or NULL     */
)
   /* return is: n>=0 for choice n selected,
		 -1 for escape
		  k for checkkey routine return value k (if not 0 nor -1)
      speedstring[0] != 0 on return if string is present
      */
{
static char far choiceinstr1a[]="Use the cursor keys to highlight your selection";
static char far choiceinstr1b[]="Use the cursor keys or type a value to make a selection";
static char far choiceinstr2a[]="Press ENTER for highlighted choice, or ESCAPE to back out";
static char far choiceinstr2b[]="Press ENTER for highlighted choice, ESCAPE to back out, or F1 for help";
static char far choiceinstr2c[]="Press ENTER for highlighted choice, or F1 for help";

   int titlelines,titlewidth;
   int reqdrows;
   int topleftrow,topleftcol;
   int topleftchoice;
   int speedrow;  /* speed key prompt */
   int boxitems;  /* boxwidth*boxdepth */
   int curkey,increment,rev_increment;
   int redisplay;
   int i,j,k;
   char far *charptr;
   char buf[81];
   int speed_match = 0;
   char curitem[81];
   char *itemptr;
   int ret,savelookatmouse;

   savelookatmouse = lookatmouse;
   lookatmouse = 0;
   ret = -1;
   if (speedstring
     && (i = strlen(speedstring)) > 0) { /* preset current to passed string */
      current = 0;
      while (current < numchoices
	&& (k = strncasecmp(speedstring,choices[current],i)) > 0)
	 ++current;
      if (k < 0 && current > 0)  /* oops - overshot */
	 --current;
      if (current >= numchoices) /* bumped end of list */
	 current = numchoices - 1;
      }

   while (1) {
      if (current >= numchoices)  /* no real choice in the list? */
	 goto fs_choice_end;
      if ((attributes[current] & 256) == 0)
	 break;
      ++current;		  /* scan for a real choice */
      }

   titlelines = titlewidth = 0;
   if (hdg) {
      charptr = hdg;		  /* count title lines, find widest */
      i = 0;
      titlelines = 1;
      while (*charptr) {
	 if (*(charptr++) == '\n') {
	    ++titlelines;
	    i = -1;
	    }
	 if (++i > titlewidth)
	    titlewidth = i;
	 }
      }

   if (colwidth == 0)		  /* find widest column */
      for (i = 0; i < numchoices; ++i)
	 if (strlen(choices[i]) > colwidth)
	    colwidth = strlen(choices[i]);

   /* title(1), blank(1), hdg(n), blank(1), body(n), blank(1), instr(?) */
   reqdrows = 3;		  /* calc rows available */
   if (hdg)
      reqdrows += titlelines + 1;
   if (instr) { 		  /* count instructions lines */
      charptr = instr;
      ++reqdrows;
      while (*charptr)
	 if (*(charptr++) == '\n')
	    ++reqdrows;
      if ((options & 8))	  /* show std instr too */
	 reqdrows += 2;
      }
   else
      reqdrows += 2;		  /* standard instructions */
   if (speedstring) ++reqdrows;   /* a row for speedkey prompt */
   if (boxdepth > (i = 25 - reqdrows)) /* limit the depth to max */
      boxdepth = i;
   if (boxwidth == 0) { 	  /* pick box width and depth */
      if (numchoices <= i - 2) {  /* single column is 1st choice if we can */
	 boxdepth = numchoices;
	 boxwidth = 1;
	 }
      else {			  /* sort-of-wide is 2nd choice */
	 boxwidth = 60 / (colwidth + 1);
	 if (boxwidth == 0
	   || (boxdepth = (numchoices+boxwidth-1)/boxwidth) > i - 2) {
	    boxwidth = 80 / (colwidth + 1); /* last gasp, full width */
	    if ((boxdepth = (numchoices+boxwidth-1)/boxwidth) > i)
	       boxdepth = i;
	    }
	 }
      }
   if ((i = 77 / boxwidth - colwidth) > 3) /* spaces to add @ left each choice */
      i = 3;
   j = boxwidth * (colwidth += i) + i;	   /* overall width of box */
   if (j < titlewidth+2)
      j = titlewidth + 2;
   if (j > 80)
      j = 80;
   if (j <= 70 && boxwidth == 2) {	   /* special case makes menus nicer */
      ++j;
      ++colwidth;
      }
   k = (80 - j) / 2;			   /* center the box */
   k -= (90 - j) / 20;
   topleftcol = k + i;			   /* column of topleft choice */
   i = (25 - reqdrows - boxdepth) / 2;
   i -= i / 4;				   /* higher is better if lots extra */
   topleftrow = 3 + titlelines + i;	   /* row of topleft choice */

   /* now set up the overall display */
   helptitle(); 			   /* clear, display title line */
   setattr(1,0,C_PROMPT_BKGRD,24*80);	   /* init rest to background */
   for (i = topleftrow-1-titlelines; i < topleftrow+boxdepth+1; ++i)
      setattr(i,k,C_PROMPT_LO,j);	   /* draw empty box */
   if (hdg) {
      textcbase = (80 - titlewidth) / 2;   /* set left margin for putstring */
      textcbase -= (90 - titlewidth) / 20; /* put heading into box */
      putstring(topleftrow-titlelines-1,0,C_PROMPT_HI,hdg);
      textcbase = 0;
      }
   if (hdg2)				   /* display 2nd heading */
      putstring(topleftrow-1,topleftcol,C_PROMPT_MED,hdg2);
   i = topleftrow + boxdepth + 1;
   if (instr == NULL || (options & 8)) {   /* display default instructions */
      if (i < 20) ++i;
      if (speedstring) {
	 speedrow = i;
	 *speedstring = 0;
	 if (++i < 22) ++i;
	 }
      putstringcenter(i++,0,80,C_PROMPT_BKGRD,
	    (speedstring) ? choiceinstr1b : choiceinstr1a);
      putstringcenter(i++,0,80,C_PROMPT_BKGRD,
	    (options&CHOICEMENU) ? choiceinstr2c
	    : ((options&CHOICEHELP) ? choiceinstr2b : choiceinstr2a));
      }
   if (instr) { 			   /* display caller's instructions */
      charptr = instr;
      j = -1;
      while ((buf[++j] = *(charptr++)))
	 if (buf[j] == '\n') {
	    buf[j] = 0;
	    putstringcenter(i++,0,80,C_PROMPT_BKGRD,buf);
	    j = -1;
	    }
      putstringcenter(i,0,80,C_PROMPT_BKGRD,buf);
      }

   boxitems = boxwidth * boxdepth;
   topleftchoice = 0;			   /* pick topleft for init display */
   while (current - topleftchoice >= boxitems
     || (current - topleftchoice > boxitems/2
	 && topleftchoice + boxitems < numchoices))
      topleftchoice += boxwidth;
   redisplay = 1;

   while (1) { /* main loop */

      if (redisplay) {			     /* display the current choices */
	 if ((options & CHOICEMENU) == 0) {
	    memset(buf,' ',80);
	    buf[boxwidth*colwidth] = 0;
	    for (i = (hdg2) ? 0 : -1; i <= boxdepth; ++i)  /* blank the box */
	       putstring(topleftrow+i,topleftcol,C_PROMPT_LO,buf);
	    }
	 for (i = 0; i+topleftchoice < numchoices && i < boxitems; ++i) {
	    /* display the choices */
	    if ((k = attributes[j = i+topleftchoice] & 3) == 1)
	       k = C_PROMPT_LO;
	    else if (k == 3)
	       k = C_PROMPT_HI;
	    else
	       k = C_PROMPT_MED;
	    if (formatitem)
	       (*formatitem)(j,charptr=buf);
	    else
	       charptr = choices[j];
	    putstring(topleftrow+i/boxwidth,topleftcol+(i%boxwidth)*colwidth,
		      k,charptr);
	    }
	 /***
	 ... format differs for summary/detail, whups, force box width to
	 ...  be 72 when detail toggle available?  (2 grey margin each
	 ...  side, 1 blue margin each side)
	 ***/
	 if (topleftchoice > 0 && hdg2 == NULL)
	    putstring(topleftrow-1,topleftcol,C_PROMPT_LO,"(more)");
	 if (topleftchoice + boxitems < numchoices)
	    putstring(topleftrow+boxdepth,topleftcol,C_PROMPT_LO,"(more)");
	 redisplay = 0;
	 }

      i = current - topleftchoice;	     /* highlight the current choice */
      if (formatitem)
	 (*formatitem)(current,itemptr=curitem);
      else
	 itemptr = choices[current];
      putstring(topleftrow+i/boxwidth,topleftcol+(i%boxwidth)*colwidth,
		C_CHOICE_CURRENT,itemptr);

      if (speedstring) {		     /* show speedstring if any */
	 memset(buf,' ',80);
	 buf[80] = 0;
	 putstring(speedrow,0,C_PROMPT_BKGRD,buf);
	 if (*speedstring) {		     /* got a speedstring on the go */
	    putstring(speedrow,15,C_CHOICE_SP_INSTR," ");
	    if (speedprompt)
	       j = speedprompt(speedrow,16,C_CHOICE_SP_INSTR,speedstring,speed_match);
	    else {
	       putstring(speedrow,16,C_CHOICE_SP_INSTR,speed_prompt);
	       j = strlen(speed_prompt);
	       }
	    strcpy(buf,speedstring);
	    i = strlen(buf);
	    while (i < 30)
	       buf[i++] = ' ';
	    buf[i] = 0;
	    putstring(speedrow,16+j,C_CHOICE_SP_INSTR," ");
	    putstring(speedrow,17+j,C_CHOICE_SP_KEYIN,buf);
	    movecursor(speedrow,17+j+strlen(speedstring));
	    }
	 else
	    movecursor(25,80);
	 }
      else
	 movecursor(25,80);

#ifndef XFRACT
      while (!keypressed()) { } /* enables help */
#else
      waitkeypressed(0); /* enables help */
#endif
      curkey = getakey();
#ifdef XFRACT
      if (curkey==F10) curkey=')';
      if (curkey==F9) curkey='(';
      if (curkey==F8) curkey='*';
#endif

      i = current - topleftchoice;	     /* unhighlight current choice */
      if ((k = attributes[current] & 3) == 1)
	 k = C_PROMPT_LO;
      else if (k == 3)
	 k = C_PROMPT_HI;
      else
	 k = C_PROMPT_MED;
      putstring(topleftrow+i/boxwidth,topleftcol+(i%boxwidth)*colwidth,
		k,itemptr);

      increment = 0;
      switch (curkey) { 		     /* deal with input key */
	 case ENTER:
	 case ENTER_2:
	    ret = current;
	    goto fs_choice_end;
	 case ESC:
	    goto fs_choice_end;
	 case DOWN_ARROW:
	 case DOWN_ARROW_2:
	    rev_increment = 0 - (increment = boxwidth);
	    break;
	 case UP_ARROW:
	 case UP_ARROW_2:
	    increment = 0 - (rev_increment = boxwidth);
	    break;
	 case RIGHT_ARROW:
	 case RIGHT_ARROW_2:
	    if (boxwidth == 1) break;
	    increment = 1; rev_increment = -1;
	    break;
	 case LEFT_ARROW:
	 case LEFT_ARROW_2:
	    if (boxwidth == 1) break;
	    increment = -1; rev_increment = 1;
	    break;
	 case PAGE_UP:
	    if (numchoices > boxitems) {
	       topleftchoice -= boxitems;
	       increment = -boxitems;
	       rev_increment = boxwidth;
	       redisplay = 1;
	       }
	    break;
	 case PAGE_DOWN:
	    if (numchoices > boxitems) {
	       topleftchoice += boxitems;
	       increment = boxitems;
	       rev_increment = -boxwidth;
	       redisplay = 1;
	       }
	    break;
	 case CTL_HOME:
	 case HOME:
	    current = -1;
	    increment = rev_increment = 1;
	    break;
	 case CTL_END:
	 case END:
	    current = numchoices;
	    increment = rev_increment = -1;
	    break;
	 default:
	    if (checkkey) {
	       if ((ret = (*checkkey)(curkey,current)) < -1 || ret > 0)
		  goto fs_choice_end;
	       if (ret == -1)
		  redisplay = -1;
	       }
	    ret = -1;
	    if (speedstring) {
	       i = strlen(speedstring);
	       if (curkey == 8 && i > 0) /* backspace */
		  speedstring[--i] = 0;
	       if (33 <= curkey && curkey <= 126 && i < 30) {
		  curkey = tolower(curkey);
		  speedstring[i] = curkey;
		  speedstring[++i] = 0;
		  }
	       if (i > 0) {		 /* locate matching type */
		  current = 0;
		  while (current < numchoices
		    && (speed_match = strncasecmp(speedstring,choices[current],i)) > 0)
		     ++current;
		  if (speed_match < 0 && current > 0)  /* oops - overshot */
		     --current;
		  if (current >= numchoices) /* bumped end of list */
		     current = numchoices - 1;
		  }
	       }
	    break;
	 }

      if (increment) {			/* apply cursor movement */
	 current += increment;
	 if (speedstring)		/* zap speedstring */
	    speedstring[0] = 0;
	 }
      while (1) {			/* adjust to a non-comment choice */
	 if (current < 0 || current >= numchoices)
	     increment = rev_increment;
	 else if ((attributes[current] & 256) == 0)
	     break;
	 current += increment;
	 }
      if (topleftchoice > numchoices - boxitems)
	 topleftchoice = ((numchoices+boxwidth-1)/boxwidth)*boxwidth - boxitems;
      if (topleftchoice < 0)
	 topleftchoice = 0;
      while (current < topleftchoice) {
	 topleftchoice -= boxwidth;
	 redisplay = 1;
	 }
      while (current >= topleftchoice + boxitems) {
	 topleftchoice += boxwidth;
	 redisplay = 1;
	 }
      }

fs_choice_end:
   lookatmouse = savelookatmouse;
   return(ret);

}


#ifndef XFRACT
/* case independent version of strncmp */
int strncasecmp(char *s,char *t,int ct)
{
   for(; (tolower(*s) == tolower(*t)) && --ct ; s++,t++)
      if(*s == '\0')
	 return(0);
   return(tolower(*s) - tolower(*t));
}
#endif


static int menutype;
#define MENU_HDG 3
#define MENU_ITEM 1

int main_menu(int fullmenu)
{
   char *choices[44]; /* 2 columns * 22 rows */
   int attributes[44];
   int choicekey[44];
   int i;
   int nextleft,nextright;
   int oldtabmode,oldhelpmode;
   oldtabmode = tabmode;
   oldhelpmode = helpmode;
top:
   menutype = fullmenu;
   tabmode = 0;
   for (i = 0; i < 44; ++i) {
      attributes[i] = 256;
      choices[i] = "";
      choicekey[i] = -1;
      }
   nextleft = -2;
   nextright = -1;

   if (fullmenu) {
      choices[nextleft+=2] = "      CURRENT IMAGE";
      attributes[nextleft] = 256+MENU_HDG;
      choicekey[nextleft+=2] = 13; /* enter */
      attributes[nextleft] = MENU_ITEM;
      if (calc_status == 2)
	 choices[nextleft] = "continue calculation";
      else
	 choices[nextleft] = "return to image";
      choicekey[nextleft+=2] = 9; /* tab */
      attributes[nextleft] = MENU_ITEM;
      choices[nextleft] = "info about image      <tab>";
      choicekey[nextleft+=2] = -10;
      attributes[nextleft] = MENU_ITEM;
      choices[nextleft] = "zoom box functions...";
      choicekey[nextleft+=2] = 'o';
      attributes[nextleft] = MENU_ITEM;
      choices[nextleft] = "orbits window          <o>";
      if(!(fractype==JULIA || fractype==JULIAFP || fractype==INVERSEJULIA))
          nextleft+=2; 
      }
   choices[nextleft+=2] = "      NEW IMAGE";
   attributes[nextleft] = 256+MENU_HDG;
   choicekey[nextleft+=2] = DELETE;
   attributes[nextleft] = MENU_ITEM;
   choices[nextleft] = "select video mode...  <del>";
   choicekey[nextleft+=2] = 't';
   attributes[nextleft] = MENU_ITEM;
   choices[nextleft] = "select fractal type    <t>";
   if (fullmenu) {
      if ((curfractalspecific->tojulia != NOFRACTAL
	  && param[0] == 0.0 && param[1] == 0.0)
	  || curfractalspecific->tomandel != NOFRACTAL) {
	     choicekey[nextleft+=2] = ' ';
	     attributes[nextleft] = MENU_ITEM;
	     choices[nextleft] = "toggle to/from julia <space>";
	  }    
      if(fractype==JULIA || fractype==JULIAFP || fractype==INVERSEJULIA) {
	     choicekey[nextleft+=2] = 'j';
	     attributes[nextleft] = MENU_ITEM;
	     choices[nextleft] = "toggle to/from inverse <j>";
	  }
      choicekey[nextleft+=2] = '\\';
      attributes[nextleft] = MENU_ITEM;
      choices[nextleft] = "return to prior image  <\\>";
   }
   nextleft += 2;
   choices[nextleft+=2] = "      OPTIONS";
   attributes[nextleft] = 256+MENU_HDG;
   choicekey[nextleft+=2] = 'x';
   attributes[nextleft] = MENU_ITEM;
   choices[nextleft] = "basic options...       <x>";
   choicekey[nextleft+=2] = 'y';
   attributes[nextleft] = MENU_ITEM;
   choices[nextleft] = "extended options...    <y>";
   choicekey[nextleft+=2] = 'z';
   attributes[nextleft] = MENU_ITEM;
   choices[nextleft] = "type-specific parms... <z>";
   choicekey[nextleft+=2] = 'v';
   attributes[nextleft] = MENU_ITEM;
   choices[nextleft] = "view window options... <v>";
   choicekey[nextleft+=2] = 'i';
   attributes[nextleft] = MENU_ITEM;
   choices[nextleft] = "fractal 3D parms...    <i>";

   choices[nextright+=2] = "        FILE";
   attributes[nextright] = 256+MENU_HDG;
   choicekey[nextright+=2] = '@';
   attributes[nextright] = MENU_ITEM;
   choices[nextright] = "run saved command set... <@>";
   if (fullmenu) {
      choicekey[nextright+=2] = 's';
      attributes[nextright] = MENU_ITEM;
      choices[nextright] = "save image to file       <s>";
      }
   choicekey[nextright+=2] = 'r';
   attributes[nextright] = MENU_ITEM;
   choices[nextright] = "load image from file...  <r>";
   choicekey[nextright+=2] = '3';
   attributes[nextright] = MENU_ITEM;
   choices[nextright] = "3d transform from file...<3>";
   if (fullmenu) {
      choicekey[nextright+=2] = '#';
      attributes[nextright] = MENU_ITEM;
      choices[nextright] = "3d overlay from file.....<#>";
      choicekey[nextright+=2] = 'b';
      attributes[nextright] = MENU_ITEM;
      choices[nextright] = "save current parameters..<b>";
      choicekey[nextright+=2] = 'p';
      attributes[nextright] = MENU_ITEM;
      choices[nextright] = "print image              <p>";
      }
   choicekey[nextright+=2] = 'd';
   attributes[nextright] = MENU_ITEM;
   choices[nextright] = "shell to dos             <d>";
   choicekey[nextright+=2] = 'g';
   attributes[nextright] = MENU_ITEM;
   choices[nextright] = "give command string      <g>";
   choicekey[nextright+=2] = ESC;
   attributes[nextright] = MENU_ITEM;
   choices[nextright] = "quit Fractint           <esc>";
   choicekey[nextright+=2] = INSERT;
   attributes[nextright] = MENU_ITEM;
   choices[nextright] = "restart Fractint        <ins>";
   if (fullmenu && gotrealdac && colors >= 16) {
      nextright += 2;
      choices[nextright+=2] = "       COLORS";
      attributes[nextright] = 256+MENU_HDG;
      /*** choicekey[nextright+=2] = -12;
	    attributes[nextright] = MENU_ITEM;
	    choices[nextright] = "color cycling menu...";
	    choicekey[nextright+=2] = -13;
	    attributes[nextright] = MENU_ITEM;
	    choices[nextright] = "palette editing menu..."; ***/
      choicekey[nextright+=2] = 'c';
      attributes[nextright] = MENU_ITEM;
      choices[nextright] = "color cycling mode       <c>";
      choicekey[nextright+=2] = '+';
      attributes[nextright] = MENU_ITEM;
      choices[nextright] = "rotate palette      <+>, <->";
      if (colors > 16) {
	 if (!reallyega) {
	    choicekey[nextright+=2] = 'e';
	    attributes[nextright] = MENU_ITEM;
	    choices[nextright] = "palette editing mode     <e>";
	    }
	 choicekey[nextright+=2] = 'a';
	 attributes[nextright] = MENU_ITEM;
	 choices[nextright] = "make starfield           <a>";
	 }
      }

   i = (keypressed()) ? getakey() : 0;
   if (menu_checkkey(i,0) == 0) {
      helpmode = HELPMAIN;	   /* switch help modes */
      if ((nextleft += 2) < nextright)
	 nextleft = nextright + 1;
      i = fullscreen_choice(CHOICEMENU,
	  "MAIN MENU",
	  NULL,NULL,nextleft,choices,attributes,
	  2,nextleft/2,29,0,NULL,NULL,NULL,menu_checkkey);
      if (i == -1)     /* escape */
	 i = ESC;
      else if (i < 0)
	 i = 0 - i;
      else {			  /* user selected a choice */
	 i = choicekey[i];
	 switch (i) {		  /* check for special cases */
	    case -10:		  /* zoombox functions */
	       helpmode = HELPZOOM;
	       help(0);
	       i = 0;
	       break;
	    }
	 }
      }
   if (i == ESC) {	       /* escape from menu exits Fractint */
      static char far s[] = "Exit from Fractint (y/n)? y";
      helptitle();
      setattr(1,0,C_GENERAL_MED,24*80);
      for (i = 9; i <= 11; ++i)
	 setattr(i,18,C_GENERAL_INPUT,40);
      putstringcenter(10,18,40,C_GENERAL_INPUT,s);
      movecursor(25,80);
      while ((i = getakey()) != 'y' && i != 'Y' && i != 13) {
	 if (i == 'n' || i == 'N')
	    goto top;
	 }
      goodbye();
      }
   if (i == TAB) {
      tab_display();
      i = 0;
      }
   if (i == ENTER || i == ENTER_2)
      i = 0;		     /* don't trigger new calc */
   tabmode = oldtabmode;
   return(i);
}

static int menu_checkkey(int curkey,int choice)
{
   int testkey;
   testkey = (curkey>='A' && curkey<='Z') ? curkey+('a'-'A') : curkey;
#ifdef XFRACT
   /* We use F2 for shift-@, annoyingly enough */
   if (testkey == F2) return(0-testkey);
#endif
   if (strchr("@txyzgvir3dj",testkey) || testkey == INSERT
     || testkey == ESC || testkey == DELETE)
      return(0-testkey);
   if (menutype) {
      if (strchr("\\sobp",testkey) || testkey == 9)
	 return(0-testkey);
      if (testkey == ' ')
	 if ((curfractalspecific->tojulia != NOFRACTAL
	      && param[0] == 0.0 && param[1] == 0.0)
	   || curfractalspecific->tomandel != NOFRACTAL)
	 return(0-testkey);
      if (gotrealdac && colors >= 16) {
	 if (strchr("c+-",testkey))
	    return(0-testkey);
	 if (colors > 16
	   && (testkey == 'a' || (!reallyega && testkey == 'e')))
	    return(0-testkey);
	 }
      }
   if (check_vidmode_key(0,testkey) >= 0)
      return(0-testkey);
   return(0);
}


int input_field(
	int options,	      /* &1 numeric, &2 integer, &4 double */
	int attr,	      /* display attribute */
	char *fld,	      /* the field itself */
	int len,	      /* field length (declare as 1 larger for \0) */
	int row,	      /* display row */
	int col,	      /* display column */
	int (*checkkey)(int)  /* routine to check non data keys, or NULL */
	)
{
   char savefld[81];
   char buf[81];
   int insert, started, offset, curkey, display;
   int i, j;
   int ret,savelookatmouse;
   savelookatmouse = lookatmouse;
   lookatmouse = 0;
   ret = -1;
   strcpy(savefld,fld);
   insert = started = offset = 0;
   display = 1;
   while (1) {
      strcpy(buf,fld);
      i = strlen(buf);
      while (i < len)
	 buf[i++] = ' ';
      buf[len] = 0;
      if (display) {				    /* display current value */
	 putstring(row,col,attr,buf);
	 display = 0;
	 }
      curkey = keycursor(row+insert,col+offset);  /* get a keystroke */
      switch (curkey) {
	 case ENTER:
	 case ENTER_2:
	    ret = 0;
	    goto inpfld_end;
	 case ESC:
	    goto inpfld_end;
	 case RIGHT_ARROW:
	 case RIGHT_ARROW_2:
	    if (offset < len-1) ++offset;
	    started = 1;
	    break;
	 case LEFT_ARROW:
	 case LEFT_ARROW_2:
	    if (offset > 0) --offset;
	    started = 1;
	    break;
	 case HOME:
	    offset = 0;
	    started = 1;
	    break;
	 case END:
	    offset = strlen(fld);
	    started = 1;
	    break;
	 case 8:
	 case 127:				/* backspace */
	    if (offset > 0) {
	       j = strlen(fld);
	       for (i = offset-1; i < j; ++i)
		  fld[i] = fld[i+1];
	       --offset;
	       }
	    started = display = 1;
	    break;
	 case DELETE:				/* delete */
	    j = strlen(fld);
	    for (i = offset; i < j; ++i)
	       fld[i] = fld[i+1];
	    started = display = 1;
	    break;
	 case INSERT:				/* insert */
	    insert ^= 0x8000;
	    started = 1;
	    break;
	 case F5:
	    strcpy(fld,savefld);
	    insert = started = offset = 0;
	    display = 1;
	    break;
	 default:
            if (nonalpha(curkey)) {
	       if (checkkey && (ret = (*checkkey)(curkey)))
		  goto inpfld_end;
	       break;				     /* non alphanum char */
	       }
	    if (offset >= len) break;		     /* at end of field */
	    if (insert && started && strlen(fld) >= len)
	       break;				     /* insert & full */
	    if ((options & 1)
	      && (curkey < '0' || curkey > '9')
	      && curkey != '+' && curkey != '-') {
	       if ((options & 2))
		  break;
	       /* allow scientific notation, and specials "e" and "p" */
	       if ( ((curkey != 'e' && curkey != 'E') || offset >= 18)
		 && ((curkey != 'p' && curkey != 'P') || offset != 0 )
		 && curkey != '.')
		  break;
	       }
	    if (started == 0) /* first char is data, zap field */
	       fld[0] = 0;
	    if (insert) {
	       j = strlen(fld);
	       while (j >= offset) {
		  fld[j+1] = fld[j];
		  --j;
		  }
	       }
	    if (offset >= strlen(fld))
	       fld[offset+1] = 0;
	    fld[offset++] = curkey;
	    /* if "e" or "p" in first col make number e or pi */
	    if ((options & 3) == 1) { /* floating point */
	       double tmpd;
	       int specialv;
	       char tmpfld[30];
	       specialv = 0;
	       if (*fld == 'e' || *fld == 'E') {
		  tmpd = exp(1.0);
		  specialv = 1;
		  }
	       if (*fld == 'p' || *fld == 'P') {
		  tmpd = atan(1.0) * 4;
		  specialv = 1;
		  }
	       if (specialv) {
		  if ((options & 4) == 0)
		     roundfloatd(&tmpd);
		  sprintf(tmpfld,"%.15g",tmpd);
		  tmpfld[len-1] = 0; /* safety, field should be long enough */
		  strcpy(fld,tmpfld);
		  offset = 0;
		  }
	       }
	    started = display = 1;
	 }
      }
inpfld_end:
   lookatmouse = savelookatmouse;
   return(ret);
}

int field_prompt(
	int options,	    /* &1 numeric value, &2 integer */
	char *hdg,	    /* heading, \n delimited lines */
	char *instr,	    /* additional instructions or NULL */
	char *fld,	    /* the field itself */
	int len,	    /* field length (declare as 1 larger for \0) */
	int (*checkkey)(int)   /* routine to check non data keys, or NULL */
	)
{
   char *charptr;
   int boxwidth,titlelines,titlecol,titlerow;
   int promptcol;
   int i,j;
   char buf[81];
   helptitle(); 			  /* clear screen, display title */
   setattr(1,0,C_PROMPT_BKGRD,24*80);	  /* init rest to background */
   charptr = hdg;			  /* count title lines, find widest */
   i = boxwidth = 0;
   titlelines = 1;
   while (*charptr) {
      if (*(charptr++) == '\n') {
	 ++titlelines;
	 i = -1;
	 }
      if (++i > boxwidth)
	 boxwidth = i;
      }
   if (len > boxwidth)
      boxwidth = len;
   i = titlelines + 4;			  /* total rows in box */
   titlerow = (25 - i) / 2;		  /* top row of it all when centered */
   titlerow -= titlerow / 4;		  /* higher is better if lots extra */
   titlecol = (80 - boxwidth) / 2;	  /* center the box */
   titlecol -= (90 - boxwidth) / 20;
   promptcol = titlecol - (boxwidth-len)/2;
   j = titlecol;			  /* add margin at each side of box */
   if ((i = (82-boxwidth)/4) > 3)
      i = 3;
   j -= i;
   boxwidth += i * 2;
   for (i = -1; i < titlelines+3; ++i)	  /* draw empty box */
      setattr(titlerow+i,j,C_PROMPT_LO,boxwidth);
   textcbase = titlecol;		  /* set left margin for putstring */
   putstring(titlerow,0,C_PROMPT_HI,hdg); /* display heading */
   textcbase = 0;
   i = titlerow + titlelines + 4;
   if (instr) { 			  /* display caller's instructions */
      charptr = instr;
      j = -1;
      while ((buf[++j] = *(charptr++)))
	 if (buf[j] == '\n') {
	    buf[j] = 0;
	    putstringcenter(i++,0,80,C_PROMPT_BKGRD,buf);
	    j = -1;
	    }
      putstringcenter(i,0,80,C_PROMPT_BKGRD,buf);
      }
   else 				  /* default instructions */
      putstringcenter(i,0,80,C_PROMPT_BKGRD,
	      "Press ENTER when finished (or ESCAPE to back out)");
   return(input_field(options,C_PROMPT_INPUT,fld,len,
		      titlerow+titlelines+1,promptcol,checkkey));
}


/* thinking(1,message):
      if thinking message not yet on display, it is displayed;
      otherwise the wheel is updated
      returns 0 to keep going, -1 if keystroke pending
   thinking(0,NULL):
      call this when thinking phase is done
   */

int thinking(int options,char *msg)
{
   static int thinkstate = -1;
   static char *wheel[] = {"-","\\","|","/"};
   static int thinkcol;
   static int count=0;
   char buf[81];
   if (options == 0) {
      if (thinkstate >= 0) {
	 thinkstate = -1;
	 unstackscreen();
	 }
      return(0);
      }
   if (thinkstate < 0) {
      stackscreen();
      thinkstate = 0;
      helptitle();
      strcpy(buf,"  ");
      strcat(buf,msg);
      strcat(buf,"    ");
      putstring(4,10,C_GENERAL_HI,buf);
      thinkcol = textcol - 3;
      count = 0;
      }
   if ((count++)<100) {
       return 0;
   }
   count = 0;
   putstring(4,thinkcol,C_GENERAL_HI,wheel[thinkstate]);
   movecursor(25,80); /* turn off cursor */
   thinkstate = (thinkstate + 1) & 3;
   return (keypressed());
}


void clear_screen(void)  /* a stub for a windows only subroutine */
{
}


/* savegraphics/restoregraphics: video.asm subroutines */

static BYTE far *swapsavebuf;
static unsigned int memhandle;
unsigned long swaptotlen;
unsigned long swapoffset;
BYTE far *swapvidbuf;
int swaplength;
static int swaptype = -1;
static int swapblklen; /* must be a power of 2 */
#ifndef XFRACT
extern BYTE suffix[4096];
#else
BYTE suffix[4096];
#endif

#ifndef XFRACT

int savegraphics()
{
   extern int made_dsktemp;
   int i;
   struct XMM_Move   xmmparms;

   discardgraphics(); /* if any emm/xmm in use from prior call, release it */
   swaptotlen = (long)(vxdots > sxdots ? vxdots : sxdots) * (long)sydots;
   i = colors;
   while (i <= 16) {
      swaptotlen >>= 1;
      i = i * i;
      }
   swapoffset = 0;
   if (debugflag != 420 && debugflag != 422 /* 422=xmm test, 420=disk test */
     && (swapsavebuf = emmquery()) != NULL
     && (memhandle = emmallocate((unsigned int)((swaptotlen + 16383) >> 14)))
	 != 0) {
      swaptype = 0; /* use expanded memory */
      swapblklen = 16384;
      }
   else if (debugflag != 420
     && xmmquery() !=0
     && (memhandle = xmmallocate((unsigned int)((swaptotlen + 1023) >> 10)))
	 != 0) {
      swaptype = 1; /* use extended memory */
      swapblklen = 16384;
      }
   else {
      swaptype = 2; /* use disk */
      swapblklen = 4096;

   /* MCP 7-7-91, If 'memhandle' is an 'unsigned int', how is it ever going
      to be equal to -1?

      if ((memhandle = open(diskfilename,O_CREAT|O_WRONLY|O_BINARY,S_IWRITE))
	 == -1) {
   */
      if ((memhandle = open(diskfilename,O_CREAT|O_WRONLY|O_BINARY,S_IWRITE))
	 == 0xffff) {


dskfile_error:
	 setvideotext(); /* text mode */
	 setclear();
         {
            extern char diskfilename[];
            static char far s1[] = {"error in temp file "};
            static char far s2[] = { " (disk full?) - aborted\n\n"};
	    printf("%Fs%s%Fs",s1,diskfilename,s2);
	 }   
	 exit(1);
	 }
      made_dsktemp = 1;
      }
   while (swapoffset < swaptotlen) {
      swaplength = swapblklen;
      if ((swapoffset & (swapblklen-1)) != 0)
	 swaplength = swapblklen - (swapoffset & (swapblklen-1));
      if (swaplength > swaptotlen - swapoffset)
	 swaplength = swaptotlen - swapoffset;
      (*swapsetup)(); /* swapoffset,swaplength -> sets swapvidbuf,swaplength */
      switch(swaptype) {
	 case 0:
	    emmgetpage((unsigned int)(swapoffset>>14),memhandle);
	    movewords(swaplength>>1,swapvidbuf,
		      swapsavebuf+(swapoffset&(swapblklen-1)));
	    break;
	 case 1:
	    xmmparms.Length = swaplength;
	    xmmparms.SourceHandle = 0; /* Source is conventional memory */
	    xmmparms.SourceOffset = (unsigned long)swapvidbuf;
	    xmmparms.DestHandle = memhandle;
	    xmmparms.DestOffset = swapoffset;
	    xmmmoveextended(&xmmparms);
	    break;
	 default:
	    movewords(swaplength>>1,swapvidbuf,(BYTE far *)suffix);
	    if (write(memhandle,suffix,swaplength) == -1)
	       goto dskfile_error;
	 }
      swapoffset += swaplength;
      }
   if (swaptype == 2)
      close(memhandle);
   return 0;
}

void restoregraphics()
{
   struct XMM_Move   xmmparms;

   swapoffset = 0;
   if (swaptype == 2)
      memhandle = open(diskfilename,O_RDONLY|O_BINARY,S_IREAD);
   swapvidbuf = MK_FP(extraseg+0x1000,0); /* for swapnormwrite case */
   while (swapoffset < swaptotlen) {
      swaplength = swapblklen;
      if ((swapoffset & (swapblklen-1)) != 0)
	 swaplength = swapblklen - (swapoffset & (swapblklen-1));
      if (swaplength > swaptotlen - swapoffset)
	 swaplength = swaptotlen - swapoffset;
      if (swapsetup != swapnormread)
	 (*swapsetup)(); /* swapoffset,swaplength -> sets swapvidbuf,swaplength */
      switch(swaptype) {
	 case 0:
	    emmgetpage((unsigned int)(swapoffset>>14),memhandle);
	    movewords(swaplength>>1,swapsavebuf+(swapoffset&(swapblklen-1)),
		      swapvidbuf);
	    break;
	 case 1:
	    xmmparms.Length = swaplength;
	    xmmparms.SourceHandle = memhandle;
	    xmmparms.SourceOffset = swapoffset;
	    xmmparms.DestHandle = 0; /* conventional memory */
	    xmmparms.DestOffset = (unsigned long)swapvidbuf;
	    xmmmoveextended(&xmmparms);
	    break;
	 default:
	    read(memhandle,suffix,swaplength);
	    movewords(swaplength>>1,(BYTE far *)suffix,swapvidbuf);
	 }
      if (swapsetup == swapnormread)
	 swapnormwrite();
      swapoffset += swaplength;
      }
   if (swaptype == 2)
      close(memhandle);
   discardgraphics();
}

#endif

void discardgraphics() /* release expanded/extended memory if any in use */
{
#ifndef XFRACT
   switch(swaptype) {
      case 0:
	 emmdeallocate(memhandle);
	 break;
      case 1:
	 xmmdeallocate(memhandle);
      }
   swaptype = -1;
#endif
}

extern int badconfig;
extern struct videoinfo far videotable[];
struct videoinfo far *vidtbl;  /* temporarily loaded fractint.cfg info */
int vidtbllen;		       /* number of entries in above	       */

int load_fractint_cfg(int options)
{
   /* Reads fractint.cfg, loading videoinfo entries into extraseg. */
   /* Sets vidtbl pointing to the loaded table, and returns the    */
   /* number of entries (also sets vidtbllen to this).		   */
   /* Past vidtbl, cfglinenums are stored for update_fractint_cfg. */
   /* If fractint.cfg is not found or invalid, issues a message    */
   /* (first time the problem occurs only, and only if options is  */
   /* zero) and uses the hard-coded table.			   */

   FILE *cfgfile;
   struct videoinfo far *vident;
   int far *cfglinenums;
   int linenum;
   int i, j, keynum, ax, bx, cx, dx, dotmode, xdots, ydots, colors;
   int commas[10];
   int textsafe2;
   char tempstring[150];

   vidtbl = MK_FP(extraseg,0);
   cfglinenums = (int far *)(&vidtbl[MAXVIDEOMODES]);

#ifdef XFRACT
    badconfig = -1;
#endif

   if (badconfig)  /* fractint.cfg already known to be missing or bad */
      goto use_resident_table;

   findpath("fractint.cfg",tempstring);
   if (tempstring[0] == 0			     /* can't find the file */
     || (cfgfile = fopen(tempstring,"r")) == NULL)   /* can't open it */
      goto bad_fractint_cfg;

   vidtbllen = 0;
   linenum = 0;
   vident = vidtbl;
   while (vidtbllen < MAXVIDEOMODES
     && fgets(tempstring, 120, cfgfile)) {
      ++linenum;
      if (tempstring[0] == ';') continue;   /* comment line */
      tempstring[120] = 0;
      tempstring[strlen(tempstring)-1] = 0; /* zap trailing \n */
      memset(commas,0,20);
      i = j = -1;
      while (1) {
	 if (tempstring[++i] < ' ') {
	    if (tempstring[i] == 0) break;
	    tempstring[i] = ' '; /* convert tab (or whatever) to blank */
	    }
	 else if (tempstring[i] == ',' && ++j < 10) {
	    commas[j] = i + 1;	 /* remember start of next field */
	    tempstring[i] = 0;	 /* make field a separate string */
	    }
	 }
      keynum = check_vidmode_keyname(tempstring);
      sscanf(&tempstring[commas[1]],"%x",&ax);
      sscanf(&tempstring[commas[2]],"%x",&bx);
      sscanf(&tempstring[commas[3]],"%x",&cx);
      sscanf(&tempstring[commas[4]],"%x",&dx);
      dotmode	  = atoi(&tempstring[commas[5]]);
      xdots	  = atoi(&tempstring[commas[6]]);
      ydots	  = atoi(&tempstring[commas[7]]);
      colors	  = atoi(&tempstring[commas[8]]);
      textsafe2   = dotmode / 100;
      dotmode	 %= 100;
      if (j != 9 ||
	    keynum < 0 ||
	    dotmode < 0 || dotmode > 30 ||
	    textsafe2 < 0 || textsafe2 > 4 ||
	    xdots < 160 || xdots > MAXPIXELS ||
	    ydots < 160 || ydots > MAXPIXELS ||
	    (colors != 0 && colors != 2 && colors != 4 && colors != 16 &&
	     colors != 256)
	   )
	 goto bad_fractint_cfg;
      cfglinenums[vidtbllen] = linenum; /* for update_fractint_cfg */
      far_memcpy(vident->name,	 (char far *)&tempstring[commas[0]],25);
      far_memcpy(vident->comment,(char far *)&tempstring[commas[9]],25);
      vident->name[25] = vident->comment[25] = 0;
      vident->keynum	  = keynum;
      vident->videomodeax = ax;
      vident->videomodebx = bx;
      vident->videomodecx = cx;
      vident->videomodedx = dx;
      vident->dotmode	  = textsafe2 * 100 + dotmode;
      vident->xdots	  = xdots;
      vident->ydots	  = ydots;
      vident->colors	  = colors;
      ++vident;
      ++vidtbllen;
      }
   fclose(cfgfile);
   return (vidtbllen);

bad_fractint_cfg:
   badconfig = -1; /* bad, no message issued yet */
   if (options == 0)
      bad_fractint_cfg_msg();

use_resident_table:
   vidtbllen = 0;
   vident = vidtbl;
   for (i = 0; i < MAXVIDEOTABLE; ++i) {
      if (videotable[i].xdots) {
	 far_memcpy((char far *)vident,(char far *)&videotable[i],
		    sizeof(*vident));
	 ++vident;
	 ++vidtbllen;
	 }
      }
   return (vidtbllen);

}

void bad_fractint_cfg_msg()
{
static char far badcfgmsg[]={"\
File FRACTINT.CFG is missing or invalid.\n\
See Hardware Support and Video Modes in the full documentation for help.\n\
I will continue with only the built-in video modes available."};
   stopmsg(0,badcfgmsg);
   badconfig = 1; /* bad, message issued */
}

void load_videotable(int options)
{
   /* Loads fractint.cfg and copies the video modes which are */
   /* assigned to function keys into videotable.	      */
   int keyents,i;
   load_fractint_cfg(options); /* load fractint.cfg to extraseg */
   keyents = 0;
   far_memset((char far *)videotable,0,sizeof(*vidtbl)*MAXVIDEOTABLE);
   for (i = 0; i < vidtbllen; ++i) {
      if (vidtbl[i].keynum > 0) {
	 far_memcpy((char far *)&videotable[keyents],(char far *)&vidtbl[i],
		    sizeof(*vidtbl));
	 if (++keyents >= MAXVIDEOTABLE)
	    break;
	 }
      }
}

int check_vidmode_key(int option,int k)
{
   int i;
   /* returns videotable entry number if the passed keystroke is a  */
   /* function key currently assigned to a video mode, -1 otherwise */
   if (k == 1400)	       /* special value from select_vid_mode  */
      return(MAXVIDEOTABLE-1); /* for last entry with no key assigned */
   if (k != 0)
      if (option == 0) { /* check resident video mode table */
	 for (i = 0; i < MAXVIDEOTABLE; ++i) {
	    if (videotable[i].keynum == k)
	       return(i);
	    }
	 }
      else { /* check full vidtbl */
	 for (i = 0; i < vidtbllen; ++i) {
	    if (vidtbl[i].keynum == k)
	       return(i);
	    }
	 }
   return(-1);
}

int check_vidmode_keyname(char *kname)
{
   /* returns key number for the passed keyname, 0 if not a keyname */
   int i,keyset;
   keyset = 1058;
   if (*kname == 'S' || *kname == 's') {
      keyset = 1083;
      ++kname;
      }
   else if (*kname == 'C' || *kname == 'c') {
      keyset = 1093;
      ++kname;
      }
   else if (*kname == 'A' || *kname == 'a') {
      keyset = 1103;
      ++kname;
      }
   if (*kname != 'F' && *kname != 'f')
      return(0);
   if (*++kname < '1' || *kname > '9')
      return(0);
   i = *kname - '0';
   if (*++kname != 0 && *kname != ' ') {
      if (*kname != '0' || i != 1)
	 return(0);
      i = 10;
      ++kname;
      }
   while (*kname)
      if (*(kname++) != ' ')
	 return(0);
   if ((i += keyset) < 2)
      i = 0;
   return(i);
}

void vidmode_keyname(int k,char *buf)
{
   /* set buffer to name of passed key number */
   *buf = 0;
   if (k > 0) {
      if (k > 1103) {
	 *(buf++) = 'A';
	 k -= 1103;
	 }
      else if (k > 1093) {
	 *(buf++) = 'C';
	 k -= 1093;
	 }
      else if (k > 1083) {
	 *(buf++) = 'S';
	 k -= 1083;
	 }
      else
	 k -= 1058;
      sprintf(buf,"F%d",k);
      }
}
