[net.micro.amiga] AmigaDisplay

woods@glacier.ARPA (Don Woods) (03/28/86)

Since my old display terminal was giving up the ghost, I latched onto
Michael Mounier's "AmigaTerm" program and turned it into a "smart display"
program that I call "AmigaDisplay".  AmigaDisplay is willing to behave like
a "dumb terminal", i.e., one with no special functions except for "clear
screen" (^L), or it will emulate a DataMedia 2500.  The dm2500 is in most
Unix termcap files, but if you'd prefer something else it shouldn't be hard
to change that part of the program.

The file send/capture menu commands now use Requesters to ask for the file
name, instead of injecting characters into the midst of the displayed text.
For the same reason, there is no indication given when a file-send
operation finishes.  (If for some reason you can't tell when it's done, you
can find out by checking whether the menu offers a "Start Send" or "Cancel
Send" command.)  The send feature offers to change \n (line feed, ^J) into
\r (carriage return, ^M), since the Amiga uses \n as line terminators but
many systems want \r instead.  The capture feature offers the option of
including control characters in the captured file; captured control
characters are translated into visible characters using the "^" notation.

I added an audible bell.  You even get to choose among different volumes
and pitches depending on whether there are people trying to sleep nearby.
(No, the program doesn't automatically detect sleeping people.)  You can
also opt for a visible bell (screen flash).

Some of the new features probably aren't of much use to people anywhere but
Stanford, but those features shouldn't get in anyone's way.  E.g., the
SU-AI machine uses 9-bit input; two extra "shift" keys control the 8th and
9th bits.  AmigaDisplay emulates this using the ALT and AMIGA keys.  If you
just want 7-bit characters, don't hold down the ALT or AMIGA keys while
you're typing.  Also, AmigaDisplay looks for a font named SAIL/8, which has
various special characters hidden in codes $80-$9F.  SAIL/8 also includes
slightly cleaner versions of some other characters, so that characters
don't run into each other as often.  If you want a copy of the font, read
on; I'm going to mail an assembler source that creates it per the example
in the ROM Kernel Manual.  But if AmigaDisplay can't find the font, it just
uses the default font, so you don't really need it.  I also constructed an
icon for the program, but I'm not sure how to distribute that to the net.

AmigaDisplay interprets the keyboard pretty much as you might expect, but
some of the more unusual keys produce characters that are frequently used
at Stanford.  E.g., cursor-down and cursor-up produce ^L and ^K, ENTER
produces ^J, and HELP produces ^@ (NUL).  The function keys produce short
sequences that SU-AI expands in various (user-specified) ways.  All of
these key assignments are easy to change if you have something you'd rather
they do (e.g., map the up/down/left/right keys to EMACS commands).

The only AmigaTerm function I left out was the xmodem stuff.  None of the
hosts I use support it, and my phone lines are good enough (well, at 300
baud anyway) that I could get by with just file send/capture.  If someone
wanted to add that stuff back, it wouldn't be hard.  I've structured the
program to be pretty modular and extensible.  Oh yeah, it also no longer
lets you change the window size; it takes over the whole screen to give you
24x80.  It has to take away the menu bar to get the room it needs, but the
mouse menu button still brings up the menus, and one menu includes commands
to move the AmigaDisplay screen to the back or front of the window stack,
so you can get at other windows while AmigaDisplay is running.  That's
where the Close Window command is hidden, too.

The source for AmigaDisplay illustrates some potentially useful techniques.
For instance, it includes a couple of fairly short subroutines that let you
construct an entire menu using just two lines: one that lists the menu
strings and one to build the menu.  And as mentioned above, it includes
examples of audio and requesters.  The source is split across 4 files: main
program, menus/requesters, display, and audio.  All four files are
concatenated below.  The SAIL/8 font will be mailed separately.

NOTE: I do NOT read netnews, since my primary host is not a usenet machine.
So if you want to get in touch with me, you'll have to send mail directly.
My primary address is Woods.pa@Xerox.ARPA, but if you can't force that
through your mailer you can send to ...!glacier!woods or to
...!decwrl!parcvax!woods and it'll forward to me.

======================================================================

/* AmigaDisplay:  A "smart display" terminal emulator for the Amiga
 *
 * Based on the AmigaTerm program by Michael Mounier, enhanced by Don Woods.
 * This version emulates either a "dumb terminal" or a DataMedia 2500.
 * Mounier's version was (c) 1985 but was freely distributed.  Woods's
 * rewrite is (c) 1986 and is likewise available if due credit is given.
 *
 * The DM2500 supports a special command sequence used at Stanford to display
 * up to 128 different character glyphs, and also supports various special
 * interpretations applied to the ALT, AMIGA, and F# keys (again for use at
 * Stanford).  These features, and the type of display being emulated, should
 * be fairly easy to change (e.g., if someone would rather emulate a VT100).
 *
 * The emulator also provides a variety of bell volumes (just for kicks), and
 * file send/capture, but NOT the so-called "xmodem" features.  The send/capture
 * stuff uses a requester to avoid mucking up the display region.
 */

/****************************************\
* This file is the main program (main.c) *
\****************************************/

#include <exec/types.h>
#include <exec/exec.h>
#include <graphics/gfxbase.h>
#include <graphics/gfx.h>
#include <graphics/text.h>
#include <devices/serial.h>
#include <devices/keymap.h>
#include <lattice/stdio.h>
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>

/*
 * Library definitions; shared with the other modules
 */

#define INTUITION_REV 1
#define GRAPHICS_REV  1
#define FONT_REV      1

struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase       *GfxBase       = NULL;
struct DiskfontBase  *DiskfontBase  = NULL;

/*
 * Functions imported from other modules
 */

/* in intuit.c */
extern InitWindowStuff(struct Window *);
extern CleanUpWindow(struct Window *);
extern FileMenu(struct Window *, int, FILE **, FILE **, int *);

/* in dpy.c */
extern SetUpDisplay(struct Window *, int);
extern char Emit(char);
extern NeedQuote(char);

/* in beep.c */
extern InitAudio(int);
extern SetBeeper(int);
extern Beep();
extern CleanUpBeeper();

/*
 * Globals and externals for using the serial port
 */

extern struct MsgPort *CreatePort();
static struct IOExtSer *inreq = NULL, *outreq = NULL;
static char inbuf[2], outbuf[2];

/*
 * MAIN PROGRAM
 */

main() {
   char c;
   int buckied; /* see ToAsc for details */
   int proceed = TRUE, fileFlags = 0; 
   FILE *receive = NULL, *send = NULL;
   struct Window *mywindow;
   struct IntuiMessage *message; /* msg struct for GetMsg */

   /* Bits set in fileFlags */
   #define RAWCAPTURE 1  /* include ctrl chars in captured file */
   #define SENDLFASCR 2  /* translate LFs to CRs when sending */

   /* Emulator window structure */
   static struct NewWindow newWindow = {
      0, 0, 640, 200,
      0,
      1,
      RAWKEY | MENUPICK | GADGETUP | REQCLEAR,
      SMART_REFRESH | ACTIVATE | BORDERLESS,
      NULL, NULL, NULL, NULL, NULL,
      100, 35, 640, 200, /* min/max dims, unused since sizegadget omitted */
      WBENCHSCREEN};

   IntuitionBase = (struct IntuitionBase *)
      OpenLibrary("intuition.library", INTUITION_REV);
   GfxBase = (struct GfxBase *)
      OpenLibrary("graphics.library", GRAPHICS_REV);
   DiskfontBase = (struct DiskfontBase *)
      OpenLibrary("diskfont.library", FONT_REV);
   if (IntuitionBase == NULL || GfxBase == NULL || DiskfontBase == NULL)
      GoAway("Can't open libraries", NULL);

   if ((mywindow = (struct Window *)OpenWindow(&newWindow)) == NULL)
      GoAway("Can't open window", NULL);

   /* Open serial device once and copy the initialised ioreq; */
   /* that way we can have exclusive access with two ports    */

   inreq = (struct IOExtSer *)AllocMem(sizeof(*inreq), MEMF_PUBLIC|MEMF_CLEAR);
   inreq->IOSer.io_Message.mn_ReplyPort = CreatePort("SerialRead", 0);
   if (OpenDevice(SERIALNAME, NULL, inreq, NULL))
      GoAway("Can't open Serial port", mywindow);

   inreq->io_SerFlags = SERF_SHARED | SERF_XDISABLED;
   inreq->IOSer.io_Command = SDCMD_SETPARAMS;
   inreq->io_ReadLen = inreq->io_WriteLen = 8;
   DoIO(inreq);

   inreq->IOSer.io_Length = 1;
   outreq = (struct IOExtSer *)AllocMem(sizeof(*outreq), MEMF_PUBLIC);
   movmem((char *)inreq, (char *)outreq, sizeof(*inreq));
   outreq->IOSer.io_Message.mn_ReplyPort = CreatePort("SerialWrite", 0);

   inreq->IOSer.io_Command = CMD_READ;
   inreq->IOSer.io_Data = (APTR)inbuf;
   outreq->IOSer.io_Command = CMD_WRITE;
   outreq->IOSer.io_Data = (APTR)outbuf;

   if (InitWindowStuff(mywindow)) {
      CloseDevice(inreq);
      CloseDevice(outreq);
      GoAway("Not enough memory", mywindow);
      }

   /* Finish initialisation */
   InitAudio(1);
   SetAPen(mywindow->RPort, 1);
   SetUpDisplay(mywindow, 1);
   SetBaud(1);
   BeginIO(inreq);

   while (proceed) {
      /* wait for something to do */
      if (send == NULL)
         Wait((1 << inreq->IOSer.io_Message.mn_ReplyPort->mp_SigBit)
            | (1 << mywindow->UserPort->mp_SigBit));
      if (CheckIO(inreq)) { /* receive a char from the host */
         WaitIO(inreq);
         c = inbuf[0] & 0x7F;
         BeginIO(inreq);
         if (fileFlags & RAWCAPTURE) Emit(c); else c = Emit(c);
         /* Emit returns hex 80 if char is non-printing */
         if (receive != NULL && c < 0x80) /* capture this character */
            if ((c >= ' ' && c <= '~') || c == '\n' || c == '\t')
               putc(c, receive); /* always capture these chars */
            else if (fileFlags & RAWCAPTURE) {
               if (c == 0x7F) fputs("^?", receive);
               else {putc('^', receive); putc(c+0x40, receive);}
               }
         }
      else if (send != NULL) { /* send file (only if host not talking) */
         if ((c=getc(send)) != EOF)
            Ship(c == '\n' && (fileFlags & SENDLFASCR) ? '\r' : c);
         else {
            fclose(send);
            send = NULL;
            FileMenu(mywindow, 99, &receive, &send, &fileFlags);
            }
         }
      while (message = (struct IntuiMessage *)GetMsg(mywindow->UserPort)) {
         ULONG class;
         USHORT code, qual;
         class = message->Class;
         code = message->Code;
         qual = message->Qualifier;
         ReplyMsg(message);
         switch (class) {
            case RAWKEY:  /* user has touched the keyboard */
               if ((buckied = ToAsc(code, qual)) >= 0) {
                  if (buckied & 0x100) Ship(0x80);
                  else if (NeedQuote(c = buckied & 0x7F)) Ship(0);
                  Ship(buckied & 0xFF);
                  }
               else if (buckied != -99) { /* function key */
                  Ship(0); /* send NUL, number, CR */
                  Ship('0'+((-buckied)/10));
                  Ship('0'+((-buckied)%10));
                  Ship('\r');
                  }
               break;
            case MENUPICK:
               if (code != MENUNULL) switch (MENUNUM(code)) {
                  case 0: FileMenu(mywindow, ITEMNUM(code), &receive, &send, &fileFlags); break;
                  case 1: BaudMenu(code); break;
                  case 2: SetUpDisplay(NULL, ITEMNUM(code)); break;
                  case 3:
                     SetBeeper(ITEMNUM(code));
                     if (Beep()) DisplayBeep(mywindow->WScreen);
                     break;
                  case 4: proceed = WindowMenu(mywindow, code); break;
                  }
               break;
            } /* end of switch (class) */
         } /* end of while (message) */
      } /* end of while (proceed) */

   /* It must be time to quit */

   CloseDevice(inreq);
   CloseDevice(outreq);
   CleanUpWindow(mywindow);
   CleanUpBeeper();
   GoAway(NULL, mywindow); /* exit will close send/receive files if open */
   } /* end of main */

/*
 * Function to clean up however much stuff got started
 */

static GoAway(text, w) char *text; struct Window *w; {
   if (w) CloseWindow(w);
   if (inreq) {
      DeletePort(inreq->IOSer.io_Message.mn_ReplyPort);
      FreeMem(inreq, sizeof(*inreq));
      }
   if (outreq) {
      DeletePort(outreq->IOSer.io_Message.mn_ReplyPort);
      FreeMem(outreq, sizeof(*outreq));
      }
   if (IntuitionBase != NULL) CloseLibrary(IntuitionBase);
   if (GfxBase != NULL) CloseLibrary(GfxBase);
   if (DiskfontBase != NULL) CloseLibrary(DiskfontBase);
   if (text) {printf("ERROR: %s\n", text); exit(100);}
   exit(FALSE);
   }

/*
 * Quickie to ship one char to serial port
 */

static Ship(c) char c; {
   outbuf[0] = c;
   DoIO(outreq);
   }

/*
 * Menu routines (FileMenu is external)
 */

static BaudMenu(code) USHORT code; {
   AbortIO(inreq);
   SetBaud(ITEMNUM(code));
   BeginIO(inreq);
   }

static SetBaud(index) USHORT index; {
   switch (index) {
      case 0: inreq->io_Baud = 300; break;
      case 1: inreq->io_Baud = 1200; break;
      case 2: inreq->io_Baud = 2400; break;
      case 3: inreq->io_Baud = 4800; break;
      case 4: inreq->io_Baud = 9600; break;
      }
   inreq->IOSer.io_Command = SDCMD_SETPARAMS;
   DoIO(inreq);
   inreq->IOSer.io_Command = CMD_READ;
   }

static WindowMenu(w, code) struct Window *w; USHORT code; {
   /* returns TRUE unless window is supposed to close */
   switch (ITEMNUM(code)) {
      case 0: WindowToBack(w); break;
      case 1: WindowToFront(w); break;
      case 2: return(FALSE);
      }
   return(TRUE);
   }

/*
 * Function to convert raw key data into ascii chars
 */

/* The system at Stanford uses 9-bit characters.  The bottom 7 bits are normal
 * ascii, although there are visible characters associated with the ctrl chars.
 * (E.g., ^H prints as lambda, ^U as existential quantifier, ^A as down-arrow.)
 * The 0x80 bit and 0x100 bit are called "bucky bits" (see Hacker's Dictionary)
 * and are typically used to distinguish command chars from regular typein.
 * These bits are set by some extra shift keys on the Stanford keyboards, called
 * CONTROL and META.  When talking to Stanford across an 8-bit serial line,
 * one sends CONTROL in the parity bit and META by a prefixed char (0x80).
 * This emulator interprets ALT as CONTROL, AMIGA as META, and CTRL as the
 * standard-ascii control key.  If you don't need the bucky-bit features, just
 * don't hold down the ALT or AMIGA keys while typing, and you'll be fine.
 * 
 * This function returns the 7-bit ascii code for the given keyboard action,
 * plus bucky-bits in 0x180.  If the keyboard action is a no-op (e.g., key up,
 * or shift key down), it returns -99.  Function keys return -(10+key#), or
 * -key# if shifted.
 */

/* qualifier bits */
#define LSHIFT  (1<<0)
#define RSHIFT  (1<<1)
#define SHLOCK  (1<<2)
#define CTL     (1<<3) /* the real "ctrl" key */
#define LCTRL   (1<<4) /* ALT keys set parity bit */
#define RCTRL   (1<<5)
#define LMETA   (1<<6) /* Amiga keys set 9th bit */
#define RMETA   (1<<7)

static ToAsc(code, qual) USHORT code, qual; {
   char c;
   static char *map = "\
`1234567890-=\\?0qwertyuiop[]?123asdfghjkl;'??456\
?zxcvbnm,./?.789 \b\t\n\r\33\177???-?\13\f";
   if (code >= 0x50 && code <= 0x59) /* function keys */
      return((qual & (LSHIFT|RSHIFT) ? -1 : -11) - (c = code&0xF));
   if (code <= 0x4D) c = map[code];
   else if (code == 0x5F) c = 0; /* HELP key translates into NUL */
   else return(-99);
   if (c == '?') return(-99);
   if (qual & (LSHIFT|RSHIFT)) {
      if (c >= 'a' && c <= 'z') c -= 0x20;
      else if (c >= ',' && c <= '=') {
         code = c - ','; /* sidestep compiler bug re subscript exprs */
         c = "<_>?)!@#$%^&*(::<+"[code];
         }
      else if (c >= '[' && c <= '`') {
         code = c - '['; /* ditto */
         c = "{|}^_~"[code];
         }
      else if (c == '\'') c = '"';
      } /* end shift */
   else if ((qual & SHLOCK) && c >= 'a' && c <= 'z') c -= 0x20;
   if (qual & CTL) c &= 0x1F;
   return(c + (qual & (LCTRL|RCTRL) ? 0x80 : 0)
      + (qual & (LMETA|RMETA) ? 0x100 : 0));
   } /* end of ToAsc */

/*********************************************************************\
* This file has Intuition-related stuff: menus, fonts, etc (intuit.c) *
\*********************************************************************/

#include <exec/types.h>
#include <exec/exec.h>
#include <lattice/stdio.h>
#include <libraries/diskfont.h>
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>

/* Library pointers imported from main.c */
extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase       *GfxBase;
extern struct DiskfontBase  *DiskfontBase;

/*
 * Initialise an IntuiText structure
 */

/* constants for pen colors */
#define FORE0  (0 << 2)
#define FORE1  (1 << 2)
#define FORE2  (2 << 2)
#define FORE3  (3 << 2)
#define BACK0  0
#define BACK1  1
#define BACK2  2
#define BACK3  3

static InitText(intui, text, pens, left, top) struct IntuiText *intui; char *text; SHORT pens, left, top; {
   intui->FrontPen = (pens >> 2) & 3;
   intui->BackPen = pens & 3;
   intui->DrawMode = JAM2;
   intui->LeftEdge = left;
   intui->TopEdge = top;
   intui->ITextFont = NULL;
   intui->NextText = NULL;
   intui->IText = (UBYTE *)text;
   }

/*
 * Menu Creation
 */

/* macro to compute size in bytes for menu with n items */
#define MenuBytes(n)  (sizeof(struct Menu) + (n)*(sizeof(struct MenuItem) + sizeof(struct IntuiText)))

static struct Menu *menuList = NULL;

static AddMenu(title, size, names, init, rightify) int size, init, rightify; char *title, *names[]; {
   struct Menu **menu;
   struct MenuItem *items = NULL;
   struct IntuiText *text = NULL;
   int n, width = 0, itemLeft = 1, menuLeft = 5;
   USHORT flags = ITEMTEXT | ITEMENABLED | HIGHBOX;
   if (init >= 0) {flags |= CHECKIT; itemLeft += CHECKWIDTH;}
   for (menu = &menuList; *menu; menu = &((*menu)->NextMenu)) menuLeft += 90;
   /* menu now points to where the next menu pointer should go */
   *menu = (struct Menu *)AllocMem(MenuBytes(size), MEMF_CHIP);
   if (*menu == NULL) return(TRUE); /* out of memory */
   items = (struct MenuItem *)(sizeof(**menu) + (UBYTE *)*menu);
   text = (struct IntuiText *)(size*sizeof(*items) + (UBYTE *)items);
   InitText(text, (*menu)->MenuName = title, 0, 0, 0); /* just to measure it */
   (*menu)->Width = 8 + IntuiTextLength(text);
   for (n=0; n<size; n++) {
      int thisWidth;
      InitText(text, *names++, FORE0|BACK1, itemLeft, 1);
      thisWidth = IntuiTextLength(text++);
      width = max(width, thisWidth); /* max is macro; mustn't do ++ twice! */
      }
   text -= size;
   width += 2;
   if (init >= 0) width += CHECKWIDTH;
   (*menu)->LeftEdge = rightify ? 630-width : menuLeft;
   (*menu)->FirstItem = items;
   (*menu)->NextMenu = NULL;
   (*menu)->TopEdge = 0;
   (*menu)->Height = 10;
   (*menu)->Flags = MENUENABLED;
   for (n=0; n<size; n++) {
      items->NextItem = items+1;
      items->LeftEdge = 0;
      items->TopEdge = 11 * n;
      items->Width = width;
      items->Height = 10;
      items->Flags = flags;
      items->MutualExclude = (~(1 << n));
      items->ItemFill = (APTR)text++;
      items->SelectFill = NULL;
      items->Command = 0;
      items->SubItem = NULL;
      items->NextSelect = 0;
      items++;
      }
   (--items)->NextItem = NULL;
   items -= (size-1);
   if (init >= 0 && init < size) items[init].Flags |= CHECKED;
   return(FALSE); /* no problems */
   } /* end of AddMenu */

/* Specific menus */

static char *fileStrings[] = {"Start Capture", "Start Send", "End Capture", "Cancel Send"};
static char *baudStrings[] = {" 300", "1200", "2400", "4800", "9600"};
static char *dpyStrings[] = {"Vanilla", "SAIL"};
static char *beepStrings[] = {"Brash", "Clear", "Calm", "Eerie", "Subdued", "Silent"};
static char *wnStrings[] = {"Move to Back", "Move to Front", "Close Window"};

static InitMenus() { /* returns TRUE if unable to create them all */
   return(AddMenu("File", 2, fileStrings, -1, FALSE)
      || AddMenu("Baud", 5, baudStrings, 1, FALSE)
      || AddMenu("Display", 2, dpyStrings, 1, FALSE)
      || AddMenu("Beep", 6, beepStrings, 1, FALSE)
      || AddMenu("Window", 3, wnStrings, -1, TRUE));
   }

/*
 * SAIL Font (weird chars in positions 0x80-0x9F)
 */

static struct TextFont *tf;

static InitSAILFont(window) struct Window *window; {
   struct TextAttr ta;
   ta.ta_Name = "SAIL.font";
   ta.ta_YSize = 8;
   ta.ta_Style = 0;
   ta.ta_Flags = FPF_ROMFONT | FPF_DISKFONT |
      FPF_PROPORTIONAL | FPF_DESIGNED;
   tf = (struct TextFont *)OpenDiskFont(&ta);
   if (tf) SetFont(window->RPort, tf);
   }

InitWindowStuff(window) struct Window *window; {
   if (InitMenus() || MakeRequester()) {FreeMenus(); return(TRUE);}
   InitSAILFont(window);
   SetMenuStrip(window, menuList);
   return(FALSE); /* no problem */
   }

CleanUpWindow(window) struct Window *window; {
   if (tf) CloseFont(tf);
   if (window) ClearMenuStrip(window);
   FreeMenus();
   }

static FreeMenus() {
   struct MenuItem *item;
   while (menuList) {
      int size = 0;
      struct Menu *next = menuList->NextMenu;
      for (item = menuList->FirstItem; item; item = item->NextItem) size++;
      FreeMem(menuList, MenuBytes(size));
      menuList = next;
      }
   }

/*
 * Function to handle file menu commands, usually via a requester
 */

FileMenu(w, item, r, s, flags) struct Window *w; int item, *flags; FILE **r, **s; {
   /* called with item<0 to fix up menu item text to match current state */
   switch (item) {
      case 0: /* capture */
         if (OpenOrCloseFile(w, r, "Capture to", "w", "Include control chars?")) *flags |= 1;
        else *flags &= ~1;
         break;
      case 1: /* send */
         if (OpenOrCloseFile(w, s, "Send", "r", "Convert LFs to CRs?")) *flags |= 2;
         else *flags &= ~2;
         break;
      }
   ((struct IntuiText *)menuList->FirstItem->ItemFill)->IText =
      (UBYTE *)fileStrings[*r == NULL ? 0 : 2];
   ((struct IntuiText *)menuList->FirstItem->NextItem->ItemFill)->IText =
      (UBYTE *)fileStrings[*s == NULL ? 1 : 3];
   }

/* The Requester */

static struct Requester myreq;

#define FNAMEBUFSIZE 60
static UBYTE fnamebuf[FNAMEBUFSIZE];

static struct StringInfo fname = {
   fnamebuf, NULL, 0, FNAMEBUFSIZE, 0, 0, 0, 0, 0, 0, 0, 0, NULL};

/* border for requester */
#define FILREQWIDTH  400
#define FILREQHEIGHT 130
#define TEXTGAP      14
#define TEXTTOP      10+TEXTGAP*2

static SHORT reqPairs[] = {
   4, 2,                             FILREQWIDTH-5, 2,
   FILREQWIDTH-5, FILREQHEIGHT-3,    4, FILREQHEIGHT-3,
   4, 2,                             5, 2,
   5, FILREQHEIGHT-3,                FILREQWIDTH-6, FILREQHEIGHT-3,
   FILREQWIDTH-6, 2 };

static SHORT textPairs[] = {0, TEXTTOP-TEXTGAP-1, 0, TEXTTOP-TEXTGAP-1};
static SHORT errorPairs[] = {0, TEXTTOP-2*TEXTGAP-1, 0, TEXTTOP-2*TEXTGAP-1};

/* border for activation gadgets */
#define OCTLAP      2
#define OCTWIDTH  100 /* tentative, adjusted based on font width */
#define OCTHEIGHT  27 /* likewise adjusted based on font height */
static SHORT gadPairs[] = {
   OCTLAP*4, -OCTLAP,
   OCTWIDTH-OCTLAP*4-1, -OCTLAP,
   OCTWIDTH+OCTLAP*2-1, OCTLAP*2,
   OCTWIDTH+OCTLAP*2-1, OCTHEIGHT-OCTLAP*2-1,
   OCTWIDTH+OCTLAP*2-2, OCTLAP*2,
   OCTWIDTH+OCTLAP*2-2, OCTHEIGHT-OCTLAP*2-1,
   OCTWIDTH+OCTLAP*2-1, OCTHEIGHT-OCTLAP*2-1, 
   OCTWIDTH-OCTLAP*4-1, OCTHEIGHT+OCTLAP-1,
   OCTLAP*4, OCTHEIGHT+OCTLAP-1,
   -OCTLAP*2, OCTHEIGHT-OCTLAP*2-1,
   -OCTLAP*2, OCTLAP*2,
   -OCTLAP*2+1, OCTHEIGHT-OCTLAP*2-1,
   -OCTLAP*2+1, OCTLAP*2,
   -OCTLAP*2, OCTLAP*2,
   OCTLAP*4, -OCTLAP };

/* border and background for yes/no gadget */
static SHORT ynBkgPairs[48];
static SHORT ynBrdPairs[] = {-2,-1, -2,3, 1,3, 1,-1, -1,-1, -1,3, 0,3, 0,-1};

static struct Border reqBorder = {0, 0, 3, 0, JAM1, 9, reqPairs, NULL};
static struct Border textBorder = {0, 0, 3, 2, JAM1, 2, textPairs, &reqBorder};
static struct Border errBorder = {0, 0, 1, 2, JAM1, 2, errorPairs, &textBorder};

static struct Border gadBorder = {0, 0, 3, 2, JAM1, 15, gadPairs, NULL};

static struct Border ynBorder = {0, 0, 1, 0, JAM1, 8, ynBrdPairs, NULL};
static struct Border ynBackground = {0, 0, 0, 2, JAM1, 24, ynBkgPairs, &ynBorder};

static struct IntuiText gadText[4], ynText[2];
static struct IntuiText reqText, errorText, optText;
static char reqTextChars[30];

static struct Gadget gadgets[5] = {
  {&gadgets[1], 20,-20-OCTHEIGHT,OCTWIDTH,OCTHEIGHT,
   GADGHCOMP | GRELBOTTOM, RELVERIFY | ENDGADGET, REQGADGET | BOOLGADGET,
   (APTR)&gadBorder, NULL, &gadText[0], NULL, NULL, TRUE, NULL},
  {&gadgets[2], -20-OCTWIDTH,-20-OCTHEIGHT,OCTWIDTH,OCTHEIGHT,
   GADGHCOMP | GRELRIGHT | GRELBOTTOM, RELVERIFY | ENDGADGET, REQGADGET | BOOLGADGET,
   (APTR)&gadBorder, NULL, &gadText[1], NULL, NULL, FALSE, NULL},
  {&gadgets[3], 0,TEXTTOP+TEXTGAP*3/2,0,0,
   GADGHCOMP, RELVERIFY, REQGADGET | BOOLGADGET,
   (APTR)&ynBackground, NULL, &ynText[0], NULL, NULL, TRUE, (APTR)&gadgets[3]},
  {&gadgets[4], 0,TEXTTOP+TEXTGAP*3/2,0,0,
   GADGHCOMP, RELVERIFY, REQGADGET | BOOLGADGET,
   (APTR)&ynBackground, NULL, &ynText[1], NULL, NULL, FALSE, (APTR)&gadgets[2]},
  {NULL, FILREQWIDTH/5,TEXTTOP,FILREQWIDTH*3/5,12,
   GADGHCOMP, STRINGCENTER, REQGADGET | STRGADGET,
   NULL, NULL, NULL, NULL, (APTR)&fname, NULL, NULL} };

static MakeRequester() {
   int n, width;
   BYTE height;
   SHORT *coord;
   InitRequester(&myreq);
   myreq.LeftEdge = (640-FILREQWIDTH)/2;
   myreq.TopEdge = 8;
   myreq.Width = FILREQWIDTH;
   myreq.Height = FILREQHEIGHT;
   myreq.ReqGadget = gadgets;
   myreq.ReqBorder = &textBorder;
   myreq.ReqText = &reqText;
   myreq.BackFill = 2;
   GetPrefs(&height, 1); /* get height of default font */
   InitText(&ynText[0], "Yes", FORE1|BACK0, 4, 2);
   InitText(&ynText[1], "No", FORE1|BACK0, 4, 2);
   width = IntuiTextLength(&ynText[0]);
   ynText[1].LeftEdge += (width-IntuiTextLength(&ynText[1])) / 2;
   gadgets[2].Width = gadgets[3].Width = width += 8;
   gadgets[2].Height = gadgets[3].Height = height+3;
   coord = ynBkgPairs;
   for (n=0; n<height+3; n++) {
      *coord++ = n&1 ? width-1 : 0;
      *coord++ = n;
      *coord++ = n&1 ? 0 : (width-1);
      *coord++ = n;
      }
   ynBackground.Count = (height+3)*2; /* number of pairs to draw background */
   coord = ynBrdPairs-1;
   for (n=0; n<ynBorder.Count; n++) {
      if (*++coord >= 0) *coord += width;
      if (*++coord >= 0) *coord += height;
      }
   InitText(&gadText[0], "  CONFIRM  ", FORE0|BACK1, 2, height+1);
   InitText(&gadText[1], " FORGET IT ", FORE0|BACK1, 2, height+1);
   InitText(&gadText[2], "           ", FORE0|BACK1, 2, 1);
   InitText(&gadText[3], "           ", FORE0|BACK1, 2, height*2+1);
   gadText[0].NextText = gadText[1].NextText = &gadText[2];
   gadText[2].NextText = &gadText[3];
   gadgets[1].Width = gadgets[0].Width = width = IntuiTextLength(gadText) + 4;
   gadgets[1].Height = gadgets[0].Height = height = height*3 + 2;
   gadgets[1].LeftEdge = -20-width;
   coord = gadPairs-1;
   for (n=0; n<gadBorder.Count; n++) {
      if (*++coord > OCTWIDTH/2) *coord += width-OCTWIDTH;
      if (*++coord > OCTHEIGHT/2) *coord += height-OCTHEIGHT;
      }
   InitText(&reqText, reqTextChars, FORE2|BACK3, 0, TEXTTOP - TEXTGAP);
   reqText.NextText = &optText;
   InitText(&optText, NULL, FORE1|BACK2, 0, gadgets[2].TopEdge + ynText[0].TopEdge);
   InitText(&errorText, " Can't open file; try again ", FORE0|BACK1, 0, TEXTTOP - 2*TEXTGAP);
   errorText.NextText = &reqText;
   width = IntuiTextLength(&errorText);
   errorPairs[0] = errorText.LeftEdge = (FILREQWIDTH-width) / 2;
   errorPairs[2] = errorPairs[0] + width - 1;
   return(FALSE); /* no problems */
   }

static OpenOrCloseFile(w, file, label, mode, option) struct Window *w; FILE **file; char *label, *mode, *option; {
   struct IntuiMessage *message;
   ULONG class;
   struct Gadget *gadg;
   int proceed, k, opt;
   if (*file != NULL) {fclose(*file); *file = NULL; return(NULL);}
   sprintf(reqTextChars, " %s file: ", label);
   k = IntuiTextLength(&reqText);
   textPairs[0] = reqText.LeftEdge = (FILREQWIDTH-k) / 2;
   textPairs[2] = textPairs[0] + k - 1;
   optText.IText = (UBYTE *)option;
   k = IntuiTextLength(&optText);
   opt = gadgets[2].Width + 2; /* distance between Yes gadget and No gadget */
   optText.LeftEdge = (FILREQWIDTH - (k + 3*opt)) / 2;
   gadgets[2].LeftEdge = optText.LeftEdge + k + opt;
   gadgets[3].LeftEdge = gadgets[2].LeftEdge + opt;
   fnamebuf[0] = k = 0;
   myreq.ReqBorder = &textBorder;
   myreq.ReqText = &reqText;
   gadgets[2].Flags &= ~SELECTED;
   gadgets[3].Flags |= SELECTED;
   gadgets[2].Activation |= TOGGLESELECT;
   gadgets[3].Activation &= ~TOGGLESELECT;
   for (;;) {
      if (! Request(&myreq, w)) return(NULL); /* couldn't open requester */
      for (proceed=TRUE; proceed; ) {
         while (message = (struct IntuiMessage *)GetMsg(w->UserPort)) {
            class = message->Class;
            gadg = (struct Gadget *)message->IAddress;
            ReplyMsg(message);
            switch (class) {
               case GADGETUP:
                  if (gadg->Activation & ENDGADGET) {
                     k = gadg->GadgetID;
                     opt = gadgets[2].Flags & SELECTED;
                     }
                  else if (gadg->Activation & TOGGLESELECT) {
                     gadg->Activation &= ~TOGGLESELECT;
                     gadg = (struct Gadget *)gadg->UserData;
                     gadg->Flags &= ~SELECTED;
                     gadg->Activation |= TOGGLESELECT;
                     RefreshGadgets(&gadgets[2], w, &myreq);
                     }
                  break;
               case REQCLEAR: proceed = FALSE; break;
               }
            }
         }
      /* requester came down, now check what we're supposed to do */
      if (!k) return(NULL); /* user said to forget it */
      if (*file = fopen(fnamebuf, mode)) return(opt); /* no problem */
      /* bring requester back, this time with error message added */
      myreq.ReqBorder = &errBorder;
      myreq.ReqText = &errorText;
      }
   }

/****************************************\
* This file controls the display (dpy.c) *
\****************************************/

#include <exec/types.h>
#include <graphics/gfxbase.h>
#include <graphics/gfx.h>
#include <graphics/text.h>
#include <lattice/stdio.h>
#include <intuition/intuitionbase.h>
#include <intuition/intuition.h>

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase       *GfxBase;

extern Beep();

static short x, y, mode, parse, saved_arg, dpytype, autocr;

static struct RastPort *rp;
static struct Window *w;

#define ctrl  0x1F  /* turns letter into ctrl char */
#define INVIS 0x80 /* returned by Emit for non-printed chars */

/* Screen dimensions */

#define LEFT    0
#define RIGHT   LEFT+8*80-1
#define TOP     4 /* overlapped by menu stripe, but so what */
#define BOTTOM  TOP+8*24-1

/* Mode bits */

#define EMPH    (1<<0)  /* emphasis (implemented as alternate background) */
#define BLINK   (1<<1)  /* blinking (implemented as colored text) */
#define INSDEL  (1<<2)  /* insert/delete mode */
#define ROLL    (1<<3)  /* what to do when cursor moves down from last line */

/* Parse-state bits */

#define NORMAL   0
#define QUOTING  1  /* next char is displayed even if special */
#define CURSOR   2  /* next 2 chars specify new cursor loc */
#define CURSOR2  3  /* first cursor-loc char is in saved_arg */

/* Display types */

#define GLASS  0  /* vanilla, handles BS, FF, and BEL specially */
#define SAIL   1  /* DM with blink/bold swapped & 128 possible chars */
#define MAX_TYPE  1

/*
 * Initialisation
 */

SetUpDisplay(window, type) struct Window *window; int type; {
   if (type >= 0 && type <= MAX_TYPE) dpytype = type;
   if (window) {
      Move(rp=((w=window)->RPort), x=LEFT, (y=TOP)+6);
      ClearScreen(rp);
      Cursor();
      }
   parse = NORMAL;
   mode = ROLL;
   autocr = FALSE;
   }

/*
 * Querying re special SAIL chars
 *
 * Certain control chars have special effects when sent to the Stanford host.
 * Since these special effects are being provided by certain function keys,
 * the control chars themselves can be interpreted as ordinary type-in.  To
 * do this, you have to ship Stanford a special prefix char.  This function
 * lets the main loop test whether such a prefix is needed.
 */

NeedQuote(c) char c; {
   if (dpytype == SAIL) switch (c) {
      case ctrl&'C':
      case ctrl&'^':
      case ctrl&'_':
         return(TRUE);
      }
   return(FALSE);
   }

/*
 * The Guts
 *
 * Process one output char; returns char to go into log file (INVIS if none)
 */

static char RealEmit(c) char c; {
   if (autocr) switch (c) { /* suppress explicit CR/LF after wrapping around */
      case ctrl&'M': return(INVIS);
      case ctrl&'J': {autocr = FALSE; return(c);};
      default: autocr = FALSE;
      }
   if (parse == CURSOR || parse == CURSOR2) {
      switch (c) {
         case ctrl&'B':
         case ctrl&'L':
         case ctrl&'Q':
         case ctrl&'R':
         case ctrl&'X':
         case ctrl&'^':
         case ctrl&'_':
            parse = NORMAL; /* new control char overrides cursor command */
            break;
         default:
            if (parse == CURSOR) {
               saved_arg = c^0x60;
               parse = CURSOR2;
               return(INVIS);
               }
            x = min(79, saved_arg&0xFF) * 8 + LEFT;
            y = min(23, (c^0x60)&0xFF) * 8 + TOP;
            parse = NORMAL;
            return(INVIS);
         }
      }
   if ((c >= ' ' && c <= '~') || parse == QUOTING) { /* display this char */
      if (mode & INSDEL && x < RIGHT-8)
         ScrollRaster(rp, -8, 0, x, y, RIGHT, y+7);
      Show(c);
      parse = NORMAL;
      return(c);
      }
   switch (c) {
      case ctrl&'B': /* home cursor */
         if (dpytype == SAIL) {x = LEFT; y = TOP;}
         break;
      case ctrl&'G': /* beep */
         if (Beep()) DisplayBeep(w->WScreen);
         break;
      case ctrl&'H': /* backspace */
         if (! (mode & INSDEL)) x = max(x-8, LEFT);
         else if (x < RIGHT-8) ScrollRaster(rp, 8, 0, x, y, RIGHT, y+7);
         else {Move(rp, x, y+6); ClearEOL(rp);}
         break;
      case ctrl&'I': /* tab */
         x = ((x-LEFT) / 64 + 1) * 64 + LEFT;
         if (x <= RIGHT) return(c);
         x = LEFT;
      case ctrl&'J': /* line feed (or tab overflow) */
         if (! (mode & INSDEL)) {DownLine(); return(c);}
         if (y < BOTTOM-8) ScrollRaster(rp, 0, -8, LEFT, y, RIGHT, BOTTOM);
         else {Move(rp, LEFT, y+6); ClearEOL(rp);}
         break;
      case ctrl&'L': /* clear screen or set cursor */
         if (dpytype == SAIL) parse = CURSOR;
         else {
            Move(rp, x=LEFT, (y=TOP)+6);
            ClearScreen(rp);
            }
         break;
      case ctrl&'M': /* carriage return (or excess tab) */
         x = LEFT;
         break;
      case ctrl&'N': /* turn on bold */
         if (dpytype == SAIL) mode |= EMPH;
         break;
      case ctrl&'O': /* turn on blinking */
         if (dpytype == SAIL) mode |= BLINK;
         break;
      case ctrl&'P': /* turn on insert/delete mode */
         if (dpytype == SAIL) mode |= INSDEL;
         break;
      case ctrl&'W': /* erase to EOL */
         if (dpytype == SAIL) {
            Move(rp, x, y+6);
            ClearEOL(rp);
            }
         break;
      case ctrl&'X': /* cancel modes */
         if (dpytype == SAIL) mode = 0;
         break;
      case ctrl&'Z': /* cursor up */
         if (dpytype != SAIL) break;
         if (! (mode & INSDEL)) y = max(TOP, y-8);
         else if (y < BOTTOM-8) ScrollRaster(rp, 0, 8, LEFT, y, RIGHT, BOTTOM);
         else {Move(rp, LEFT, y+6); ClearEOL(rp);}
         break;
      case ctrl&'[': /* quote next character */
         if (dpytype == SAIL) parse = QUOTING;
         break;
      case ctrl&'\\': /* cursor right */
         if (dpytype != SAIL) break;
         if (mode & INSDEL) {
            if (x < RIGHT-8) ScrollRaster(rp, -8, 0, x, y, RIGHT, y+7);
            else {Move(rp, x, y+6); ClearEOL(rp);}
            }
         else if ((x += 8) > RIGHT) {
            x = LEFT;
            if ((y += 8) > BOTTOM) y = TOP;
            }
         break;
      case ctrl&']': /* turn on roll mode */
         if (dpytype == SAIL) mode |= ROLL;
         break;
      case ctrl&'^': /* master clear */
      case ctrl&'_': /* clear unprotected text */
         if (dpytype != SAIL) break;
         mode &= ~(BLINK | EMPH | INSDEL);
         Move(rp, x=LEFT, (y=TOP)+6);
         ClearScreen(rp);
         break;
      } /* end of switch (c) for control chars */
   return(INVIS);
   } /* end of RealEmit */

/*
 * Various screen-action functions
 */

static Show(c) char c; {
   Move(rp, x, y+6);
   if (c < ' ') c += 0x80; /* chars 00-1F are hidden in 80-9F in the font */
   if (mode & EMPH) SetBPen(rp, 2); /* emphasis by changing background */
   if (mode & BLINK) SetAPen(rp, 3); /* "blink" by changing foreground */
   Text(rp, &c, 1);
   if (mode & EMPH) SetBPen(rp, 0);
   if (mode & BLINK) SetAPen(rp, 1);
   if ((x += 8) > RIGHT) {
      x = LEFT;
      DownLine();
      autocr = TRUE;
      }
   }

static DownLine() {
   if ((y += 8) > BOTTOM) {
      y -= 8;
      if (mode & ROLL) ScrollRaster(rp, 0, 8, LEFT, TOP, RIGHT, BOTTOM);
      else y = TOP;
      }
   }

static Cursor() {
   SetAPen(rp, 3);
   SetDrMd(rp, COMPLEMENT);
   RectFill(rp, x, y, x+7, y+7);
   SetDrMd(rp, JAM2);
   SetAPen(rp, 1);
   }

/*
 * Public function to process one char
 */

char Emit(c) char c; {
   Cursor();
   c = RealEmit(c & 0x7F);
   Cursor();
   return(c);
   }

/*
 * Public function to process a string of chars (not currently used)
 */

Emits(string) char *string; {
   Cursor();
   while (*string) RealEmit(*string++ & 0x7F);
   Cursor();
   }

/***************************************\
* This file controls the audio (beep.c) *
\***************************************/

#include "exec/types.h"
#include "exec/memory.h"
#include "devices/audio.h"

extern struct MsgPort *CreatePort();

/* allocation map: first try for a left chan, else right */
static UBYTE allocationMap[] = {1, 8, 2, 4};

#define REPS    3          /* number of copies of pattern in buffer */
#define BUFSIZE (32*REPS)

static struct IOAudio *ioa = NULL;

InitAudio(loudness) int loudness; {
   int i;
   ioa = (struct IOAudio *)AllocMem(sizeof(*ioa), MEMF_PUBLIC|MEMF_CLEAR);
   if (ioa == NULL) return(TRUE);
   ioa->ioa_Request.io_Message.mn_Node.ln_Pri = 10;
   ioa->ioa_Request.io_Message.mn_ReplyPort = CreatePort("Beeper", 0);
   if (ioa->ioa_Request.io_Message.mn_ReplyPort == NULL) return(Flush(FALSE));
   ioa->ioa_Data = allocationMap;
   ioa->ioa_Length = sizeof(allocationMap);
   if (OpenDevice(AUDIONAME, 0, ioa, 0)) return(Flush(FALSE));
   ioa->ioa_Request.io_Command = CMD_WRITE;
   ioa->ioa_Request.io_Flags = ADIOF_PERVOL;
   ioa->ioa_Data = (UBYTE *)AllocMem(BUFSIZE, MEMF_CHIP);
   if (ioa->ioa_Data == NULL) return(Flush(TRUE));
   for (i=0; i<BUFSIZE; i++) {
      int j;
      j = Sine32nd(i*2) * 4 + Sine32nd(i*3) * 9;
      ioa->ioa_Data[i] = (j > 0 ? j+65 : j-65) / 130;
      }
   ioa->ioa_Length = BUFSIZE;
   /* start a request so we can wait for it later (in Flush) */
   ioa->ioa_Period = ioa->ioa_Cycles = 200;
   ioa->ioa_Volume = 0;
   BeginIO(ioa);
   SetBeeper(loudness);
   return(FALSE);
   }

SetBeeper(loudness) int loudness; {
   if (loudness < 0 || loudness > 4) ioa->ioa_Volume = 0;
   else {
      ioa->ioa_Period = 127*(loudness+2)/2;
      ioa->ioa_Cycles = 480/(loudness+2);
      ioa->ioa_Volume = (60 >> loudness) + 4;
      }
   }

Beep() {
   if ((! ioa) || ioa->ioa_Volume == 0) return(TRUE); /* need video beep */
   BeginIO(ioa);
   return(FALSE);
   }

CleanUpBeeper() {
   if (ioa) Flush(TRUE);
   }

static Flush(opened) int opened; {
   if (opened) {
      WaitIO(ioa);
      if (ioa->ioa_Data) FreeMem(ioa->ioa_Data, BUFSIZE);
      CloseDevice(ioa);
      }
   if (ioa->ioa_Request.io_Message.mn_ReplyPort)
      DeletePort(ioa->ioa_Request.io_Message.mn_ReplyPort);
   FreeMem(ioa, sizeof(*ioa));
   ioa = NULL;
   return(TRUE);
   }

static Sine32nd(n) int n; { /* returns 1270*sin(2PI*n/32), rounded */
   static int qtr[] = {0, 248, 486, 706, 898, 1056, 1173, 1246, 1270};
   if (n < 0) return(-Sine32nd(-n));
   n %= 32;
   switch (n/8) {
      case 0: return(qtr[n]);
      case 1: {n = 16-n; return(qtr[n]);}
      case 2: {n -= 16; return(-qtr[n]);}
      case 3: {n = 32-n; return(-qtr[n]);}
      }
   return(0);
   }