/***************************************************************************
 *                                                                         *
 *   CNF.C                                                                 *
 *                                                                         *
 *   Copyright (c) 1988-1997 Galacticomm, Inc.  All Rights Reserved.       *
 *                                                                         *
 *   This utility manages the operation of the server by editing           *
 *   operating parameters and text messages, installing them into the      *
 *   .MSG files.                                                           *
 *                                                                         *
 *                                                     RNStein  July 1990  *
 *                                                     TJS & SJB Mar 1992  *
 *                                                     RNStein   Jan 1993  *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "protstuf.h"
#include "mdlogic.h"
#include "tfscan.h"
#include "edtoff.h"
#include "excphand.h"

#define FILREV "$Revision: 23 $"

#define ESCNHGT 15                  /* Height of option work area of screen */
#define LINLEN  59          /* Maximum length of option line on main screen */
#define VALLEN  80   /* Max length of option value (except types 'S' & 'T') */
#define ENLLEN  250     /* Max length of enumerated list of type 'E' option */
#define SRCLEN  40                    /* max length of string to search for */
#define EXCSTG  "UNUSED"           // string to keyoff excluding a option always

INT enarray[]={F3,F4,F5,F6,F7};                   /* index => softkey array */

#define ENMAX (sizeof(enarray)/sizeof(INT))

                                                        /* OPTION STRUCTURE */
struct option {                                         /* ---------------- */
     UINT index;                                    /* 0...totopt-1 */
     struct option *next,*prev;                /* forward/backward pointers */
     struct msgfil *file;                    /* pointer to appropriate file */
     CHAR name[MOPTLEN+1];            /* Name of option (name in .MSG file) */
     CHAR *lindsc;                                  /* one-line description */
     CHAR *value;       /* value of option (see altern() about 'T' options) */
     CHAR flags;                                             /* (see below) */
     CHAR type;                           /* Type of option (C,S,H,N,L,B,E) */

                            /* end of condensed portion, start of full size */
                   /* DANGER! if you change above fields, change OPCORE too */

   /* following fields not needed for types C,B,T if not hinged and no help */

     LONG floor,ceiling;/* Limits for type N,L,H options, maxlen of S opt */
     CHAR *enlist;                               /* List for type E options */
     LONG parptr;           /* .MSG file pointer to description paragraph */
     INT parlen;                         /* Length of description paragraph */
     CHAR hngop;                 /* Hinge operator:  "=" for EQ, "#" for NE */
     struct option *hngptr;             /* Option ptr to the "hinge" option */
     CHAR *hngval;                                        /* Hinge value(s) */
} *ophead,                                    /* option list header pointer */
  *optail,                                      /* option list tail pointer */
  *op;                                           /* option "cursor" pointer */

#define OPCORE (sizeof(struct option) \
          -(3*sizeof(LONG)+2*sizeof(CHAR *)+sizeof(INT) \
            +sizeof(CHAR)+sizeof(struct option *)))

#define FBORDER '-'                        /* pseudo-option between borders */

/*--- Flags ---*/
#define ANYCHG 0x01                /* Has the value of this option changed? */
#define ISHING 0x02                 /* Does this option hinge upon another? */
#define ISHPAR 0x04                   /* is there a paragraph help message? */
#define FILOAD 0x08                        /* this file has been lodopt()'d */
#define FILANG 0x10                    /* this file has new languages in it */

#define MAINFL "wgsmajor.msg"                 /* main MAJORBBS message file */

                                                   /* OPTION FILE STRUCTURE */
struct msgfil {                                    /* --------------------- */
     struct msgfil *next;                                /* forward pointer */
     CHAR name[12+1];                                          /* file name */
     FILE *fp;                                              /* file pointer */
     struct option *fstopt;     /* index into "option" list of first option */
     INT numopt;                        /* number of options from this file */
     LONG bofopt;             /* position in MSG file where options start */
     LONG eofopt;               /* position in MSG file after last option */
     LONG boflng;             /* position in MSG file where options start */
     LONG eoflng;               /* position in MSG file after last option */
     CHAR flags;        /* uses ANYCHG mask, same as for options, see above */
     INT lngcnt;                   /* number of languages in this .MSG file */
     INT *mslidx;                  /*   xlate:  0..nlingo-1 to 0..lngcnt-1 */
} mfhead={NULL,MAINFL,NULL,NULL,0,0L,0L,0L,0L,0,0,NULL},*mftail=&mfhead;
INT nfiles=1;                    /* Number of .MSG files -- set by fndfls() */

FILE *fp;                              /* pointer to file of current option */
UINT totopt;                             /* total number of options */
CHAR scnsav[4000];                         /* save area for CNF main screen */
CHAR savscn[GVIDSCNSIZ];
CHAR *scnptr=scnsav;        /* Pointer to main CNF screen (NULL if visible) */
CHAR value[LINLEN+1];                            /* value of current option */
CHAR rdval[LINLEN+1];                           /* value buffer for rdopt() */
CHAR lindsc[LINLEN+1];              /* 1-line description of current option */
CHAR enlist[ENLLEN+1];               /* enumerated list for type 'E' option */
CHAR hngnam[MOPTLEN+1];                             /* name of hinge option */
CHAR hngval[VALLEN+1];                    /* value(s) for that hinge option */
INT edit=0;                /* Has this option been changed by the operator? */
CHAR trash[LINLEN+1]={""};                /* stowed by <-   retrieved by -> */
SHORT nchang=0;                                /* Number of options changed */
#ifdef GCDOS
INT memrept=0;                                   /* Memory available report */
LONG membeg,memmin=0x2000000L;    /* Beginning and minimum memory available */
#define SAFECORE 40000L                   /* when to start warning operator */
#endif // GCDOS
INT level=5;                              /* from -L<n> command line option */
INT indx;                             /* global return variable of elpick() */
INT done=0;                                 /* totally done with everything */
INT expmode;                                            /* expert mode flag */
INT outbsz;                        /* max output buffer size when svr is up */

#define altern(optr) ((struct altlng *)((optr)->value))
                 /* For 'T' options, op->value.ptr points to an array of    */
                 /* nlingo of these altlng structures.  Remember, the       */
                 /* indexing of this array is 0..lngcnt-1, NOT 0..nlingo-1. */
                 /* To translate, use the mslidx[] array.                   */

CHAR **langchoose;                             /* choose list for languages */

CHAR funkx[]={1,8,15,22,33,40,47,54,65,72}; /* Softkey coordinates, H model */
#define FUNKY 22                            /* Y coord is now constant      */

CHAR *softies[]={" HELP       ",    /* F1 */            /* Soft key legends */
                 "            ",    /* F2 */
                 "            ",    /* F3 */
                 "            ",    /* F4 */
                 "            ",    /* F5 */
                 "            ",    /* F6 */
                 "            ",    /* F7 */
                 "SEARCH      ",    /* F8 */
                 "FORGET  IT  ",    /* F9 */
                 "SAVE & EXIT "};   /* F10 */

CHAR *blanksf[]={"            ",    /* F1 */            /* Soft key legends */
                 "            ",    /* F2 */
                 "            ",    /* F3 */
                 "            ",    /* F4 */
                 "            ",    /* F5 */
                 "            ",    /* F6 */
                 "            ",    /* F7 */
                 "            ",    /* F8 */
                 "            ",    /* F9 */
                 "            "};   /* F10 */


/*  DISPLAY ATTRIBUTES  (upper byte color, lower byte monochrome) */
/*----------------------*/
#define ATRTTL  0x7070                           /* Title at head of screen */
#define ATRIUP  0x1707                       /* when files are being loaded */
#define ATRIDN  0x1D07                    /* when files are being installed */
#define ATRIPC  0x1D07                      /* percent complete in outmsg() */
#define ATRIBR  0x5D07                                /* outmsg() bar graph */
#define ATRCMD  0x1F0F                        /* confirm abandoning changes */
#define ATRFMT  0x1C07                            /* format description box */
#define ATRFLG  0x2E0F                /* alternate language in format field */
#define ATRSBG  0x1A07                                 /* status background */
#define ATRLBG  0x1907                                 /* loading bar-graph */
#define ATRSFG  0x1E0F                                 /* status foreground */
#define ATRSNW  0x4E70                 /* status foreground, changed option */
#define ATRLIN  0x1B07              /* line description of unchanged option */
#define ATRLCH  0x4F0F              /* line description of a changed option */
#define ATRLLU  0x1307  /* line description of unchanged non-lingual option */
#define ATRLLC  0x4707    /* line description of changed non-lingual option */
#define ATRCHB  0x2A07                     /* choose list background border */
#define ATRCHL  0x2E0F                          /* choose list of languages */
#define ATRCHC  0x7070                                /* choose list cursor */
#define ATRCHT  0x2F70                             /* choose list title bar */
#define ATREPB  0x1B07          /* enumerated choose list background border */
#define ATREPL  0x1F07                      /* choose list, multiple choice */
#define ATREPC  0x7070                                /* choose list cursor */
#define ATREPT  0x1B70                             /* choose list title bar */
#define ATRVAL  0x1F0F                     /* value of an option, unchanged */
#define ATRENT  0x4F70                 /* value of an option, while editing */
#define ATRNEW  0x4E70                 /* value of an option, after changed */
#define ATRKEY  0x1F07                                    /* softkey legend */
#define ATREKY  0x1F0F                                     /* Edit soft-key */
#define ATRLKY  0x1F07                          /* Choose-language soft-key */
#define ATRSPK  0x3070                /* softkey legend, enumerated choices */
#define ATRELD  0x1E07                 /* message while waiting for END key */
#define ATRHLF  0x1207                           /* option help, top border */
#define ATRHLP  0x2F70                         /* option help, help message */
#define ATRHLN  0x2170                      /* option help, title underline */
#define ATRHRM  0x2070                           /* option help, title line */
#define ATRGHP  0x1E07                                  /* general help box */
#define ATRMEM  0x1C0F            /* Low memory warning message in help box */
#define ATRSPW  0x1C0F              /* spawn()/system() shelling out failed */
#define ATRSPC  0x1E0F                                /* CNF Editor command */
#define ATRCLG  0x1A0F               /* creating a new version of an option */
#define ATRWOS  0x1E0F                    /* warning about change to OUTBSZ */
#define ATRWOB  0x1C0F        /* warning that OUTBSZ exceeded after editing */
#define ATRWBB  0x1C0F         /* warning that OUTBSZ exceeded when loading */
#define ATRWSP  0x1C0F          /* warning that % symbols have been changed */
#define ATRWS2  0x1F8F              /* warning about %'s, prompt characters */
#define ATRHKY  0x1F0F                                  /* Un-help soft-key */
#define ATRSRC  0x1E0F                                    /* Search command */
#define ATRBDR  0x3170                                       /* file border */
#define ATRBFN  0x3170                               /* file name in border */
#define ATRBKG  0x0707                   /* background when shelling to DOS */
#define ATRXHB  0x1F70                 /* exit-to-DOS help bar in shell-out */

#define ECYBOT 16                          /* bottom Y of option entry area */
INT ecx=18,ecy=ECYBOT;                   /* screen cursor for option screen */
                                     /* command line parameter help message */
static CHAR parhlp[]="\nSyntax:  WGSCNF -L# [-M] [?]\n";

INT skipped;     /* how many options has upop() or dnop() actually skipped? */

CHAR srcbuf[SRCLEN+1]="";         /* search buffer with string searched for */
struct option *srcop;             /* searching option now under examination */
CHAR *srcwhr;                       /* return code of srcfwd() and srcbkw() */

static
INT ccratr=0x0707;                              /* current screen attribute */
INT warnbsz=0;                              /* flag that OUTBSZ has changed */
LONG allbytes=0L;                 /* global between lodnxt() and shosts() */
LONG ldgbytes=0L;                 /* global between lodnxt() and shosts() */
LONG omgbytes;                    /* global between outmsg() and shobar() */

CHAR *ombuff;                                        /* buffer for outmsg() */
#define OMBSIZ 16384                                     /* bytes in ombuff */

#define MAXSTP 128                       /* max number of % symbols checked */
CHAR oldstrip[MAXSTP];                 /* stripped % symbols before editing */
CHAR newstrip[MAXSTP];                  /* stripped % symbols after editing */
#define CNFINIT 6                       /* scntbl[][] index for CNFINIT.SCN */
#define NSCNS 8                    /* number of screen images in scntbl[][] */
CHAR scorig[NSCNS][GVIDSCNSIZ];                   /* original states of screens */
INT blindcur=0;                       /* cursor sneaking around the screen? */
CHAR *altbuf;                                             /* editing buffer */
CHAR boxscn[GVIDSCNSIZ];  /* for drawing language choice & enumerated pick list */
CHAR chantyps[ENLLEN+1];     /* extra channel types for WGSMAJOR.MSG GROUPn */
CHAR hdwrtyps[ENLLEN+1];    /* extra hardware types for WGSMAJOR.MSG GROUPn */

VOID init(VOID);
VOID inichlang(VOID);
VOID title(VOID);
INT confin(INT ec);
VOID shostx(VOID);
VOID getpar(INT argc,CHAR *argv[]);
VOID readfc(VOID);
INT setcol(CHAR *opt);
VOID inilod(VOID);
INT lodnxt(VOID);
static VOID scanlng(VOID);
static VOID undeflang(CHAR *lgname);
VOID oplinkup(struct option *opa,struct option *opb);
VOID lodopt(UINT golopt);
VOID ldrbkg(VOID);
VOID shohlp(VOID);
INT getchq(VOID);
VOID hdlop(VOID);
INT canedit(VOID);
VOID customedit(VOID);
INT outmsg(VOID);
VOID shobar(LONG nbytes);
VOID kbflush(VOID);
VOID zapmcv(VOID);
VOID hdlchl(VOID);
VOID hdlcll(VOID);
struct option *upop(struct option *from,INT n,INT tonly);
struct option *dnop(struct option *from,INT n,INT tonly);
VOID hdlsrc(INT entch);
INT ssentry(VOID);
VOID msrfwd(VOID);
VOID msrbkw(VOID);
INT srcfwd(INT text);
INT srcbkw(INT text);
INT thisit(INT text);
INT mlingo(struct msgfil *mfp,INT lngidx);
INT strinit(CHAR *string);
VOID hdlxit(VOID);
VOID hdlqit(VOID);
CHAR *skblk(CHAR *string);
CHAR *skword(CHAR *string);
CHAR *eltokn(INT n);
INT elquan(VOID);
VOID cpywrd(CHAR *to,CHAR *from);
INT elpick(CHAR *str);
VOID newpos(VOID);
VOID shopos(VOID);
VOID sksts(VOID);
VOID shosts(VOID);
VOID shocnt(VOID);
VOID sholbg(VOID);
VOID shofmt(VOID);
CHAR *l2ah(LONG lnum);
VOID hdlepk(VOID);
VOID hdlspk(INT n);
VOID shospk(VOID);
INT altered(VOID);
VOID hdlabt(VOID);
VOID rnwscn(VOID);
VOID cndent(VOID);
INT hdlent(VOID);
INT curval(VOID);
VOID savcnf(VOID);
VOID rstcnf(VOID);
INT hlprd(struct option *opp);
VOID hdlhlp(VOID);
VOID hdlclr(VOID);
VOID hdlbks(INT save);
VOID hdlfws(VOID);
VOID hdlchr(CHAR keystr);
INT chkvis(struct option *opt);
VOID shopag(VOID);
VOID hdldn(INT n);
VOID movup(INT n,INT scl);
VOID hdlup(INT n);
VOID hdlpgh(VOID);
INT syncdn(VOID);
VOID hopnew(VOID);
VOID hdlpge(VOID);
VOID hdlcpd(VOID);
VOID hdlcpu(VOID);
VOID rsvhng(struct option *rop);
VOID shoopt(struct option *lop,CHAR *val);
VOID repeat(INT c,INT n);
VOID soft(INT n,CHAR *sft,INT attr);
VOID softy(CHAR *sft[]);
VOID swinde(VOID);
VOID swindh(INT scropt);
VOID swinds(VOID);
VOID swindf(VOID);
VOID swindc(VOID);
VOID scdown(CHAR *scnimg,INT xl,INT yt,INT xr,INT yb,GBOOL topblk);
VOID center(CHAR *string,INT width);
VOID parms(struct option *opp,CHAR *cs,INT np);
VOID parse(VOID);
VOID parsec(VOID);
VOID parstg(CHAR *to,INT len);
VOID addlang(struct msgfil *mfp,INT ilingo);
INT rdopt(struct option *opp);
VOID fndfls(VOID);
INT msgxst(CHAR *msgnam);
VOID loaded(VOID);
VOID stored(VOID);
#ifdef GCDOS
VOID ckmem(VOID);
#endif
INT atrval(INT attr);
VOID cfatr(INT attr);
VOID print2a(INT attr1,INT attr2,CHAR *string);
VOID caplin(VOID);
VOID cappar(VOID);
VOID sdwnpc(CHAR *buf,CHAR *stripped);

INT
main(
INT argc,
CHAR *argv[])
{
TRY
     INT rc;

#ifdef GCWINNT
     if (!canRunUtil()) {
          MessageBox(NULL,NOPROCEED,
                    argv[0],MB_ICONSTOP|MB_OK|MB_TASKMODAL|MB_SETFOREGROUND);
          return(1);
     }
#endif // GCWINNT
     protinit("WGSCNF ");
     initvid();
#ifdef GCDOS
     membeg=sizmem();
     ckmem();
#endif
     if ((rc=setjmp(disaster)) != 0) {
          locate(0,22);
          return(confin(rc));
     }
     getpar(argc,argv);
     init();
#ifdef GCDOS
     ckmem();
     if (!memrept && memmin < SAFECORE) {
          catastro("YOU DON'T HAVE ENOUGH MEMORY TO SAFELY CONTINUE");
     }
#endif
     hdlop();
#ifdef GCDOS
     ckmem();
#endif
     return(confin(0));
EXCEPT
#ifdef GCWINNT
     return(1);
#endif
}

VOID
init(VOID)                         /* initialization                       */
{
     inilingo();
     inichlang();
     inimsgrdr(1000);
     movmem(scntbl,scorig,GVIDSCNSIZ*NSCNS);
     readfc();
     inimsgrdr(outbsz);
     altbuf=alcmem(outbsz);
     fndfls();
     movmem(scntbl[CNFINIT],scnsav,GVIDSCNSIZ);
     title();
     softy(softies);
     swindc();
     cfatr(ATRIUP);
     printf("Initializing, one moment please...");
     rstcnf();
     inilod();
     ombuff=alcmem(OMBSIZ);
     ibdraw(0);
     eospawn();
     edtskp=1;
}

VOID
inichlang(VOID)            /* build *langchoose[] choose list for languages */
{
     INT ilingo;

     langchoose=(CHAR **)alczer((nlingo+1)*sizeof(CHAR *));
     for (ilingo=0 ; ilingo < nlingo ; ilingo++) {
          langchoose[ilingo]=alcdup(spr("%-15s %s",languages[ilingo]->name,
                         languages[ilingo]->desc));
     }
}

VOID
appgprept(VOID)                      /* application-specific GP info (stub) */
{
}

VOID
appgprecd(VOID)                    /* application-specific GP record (stub) */
{
}

VOID
title(VOID)                                 /* form title for top of screen */
{
     CHAR buff[80],*bp,bff[20];

     setwin(scnptr,0,0,79,0,0);
     cfatr(ATRTTL);
     switch (level) {
     case 1:
          bp="HARDWARE SETUP";
          break;
     case 3:
          bp="SECURITY & ACCOUNTING";
          break;
     case 4:
          bp="GENERAL SETUP";
          break;
     case 6:
          bp="TEXT BLOCKS";
          break;
     default:
          sprintf(bp=bff,"LEVEL %d",level);
          break;
     }
     sprintf(buff,"  " SVR_NAMER " CONFIGURATION FACILITY  Ý %s  ",bp);
     locate((80-strlen(buff))>>1,0);
     printf("%s",buff);
     rstwin();
}

INT
confin(                            /* memory available report (-M option)  */
INT ec)
{
#ifdef GCDOS
     INT pct;
     CHAR buff[80];

     if (memrept) {
          ckmem();
          swindc();
          cfatr(ATRIUP);
          pct=(INT)(((membeg-memmin)*100+50)/membeg);
          sprintf(buff,"\n%ld out of %ld bytes used (%d%%).",
                  membeg-memmin,membeg,pct);
          printf("%s",buff);
     }
#endif // GCDOS
     cursiz(GVIDLILCURS);
     clsvid();
     return(ec);
}

VOID
shostx(VOID)                         /* help message for bungling operators */
{
     printf(parhlp);
     clsvid();
     exit(1);
}

VOID
getpar(argc,argv)                            /* get command line parameters */
INT argc;
CHAR *argv[];
{
     INT i;

     ansion(0);
     for (i=1 ; i < argc ; i++) {
          if (*argv[i] == '-') {
               switch (toupper(argv[i][1])) {
               case 'L':
                    if ((level=atoi(&argv[i][2])) < 1 || level > 99) {
                         shostx();
                    }
                    break;
#ifdef GCDOS
               case 'M':
                    memrept=1;
                    break;
#endif // GCDOS
               default:
                    shostx();
               }
          }
          else {
               shostx();
          }
     }
}

VOID
readfc(VOID)                        /* read in CRT and EXPMODE options     */
{
     INT gotcol=0,gotexp=0,gotbsz=0;

     if ((rdfp=fp=fopen(curfn=MAINFL,FOPRB)) == NULL) {
          catastro("CANNOT OPEN "MAINFL);
     }
     allbytes+=filelength(fileno(rdfp));
     while (!gotcol || !gotexp || !gotbsz) {
          if (!rdmsg()) {
               catastro(MAINFL" is missing one or more of these options:\n"
                        "CRT, EXPMODE, or OUTBSZ.");
          }
          if (sameas(msgnam,"CRT")) {
               parse();
               setcol(rdval);
               gotcol=1;
          }
          if (sameas(msgnam,"EXPMODE")) {
               parse();
               expmode=sameas(rdval,"YES");
               gotexp=1;
          }
          if (sameas(msgnam,"OUTBSZ")) {
               parse();
               outbsz=(INT)atol(rdval);
               gotbsz=1;
          }
          scanalt();
     }
     fclose(rdfp);
}

INT
setcol(opt)             /* sets color per option, returns true if different */
CHAR *opt;
{
     INT oldcol,i;

     oldcol=color;
     if (sameas(opt,"COLOR")) {
          color=1;
     }
     else if (sameas(opt,"MONO")) {
          color=0;
     }
     else {
          imonorcol();
     }
     movmem(scorig,scntbl,GVIDSCNSIZ*NSCNS);
     for (i=0 ; i <= CNFINIT ; i++) {
          cvtscn(scntbl[i]);
     }
     return(color != oldcol);
}

VOID
inilod(VOID)                                /* initialize to first position */
{
     while (totopt < 1) {
          if (lodnxt() == 0) {
               catastro("NO OPTIONS");
          }
     }
     sksts();
     op=ophead;
     newpos();
     ecy--;
     shoopt(op,value);
     ecy++;
     syncdn();
     newpos();
     shoopt(op,value);
     shopos();
     swinde();
}

#define LDRINI 0
#define LDRFIL 1
#define LDRSCN 2
#define LDROPT 3
#define LDRFWU 4
#define LDRFNX 5
#define LDRFIN 6
#define LDRNUL 7
static CHAR ldrstt=LDRINI;
static struct msgfil *ldrmfp=NULL;

INT
lodnxt(VOID)                          /* option loader finite state machine */
{                     /* returns 0=done 1=working 2=refresh progress report */
     static struct option optemp;
     static struct option *ldrop;
     static LONG fpos;
     struct option *newopt;
     INT thislev=-1;
     INT nalc,oldgv;
     INT rc=1;

     switch (ldrstt) {
     case LDRINI:
          totopt=0;
          ophead=optail=ldrop=NULL;
          ldrstt=LDRFIL;
          ldrmfp=&mfhead;
          break;
     case LDRFIL:
#ifdef GCDOS
          if (ldrmfp == NULL || sizmem() < SAFECORE) {
#else
          if (ldrmfp == NULL) {
#endif // GCDOS
               ldrstt=LDRFIN;
               break;
          }
          ldrmfp->fstopt=NULL;
          if ((ldrmfp->fp=fopen(curfn=ldrmfp->name,FOPRB)) == NULL) {
               ldrstt=LDRFNX;                    /* skip missing .MSG files */
               break;
          }
          fpos=ldrmfp->bofopt=ldrmfp->eofopt=ftell(ldrmfp->fp);
          ldrmfp->numopt=0;
          ldrmfp->mslidx=(INT *)alczer(nlingo*sizeof(INT));
          ldrstt=LDRSCN;
          break;
     case LDRSCN:                         /* scan thru lower levels in file */
          rdfp=ldrmfp->fp;
          curfn=ldrmfp->name;
          fseek(rdfp,fpos,0);
          if (!rdmsg()) {
               ldrstt=LDRFWU;
               break;
          }
          if (sameas("LANGUAGE",msgnam)) {
               if (!sameas(txtbuf,languages[0]->name)) {
                    catastro("%s is not based on the \"%s\" language\n"
                             "(Language 0 is \"%s\" instead.)",
                             curfn,languages[0]->name,txtbuf);
               }
               if (ldrmfp->lngcnt != 0) {
                    catastro("More than one LANGUAGE{} option in %s",curfn);
               }
               ldrmfp->boflng=altlnm[0].value.fsk;
               scanlng();
               ldrmfp->eoflng=ftell(rdfp)-1;
               ldrmfp->lngcnt=salingo;
          }
          else if (sameto("LEVEL",msgnam) && alldgs(msgnam+5)) {
               if ((thislev=atoi(&msgnam[5])) > level) {
                    ldrstt=LDRFWU;                  /* skip higher levels */
               }
               else if (thislev == level) {
                    newopt=(struct option *)alczer(OPCORE);
                    oplinkup(ldrop,newopt);
                    ldrop=newopt;
                    ldrop->file=ldrmfp;
                    ldrop->type=FBORDER;
                    ldrstt=LDROPT;
                    gvbust=0;
                    if (ldrmfp->lngcnt == 0) {
                         ldrmfp->lngcnt=1;
                    }
               }
          }
          else if (thislev > 0 && !rdopt(&optemp)) {
               ldrstt=LDRFWU;                      /* (skip lower levels) */
          }                           /* done if file only has lower levels */
          fpos=ftell(rdfp);
          break;
     case LDROPT:                        /* Init for first option in a file */
#ifdef GCDOS
          if (sizmem() < SAFECORE) {
               ldrstt=LDRFWU;
               break;
          }
#endif // GCDOS
          rdfp=ldrmfp->fp;
          curfn=ldrmfp->name;
          fseek(rdfp,fpos,0);
          setmem(&optemp,sizeof(struct option),0);
          oldgv=gvbust;
          if (!rdmsg()
           || (sameto("LEVEL",msgnam) && alldgs(msgnam+5))
           || !rdopt(&optemp)) {
               ldrstt=LDRFWU;
               break;
          }
          if (gvbust && !oldgv) {
               swindc();
               cfatr(ATRWBB);
               printf("\fOption %s in %s is too large.  It has been truncated"
                       " to %u\nbytes.  (You may want to increase offline "
                       "Configuration option OUTBSZ.)",msgnam,curfn,bufsize-1);
          }
          if (strchr("NLHSEB",optemp.type) != NULL
           || (optemp.flags&(ISHING+ISHPAR))) {
               nalc=sizeof(struct option);           /* full size structure */
          }
          else {
               nalc=OPCORE;                          /* condensed structure */
          }
          newopt=(struct option *)alcmem(nalc);
          movmem(&optemp,newopt,nalc);
          oplinkup(ldrop,newopt);
          ldrop=newopt;
          ldrop->file=ldrmfp;
          ldrop->index=totopt;
          if (ldrmfp->numopt == 0) {
               ldrmfp->bofopt=cmtptr;            /* Mark comment of 1st opt */
               ldrmfp->fstopt=ldrop;
          }
          ldrmfp->eofopt=ftell(ldrmfp->fp); /* Remember end of last message */
          ldrmfp->numopt++;
          if (++totopt == 0) {
               catastro("MORE THAN 65535 OPTIONS!");
          }
          rc=2;
          switch (ldrop->type) {
          case 'S':
               stzcpy(ldrop->value=alcmem(LINLEN+1),rdval,LINLEN+1);
               break;
          case 'T':
               if (salingo > ldrmfp->lngcnt) {
                    catastro("Too many language-versions of option %s\n"
                             "for the %s file",msgnam,curfn);
               }
               ldrop->value=alczer(nlingo*sizeof(struct altlng));
               if (salingo > 0) {
                    movmem(altlnm,altern(ldrop),salingo*sizeof(struct altlng));
               }
               break;
          default:
               stzcpy(ldrop->value=alcmem(VALLEN+1),rdval,VALLEN+1);
               break;
          }
          if (ldrop->prev != NULL
           && ldrop->prev->lindsc != NULL
           && sameas(ldrop->prev->lindsc,lindsc)) {
               ldrop->lindsc=ldrop->prev->lindsc;
          }
          else {
               ldrop->lindsc=alcdup(lindsc);
          }
          rsvhng(ldrop);
          if (ldrop->type == 'E' || ldrop->type == 'B') {
               if (sameas(curfn,MAINFL)
             && sameto("GROUP",ldrop->name)
             && strstr(enlist,"<NONE>") != NULL) {
                    stzcat(enlist,chantyps,ENLLEN+1);
               }
               else if (sameas(curfn,MAINFL)
               && sameto("HTYPE",ldrop->name)
               && strstr(enlist,"SINGLE") != NULL) {
                    stzcat(enlist,hdwrtyps,ENLLEN+1);
               }
               ldrop->enlist=alcdup(enlist);
          }
          fpos=ftell(rdfp);
          break;
     case LDRFWU:                                           /* file wind-up */
#ifdef UNIX
          if (ldrmfp->numopt > 0
           && fseek(ldrmfp->fp,ldrmfp->eofopt,0) == 0
           && getc(ldrmfp->fp) == '\n') {
               ldrmfp->eofopt+=1;
          }           /* sync up with remainder of file (for outmsg()) */
#else
          if (ldrmfp->numopt > 0
           && fseek(ldrmfp->fp,ldrmfp->eofopt,0) == 0
           && getc(ldrmfp->fp) == '\r'
           && getc(ldrmfp->fp) == '\n') {
               ldrmfp->eofopt+=2;
          }           /* sync up with remainder of file (for outmsg()) */
#endif
          ldrstt=LDRFNX;
          break;
     case LDRFNX:
          ldgbytes+=filelength(fileno(ldrmfp->fp));
          ldrmfp->flags|=FILOAD;
          ldrmfp=ldrmfp->next;
          ldrstt=LDRFIL;
          rc=2;
          break;
     case LDRFIN:
#ifdef GCDOS
          ckmem();
#endif
          ldrstt=LDRNUL;
     case LDRNUL:
          rc=0;
          break;
     }
     return(rc);
}

static VOID
scanlng(VOID)                                 /* scan the LANGUAGE{} option */
{
     INT ilingo,needcomma;

     salingo=0;
     needcomma=1;
     while (1) {
          switch(getc(rdfp)) {
          case ',':
               salingo++;
               needcomma=0;
               break;
          case OPNMSG:
               if (!needcomma) {
                    getval(rdfp);
                    if ((ilingo=lngfnd(txtbuf)) == -1) {
                         undeflang(txtbuf);
                    }
                    ldrmfp->mslidx[ilingo]=salingo;
                    needcomma=1;
                    break;
               }
          default:
               catastro("Bad format for LANGUAGE{} option in %s",
                        curfn);
          case CHR_EOL:
          case EOF:
               salingo++;
               return;
          }
     }
}

static VOID
undeflang(lgname)                 /* error message about undefined language */
CHAR *lgname;
{
     CHAR dmdnam[12+1];

     if (tfsopn("*.dmd") > 0) {
          dmdnam[0]='\0';
          while (tfsrdl() != TFSDUN) {
               if (tfstate == TFSLIN
             && tfspfx("Language:")
             && sameas(tfspst,lgname)) {
                    strcpy(dmdnam,tfsfb.ff_name);
                    break;
               }
          }
          tfsopn(dmdnam);
          while (tfsrdl() != TFSDUN) {
               if (tfstate == TFSLIN
             && tfspfx("Module Name:")) {
                    catastro("%s has an undefined language: \"%s\"\n"
                             "(Hint:  use the offline Basic Utility "
                             "WGSDMOD to re-enable\n"
                             "the \"%s\" module.  Then use WGSLANG\n"
                             "to remove the language from this .MSG file.)"
                             ,curfn,lgname,tfspst);
               }
          }
     }
     catastro("%s has an undefined language: \"%s\"\n"
              "(Hint:  you could use the offline Basic Utility WGSLANG\n"
              "to define this language with the <Alt-N> key.  Then you\n"
              "could also use WGSLANG to remove it from this .MSG file.)",
              curfn,txtbuf);
}

VOID
oplinkup(opa,opb)      /* link a new option onto the end of the option list */
struct option *opa;        /* old, parent option (or NULL if opb brand new) */
struct option *opb;
{
     if (opa == NULL) {
          ophead=opb;
     }
     else {
          opa->next=opb;
     }
     opb->prev=opa;
     opb->next=NULL;
     optail=opb;
}

VOID
lodopt(golopt)      /* Load option specifications and values from MSG files */
UINT golopt;                     /* total number of options desired */
{
     INT rc;

     if (ldrstt != LDRNUL) {
          do {
               rc=lodnxt();
               if (rc != 1) {
                    shocnt();
                    rstwin();
                    sholbg();
                    rstwin();
               }
          } while (rc != 0 && !kbhit() && totopt < golopt);
#ifdef GCDOS
          if (ldrstt == LDRNUL && sizmem() < SAFECORE) {
#else
          if (ldrstt == LDRNUL) {
#endif // GCDOS
               shohlp();                          /* in case memory warning */
               rstwin();
          }
     }
}

VOID
ldrbkg(VOID)                    /* background loader (one option at a time) */
{
     INT oldx,oldy,oldatr;

     if (ldrstt != LDRNUL) {
          cursact(0);
          blindcur=1;
          oldatr=ccratr;
          oldx=curcurx();
          oldy=curcury();
          lodopt(0);
          locate(oldx,oldy);
          cfatr(oldatr);
          cursact(1);
          blindcur=0;
     }
}

VOID
shohlp(VOID)                                       /* show general help box */
{
     swindc();
#ifdef GCDOS
     if (sizmem() < SAFECORE) {
          cfatr(ATRMEM);
          printf("\fLOW MEMORY WARNING -- %dK LEFT\nYou should save (F10)"
                  " or quit (F9) as soon as possible.",(INT)(sizmem()>>10));
          return;
     }
#endif // GCDOS
     cfatr(ATRGHP);
     switch (op->type) {
     case 'N':
     case 'H':
     case 'L':
     case 'S':
          printf("\fUse the  keys to scan options.  To change "
                  "this option, use the \033 \032 keys,\n"
                  "retype it, and then hit the ÄÙ key.  "
                  "Also: Home, End, PgUp/Dn, C-PgUp/Dn.");
          break;
     case 'C':
          printf("\fUse the  keys to scan options.  "
                  "To change this option, type any alpha-\n"
                  "numeric keys, and hit the ÄÙ key.  "
                  "Also: Home, End, PgUp/Dn, C-PgUp/Dn.");
          break;
     case 'B':
          printf("\fUse the  keys to scan options.  "
                  "To change this option, hit the F3 key for\n"
                  "NO, or hit the F4 key for YES.  Also: "
                  "Home, End, PgUp/Dn, C-PgUp/Dn.");
          break;
     case 'E':
          if (elquan() > ENMAX) {
               printf("\fUse the  keys to scan options.  "
                       "To change this option, hit F2, make a\n"
                       "choice with , then hit Enter (ÄÙ).  "
                       "Also: Home, End, PgUp/Dn, C-PgUp/Dn.");
          }
          else {
               printf("\fUse the  keys to scan options.  "
                       "To change this option, hit one of the\n"
                       "highlighted softkeys, shown below.  "
                       "Also: Home, End, PgUp/Dn, C-PgUp/Dn.");
          }
          break;
     case 'T':
          printf("\fUse the  keys to scan options.  "
                  "To view or edit this option, press F2 or\n"
                  "Enter (ÄÙ).  Other scan keys:  Home, End, "
                  "PgUp/Dn, C-PgUp/Dn.");
          break;
     }
}

INT
getchq(VOID)                                 /* get character from operator */
{
     while (!kbhit()) {
          ldrbkg();
     }
     return(getchc());
}

VOID
hdlop(VOID)                        /* handle operator keystrokes           */
{
     INT keystr;
     INT new=1;

     shoopt(op,value);
     do {
          keystr=getchq();
#ifdef GCDOS
          if (memrept) {
               ckmem();
          }
#endif
          if (new) {
               new=0;
               if (expmode) {
                    setwin(NULL,17,2,77,14,0);
                    cfatr(ATRLIN); /* Upon first keystroke, clear all but  */
                    printf("\f"); /* the very bottom 2 lines of main box  */
                    swinde();
               }
          }
          switch (keystr) {
          case '\0':
          case '\f':
               break;
          case '\r':
               if (op->type == 'T' && canedit()) {
                    customedit();
               }
               else {
                    kbflush();
                    if (!edit && op->type == 'E' && elquan() > ENMAX) {
                         hdlepk();
                    }
                    else {
                         hdldn(hdlent());
                    }
               }
               break;
          case '\b':
#ifdef GCDOS
               if (lascan() == 0) {
                    hdlchr(keystr);
                    break;
               }
#endif // GCDOS
          case CRSRLF:
               hdlbks(1);
               break;
          case DEL:
               hdlbks(0);
               break;
          case F1:
               if (expmode) {
                    expmode=0;
               }
               else {
                    expmode=1;
                    shopag();
               }
               shopos();
               shoopt(op,value);
               break;
          case F2:
               switch (op->type) {
               case 'T':
                    if (canedit()) {
                         customedit();
                    }
                    else {
                         hdlclr();
                         edit=0;     /* hdlclr() incorrectly sets edit to 1 */
                    }
                    break;
               case 'B':
               case 'E':
                    hdlepk();
                    break;
               default:
                    hdlclr();
                    break;
               }
               break;
          case F3:
               if (op->type == 'T') {
                    hdlchl();
               }
               else {
                    hdlspk(0);
               }
               break;
          case F4:
               if (op->type == 'T') {
                    hdlcll();
               }
               else {
                    hdlspk(1);
               }
               break;
          case F5:
          case F6:
          case F7:
               hdlspk((keystr-F3)>>8);
               break;
          case CRSRUP:
               kbflush();
               cndent();
               hdlup(1);
               break;
          case CRSRDN:
               kbflush();
               cndent();
               hdldn(1);
               break;
          case F8:
          case SHIFT+F8:
          case CTRL+F8:
               cndent();
               hdlsrc(keystr);
               break;
          case PGDN:
               kbflush();
               cndent();
               hdldn(14);
               break;
          case PGUP:
               kbflush();
               cndent();
               hdlup(14);
               break;
          case CTRLPGUP:
               cndent();
               hdlcpu();
               break;
          case CTRLPGDN:
               cndent();
               hdlcpd();
               break;
          case HOME:
               cndent();
               hdlpgh();
               break;
          case END:
               cndent();
               hdlpge();
               break;
          case ESC:
#ifdef GCDOS
               if (lascan() == 0) {
                    hdlchr(keystr);
               }
               else if (edit) {
                    hdlabt();
                    cursiz(GVIDLILCURS);
               }
               else {
                    hdlqit();
               }
#else
               if (edit) {
                    hdlabt();
                    cursiz(GVIDLILCURS);
               }
               else {
                    hdlqit();
               }
#endif // GCDOS
               break;
          case ALT_X:
          case F9:
               hdlabt();
               hdlqit();
               break;
          case ALT_S:
          case F10:
               cndent();
               hdlxit();
               break;
          case CRSRRT:
               hdlfws();
               break;
          case INS:
               hdlchr(' ');
               break;
          case '\n':
               break;
          case ALT_J:
#ifdef GCDOS
               if (sizmem() < 10000) {
                    swindc();
                    cfatr(ATRIDN);
                    printf("\fInsufficient memory available to shell to"
                   " DOS!");
               }
               else {
#endif // GCDOS
                    savcnf();
                    setwin(0L,0,0,79,24,0);
                    cfatr(ATRBKG);
                    printf("\f");
                    cfatr(ATRXHB);
                    printf("\n Type EXIT ÄÙ to return to WGSCNF... \n");
                    system("");
                    rstcnf();
                    shohlp();
#ifdef GCDOS
               }
#endif
               swinde();
               break;
          case ALT+F8:
               caplin();
               cndent();
               hdldn(1);
               break;
          case ALT+F9:
               caplin();
               cappar();
               cndent();
               hdldn(1);
               break;
          case ALT+F10:
               {
                    FILE *fp;
                    static INT idx=0;

                    while((fp=fopen(spr("CNF%d.BIN",idx),FOPRB)) != NULL) {
                         fclose(fp);
                         idx++;
                    }
                    fp=fopen(spr("CNF%d.BIN",idx++),FOPWB);
                    scn2mem(savscn,0,GVIDSCNSIZ);
                    fwrite(savscn,GVIDSCNSIZ,1,fp);
                    fclose(fp);
                    belper(350);
                    printf("\7");
               }
               break;
          default:
               if (keystr <= 0xFF) {
                    hdlchr(keystr);
               }
               break;
          }
          if (warnbsz) {
               swindc();
               cfatr(ATRWOS);
               printf("\fNote:  OUTBSZ, the server's output buffer size, also"
                       " limits the size of\nWGSCNF text options.  This will "
                       "take effect the next time you run WGSCNF.");
               warnbsz=0;
          }
     } while (!done);
}

INT
canedit(VOID)                         /* see if RIP text block is editable */
{
     INT savcur,savx,savy;

     if (!samend(languages[clingo]->name,RIPSFX)
         || !samein("ripaint.dll",languages[clingo]->editor)) {
          return(1);
     }
     if (isfile("ripaint.dll")) {
          return(1);
     }
     scn2mem(savscn,0,GVIDSCNSIZ);
     savcur=curcurs();
     savx=curcurx();
     savy=curcury();
     cursiz(GVIDNOCURS);
     explode(scntbl[7],11,5,65,17);
     sstatr(0x4E);
     locate(34,8);
     printf("%s",languages[clingo]->name);
     sstatr(0x4F);
     printf(" versions");
     locate(13,9);
     printf("of these text blocks, you'll need a copy of the");
     getchc();
     mem2scn(savscn,0,GVIDSCNSIZ);
     locate(savx,savy);
     cursiz(savcur);
     return(0);
}

VOID
customedit(VOID)                              /* shell out to custom editor */
{                                           /* returns 1=success, 0=failure */
     CHAR cmdbuf[128+20];
     CHAR tfn[8+1+3+1];
     INT rc,redo,badkey,c,redone;

     sprintf(tfn,"CNF*%s",languages[clingo]->extans);
     uniqfn(tfn);
     sprintf(cmdbuf,languages[clingo]->editor,tfn,tfn);
     loaded();
     sdwnpc(altbuf,oldstrip);
     redone=0;
     do {
          if (scnptr == NULL) {
               savcnf();
          }
          rc=edtoff(cmdbuf,altbuf,bufsize,tfn);
          if (rc == EONOTME) {
               rc=EONOCHG;
          }
          sdwnpc(altbuf,newstrip);
          redo=0;
          if ((redone || (rc&EOWHICH) == EOSAVE)
           && !sameto(newstrip,oldstrip)
           && !((sameas(op->name,"RENTRY")
              || sameas(op->name,"EDTENT")) &&
                 sameas(op->file->name,"GALREGIS.MSG"))) {
               rstcnf();
               do {
                    swindc();
                    print2a(ATRWSP,ATRWS2,
                            "\fYou've modified the '%' symbols in this "
                            "option.  (See about %-symbols in the\nSystem "
                            "Operations Manual.)  \0K\0eep changes, "
                            "\0U\0ndo changes or \0E\0dit some more? \0");
                    belper(700);
                    printf("\7");
                    badkey=0;
                    kbflush();
                    cursiz(GVIDLILCURS);
                    c=getchq();
                    printf("\f");
                    switch(c) {
                    case 'K':
                    case 'k':
                         rc=EOSAVE;
                         break;
                    case ESC:
                    case 'U':
                    case 'u':
                         rc=EONOCHG;
                         break;
                    case F2:
                    case 'E':
                    case 'e':
                         redo=1;
                         break;
                    default:
                         badkey=1;
                    }
               } while (badkey);
          }
          else if (rc&(EOPGUP+EOPGDN)) {
               kbflush();
               if ((rc&EOWHICH) == EOSAVE) {
                    stored();
               }
               op=(rc&EOPGUP) ? upop(op,1,1) : dnop(op,1,1);
               if (skipped) {
                    newpos();
                    shopag();
                    shopos();
                    loaded();
                    sdwnpc(altbuf,oldstrip);
                    redo=1;
               }
          }
          redone=1;
     } while (redo);
     if (scnptr != NULL) {
          rstcnf();
     }
     switch (rc&EOWHICH) {
     case EOERROR:
          swindc();
          cfatr(ATRSPW);
          printf("\fThis WGSCNF Editor command %s:\n\"",edterr);
          cfatr(ATRSPC);
          if (strlen(cmdbuf) > 73) {
               strcpy(cmdbuf+70,"...");
          }
          printf("%s",cmdbuf);
          cfatr(ATRSPW);
          belper(700);
          printf("\"\7");
          break;
     case EOTRUNC:
          stored();
          shopos();
          swindc();
          cfatr(ATRWOB);
          printf("\fWe had to truncate that information down to "
                  "about %u bytes.\n(You may want to increase "
                  "offline Configuration option OUTBSZ.)",bufsize-1);
          belper(700);
          printf("\7");
          break;
     case EOSAVE:
          stored();
     default:
          shopos();
          break;
     }
     shoopt(op,value);
     cursiz(GVIDLILCURS);
}

FILE *capfil=NULL;

VOID
caplin(VOID)
{
     INT n;

     if (capfil == NULL) {
          if ((capfil=fopen("CONFIG.DSC",FOPAB)) == NULL) {
               return;
          }
     }
     fprintf(capfil,"%-8.8s  ",op->name);
     fprintf(capfil,"%s",op->lindsc);
     if (op->type == 'T') {
          fprintf(capfil,"%*s",LINLEN-(strlen(op->lindsc)),"");
     } else {
          if ((n=LINLEN-(strlen(op->lindsc))-strlen(op->value)-2) >= 0) {
               fprintf(capfil," ");
          }
          while (n-- > 0) {
               fprintf(capfil,".");
          }
          fprintf(capfil," ");
          fprintf(capfil,"%s",op->value);
     }
     fprintf(capfil,STR_EOL);
     belper(350);
     printf("\7");
}

VOID
cappar(VOID)
{
     if (capfil != NULL && op->flags&ISHPAR) {
          hlprd(op);
       fprintf(capfil,STR_EOL"%s"STR_EOL STR_EOL STR_EOL,hlpbuf);
     }
}

INT                                          /* 0=no disk space; 1=ok, done */
outmsg(VOID)                           /* rewrite new messages to MSG files */
{
     INT nchg=0;
     FILE *tmp;
     struct msgfil *mfp;
     CHAR realnm[12+1],tempnm[12+1];
     struct ffblk fb;
     INT lngidx,lngnum;
     LONG actbytes,nbof,ln;

     omgbytes=0L;
     for (mfp=&mfhead ; mfp != NULL ; mfp=mfp->next) {
          if (mfp->flags&ANYCHG) {
               omgbytes+=filelength(fileno(mfp->fp));
          }
     }
     swindc();
     cfatr(ATRIDN);
     printf("\f");
     actbytes=0L;
     for (mfp=&mfhead ; mfp != NULL ; mfp=mfp->next) {
          if (mfp->flags&ANYCHG) {
               while (!(mfp->flags&FILOAD)) {
                    if (lodnxt() == 0) {
                         catastro("CANNOT FULLY LOAD FILE \"%s\"",mfp->name);
                    }
               }
#ifdef GCDOS
               ckmem();
#endif
               fndfile(&fb,mfp->name,0);
#ifdef GCDOS
               if (dskfre(".") < ((fb.ff_fsize+32767+membeg-memmin)>>10)) {
#else
               if (dskfre(".") < (fb.ff_fsize>>10)) {
#endif
                    printf("\fINSUFFICIENT DISK SPACE TO RE-WRITE \"%s\"\n",
                           mfp->name);
                    printf("Please free some disk space...");
                    return(0);
               }
               printf("\rRebuilding %s...",mfp->name);
               cleareol();
               strcpy(realnm,mfp->name);
               strcpy(tempnm,mfp->name);
               strcpy(tempnm+strlen(tempnm)-4,".$$$");
               if ((tmp=fopen(tempnm,FOPWB)) == NULL) {
                    catastro("CANNOT CREATE %s",tempnm);
               }
               fp=mfp->fp;
               if (setvbuf(tmp,ombuff,_IOFBF,OMBSIZ) != 0) {
                    catastro("Cannot set output buffer for %s",tempnm);
               }
               cleareol();
               rewind(fp);
               if (mfp->flags&FILANG) {
                    if (mfp->boflng != 0L) {
                         xfrfil(fp,tmp,mfp->boflng);
                    }
                    else {
                fprintf(tmp,STR_EOL"LANGUAGE {");
                    }
                    fprintf(tmp,"%s}",languages[0]->name);
                    for (lngidx=1 ; lngidx < mfp->lngcnt ; lngidx++) {
                         fprintf(tmp,",{%s}",
                                 languages[mlingo(mfp,lngidx)]->name);
                    }
                    if (mfp->boflng != 0L) {
                         fseek(fp,mfp->eoflng,0);
                         nbof=mfp->bofopt-mfp->eoflng;
                    }
                    else {
                fprintf(tmp,STR_EOL STR_EOL);
                         nbof=mfp->bofopt;
                    }
               }
               else {
                    nbof=mfp->bofopt;
               }
               while (nbof > 0L) {
                    ln=min(nbof,1000L);
                    xfrfil(fp,tmp,ln);
                    nbof-=ln;
                    shobar(actbytes+ftell(fp));
               }
               for (op=mfp->fstopt ; op != NULL
                                  && op->file == mfp ; op=op->next) {
                    if (op->type == FBORDER) {
                         continue;
                    }
                    printf("\rInstalling %s option %s",mfp->name,op->name);
                    cleareol();
                    if (op->flags&ISHPAR) {
                         fseek(fp,op->parptr,0);
                         xfrfil(fp,tmp,(LONG)op->parlen);
                    }
              fprintf(tmp,STR_EOL"%s {",op->name);
                    switch (op->type) {
                    case 'S':
                         strcpy(txtbuf,op->value);
                         break;
                    case 'T':
                         loadtv(fp,altern(op));
                         break;
                    default:
                         sprintf(txtbuf,"%s %s",op->lindsc,op->value);
                         break;
                    }
                    putval(tmp);
                    fprintf(tmp,"}");
                    if (op->type == 'T') {
                         for (lngnum=mfp->lngcnt ; lngnum > 1 ; lngnum--) {
                              if (altern(op)[lngnum-1].value.fsk != 0L) {
                                   break;
                              }
                         }
                         for (lngidx=1 ; lngidx < lngnum ; lngidx++) {
                              fprintf(tmp,",");
                              if (altern(op)[lngidx].value.fsk != 0L) {
                                   fprintf(tmp,"{");
                                   loadtv(fp,&altern(op)[lngidx]);
                                   putval(tmp);
                                   fprintf(tmp,"}");
                              }
                         }
                    }
                    if (op->flags&ISHING) {
                         if (op->hngop == HNGEXC) {
                              fprintf(tmp," (%s%c)",op->hngval,op->hngop);

                         }
                         else {
                              fprintf(tmp," (%s%c%s)",
                                      op->hngptr->name,op->hngop,op->hngval);
                         }
                    }
                    switch (op->type) {
                    case 'C':
                    case 'B':
                         fprintf(tmp," %c",op->type);
                         break;
                    case 'S':
                         fprintf(tmp," S %ld %s",op->ceiling,op->lindsc);
                         break;
                    case 'T':
                         fprintf(tmp," T %s",op->lindsc);
                         break;
                    case 'N':
                    case 'L':
                         fprintf(tmp," %c %ld %ld",
                                 op->type,op->floor,op->ceiling);
                         break;
                    case 'H':
                         fprintf(tmp," H %lX %lX",op->floor,op->ceiling);
                         break;
                    case 'E':
                         if (sameas(mfp->name,MAINFL)
                          && sameto("GROUP",op->name)
                          && samend(op->enlist,chantyps)) {
                              op->enlist[strlen(op->enlist)
                                        -strlen(chantyps)]='\0';
                         }
                         else if (sameas(mfp->name,MAINFL)
                          && sameto("HTYPE",op->name)
                          && samend(op->enlist,hdwrtyps)) {
                              op->enlist[strlen(op->enlist)
                                        -strlen(hdwrtyps)]='\0';
                         }
                         fprintf(tmp," E %s",op->enlist);
                         break;
                    }
              fprintf(tmp,STR_EOL STR_EOL);
                    shobar(actbytes+ftell(fp));
               }
               printf("\rClosing %s...",mfp->name);
               cleareol();
               fseek(fp,mfp->eofopt,0);
               while (xfrfil(fp,tmp,1000) == 1000) {
                    shobar(actbytes+ftell(fp));
               }
               actbytes+=filelength(fileno(mfp->fp));
               shobar(actbytes);
               fclose(tmp);
               fclose(fp);
               unlink(realnm);
               rename(tempnm,realnm);
               nchg++;
          }
     }
     printf("\f");
     switch (nchg) {
     case 0:
          printf("No changes to configuration.");
          break;
     case 1:
          printf("1 file changed.\n");
          break;
     default:
          printf("%d files changed.\n",nchg);
          break;
     }
     return(1);
}

VOID
shobar(nbytes)                                /* display shutdown bar graph */
LONG nbytes;
{
     CHAR attrib,*bp;

     bp=bargph(65,nbytes,omgbytes);
     locate(2,19);
     attrib=curatr.attrib;
     cfatr(ATRIPC);
     printf("%3d%% done ",bgperc);
     cfatr(ATRIBR);
     printf("%s",bp);
     setatr(attrib);
     rstloc();
}

VOID
kbflush(VOID)                                             /* flush keyboard */
{
     while (kbhit()) {
          getchq();
     }
}

VOID
zapmcv(VOID)                              /* delete any changed MCV file(s) */
{
     struct msgfil *mfp;
     CHAR *cp;

     for (mfp=&mfhead ; mfp != NULL ; mfp=mfp->next) {
          if (mfp->flags&ANYCHG) {
               strcpy(strchr(cp=spr("%s",mfp->name),'.'),".mcv");
               unlink(cp);
          }
     }
}

VOID
hdlchl(VOID)                                           /* choose a language */
{
     INT c;
     INT nshow,i;
     INT newlingo;
#define LANGLEFT 7
#define LANGTOP 6
#define MAXLINES 17

     if (nlingo > 1) {
          savcnf();
          nshow=min(nlingo,MAXLINES);
          cfatr(ATRCHB);
          cursiz(GVIDNOCURS);
          setwin(boxscn,LANGLEFT-2,LANGTOP-1,79,24,0);
          locate(LANGLEFT-2,LANGTOP-1);
          printf("\fÛ");
          repeat('ß',1+LNGSIZ-1+1+LNGDSC-1+1);
          printf("Û\n");
          for (i=0 ; i < nshow ; i++) {
               printf("Û");
               repeat(' ',1+LNGSIZ-1+1+LNGDSC-1+1);
               printf("Û\n");
          }
          printf("Û");
          repeat('Ü',1+LNGSIZ-1+1+LNGDSC-1+1);
          printf("Û");
          nslatr=atrval(ATRCHL);
          selatr=atrval(ATRCHC);
          setwin(NULL,0,0,79,24,0);
          explode(boxscn,LANGLEFT-2,
                         LANGTOP-1,
                         LANGLEFT+LNGSIZ-1+1+LNGDSC-1+2-1,
                         LANGTOP+nshow+1-1);
          locate(LANGLEFT-1,LANGTOP-2);
          cfatr(ATRCHT);
          printf("        Choose a language with the "
                  " keys and hit Enter ÄÙ        ");
          supchc(nlingo,langchoose,2,1,2+LNGSIZ-1+1+LNGDSC-1-1,1+nshow-1,TRUE);
          jmp2chc(clingo);
          dspchc();
          cursiz(GVIDNOCURS);
          do {
               c=getchq();
               if (c == ALT+F10) {
                    FILE *fp;
                    static INT idx=0;

                    while((fp=fopen(spr("CNF%d.BIN",idx),FOPRB)) != NULL) {
                         fclose(fp);
                         idx++;
                    }
                    fp=fopen(spr("CNF%d.BIN",idx++),FOPWB);
                    scn2mem(savscn,0,GVIDSCNSIZ);
                    fwrite(savscn,GVIDSCNSIZ,1,fp);
                    fclose(fp);
                    belper(350);
                    printf("\7");
               }
               newlingo=hdlchc(c);
          } while (newlingo == nlingo);
          rstcur();
          rstcnf();
          if (newlingo >= 0) {
               clingo=newlingo;
               hopnew();
          }
          else {
               shoopt(op,value);
          }
     }
}

VOID
hdlcll(VOID)                           /* clear option/language association */
{
     struct altlng *alp;
     INT mslidx;

     if (clingo > 0
      && (mslidx=op->file->mslidx[clingo]) != 0
      && (alp=&altern(op)[mslidx])->value.ptr != NULL) {
          if (alp->altered) {
               free(alp->value.ptr);
          }
          alp->value.ptr=NULL;
          alp->altered=1;
          altered();
          shopos();
          shoopt(op,value);
          cursiz(GVIDLILCURS);
     }
}

struct option *
upop(                                        /* Move up to a visible option */
struct option *from,                           /* starting from this option */
INT n,                            /* advance up this many (visible) options */
INT tonly)                                /* Jump to Text-type option only? */
{
     struct option *seen;    /* return ptr to option that is n options away */

     for (skipped=0,seen=from ; skipped < n && (from=from->prev) != NULL ; ) {
          if (chkvis(from) && (!tonly || from->type == 'T')) {
               seen=from;
               skipped++;
          }
     }
     return(seen);
}

struct option *
dnop(                                      /* Move down to a visible option */
struct option *from,                           /* starting from this option */
INT n,                          /* advance down this many (visible) options */
INT tonly)                                /* Jump to Text-type option only? */
{
     struct option *seen;    /* return ptr to option that is n options away */

     for (skipped=0,seen=from ; skipped < n && (from=from->next) != NULL ; ) {
          if (chkvis(from) && (!tonly || from->type == 'T')) {
               seen=from;
               skipped++;
          }
     }
     return(seen);
}

VOID
hdlsrc(entch)                                   /* Handle search for string */
INT entch;
{
     INT x,y;

     softy(blanksf);
     swindc();
     cfatr(ATRSRC);
     switch (entch) {
     case F8:
          printf("\fSearch for string: ");
          x=curcurx();
          y=curcury();
          printf("\n(Enter=search forward, Ctrl-F8=search backward)");
          locate(x,y);
          switch (ssentry()) {
          case -1:
               msrbkw();
               break;
          case 1:
               msrfwd();
               break;
          case 0:
               shohlp();
               break;
          }
          break;
     case CTRL+F8:
          msrbkw();
          break;
     case SHIFT+F8:
          msrfwd();
          break;
     }
     softy(softies);
     shospk();
     syncdn();
     shopag();
     newpos();
     shocnt();
     shosts();
     shofmt();
     shoopt(op,op->value);
}

INT
ssentry(VOID)                                        /* search string entry */
{                          /* returns -1=backward search 1=forward 0=escape */
     CHAR srcent[SRCLEN+1];
     CHAR *sp;
     INT c;

     sp=srcent;
     cursiz(GVIDLILCURS);
     while (1) {
          switch (c=getchq()) {
          case ESC:
          case F8:
               return(0);
          case '\r':
          case SHIFT+F8:
          case CTRL+F8:
               *sp='\0';
               strcpy(srcbuf,srcent);
               return(c == CTRL+F8 ? -1 : 1);
          case '\b':
          case CRSRLF:
               if (sp > srcent) {
                    printf("\b \b");
                    sp-=1;
               }
               break;
          default:
               if (c <= 255 && sp-srcent < SRCLEN) {
                    *sp++=c;
                    cfatr(ATRSRC);
                    printf("%c",c);
               }
               break;
          }
     }
}

VOID
msrfwd(VOID)                                  /* Main-screen search forward */
{
     CHAR *sep;

     srcop=op;
     swindc();
     cfatr(ATRSRC);
     printf("\fSearching forward...   (hit any key to abort)\nSearching for:"
            "  \"%s\"",srcbuf);
     switch (srcfwd(0)) {
     case 0:
          printf("\f\"%s\" not found.\n(type Control-F8 to search backward)",
                 srcbuf);
          break;
     case 1:
          op=srcop;
          swindc();
          cfatr(ATRSRC);
          sep=(strlen(srcbuf)+strlen(srcwhr)+12 >= 76) ? "  " : "\n";
          printf("\fFound \"%s\"!   %s%s(Shift-F8 to search forward, Ctrl-F8 "
                 "for backward)",srcbuf,srcwhr,sep);
          break;
     case -1:
          printf("\fSearch for \"%s\" aborted.\n(type: Shift-F8=search "
                 "forward, Ctrl-F8=search backward)",srcbuf);
          break;
     }
}

VOID
msrbkw(VOID)                                 /* Main-screen search backward */
{
     CHAR *sep;

     swindc();
     cfatr(ATRSRC);
     printf("\fSearching backward...   (hit any key to abort)\nSearching "
             "for:  \"%s\"",srcbuf);
     switch (srcbkw(0)) {
     case 0:
          printf("\f\"%s\" not found.\n(type Shift-F8 to search forward)",
                  srcbuf);
          break;
     case 1:
          op=srcop;
          swindc();
          cfatr(ATRSRC);
          sep=(strlen(srcbuf)+strlen(srcwhr)+12 >= 76) ? "  " : "\n";
          printf("\fFound \"%s\"!   %s%s(Shift-F8 to search forward, Ctrl-F8 "
                  "for backward)",srcbuf,srcwhr,sep);
          break;
     case -1:
          printf("\fSearch for \"%s\" aborted.\n(type: Shift-F8=search "
                  "forward, Ctrl-F8=search backward)",srcbuf);
          break;
     }
}

INT
srcfwd(text)                                      /* search forward utility */
INT text;                                            /* 1=text options only */
{                                         /* -1=aborted 0=not found 1=found */
     srcop=op;
     while (1) {
          srcop=dnop(srcop,1,text);
          if (skipped == 0) {
               if (ldrstt != LDRNUL) {
                    ldrbkg();
               }
               else {
                    return(0);
               }
          }
          if (thisit(text)) {
               return(1);
          }
          if (kbhit()) {
               kbflush();
               return(-1);
          }
     }
}

INT
srcbkw(text)                                     /* search backward utility */
INT text;                                            /* 1=text options only */
{                                         /* -1=aborted 0=not found 1=found */
     srcop=op;
     while (1) {
          srcop=upop(srcop,1,text);
          if (skipped == 0) {
               return(0);
          }
          if (thisit(text)) {
               return(1);
          }
          if (srcop->type != FBORDER && kbhit()) {
               getchq();
               return(-1);
          }
     }
}

INT
thisit(text)                                           /* found the string? */
INT text;                              /* 0=search in help messages 1=don't */
{                                    /* 0=no, 1=yes, 2=yes in text contents */
                       /* if yes, returns srcwhr=description of where found */
     INT found;
     struct altlng *alp;
     INT lngidx,ilingo;

     if (srcop->type == FBORDER) {
          found=0;
     }
     else if (strinit(srcop->file->name)) {
          srcwhr="";
          found=1;
     }
     else if (strinit(srcop->name)) {
          srcwhr="";
          found=1;
     }
     else if (strinit(srcop->lindsc)) {
          srcwhr="";
          found=1;
     }
     else if (srcop->type == 'E' && strinit(srcop->enlist)) {
          srcwhr="";
          found=1;
     }
     else {
          switch (srcop->type) {
          case 'T':
               found=0;
               for (lngidx=0 ; lngidx < srcop->file->lngcnt ; lngidx++) {
                    alp=&altern(srcop)[lngidx];
                    if (alp->value.ptr != NULL) {
                         loadtv(srcop->file->fp,alp);
                         if (strinit(txtbuf)) {
                              found=2;
                              break;
                         }
                    }
               }
               if (found == 0) {
                    break;
               }
               ilingo=mlingo(srcop->file,lngidx);
               if (ilingo == clingo) {
                    srcwhr="(hit F2 or ÄÙ to see it)";
               }
               else {
                    srcwhr=spr("(to see, hit F3 to choose %s, then F2)",
                               languages[ilingo]->name);
               }
               break;
          default:
               found=strinit(srcop->value);
               srcwhr="";
               break;
          }
          if (!found && !text) {
               if (hlprd(srcop) && strinit(hlpbuf)) {
                    srcwhr="(in the F1-help message)";
                    found=1;
               }
          }
     }
     return(found);
}

INT
mlingo(mfp,lngidx)     /* xlate language index for file -> index for system */
struct msgfil *mfp;                                       /* .msg file info */
INT lngidx;                                     /* must be 0..mfp->lngcnt-1 */
{                                           /* returns a number 0..nlingo-1 */
     INT ilingo;

     for (ilingo=0 ; ilingo < nlingo ; ilingo++) {
          if (mfp->mslidx[ilingo] == lngidx) {
               return(ilingo);
          }
     }
     catastro("MLINGO: language %d of %d disappeared from %s",
              lngidx,mfp->lngcnt,mfp->name);
     return(0);
}

INT
strinit(string)                                   /* is search string here? */
CHAR *string;
{
     CHAR *np,*ip,*jp;
     CHAR c;
     CHAR bad;

     for (np=string ; *np != '\0' ; np++) {
          bad=0;
          for (ip=srcbuf,jp=np ; (c=*ip) != '\0' ; ip++,jp++) {
               if (toupper(c) != toupper(*jp)) {
                    bad=1;
                    break;
               }
          }
          if (!bad) {
               return(1);
          }
     }
     return(0);
}

VOID
hdlxit(VOID)                                /* handle save & exit keystroke */
{
     softy(blanksf);
     if (outmsg()) {
          zapmcv();
          done=1;
     }
     else {
          softy(softies);
          shospk();
          swinde();
     }
}

VOID
hdlqit(VOID)                                    /* handle abandon keystroke */
{
     INT c;

     softy(blanksf);
     if (nchang != 0) {
          swindc();
          cfatr(ATRCMD);
          printf("\fAre you sure you want to quit\n"
                    "and forget about your changes to %d option%s? ",
                    nchang,nchang == 1 ? "" : "s");
          cursiz(GVIDLILCURS);
          while ((c=getchq()) == F9 || c == ESC) {
          }
          if (c == 'Y' || c == 'y') {
               printf("\f%d change%s abandoned.",nchang,nchang == 1 ? "" : "s");
          }
          else {
               softy(softies);
               shospk();
               shohlp();
               swinde();
               return;
          }
     }
     done=1;
}

CHAR *
skblk(string)                                                /* skip blanks */
CHAR *string;
{
     while (*string == ' ') {
          string++;
     }
     return(string);
}

CHAR *
skword(string)                                                 /* skip word */
CHAR *string;
{
     while (*string != '\0' && *string != ' ') {
          string++;
     }
     return(skblk(string));
}

CHAR *
eltokn(n)                                 /* get token from enumerated list */
INT n;
{
     CHAR *cp;
     INT i;

     for (i=0,cp=skblk(op->enlist) ; i < n && *cp ; i++,cp=skword(cp)) {
     }
     return(cp);
}

INT
elquan(VOID)                                           /* number of choices */
{
     CHAR *cp;
     INT i;

     for (i=0,cp=skblk(op->enlist) ; *cp != '\0' ; i++,cp=skword(cp)) {
     }
     return(i);
}

VOID
cpywrd(to,from)                                            /* copy one word */
CHAR *to,*from;
{
     INT i;

     for (i=0 ; i < VALLEN && *from != '\0' && *from != ' ' ; i++) {
          *to++=*from++;
     }
     *to='\0';
}

INT
elpick(str)                       /* pick a string from the enumerated list */
CHAR *str;
{
     INT i,matches=0;
     CHAR *cp;

     for (i=0 ; *(cp=eltokn(i)) != '\0' ; i++) {
          if (sameas(str,cp)) {
               indx=i;
               return(1);
          }
          if (sameto(str,cp)) {
               matches++;
               indx=i;
          }
     }
     return(matches);
}

VOID
newpos(VOID)                                        /* move to a new option */
{
     fp=op->file->fp;
     if (op->type != 'T' && op->type != FBORDER) {
          strcpy(value,op->value);
     }
}

VOID
shopos(VOID)                                         /* show the new option */
{
     shocnt();
     shosts();
     shofmt();
     shospk();
     shohlp();
}

VOID
sksts(VOID)                             /* show skeleton for the status box */
{
     swinds();
     cfatr(ATRSBG);
     printf("\f  Configuring\n\n\n    Option\n");
}

VOID
shosts(VOID)                                   /* show status of the option */
{
     INT mslidx;
     struct altlng *alp;

     swinds();
     locate(2,3);
     cfatr(op->file->flags&ANYCHG ? ATRSNW : ATRSFG);
     center(op->file->name,13);
     locate(2,7);
     if (op->type == 'T') {
          mslidx=op->file->mslidx[clingo];
          alp=&altern(op)[mslidx];
          cfatr(((clingo == 0 || mslidx != 0) && alp->altered) ? ATRSNW
                                                               : ATRSFG);
     }
     else {
          cfatr(op->flags&ANYCHG ? ATRSNW : ATRSFG);
     }
     center(op->name,13);
}

VOID
shocnt(VOID)                                           /* show option count */
{
     swinds();
     locate(2,6);
     cfatr(ATRSBG);
     center(spr("%u of %u:",op->index+1,totopt),13);
     cleareol();
}

VOID
sholbg(VOID)                                   /* show the loading bar-graph */
{
     swinds();
     locate(1,4);
     if (ldrmfp == NULL) {
          cleareol();
     }
     else if (ldrmfp->fp != NULL) {
          cfatr(ATRLBG);
          printf("%s",bargph(15,ldgbytes+ftell(ldrmfp->fp),allbytes));
     }
}

VOID
shofmt(VOID)                                   /* show format of the option */
{
     CHAR *cb,*fb,wdbuff[VALLEN+1],fptmp[GCMAXFNM];
     INT i,n;

     switch (op->type) {
     case 'N':
     case 'L':
          fb=l2as(op->floor);
     case 'S':
          cb=l2as(op->ceiling);
          break;
     }
     swindf();
     switch (op->type) {
     case 'C':
          printf("\f    Format:\n"
                   "   CHARACTER\n"
                   "\n"
                   "   Type any\n"
                   "   printable\n"
                   "   character.");
          break;
     case 'S':
          printf("\f    Format:\n    STRING\n\n     Up to\n");
          center(cb,15);
          printf("  characters\n     long");
          break;
     case 'T':
          printf("\f    Format:\n"
                   "     TEXT\n"
                   "\n"
                   " Hit F2 or ÄÙ\n"
                   "  to edit this\n");
          if (nlingo > 1) {
               if (clingo > 0) {
                    cfatr(ATRFLG);
                    center(languages[clingo]->name,15);
                    cfatr(ATRFMT);
               }
               else {
                    center(languages[0]->name,15);
               }
          }
          printf("   text using\n");
          center(fileparts(GCPART_FILE,
                           firstwd(languages[clingo]->editor),fptmp,GCMAXFNM),
                 15);
          break;
     case 'N':
          printf("\f    Format:\n    NUMERIC\n\n  Lower limit\n");
          center(fb,15);
          printf("\n  Upper limit\n");
          center(cb,15);
          break;
     case 'H':
          printf("\f    Format:\n  HEXADECIMAL\n\n  Lower limit\n");
          center(l2ah(op->floor),15);
          printf("\n  Upper limit\n");
          center(l2ah(op->ceiling),15);
          break;
     case 'L':
          printf("\f    Format:\n LARGE NUMERIC\n\n  Lower limit\n");
          center(fb,15);
          printf("\n  Upper limit\n");
          center(cb,15);
          break;
     case 'B':
          printf("\f    Format:\n\n   YES or NO");
          break;
     case 'E':
          printf("\f    Format:\n MULTI-CHOICE\n");
          if ((n=elquan()) <= ENMAX) {
               printf("\n");
          }
          for (i=0 ; i < ENMAX ; i++) {
               cpywrd(wdbuff,eltokn(i));
               center(wdbuff,15);
          }
          if (n > ENMAX) {
               center(spr("(plus %d more)",n-ENMAX),15);
          }
          break;
     }
}

CHAR *
l2ah(                           /* convert long int to a hexadecimal string */
LONG lnum)
{
     static CHAR buffer[8+1];

     sprintf(buffer,"%lX",lnum);
     return(buffer);
}

VOID
hdlepk(VOID)                            /* handle PICK from enumerated list */
{
     INT i,n,nshow,c,newval,rc;
     CHAR **choices;
#define EPKLEFT 66
#define EPKBOTTOM 16
#define EPKMAXLIN 12
#define EPKWIDTH 8
#define EPKTOP (EPKBOTTOM-nshow-1)

     if ((n=elquan()) > ENMAX) {
          choices=(CHAR **)alcmem(n*sizeof(CHAR *));
          for (i=0 ; i < n ; i++) {
               choices[i]=strdup(firstwd(eltokn(i)));
          }
          savcnf();
          nshow=min(n,EPKMAXLIN);
          cfatr(ATREPB);
          cursiz(GVIDNOCURS);
          setwin(boxscn,EPKLEFT-2,EPKTOP-1,79,24,0);
          locate(EPKLEFT-2,EPKTOP-1);
          printf("\fÛ");
          repeat('ß',1+EPKWIDTH+1);
          printf("Û\n");
          for (i=0 ; i < nshow ; i++) {
               printf("Û");
               repeat(' ',1+EPKWIDTH+1);
               printf("Û\n");
          }
          printf("Û");
          repeat('Ü',1+EPKWIDTH+1);
          printf("Û");
          nslatr=atrval(ATREPL);
          selatr=atrval(ATREPC);
          setwin(NULL,0,0,79,24,0);
          explode(boxscn,EPKLEFT-2,
                         EPKTOP-1,
                         EPKLEFT+EPKWIDTH+2-1,
                         EPKBOTTOM-1);
          supchc(n,choices,2,1,2+EPKWIDTH-1,1+nshow-1,TRUE);
          indx=0;
          elpick(op->value);
          jmp2chc(indx);
          dspchc();
          cursiz(GVIDNOCURS);
          do {
               c=getchq();
               if (c == ALT+F10) {
                    FILE *fp;
                    static INT idx=0;

                    while((fp=fopen(spr("CNF%d.BIN",idx),FOPRB)) != NULL) {
                         fclose(fp);
                         idx++;
                    }
                    fp=fopen(spr("CNF%d.BIN",idx++),FOPWB);
                    scn2mem(savscn,0,GVIDSCNSIZ);
                    fwrite(savscn,GVIDSCNSIZ,1,fp);
                    fclose(fp);
                    belper(350);
                    printf("\7");
               }
               newval=hdlchc(c == F2 ? '\r' : c);
          } while (newval == n);
          rstcur();
          rstcnf();
          if (newval >= 0) {
               cpywrd(value,eltokn(newval));
               rc=hdlent();
               if (c != F2) {
                    hdldn(rc);
               }
          }
          for (i=0 ; i < n ; i++) {
               free(choices[i]);
          }
          free(choices);
     }
}

VOID
hdlspk(n)               /* handle a special softkey (for enumerated option) */
INT n;                                             /* n is index 0..ENMAX-1 */
{
     CHAR *cp;

     if ((op->type == 'E' || op->type == 'B')
      && elquan() <= ENMAX
      && *(cp=eltokn(n)) != '\0') {
          cpywrd(value,cp);
          hdldn(hdlent());
     }
}

VOID
shospk(VOID)              /* show a special softkey (for enumerated option) */
{
     CHAR *cp;
     INT i,k,n;
     static CHAR wdbuff[2+VALLEN+1]={"  "};
     INT blankey=F3;
     INT mslidx;

     switch (op->type) {
     case 'T':
          soft(F2," EDIT       ",ATREKY);
          soft(F3,nlingo > 1 ? "CHOOSE LANG " : "",ATRLKY);
          if (clingo != 0
           && (mslidx=op->file->mslidx[clingo]) != 0
           && altern(op)[mslidx].value.fsk != 0L) {
               soft(F4,"CLEAR OPTION",ATRKEY);
               blankey=F5;
          }
          else {
               blankey=F4;
          }
          break;
     case 'B':
     case 'E':
          if (elquan() > ENMAX) {
               soft(F2," PICK   ONE ",ATRKEY);
               blankey=F3;
          }
          else {
               soft(F2,"            ",ATRKEY);
               for (i=0 ; i < ENMAX ; i++) {
                    k=enarray[i];
                    cpywrd(wdbuff+2,eltokn(i));
                    cp=wdbuff+min(2,((n=strlen(wdbuff+2))-1)/2);
                    wdbuff[8]='\0';
                    soft(k,cp,n > 0 ? ATRSPK : ATRKEY);
               }
               return;
          }
          break;
     case 'C':
          blankey=F2;
          break;
     default:
          soft(F2,"CLEAR       ",ATRKEY);
          break;
     }
     for ( ; blankey <= F7 ; blankey+=F2-F1) {
          soft(blankey,"",ATRKEY);
     }
}

INT
altered(VOID)                                  /* Flag an option as changed */
{                       /* Returns true iff option was formerly NOT changed */
     if (!(op->flags&ANYCHG)) {
          op->flags|=ANYCHG;
          op->file->flags|=ANYCHG;
          if (++nchang < 0) {
               nchang=GCMAXSHORT;
          }
          return(1);
     }
     else {
          return(0);
     }
}

VOID
hdlabt(VOID)                                  /* abort changes to an option */
{
     if (edit && op->type != 'T') {
          edit=0;
          trash[0]='\0';
          strcpy(value,op->value);
          shoopt(op,op->value);
     }
}

VOID
rnwscn(VOID)    /* Renew the main screen after change in color or soft keys */
{
     cursiz(GVIDNOCURS);
     savcnf();
     movmem(scntbl[CNFINIT],scnsav,GVIDSCNSIZ);
     title();
     softy(softies);
     sksts();
     newpos();
     shopos();
     shopag();
     rstcnf();
}

VOID
cndent(VOID)        /* conditional Enter:  accept if valid, toss if invalid */
{
     if (curval()) {
          hdlent();
     }
     else {
          hdlabt();
     }
}

INT
hdlent(VOID)  /* Handle the Enter keystroke: enter new data into the option */
{
     INT rc;                               /* returns 1=accepted 0=rejected */

     if (op->type == 'T') {
          rc=1;
     }
     else if (curval()) {
          if (strcmp(op->value,value) != 0) {
               altered();
               strcpy(op->value,value);
               if (sameas(op->name,"CRT")) {
                    if (setcol(value)) {
                         rnwscn();
                    }
               }
               if (sameas(op->name,"EXPMODE")) {
                    expmode=sameas(value,"YES");
                    rnwscn();
               }
               if (sameas(op->name,"OUTBSZ")) {
                    warnbsz=1;
               }
          }
          edit=1;
          hdlabt();
          cursiz(GVIDLILCURS);
          rc=1;
     }
     else {
          edit=1;
          hdlabt();
          cursiz(GVIDLILCURS);
          rc=0;
     }
     return(rc);
}

INT
curval(VOID)                        /* is the current entered string valid? */
{
     INT ok=1;
     LONG ln;

     switch (op->type) {
     case 'B':
     case 'E':
          ok=elpick(value) == 1;
          cpywrd(value,eltokn(indx));
          break;
     case 'N':
     case 'L':
          ok=sscanf(value,"%ld",&ln) == 1 && ln <= op->ceiling
         && ln >= op->floor;
          break;
     case 'H':
          ok=sscanf(value,"%lx",&ln) == 1 && ln <= op->ceiling
         && ln >= op->floor;
          break;
     case 'C':
          ok=(0x20 <= value[0] && value[0] != '~');
          break;
     }
     return(ok);
}

VOID
savcnf(VOID)                                    /* Save the main CNF screen */
{
     scn2mem(scnsav,0,GVIDSCNSIZ);
     scnptr=scnsav;
}

VOID
rstcnf(VOID)                                 /* Restore the main CNF screen */
{
     mem2scn(scnsav,0,GVIDSCNSIZ);
     scnptr=NULL;
     swinde();
}

INT
hlprd(opp)                             /* read help paragraph into hlpbuf[] */
struct option *opp;
{
     INT i;
     CHAR *cp;
     FILE *tfp;

     if (opp->flags&ISHPAR) {
          fseek(tfp=opp->file->fp,opp->parptr,0);
          for (i=0,cp=hlpbuf ; i < opp->parlen ; i++,cp++) {
               *cp=getc(tfp);
          }
          *cp='\0';
          return(1);
     }
     return(0);
}

VOID
hdlhlp(VOID)                              /* Handle help on main CNF screen */
{
     INT n;
     CHAR *hp;

     swindh(1);
     cfatr(ATRHRM);
     printf("\14");
     if (op->flags&ISHPAR) {
          hlprd(op);
          for (hp=hlpbuf,n=0 ; *hp != '\0' ; hp++) {
               if (*hp == '\n') {
                    n++;
               }
          }
          repeat('\n',max(0,11-n));
          center(spr("OPTION %-s",op->name),61);
          cfatr(ATRHLN);
          repeat('Ä',61);
          cfatr(ATRHLP);
          printf("%s",hlpbuf);
     }
     else {
          repeat('\n',10);
          if (op->type == 'T') {
                 printf("          Hit F2 or ÄÙ to view or edit this text."
                        "          ");
          }
          else {
               center(spr("<< No detailed info is available for option %s >>",
                          op->name),61);
          }
     }
     softies[0]="EXPERT MODE ";
     soft(F1,softies[0],ATRKEY);
}

VOID
hdlclr(VOID)             /* Handle the F2 softkey (non-'T' options):  CLEAR */
{
     switch (op->type) {
     case 'C':
          break;
     default:
          value[0]='\0';
          edit=1;
          break;
     }
     shoopt(op,value);
     cursiz(GVIDLILCURS);
}

VOID
hdlbks(save)                                    /* handle the backspace key */
INT save;
{
     INT n,m;

     if (op->type == 'T' || op->type == 'C') {
          return;
     }
     else if ((n=strlen(value)) > 0) {
          if (save && (m=strlen(trash)) < LINLEN) {
               trash[m]=value[n-1];
               trash[m+1]='\0';
          }
          value[n-1]='\0';
          edit=1;
     }
     shoopt(op,value);
     cursiz(GVIDLILCURS);
}

VOID
hdlfws(VOID)                    /* handle the non-destructive forward space */
{
     INT n;

     if ((n=strlen(trash)) > 0) {
          hdlchr(trash[n-1]);
          trash[n-1]='\0';
     }
}

VOID
hdlchr(                            /* handle entry of a printable character */
CHAR keystr)
{
     INT n,m,ok=0,ep;
     static CHAR newkey[2];

     if (op->type == 'T') {
          return;
     }
     else if (op->type == 'S') {
          m=(INT)(op->ceiling);
     }
     else {
          m=min(VALLEN,LINLEN-strlen(op->lindsc)-1);
     }
     if ((n=strlen(value)) < m) {
          newkey[0]=keystr;
          switch (op->type) {
          case 'L':
          case 'N':
               ok=isdigit(keystr)
                  || (keystr == '-' && (n == 0 || !edit) && op->floor < 0L);
               if (ok) {
                    if (edit) {
                         strcat(value,newkey);
                    }
                    else {
                         strcpy(value,newkey);
                    }
               }
               break;
          case 'H':
               if ((ok=isxdigit(keystr)) != 0) {
                    if (!edit) {
                         n=0;
                    }
                    value[n]=toupper(keystr);
                    value[n+1]='\0';
               }
               break;
          case 'E':
          case 'B':
               if (keystr != ' ') {
                    value[n]=toupper(keystr);
                    value[n+1]='\0';
                    if ((ep=elpick(value)) > 0 || (ep=elpick(newkey)) > 0) {
                         ok=1;
                         if (ep == 1) {
                              cpywrd(value,eltokn(indx));
                         }
                    }
               }
               break;
          case 'S':
               ok=1;
               strcat(value,newkey);
               break;
          case 'C':
               if ((ok=(0x20 <= keystr)) != 0 && keystr != '~') {
                    value[0]=keystr;
                    value[1]='\0';
               }
               break;
          }
          if (ok) {
               edit=1;
          }
          else {
               value[n]='\0';
          }
     }
     shoopt(op,value);
     cursiz(GVIDLILCURS);
}

INT
chkvis(          /* Is target option visible? (based on its "hinge" option) */
struct option *opt)
{                                                /* return true iff visible */
     CHAR *ap;         /* pointer scanning value of hinge-referenced option */
     CHAR *bp;                       /* pointer scanning each value in list */
     CHAR *cp;              /* ptr to beginning of each hinge value in list */
     INT same;                           /* found a match anywhere in list? */

     if (!(opt->flags&ISHING)) {
          return(1);                               /* visible if not hinged */
     }
     if (opt->hngop == HNGEXC) {   // don't ever show an option with this
          return(0);
     }
     if (opt != opt->hngptr && !chkvis(opt->hngptr)) {
          return(0);            /* not vis if hinged on an invisible option */
     }
     if (opt->hngptr->type == 'T') {
          return(0);                   /* not vis if hinged on a 'T' option */
     }
     if (*opt->hngval == '\0') {
          return((opt->hngop == HNGEQ) == (*opt->hngptr->value == '\0'));
     }                  /* =) tests option empty, #) tests option non-empty */
     for (cp=opt->hngval ; *cp ; ) {
          same=1;
          for (ap=opt->hngptr->value,bp=cp
            ; (*bp != '\0' && *bp != HNGDLM) || *ap != '\0'
            ; bp++,ap++) {
               if (toupper(*bp) != toupper(*ap)) {
                    same=0;
                    break;
               }
          }
          if (same) {
               return(opt->hngop == HNGEQ);
          }
          while (*cp && *cp != HNGDLM) {
               cp++;
          }
          if (*cp == HNGDLM) {
               cp++;
          }
     }
     return(opt->hngop == HNGNE);
}

VOID
shopag(VOID)                           /* show the entire screen of options */
{
     INT i;
     struct option *s;

     softies[0]=" HELP       ";
     soft(F1,softies[0],ATRKEY);
     swinde();
     if (expmode) {
          printf("\f");
          for (i=0,ecy=ECYBOT,s=op ; i < 15 ; i++,ecy--) {
               shoopt(s,s->value);
               s=upop(s,1,0);
               if (!skipped) {
                    break;
               }
          }
     }
     ecy=ECYBOT;
}

VOID
hdldn(n)                                             /* move down n options */
INT n;
{
     INT i;

     swinde();
     for (i=0 ; i < n || op->type == FBORDER ; i++) {
          op=dnop(op,1,0);
          if (!skipped) {
               break;
          }
          if (expmode) {
               cfatr(ATRLIN);
               printf("\n");
               shoopt(op,op->value);
          }
     }
     if (syncdn()) {
          shopag();
     }
     shoopt(op,op->value);
     newpos();
     shopos();
     swinde();
}

VOID
movup(n,scl)                                           /* move up n options */
INT n;
INT scl;                  /* 1=visibly scroll if expert mode 0=be invisible */
{
     INT i;
     struct option *optop;

     swinde();
     optop=upop(op,14,0);
     for (i=0 ; i < n || op->type == FBORDER ; i++) {
          if (op == ophead->next) {
               break;
          }
          op=upop(op,1,0);
          if (!skipped) {
               break;
          }
          if (expmode && scl) {
               scdown(scnptr,17,2,77,16,TRUE);
          }
          optop=upop(optop,1,0);
          if (skipped && expmode && scl) {
               ecy+=1-ESCNHGT;
               shoopt(optop,optop->value);
               ecy+=ESCNHGT-1;
          }
     }
}

VOID
hdlup(n)                                   /* move up n options and display */
INT n;
{
     movup(n,1);
     shoopt(op,op->value);
     newpos();
     shopos();
     swinde();
}

VOID
hdlpgh(VOID)                  /* jump to the first option in the first file */
{
     op=ophead;
     hopnew();
}

INT
syncdn(VOID)    /* if pointing to a file-border, find a better spot to rest */
{
     INT rc=0;

     if (op->type == FBORDER || !chkvis(op)) {
          op=dnop(op,1,0);
          if (skipped == 0) {
               do {
                    op=upop(op,1,0);
               } while (op->type == FBORDER && skipped != 0);
          }
          rc=1;
     }
     return(rc);
}

VOID
hopnew(VOID)                                        /* jump to a new option */
{                                         /* (changed op is implicit input) */
     syncdn();
     shopag();
     newpos();
     shopos();
     shoopt(op,op->value);
}

VOID
hdlpge(VOID)                                              /* handle END-key */
{
     if (ldrstt != LDRNUL) {
          softy(blanksf);
          swinde();
          printf("\f\n\n\n\n\n"
                 "                       Still loading...\n"
                 "\n"
                 "        Now we're waiting for all the options to load\n"
                 "             so we can jump to the very last one.\n"
                "\n"
                 "             But you can hit any key to stop now\n"
                 "                 at the latest option loaded.");
          lodopt(65535U);
          kbflush();
          softy(softies);
          shospk();
     }
     op=dnop(upop(optail,1,0),1,0);
     syncdn();
     hopnew();
}

VOID
hdlcpd(VOID)                                           /* control page down */
{
     struct msgfil *mfp;

     if (op->file->next == NULL) {
          hdlpge();
     }
     else if (op->file->next->fstopt != NULL) {
          op=op->file->next->fstopt;                  /* (most likely case) */
          hopnew();
     }
     else {
          if (ldrstt != LDRNUL) {
               swinde();
               printf("\f");
               locate(29,9);
               cfatr(ATRELD);
               printf("Loading...   (hit any key to stop)");
          }
          for (mfp=op->file->next ; mfp != NULL
                                 && !kbhit() ; mfp=mfp->next) {
               while (ldrstt != LDRNUL
                   && mfp->fstopt == NULL
                   && !(mfp->flags&FILOAD)
                   && !kbhit()) {
                    lodopt(totopt+1);
               }
               if (mfp->fstopt != NULL) {
                    break;
               }
          }
          if (kbhit()) {
               kbflush();
               hopnew();
          }
          else if (mfp != NULL && mfp->fstopt != NULL) {
               op=mfp->fstopt;
               hopnew();
          }
          else {
               hdlpge();
          }
     }
}

VOID
hdlcpu(VOID)                                             /* control page up */
{
     movup(1,0);
     op=op->file->fstopt;
     hopnew();
}

VOID
rsvhng(rop)                                    /* resolve a hinge reference */
struct option *rop;
/* Implicit inputs:     */
/*   char hngnam[]      */
/*   char hngval[]      */
/*   rop->flags&ISHING  */
/* Implicit outputs:    */
/*   rop->hngptr        */
/*   rop->hngval        */
{
     struct option *opp;
     CHAR buff[160];

     if (rop->flags&ISHING) {
          if (rop->hngop == HNGEXC && sameas(hngnam,EXCSTG)) {
               rop->hngval=alcdup(EXCSTG);
               rop->hngptr=rop;
               return;
          }
          for (opp=rop->file->fstopt ; opp != NULL ; opp=opp->next) {
               if (sameas(opp->name,hngnam)) {
                    rop->hngptr=opp;
                    rop->hngval=alcdup(hngval);
                    return;      /* priority:  hinge to option in same file */
               }
               if (opp == rop) {
                    break;   /* self is last chance in same file */
               }
          }
          for (opp=mfhead.fstopt ; opp != NULL
                                && opp->file == &mfhead ; opp=opp->next) {
               if (sameas(opp->name,hngnam)) {
                    rop->hngptr=opp;
                    rop->hngval=alcdup(hngval);
                    return;         /* allow:  hinge to option in main file */
               }
          }
          sprintf(buff,"Option %s in %s should not hinge on\noption %s.  It "
                       "should only hinge on an option that\nappears earlier "
                       "in the same .MSG file, and at the same\nlevel.",
                       rop->name,rop->file->name,hngnam);
          catastro(buff);
     }
}

VOID
shoopt(lop,val)                                           /* show an option */
struct option *lop;
CHAR *val;
{
     INT n,mslidx;
     struct altlng *alp;

     if (lop->type == FBORDER) {
          swinde();
          cfatr(ATRBDR);
          printf("\r "
                 "ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßß");
          cfatr(ATRBFN);
          printf("\rÛß  %s options  ",lop->file->name);
          cfatr(ATRLIN);
          return;
     }
     if (!expmode) {
          hdlhlp();
     }
     swinde();
     if (lop->type == 'T') {
          mslidx=lop->file->mslidx[clingo];
          alp=&altern(lop)[mslidx];
          if ((clingo == 0 || mslidx != 0) && alp->altered) {
               cfatr(alp->value.ptr == NULL ? ATRLLC : ATRLCH);
          }
          else {
               cfatr((clingo != 0 && mslidx == 0)
                  || alp->value.ptr == NULL ? ATRLLU : ATRLIN);
          }
     }
     else {
          cfatr((lop->flags&ANYCHG) ? ATRLCH : ATRLIN);
     }
     printf("\r %s",lop->lindsc);
     if (lop->type == 'T') {
          repeat(' ',LINLEN-(curcurx()-18));
     }
     else {
          if ((n=LINLEN-(curcurx()-18)-strlen(val)-2) >= 0) {
               printf(" ");
          }
          repeat('ú',n);
          printf(" ");
          cfatr((edit && ecy == ECYBOT)
                ? ATRENT : ((lop->flags&ANYCHG) ? ATRNEW : ATRVAL));
          printf("%s",val);
     }
     cleareol();
     cfatr(ATRLIN);
     ecx=curcurx();
     ecy=curcury();
}

VOID
repeat(c,n)                                         /* display n characters */
INT c,n;
{
     CHAR buff[80+1];

     if ((n=min(n,80)) > 0) {
          setmem(buff,n,c);
          buff[n]='\0';
          printf("%s",buff);
     }
}

VOID
soft(n,sft,attr)                                 /* Soft key legend display */
INT n;                       /* Function key code (fm kstroke.h) F1,...,F10 */
CHAR *sft;                                               /* soft key legend */
INT attr;                                   /* display attribute for legend */
{
     INT i;

     i=(n-F1)>>8;
     cursiz(GVIDNOCURS);
     setwin(scnptr,funkx[i],FUNKY,funkx[i]+5,FUNKY+1,0);
     locate(funkx[i],FUNKY);
     cfatr(attr);
     printf("\f%s",sft);
     rstwin();
}

VOID
softy(sft)                                   /* display all softkey legends */
CHAR *sft[];
{
     INT c,i;

     for (c=F1,i=0 ; c <= F10 ; c+=(1<<8),i++) {
          soft(c,sft[i],ATRKEY);
     }
}

VOID
swinde(VOID)                              /* set window for option edit box */
{
     setwin(scnptr,17,2,77,16,1);
     locate(ecx,ecy);
     cfatr(ATRLIN);
     cursiz(GVIDLILCURS);
}

VOID
swindh(scropt)                                /* set window for option help */
INT scropt;
{
     setwin(scnptr,17,2,77,15,scropt);
     cursiz(GVIDNOCURS);
}

VOID
swinds(VOID)                                   /* set window for status box */
{
     if (!blindcur) {
          cursiz(GVIDNOCURS);
     }
     setwin(scnptr,1,2,15,7,0);
     locate(2,2);
}

VOID
swindf(VOID)                                   /* set window for format box */
{
     cursiz(GVIDNOCURS);
     setwin(scnptr,1,9,15,16,0);
     locate(2,9);
     cfatr(ATRFMT);
}

VOID
swindc(VOID)                                 /* set window for command area */
{
     if (!blindcur) {
          cursiz(GVIDNOCURS);
     }
     setwin(scnptr,2,18,77,19,0);
     locate(2,19);
}

VOID
scdown(                            /* scroll a window down                 */
CHAR *scnimg,                      /*   screen pointer to write to         */
INT xl,                            /*   left edge of region to scroll      */
INT yt,                            /*   top of region to scroll            */
INT xr,                            /*   right edge of region to scroll     */
INT yb,                            /*   bottom of region to scroll         */
GBOOL topblk)                      /*   blank the top line?                */
{
     INT y;
     UINT coffset;

     coffset=yb*160+xl*2;
     for (y=yb ; y > yt ; y--,coffset-=160) {
          scn2scn(coffset-160,coffset,(xr-xl+1)*2);
     }
     if (topblk) {
          setwin(scnimg,xl,yt,xr,yt,0);
          printf("\f");
          rstwin();
     }
}

VOID
center(string,width)                               /* display text centered */
CHAR *string;
INT width;
{
     INT n;
     CHAR buff[80+1];

     width=min(width,80);
     setmem(buff,width,' ');
     buff[width]='\0';
     n=min(strlen(string),width);
     movmem(string,buff+((width-n+1)>>1),n);
     printf("%s",buff);
}

VOID
parms(                       /* parse the numeric parameter(s) of an option */
struct option *opp,                                               /* option */
CHAR *cs,                 /* scanf control string for one or two long-int's */
INT np)                                    /* number of parameters (1 or 2) */
{
     if (fscanf(rdfp,cs,&opp->floor,&opp->ceiling) != np) {
          catastro("BAD OR MISSING PARAMETERS AFTER OPTION %s IN %s",
                   opp->name,curfn);
     }
}

VOID
parse(VOID)  /* Parse the description and value from text from between {}'s */
{              /* value is last word of txtbuf, lindsc is whatever precedes */
     CHAR *cp;

     cp=lastwd(txtbuf);
     cp[VALLEN]='\0';
     strcpy(rdval,cp);
     *cp='\0';
     txtbuf[LINLEN]='\0';
     strcpy(lindsc,txtbuf);
}

VOID
parsec(VOID) /* Parse descrip and val from text from between {}'s for C opt */
{              /* value is last char of txtbuf, lindsc is whatever precedes */
     CHAR *cp;

     cp=&txtbuf[strlen(txtbuf)-1];
     cp[VALLEN]='\0';
     strcpy(rdval,cp);
     *cp='\0';
     txtbuf[LINLEN]='\0';
     strcpy(lindsc,txtbuf);
}

VOID
parstg(to,len)       /* Parse the string parameter at the end of the option */
CHAR *to;       /* make to==NULL and len==0 if there is no string parameter */
INT len;
{
     INT i,c;
     if (to != NULL) {
          *to='\0';
     }
     if ((c=getc(rdfp)) == ' ') {
          c=getc(rdfp);
     }
     for (i=0 ; c != EOF && c != CHR_EOL ; i++) {
          if (i < len) {
               *to++=c;
               *to='\0';
          }
          c=getc(rdfp);
     }
     if (c == CHR_EOL) {
          getc(rdfp);
     }
}

VOID
addlang(mfp,ilingo)                             /* add a language to a file */
struct msgfil *mfp;
INT ilingo;
{
     if (mfp->lngcnt >= nlingo) {
          catastro("Already %d languages in %s",mfp->lngcnt,mfp->name);
     }
     if (ilingo == 0 || mfp->mslidx[ilingo] != 0) {
          catastro("%s already has a %s language",mfp->name,
                                                  languages[ilingo]->name);
     }
     mfp->mslidx[ilingo]=mfp->lngcnt++;
     mfp->flags|=FILANG;
}

INT
rdopt(                                    /* read an option from a MSG file */
struct option *opp)                                        /* put data here */
        /* uses data from rdmsg(), reads in hinge and option specifications */
                                                   /* returns 1=done, 0=EOF */
                        /* Implicit inputs:     */
                        /*   FILE *rdfp;        */
                        /*   CHAR *curfn;       */
                        /*   CHAR *msgnam;      */
                        /*   CHAR *cmtptr;      */
                        /* Implicit outputs:    */
                        /*   opp->name          */
                        /*   opp->parptr        */
                        /*   opp->parlen        */
                        /*   opp->flags&ISHPAR  */
                        /*   opp->hngop         */
                        /*   opp->flags&ISHING  */
                        /*   opp->type          */
                        /*   opp->ceiling       */
                        /*   opp->floor         */
                        /*   CHAR hngnam[]      */
                        /*   CHAR hngval[]      */
                        /*   CHAR rdval[]       */
                        /*   CHAR lindsc[]      */
                        /*   CHAR txtbuf[]      */
                        /*   struct altlng altlnm[] */
{
     INT i,c;

     opp->parptr=cmtptr;
     if ((opp->parlen=cmtlen) > 0) {
          opp->flags|=ISHPAR;
     }
     else {
          opp->flags&=~ISHPAR;
     }
     strcpy(opp->name,msgnam);
     if ((c=scanalt()) != EOF && isspace(c)) {
          c=nxtink();
     }
     if (c == EOF) {
          return(0);
     }
     if (c == OPNHNG) {
          for (i=0 ; isalnum(c=getc(rdfp)) && i < MOPTLEN ; i++) {
               hngnam[i]=c;
          }
          hngnam[i]='\0';
          if (c == HNGEXC) {       // for special UNUSED* hinge option
               if (!sameas(hngnam,EXCSTG)) {
                    catastro("BAD HINGE SPECIFICATION IN %s OPTION %s",curfn,
                                                                       opp->name);
               }
          }
          else if (c != HNGEQ && c != HNGNE) {
               catastro("BAD HINGE SPECIFICATION IN %s OPTION %s",curfn,
                                                                  opp->name);
          }
          opp->hngop=c;
          for (i=0 ; (c=getc(rdfp)) != EOF && c != CLSHNG && i < VALLEN ; i++) {
               hngval[i]=c;
          }
          hngval[i]='\0';
          if (c != CLSHNG) {
               catastro("BAD HINGE VALUE IN %s OPTION %s",curfn,opp->name);
          }
          if ((c=nxtink()) == EOF) {
               return(0);
          }
          opp->flags|=ISHING;
     }
#ifdef UNIX
     if (c == OPNHNG) { // we have multiple hinges this is for non-unix options
          for (i=0 ; (c=getc(rdfp)) != EOF && c != CLSHNG && i < VALLEN ; i++) {
               /* NULL */
          }
          if (c != CLSHNG) {
               catastro("BAD HINGE VALUE IN %s OPTION %s",curfn,opp->name);
          }
          if ((c=nxtink()) == EOF) {
               return(0);
          }
     }
#endif                             // UNIX
     switch (opp->type=toupper(c)) {
     case 'H':
          parms(opp,"%lx%lx",2);
          parstg(NULL,0);
          parse();
          break;
     case 'N':
     case 'L':
          parms(opp,"%ld%ld",2);
          parstg(NULL,0);
          parse();
          break;
     case 'C':
          parstg(NULL,0);
          parsec();
          rdval[1]='\0';
          break;
     case 'B':
          parstg(NULL,0);
          parse();
          strcpy(enlist,"NO YES");
          break;
     case 'E':
          parstg(enlist,ENLLEN);
          parse();
          break;
     case 'S':
          parms(opp,"%ld",1);
          if ((opp->ceiling=opp->floor) == 0L) {
               opp->ceiling=LINLEN;
          }
          strcpy(rdval,txtbuf);
          parstg(lindsc,LINLEN);
          opp->ceiling=min(opp->ceiling,LINLEN-strlen(lindsc)-1);
          rdval[(INT)opp->ceiling]='\0';
          break;
     case 'T':
          parstg(lindsc,LINLEN);
          break;
     default:
          catastro("BAD OR MISSING OPTION TYPE AFTER %s IN %s",opp->name,curfn);
          break;
     }
     depad(lindsc);
     return(1);
}

VOID
fndfls(VOID)               /* find out what .MSG files we need to configure */
{
     CHAR *msgnam;
     struct msgfil *mfp;
     struct ffblk fb;

     if (tfsopn("wgserv.cfg") != 1) {
          catastro("CANNOT FIND wgserv.cfg");
     }
     while (tfsrdl() != TFSDUN) {
          if (tfstate == TFSLIN
           && tfspfx("MSG=")
           && !msgxst(msgnam=spr("%s.msg",tfspst))) {      /* weed dups! */
               if (!fndfile(&fb,msgnam,0)) {
                    catastro("Cannot find %s",msgnam);
               }
               allbytes+=fb.ff_fsize;
               mfp=(struct msgfil *)alczer(sizeof(struct msgfil));
               strcpy(mfp->name,msgnam);
               mftail->next=mfp;
               mftail=mfp;
               nfiles++;
          }
          else if (tfstate == TFSLIN
             && tfspfx("CHANTYPE=")) {
               stzcat(chantyps," ",ENLLEN+1);
               stzcat(chantyps,tfspst,ENLLEN+1);
          }
          else if (tfstate == TFSLIN
             && tfspfx("HDWRTYPE=")) {
               stzcat(hdwrtyps," ",ENLLEN+1);
               stzcat(hdwrtyps,tfspst,ENLLEN+1);
          }
     }
}

INT
msgxst(msgnam)                /* does this .MSG file exist in list already? */
CHAR *msgnam;
{
     struct msgfil *mfsp;

     for (mfsp=&mfhead ; mfsp != NULL ; mfsp=mfsp->next) {
          if (sameas(mfsp->name,msgnam)) {
               return(1);
          }
     }
     return(0);
}

VOID
loaded(VOID)            /* Prepare structures for editing ('T' option only) */
{                                       /* read value of option into altbuf */
     struct altlng *alp;
     INT rlingo,mslidx;

     for (alg1st(rlingo=clingo) ; 1 ; rlingo=algnxt()) {
          mslidx=op->file->mslidx[rlingo];
          alp=&altern(op)[mslidx];
          if (rlingo == 0 || (mslidx != 0 && alp->value.ptr != NULL)) {
               break;
          }
     }
     if (rlingo != clingo) {
          swindc();
          cfatr(ATRCLG);

          printf("\fCreating a new %s version of %s,\nbased upon the %s "
                 "version...",languages[clingo]->name,op->name,
                              languages[rlingo]->name);
     }
     loadtv(fp,alp);
     strcpy(altbuf,txtbuf);
}

VOID
stored()               /* save edited stuff in altbuf (for 'T' option only) */
{
     struct altlng *alp;

     if (clingo != 0 && op->file->mslidx[clingo] == 0) {
          addlang(op->file,clingo);
     }
#ifdef GCDOS
     ckmem();
#endif
     alp=&altern(op)[op->file->mslidx[clingo]];
     if (alp->altered) {
          free(alp->value.ptr);
     }
     alp->value.ptr=alcdup(altbuf);
     alp->altered=1;
     altered();
#ifdef GCDOS
     ckmem();
#endif
}

#ifdef GCDOS
VOID
ckmem(VOID)            /* check on memory available (searching for minimum) */
{
     LONG l;

     if ((l=sizmem()) < memmin) {
          memmin=l;
     }
}
#endif // GCDOS

VOID
print2a(attr1,attr2,string)                /* print a string w/2 attributes */
INT attr1,attr2;
CHAR *string;
{
     CHAR *cp;
     INT toggle=0;

     for (cp=string ; *cp != '\0' ; cp+=strlen(cp)+1) {
          cfatr((toggle=!toggle) != 0 ? attr1 : attr2);
          printf("%s",cp);
     }
}

VOID
cfatr(attr)                              /* set color/monochrome attributes */
INT attr;
{
     setatr(atrval(attr));
     ccratr=attr;
}

INT
atrval(attr)   /* translate 2-byte ATRXXX value into 1-byte video attribute */
INT attr;
{
     return((color ? (attr)>>8 : (attr))&0xFF);
}

VOID
sdwnpc(buf,stripped)               /* strip buffer down to %?'s only       */
CHAR *buf;                              /* buffer to strip down            */
CHAR *stripped;
{
     INT len=0,intv=0;

     while (*buf != '\0') {
          if (*buf == 1) {              /* skip over text variables        */
               intv=!intv;
          }
          if (*buf++ == '%' && !intv) {
               switch (*buf) {
               case '%':
               case ' ':
                    buf++;
                    break;
               default:
                    while (*buf != '\0') {
                         if (isalpha(*buf)) {
                              if (len < MAXSTP-1) {
                                   stripped[len++]=*buf;
                              }
                              break;
                         }
                         buf++;
                    }
               }
          }
     }
     stripped[len]='\0';
}

/*

This program reads options from the .MSG files on the current directory,
allows the operator to edit these options, and then stores them back into the
.MSG files.

The file WGSERV.CFG defines the user-languages and the .MSG files to be
searched for options.


Syntax of Options
-----------------

     descriptive text  <option name> { <option value> } <option specs>

the option name is 1 to 8 characters, the option specs always fit on one line


Example syntax of an option in an .MSG file (All types but types 'S' and 'T'):

     This is a paragraph-size
     description of the option:
     the "help" on the option.

     NAME {One line description of option:  VALUE} <format code> <format parms>


Example syntax of a type 'S' option in the .MSG file:

     This is a paragraph-size
     description of the option:
     the "help" on the option.

     NAME {String value of the option} S <length> One line description of option


Example syntax of a type 'T' option in the .MSG file:

     This is a paragraph-size
     description of the option:
     the "help" on the option.

     NAME {
     The value of this option can be
     an entire screen-ful of text
     } T One line description of option



Format Codes, and Parameters that follow, for various option types
------------------------------------------------------------------
C                     single printable character
S <L> <Descr>         one-line string (with max length and description)
N <F> <C>             decimal number, with floor and ceiling
H <F> <C>             hexadecimal number, with floor and ceiling
L <F> <C>             long decimal number, with floor and ceiling
B                     binary:  either YES or NO accepted
E <Val1> <Val2> ...   enumeration of the possible values of the option
T <Descr>             Text paragraph of information



Levels
------

Options are categorized into levels 1 and up.  Each .MSG file starts out at an
undefined level and starts a new level with a "null option", for example:

     LEVEL1 {}

All options which follow this one would be considered level 1 options.  Level
1, 2, and 3 options are edited in exclusive sets by running this program with
a "-L1", "-L2", or "-L3" command line directive.



Hinging
-------

A hinge is a device that can inhibit certain options based on the values of
other PRECEDING options.  Example:  If there is no "card #3", then you don't
want to ask for the base address, number of channels for that card, etc.  We
say that these options "hinge" on the selection of card #3.

The hinge specifier precedes the format codes and parameters (above) for the
option which may be inhibited.  The syntax is:

     (<hinge option><hinge operator><hinge value>)

          <hinge option>     The name of the option upon which the current
                             option hinges
          <hinge operator>   Relational operator "=" for equals, "#" for not
                             equals
          <hinge value>      The value to which is compared the value of the
                             hinge option (according to the hinge operator)

Example:

     THISOPT {Some string value} (THATOPT=YES) S 44 Description

Then the option THISOPT is only seen on the WGSCNF screen when the option
THATOPT has a value exactly equal to "YES".  THATOPT must precede THISOPT.

     IMPORTANT:  The hinge option must reference a PRE-DEFINED option,
     that is, one that precedes it in the current file.

Note:  You cannot hinge on the value of a text option


Note #2: If a hinge option with the verbage "UNUSED*" (no quotes) is used in
an option, the option is never displayed.  This is useful for having certain
messages not show up under one OS but not under another.  "UNUSED" gets put in
the option hngval field and a "*" gets put in the hngop field.  The code
actually checks the hngop field to determine if the option should be forced
invisible.


Command line parameters:
------------------------

WGSCNF -Ln [-M] [?]

     -Ln  Configure only LEVEL<n> options
     -M   Display memory usage statistics upon return to DOS
     ?    Gives you a parameter help message


Exit codes (for use by batch files):
------------------------------------

    errorlevel 0 -  operator quit(F9) or exit(F10)
    errorlevel 1 -  bad command line parameters
    errorlevel 49 - out of memory
    errorlevel 70 - 1 catastro, clean kill
    errorlevel 80 - 2 cata's, need reboot
    errorlevel 90 - cata w/no shutdown vec

*/
