//********************************************************************************
//* File       : AnsiCmdTest.cpp                                                 *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2022-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice below                               *
//* Date       : 06-Apr-2026                                                     *
//* Version    : (see AnsiCmdVersion string in AnsiCmd.cpp)                      *
//*                                                                              *
//* Description: AnsiCmd class test methods. All these methods are under         *
//*              conditional-compilation statements and are not required by      *
//*              the AnsiCmd mainline functionality.                             *
//*              The primary purpose of the test methods is to determine         *
//*              which ANSI commands are supported by the host terminal          *
//*              program.                                                        *
//*                                                                              *
//* Important Note: Some of these tests use some very questionable and           *
//* potentially dangerous constructs that should NEVER be used at the application*
//* level. These tests use those constructs to validate the internal data of the *
//* AnsiCmd, ACWin, ACDlg, skForm, skField classes.                              *
//*              As they say on Jackass: "Don't try this at home!"               *
//*                                                                              *
//* Technical Note: The compiler will not allow self-referenced initialization   *
//* of parameters within an skField array; declaring that the referenced element *
//* is being used 'uninitialized'. This is annoying because the relative         *
//* positions of the fields is a major issue during instantiation.               *
//* For this reason, we have used a work-around, employing an external WinPos    *
//* to track the relative positions. In the real world, this would be considered *
//* questionable because it would result in painful code maintenance.            *
//* Fortunately, within this test code, we live in a world of our own creation:-)*
//*                                                                              *
//********************************************************************************

//*****************
//* Include Files *
//*****************
#include "AnsiCmd.hpp"     //* AnsiCmd-class definition
#include "AnsiCmdWin.hpp"  //* AnsiCmd-class window-object definitions

//** Compilation of the entire module is controlled by this statement. **
#if DEBUG_ANSICMD != 0 && DEBUG_ANSICMD_TEST != 0 && INCLUDE_API  != 0

//***************
//* Definitions *
//***************
#if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
extern ofstream ofsdbg ;
extern gString  gsdbg ;
#endif   // DEBUG_ANSICMD && DEBUG_LOG

//**************
//* Local data *
//**************

//** ANSI commands (escape sequences)             **
//** These are indexed via members of enum aeSeq. **
extern const wchar_t *ansiSeq[aesCOUNT] ;

//* For callback functions, pointer to ACWin object *
static ACWin* cbPtr = NULL ;

//******************
//* Static Methods *
//******************
static void tuiReport ( gString& gs, const void *buff, short cnt, bool wStream ) ;
static void formatRgbString ( gString& gsOut, short colWidth, uint8_t wsrgb ) ;
static bool twDecodeFgBg ( gString& gsIn, short& fgVal, short& bgVal, 
                           bool& isRgbFg, bool& isRgbBg ) ;
static wchar_t tfCallback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;
static wchar_t tseA_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;
static void tmw_Multicolor ( const skField* fptr, acAttr* acArray, short& rowcnt ) ;
static wchar_t tmw_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;
static void tdSidebar ( ACWin* win, short winHgt, short winWid ) ;
static wchar_t tcwCallback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;
static wchar_t thtCallback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;
static wchar_t tleCallback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;
static wchar_t ddzCallback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;


//*************************
//*     ansiTest_Menu     *
//*************************
//********************************************************************************
//* Perform the specified test of terminal functionality. This functionality     *
//* is divided into different groups (see below). There will of course be some   *
//* overlap in the tests, but we try to minimize it to produce clean tests.      *
//*                                                                              *
//* Please refer to the documentation for a fuller description of the individual *
//* tests within each test group.                                                *
//*                                                                              *
//* Major  Minor  Supp   Description                                             *
//* -----  -----  ----   ------------------------------------------------------- *
//*  'C'   f,b,t,        Test color attributes                                   *
//*        r,g,a,                                                                *
//*        d,s                                                                   *
//*  'P'                 Test cursor-positioning commands. The commands may      *
//*                      indicate absolute row or column positions or movement   *
//*                      relative to the current cursor position.                *
//*  'E'                 Test erasure commands. These commands erase existing    *
//*                      text specific areas of the terminal window.             *
//*  'U'                 Test the unbuffered input stream. The wide stream is    *
//*                      assumed.                                                *
//*  'A'                 Test the ASCII control codes and ALT-key combinations.  *
//*  'F'                 Test the alternate-font commands. In addition to the    *
//*                      primary terminal font, up to nine(9) alternate fonts    *
//*                      may be supported.                                       *
//*  'I'                 Test the Ideogram group of commands. It is likely that  *
//*                      these commands are not supported by any mainstream      *
//*                      terminal emulation software.                            *
//*  'T'                 Test terminal setup options and validate the member     *
//*                      variable values that track terminal settings.           *
//*        i             Input stream (stdin)                                    *
//*        e      a,b,c, Input Echo options                                      *
//*               d,e,r,                                                         *
//*               t,n                                                            *
//*        t             Terminal setup                                          *
//*        b,r,          Blocking, non-blocking read of input stream             *
//*        o      a      Output stream (stdout)                                  *
//*        m             Text Modifiers                                          *
//*                                                                              *
//*  'W'   w,f,t,        Window-oriented tests (application-level functionality).*
//*        d,s,m,                                                                *
//*        h,e,b,l,                                                                *
//*        x      a      API clipboard access                                    *
//*               w      Window (local) clipboard interface                      *
//*               t      Temporary-file management                               *
//*               s      ShellOut                                                *
//*               l      Launch external application                             *
//*                                                                              *
//*  'D'                 Dialog-oriented tests (application-level functionality).*
//*        q      1,e,c, QuickDialog group                                       *
//*               v,s,t,                                                         *
//*               p,w,h,                                                         *
//*               r                                                              *
//*        d      a,b,c, Full-initialization dialog group                        *
//*               d,e,f,                                                         *
//*               t,w,h,                                                         *
//*               i,v,u,                                                         *
//*               z                                                              *
//*        0             Test menu                                               *
//*        s             Sidebar test (not related to dialogs                    *
//*                                                                              *
//*  'S'           Sandbox. This command executes user-defined functionality     *
//*                created for ad hoc testing. Code in the sandbox will be       *
//*                affected by the AnsiCmd class initialization. Be aware.       *
//*                For details, see the TermConfig class which is passed to      *
//*                the AnsiCmd constructor.                                      *
//*                                                                              *
//*  '?'           If a parameter contains the '?' character, it indicates       *
//*                that user did not specify that argument. In most cases, this  *
//*                will invoke the associated test menu.                         *
//*                                                                              *
//*                                                                              *
//* Input  : args :                                                              *
//*            major   : specifies the test to perform (see table above)         *
//*            minor   : specifies test sub-section    (see table above)         *
//*            supp    : specifies sub-test for RGB _or_ Echo _or_ dialog tests  *
//*                      (see table above)                                       *
//*            attr    : specifies alternate foreground color attribute          *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Notes:                                                          *
//* 1) These tests have been performed on our favorite terminal programs:        *
//*      Gnometerm v:3.40.3                                                      *
//*      Xterm v:366                                                             *
//*      Konsole v:21.08.3                                                       *
//*    Any notes about available functionality refer to execution on these       *
//*    terminal emulators.                                                       *
//*                                                                              *
//* 2) This method makes some intimate assumptions about the way ANSI escape     *
//*    sequences are constructed and what data they contain. We feel fairly      *
//*    comfortable taking these liberties because the ANSI definitions are       *
//*    well-established and because this is "only" a test method.                *
//*    (People familiar with our work, know there is no such thing as "only".)   *
//*    (The author is a nerd with OCD; but then, aren't we all?!             )   *
//*                                                                              *
//* 3) On Gnometerm v:3.40.3 the "bold-off/dbl-underline" (21) produces a        *
//*    double underline. Also on this terminal the "neither-bold-nor-dim"        *
//*    (aesBOLDE_OFF==22) DOES work to disable bold text.                        *
//*    For this reason, we _optionally_ invoke the "Reset" (aesRESET==0)         *
//*    to switch off bold/dim text.                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

void AnsiCmd::ansiTest_Menu ( dbgArgs& args )
{
   const wchar_t *testOptions = 
           L"     ANSI Command - Test Options\n"
            "-----------------------------------------------\n"
            "'C'  Color: color-attribute tests.\n"
            "'P'  Positioning: cursor-positioning tests.\n"
            "'E'  Erasure: text-erasure (clear-area) tests.\n"
            "'A'  ASCII: control codes and combination keys.\n"
            "'F'  Font: alternate-font selection tests.\n"
            "'I'  Ideogram: ideogram (logogram) group tests.\n"
            "'T'  Terminal: Setup options, data validation.\n"
            "'W'  Window: Application-level functionality.\n"
            "'D'  Dialogs within a window.\n"
            "'S'  Sandbox: Design temporary tests.\n" ;
   const wchar_t *attrOptions = 
           L"     Color Attribute Testing Options:\n"
            "-----------------------------------------------\n"
            "'f'  Four-bit Fgnd/Bkgnd color attributes.\n"
            "'b'  Background color attribute options.\n"
            "'t'  Table lookup, 8-bit color attributes.\n"
            "'r'  R/G/B color-attribute matrix (6x6x6).\n"
            "'g'  Greyscale color attributes.\n"
            "'a'  Aixterm Fg/Bg extensions (non-standard).\n"
            "'d'  Decode tracking data for attribute mods.\n"
            "'s'  Swap acaExpand foreground/background\n" ;
   const wchar_t *termOptions = 
           L"'i'  Input Stream capture methods.\n"
            "'e'  Echo options for key input.\n"
            "'t'  Terminal-setup flags verification.\n"
            "'b'  Non-blocking read from 'stdin'.\n"
            "'r'  Raw (unprocessed) input from 'stdin'.\n"
            "'o'  Output of multi-byte UTF-8 text.\n"
            "'m'  Modifiers, text-mods verification.\n"
            "'f'  Formatted-text output verification.\n" ;
   const wchar_t *echoOptions =
           L"'a' All soft-echo special keys translated.\n"
            "'b' Basic set of special keys translated.\n"
            "'c' Cursor-movement special keys translated.\n"
            "'d' Disable soft-echo special-key translation.\n"
            "'e' Edit keys: Backspace/Del/Insert translated.\n"
            "'r' Report special key translations.\n"
            "'t' Terminal echo of key input.\n"
            "'n' No echo of key input.\n" ;
   const wchar_t *windowOptions =
           L"'w'  ACWin-class (basic test)\n"
            "'f'  skForm-class (all field types)\n"
            "'t'  Text auto-formatting\n"
            "'d'  ACWin-class (default constructor)\n"
            "'s'  Save/Restore window (BlockFifo-class)\n"
            "'m'  Menu fields and menubar group\n"
            "'h'  Highlight field text segment\n"
            "'e'  Examples for docs (screenshots)[2|3|4]\n"
            "'b'  AC_Box-class (draw rectangles)\n"
            "'l'  Line drawing\n"
            "'x'  eXternal interface tests\n" ;
   const wchar_t *externalOptions = 
           L"'a'  API (application) clipboard interface\n"
            "'w'  Window (ACWin class) clipboard interface\n"
            "'t'  Temporary-file management\n"
            "'s'  Shell out to command line\n"
            "'l'  Launch external application\n" ;

   //* Normalize upper/lower case *
   args.major = toupper ( args.major ) ;
   args.minor = tolower ( args.minor ) ;
   args.supp  = tolower ( args.supp ) ;
   args.attr  = tolower ( args.attr ) ;

   //* If alternate foreground attribute specified, *
   //* move it to the 'attr' field.                 *
   //* Note that color attribute is ignored for     *
   //* 'C' (color) and 'S' (sandbox) tests.         *
   aeSeq fg ;           // selected foreground color for test
   if ( args.major != L'?' && (args.major != L'C') && (args.major != L'S') )
   {
      //* If 'attr' not initialized, initialize it  *
      //* to the color selection character (if any).*
      if ( args.attr == L'?' )         // if 'attr' not initialized
      {
         //* 'W'indow Tests *
         if ( args.major == L'W' )
         {
            switch ( args.minor )
            {
               case L'w':     // supplementary argument will be color attribute
               case L'f':
               case L't':
               case L'd':
               case L's':
               case L'm':
               case L'e':
               case L'b':
               case L'l':
                  args.attr = args.supp ;
                  break ;
               case L'h':     // suppementary argument for 'h' (undocumented) modifier
               case L'x':     // supplementary argument for 'x' is the sub-test
               default:
                  break ;
            }
         }
         //* 'T'erminal Tests *
         else if ( args.major == L'T' )
         {
            switch ( args.minor )
            {
               case L'i':     // supplementary argument will be color attribute
               case L't':
               case L'b':
               case L'r':
               case L'o':
               case L'm':
               case L'f':
                  args.attr = args.supp ;
                  break ;
               case L'e':     // 'attr' is already set or is unused
               default:
                  break ;
            }
         }
         //* Other tests have no secondary arguments *
         else
            args.attr = args.minor ;
      }
      //* Decode the color selection *
      switch ( args.attr )
      {
         case L'k':  fg = aesFG_BLACK ;      break ;
         case L'r':  fg = aesFG_RED ;        break ;
         case L'g':  fg = aesFG_GREEN ;      break ;
         case L'n':  fg = aesFG_BROWN ;      break ;
         case L'b':  fg = aesFG_BLUE ;       break ;
         case L'm':  fg = aesFG_MAGENTA ;    break ;
         case L'c':  fg = aesFG_CYAN ;       break ;
         case L'y':  fg = aesFG_GREY ;       break ;
         default:    fg = aesFG_DFLT ;       break ;
      } ;
   }
   //* For decoding test, if unspecified (or invalid) argument, set default.*
   if ( (args.major == L'C') && (args.minor == 'd') && (args.supp != L'b') )
      args.supp = L'a' ;

   //* Set all ANSI attributes and attribute-tracking     *
   //* members to their default values.                   *
   this->acReset () ;

   //* Call the system to clear the terminal window, *
   //* then write the title and test parameters.     *
   // Programmer's Note: We call the system to clear the window because 
   // it is not known at this point whether the terminal supports the 
   // ANSI clear-window command.
   system ( "clear" ) ;
   gString gsOut ;
   //* For tests that use the 'supp' parameter, report it. *
   if ( (args.major == 'C' && ((args.minor == 'r') || (args.minor == 'd'))) || 
        (args.major == 'T' && args.minor == 'e') ||
        (args.major == 'W' && ((args.minor == 'e') || (args.minor == 'x'))) || 
        (args.major == 'S') ||
        (args.major == 'D') )
   {
      gsOut.compose( "Test ANSI Commands: '%C' '%C' '%C'\n" // text formatting
                     "-------------------------------\n\n", 
                     &args.major, &args.minor, &args.supp ) ;
   }
   else
   {
      gsOut.compose( "Test ANSI Commands: '%C' '%C'\n" // text formatting
                     "---------------------------\n\n", 
                     &args.major, &args.minor ) ;
   }
   this->ttyWrite ( gsOut ) ;

   if ( args.major != L'?' )
   {
      if ( args.major == L'C' )                          // Color tests
      {
         if ( args.minor == L'f' )                       // Basic 4-bit Fg/Bg
            this->Test_4Bit () ;

         else if ( args.minor == L'b' )                  // Background
            this->Test_Bg () ;

         else if ( args.minor == L't' )                  // Table lookup
            this->Test_8bitColor () ;

         else if ( args.minor == L'g' )                  // Greyscale
            this->Test_Greyscale () ;

         else if ( args.minor == L'r' )                  // RGB
            this->Test_RGB ( args.supp ) ;

         else if ( args.minor == L'a' )                  // Aixterm
            this->Test_AixFgBg () ;

         else if ( args.minor == 'd' )                   // Decode tracking data
            this->Test_acaExpand ( args.supp ) ;

         else if ( args.minor == 's' )                   // Swap acaExpand fg/bg
            this->Test_acaExpandSwap () ;

         //* Sub-test not specified, or invalid option, display test options.*
         else
         {
            this->ttyWrite ( attrOptions, true ) ;
         }
      }
      else if ( args.major == L'S' )      // Sandbox
      {
         this->Test_Sandbox ( args.minor, args.supp ) ;
      }
      else if ( args.major == L'P' )      // Positioning test
         this->Test_CursorPos ( fg ) ;
      else if ( args.major == L'E' )      // Erasure test
         this->Test_Erasures ( fg ) ;
      else if ( args.major == L'A' )      // ASCII control-code and Alt+n and Ctrl+n test
         this->Test_AsciiCodes ( fg ) ;
      else if ( args.major == L'F' )      // Alternate-font test
         this->Test_AltFonts ( fg ) ;
      else if ( args.major == L'I' )      // Ideogram-group test
         this->Test_Ideogram ( fg ) ;

      else if ( args.major == L'T' )      // Terminal Setup
      {
         if ( args.minor == L'i' )        // input stream test
            this->Test_UnbufferedInput ( fg ) ;
         else if ( args.minor == L'e' )   // input-echo test
         {
            if ( (args.supp == 'a') || (args.supp == 'b') || (args.supp == 'c') || 
                 (args.supp == 'd') || (args.supp == 'e') || (args.supp == 'r') || 
                 (args.supp == 't') || (args.supp == 'n') )
            { this->Test_SoftEcho ( args.supp, fg ) ; }
            else
               this->ttyWrite ( echoOptions ) ;
         }
         else if ( args.minor == L'b' )   // non-blocking read test
            this->Test_NonBlock ( fg ) ;
         else if ( args.minor == L'r' )   // raw input-stream test
            this->Test_RawInput ( fg ) ;
         else if ( args.minor == L't' )   // terminal-flags test
            this->Test_TermFlags ( fg ) ;
         else if ( args.minor == L'o' )   // output test
            this->Test_OutputStream ( fg ) ;
         else if ( args.minor == L'm' )   // mods-flags test
            this->Test_ModFlags ( fg ) ;
         else if ( args.minor == L'f' )   // formatted-text test
            this->Test_Write ( fg ) ;
         else
            this->ttyWrite ( termOptions ) ;
      }

      else if ( args.major == L'W' )      // Window-definition tests
      {
         if ( args.minor == L'w' )
            this->Test_Window ( fg ) ;
         else if ( args.minor == L'f' )
            this->Test_Form ( fg ) ;
         else if ( args.minor == L't' )
            this->Test_Wrap ( fg ) ;
         else if ( args.minor == L'd' )
            this->Test_WinDefault ( fg ) ;
         else if ( args.minor == L's' )
            this->Test_Fifo ( fg ) ;
         else if ( args.minor == L'm' )
            this->Test_Menu ( fg ) ;
         else if ( args.minor == L'h' )
            this->Test_Hilite ( args.supp, fg ) ;
         else if ( args.minor == L'e' )
            this->Test_Cookie ( args.supp, fg ) ;
         else if ( args.minor == L'b' )
            this->Test_Box ( fg ) ;
         else if ( args.minor == L'l' )
            this->Test_Line ( fg ) ;
         else if ( args.minor == L'x' )
         {
            if ( args.supp == L'a' )
               this->Test_Clipboard_API ( fg ) ;
            else if ( args.supp == L'w' )
               this->Test_Clipboard_Win ( fg ) ;
            else if ( args.supp == L't' )
               this->Test_Tfman ( fg ) ;
            else if ( (args.supp == L's') || (args.supp == L'l') )
               this->Test_LaunchExt ( args.supp, fg ) ;
            else
               this->ttyWrite ( externalOptions ) ;
         }
         else
            this->ttyWrite ( windowOptions ) ;
      }

      else if ( args.major == L'D' )      // Dialog-definition tests
      {
         this->Test_Dialog ( args.minor, args.supp  ) ;
      }

      else
      { this->ttyWrite ( testOptions ) ; }
   }
   else
   { this->ttyWrite ( testOptions ) ; }

}  //* End ansiTest_Menu() *

//*************************
//*       Test_4Bit       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test 4-bit foreground and background color sequences.                        *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note: See also the Test_Bg() method which displays the Intense  *
//* (Bold) background attributes in basic, synthesized and AIX configurations.   *
//********************************************************************************

void AnsiCmd::Test_4Bit ( void )
{
   //* Loop control: two groups of 8 color sequences + the default sequence *
   const short loopCNT = 17 ;
   const wchar_t *ColorName[loopCNT] = 
   { 
      L"00) Black", L"01) Red",     L"02) Green", L"03) Brown", 
      L"04) Blue",  L"05) Magenta", L"06) Cyan",  L"07) Grey",
      L"08) Intense Black", L"09) Intense Red", L"10) Intense Green", 
      L"11) Intense Brown", L"12) Intense Blue", L"13) Intense Magenta", 
      L"14) Intense Cyan",  L"15) Intense Grey", L"--) Default"
   } ;

   const wchar_t *DescFg = 
         L"Foreground Colors Test:\n"
          "-----------------------------------------------------------\n"
          "The terminal defines sixteen(16) basic foreground colors:\n"
          "   Eight(8) standard colors, plus\n"
          "   Eight(8) intense (bold) colors. (std + \"intense\" bit).\n"
          "The actual color for each of these is defined in the\n"
          "terminal's setup.\n"
          "For Gnometerm and Konsole this is the \"Profile\".\n"
          "For Xterm, these colors are defined in the \".Xdefaults\"\n"
          "or \".Xresources\" file, but note that under Wayland the\n"
          "file will not be read.\n"
          "\n"
          "Please note that if the foreground and background are the\n"
          "same color, the text will be invisible.\n"
          "See the Intense Grey example which causes the text to be\n"
          "rendered as white. We display it on a grey background to\n"
          "distinguish the text from the (presumed) white background." ;
   const wchar_t *DescBg = 
         L"Background Colors Test:\n"
          "------------------------------------------------------------\n"
          "The terminal defines sixteen(16) basic background colors:\n"
          "   Eight(8) standard colors, plus\n"
          "   Eight(8) intense (bold) colors. (std + \"intense\" bit).\n"
          "The actual color for each of these is defined in the\n"
          "terminal's setup.\n"
          "For Gnometerm and Konsole this is the \"Profile\".\n"
          "For Xterm, these colors are defined in the \".Xdefaults\"\n"
          "or \".Xresources\" file, but note that under Wayland the\n"
          "the file will not be read.\n"
          "\n"
          "Unfortunately, the default intense background colors are not\n"
          "rendered as expected because the \"intense\" bit affects\n"
          "only the foreground attribute. For this reason, we provide\n"
          "synthesized versions of the intense background attributes.\n"
          "See the background-specific test for additional information." ;

   const short baseROW = 3 ;     // base display row
   const short exitROW = this->termRows - 1 ; // cursor row when preparing to exit
   const short descCOL = 60 ;    // offset for description text
   const short dumpCOL = 22 ;    // offset for settings dump

   gString gsOut ;               // text formatting
   short row = baseROW - 1,      // output row
         bgndRow,                // first row for display of background attributes
         maxindx = aesFG_BLACK + loopCNT, // loop termination
         cnindx  = ZERO ;        // 'ColorName' index
   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, "  (requires terminal dimensions of 39 rows X 155 columns)" ) ;

   //* Clear the heading row and display the column headings.*
   this->acSetCursor ( aesCUR_ABSPOS, row, 1 ) ;
   this->acEraseArea ( aesERASE_LINE ) ;
   this->acCaptureAttr ( gsOut, 1, true, true ) ;
   this->acWrite ( row, dumpCOL, gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, ++row, 1 ) ;

   //** Test aesFG_xxxx group **
   for ( short regindx = aesFG_BLACK ; regindx < maxindx ; ++regindx, ++row )
   {
      if ( regindx == aesFGb_GREY )
         this->acSetBg ( aesBG_BLACK ) ;
      this->acSetFg ( aeSeq(regindx) ) ;        // set the foreground color
      this->ttyWrite ( ColorName[cnindx++] ) ; // write the text data

      //* Capture and format the tracking data *
      this->acCaptureAttr ( gsOut, 1, true ) ;

      this->acSetFg ( aesFG_DFLT ) ;            // return to default foreground
      if ( regindx == aesFGb_GREY )
         this->acSetBg ( aesBG_DFLT ) ;
      this->acFlushOut () ;                     // flush the output stream

      //* Report the tracking data *
      this->acSetCursor ( row, dumpCOL ) ;
      this->ttyWrite ( gsOut ) ;
   }
   bgndRow = ++row ;                            // leave some space
   cnindx = ZERO ;                              // reset the array index
   maxindx = aesBG_BLACK + loopCNT ;            // loop termination
   this->acSetCursor ( aesCUR_ABSPOS, row, 1 ) ;

   //** Test aesBG_xxxx group **
   for ( short regindx = aesBG_BLACK ; regindx < maxindx ; ++regindx, ++row )
   {
      //* Make foreground visible for black backgrounds.       *
      //* (This assumes that the default foreground is black.) *
      if ( (regindx == aesBG_BLACK) || (regindx == aesBGb_BLACK) )
         this->acSetFg ( aesFG_GREY ) ;
      this->acSetBg ( aeSeq(regindx), false ) ; // set the background color
      this->ttyWrite ( ColorName[cnindx++] ) ; // write the text data

      //* Capture and format the tracking data *
      this->acCaptureAttr ( gsOut, 1, true ) ;

      this->acSetBg ( aesBG_DFLT ) ;            // return to default foreground
      if ( (regindx == aesBG_BLACK) || (regindx == aesBGb_BLACK) )
         this->acSetFg ( aesFG_DFLT ) ;
      this->acFlushOut () ;                     // flush the output stream

      //* Report the tracking data *
      this->acSetCursor ( row, dumpCOL ) ;
      this->ttyWrite ( gsOut ) ;
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   this->acWrite ( (baseROW - 1), descCOL, DescFg, false ) ;
   this->acWrite ( bgndRow, descCOL, DescBg, true ) ;

   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;

}  //* End Test_4Bit() *

//*************************
//*        Test_Bg        *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test background color sequences. The Intense (Bold) sequences are rendered   *
//* in three flavors:                                                            *
//*   1) Basic 4-bit i.e. table indices 8-15.                                    *
//*   2) Synthesized 4-bit, based on the brightest of the R/G/B combinations     *
//*   3) AIX implementation of Intense background colors.                        *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Bg ( void )
{
   //* Loop control: two groups of 8 color sequences + the default sequence *
   const short loopCNT = 17 ;
   const wchar_t *ColorName[loopCNT] = 
   { L" 00) Black   \n",
     L" 01) Red     \n",
     L" 02) Green   \n",
     L" 03) Brown   \n", 
     L" 04) Blue    \n",
     L" 05) Magenta \n",
     L" 06) Cyan    \n",
     L" 07) Grey    \n",
     L" 08) Intense Black   \n",
     L" 09) Intense Red     \n",
     L" 10) Intense Green   \n", 
     L" 11) Intense Brown   \n",
     L" 12) Intense Blue    \n",
     L" 13) Intense Magenta \n", 
     L" 14) Intense Cyan    \n",
     L" 15) Intense Grey    \n",
     L" --) Default         \n"
   } ;
   const wchar_t *AIX_Colors[] = 
   {
     L" 100) Intense Black Background   \n", L" 101) Intense Red Background     \n", 
     L" 102) Intense Green Background   \n", L" 103) Intense Brown Background   \n", 
     L" 104) Intense Blue Background    \n", L" 105) Intense Magenta Background \n", 
     L" 106) Intense Cyan Background    \n", L" 107) Intense Grey Background    \n",
   } ;
   const wchar_t *Desc_a = 
         L"Background Colors Test:\n"
          "------------------------------------------------------------\n"
          "The terminal defines sixteen(16) basic background colors:\n"
          "   Eight(8) standard colors, plus\n"
          "   Eight(8) intense (bold) colors. (std + \"intense\" bit).\n"
          "The actual color for each of these is defined in the\n"
          "terminal's setup.\n"
          "For Gnometerm and Konsole this is the \"Profile\".\n"
          "For Xterm, these colors are defined in the \".Xdefaults\"\n"
          "or \".Xresources\" file, but note that under Wayland the\n"
          "the file will not be read.\n"
          "\n" ;
   const wchar_t *Desc_b = 
         L"Unfortunately, the default intense background colors are not\n"
          "rendered as expected because the \"intense\" bit affects\n"
          "only the foreground attribute. For this reason, we provide\n"
          "synthesized versions of the intense background attributes\n"
          "which are displayed in the second columm.\n"
          "\n"
          "Displayed in the third column, are the intense background\n"
          "attributes defined as part of the IBM AIX operating system\n"
          "as a means of terminal compatibility between AIXterm and\n"
          "Linux. Although the Aixterm foreground and background color\n"
          "attribute commands are not part of the ANSI standard, they\n"
          "are supported by many Linux-based terminal emulators." ;

   const short baseROW  = 4 ;    // base display row
   const short exitROW  = 28 ;   // cursor row when preparing to exit
   const short descCOL  = 85 ;   // offset for description text
   const short synthCOL = 25 ;   // column for display of synthesized values
   const short aixCOL   = 49 ;   // column for display of AIX values

   gString gsOut ;               // text formatting
   WinPos  wp( baseROW, 1 ) ;    // text position
   short maxindx = aesBG_BLACK + loopCNT, // loop termination
         cnindx  = ZERO ;        // 'ColorName' index

   //** Test aesBG_xxxx group **
   for ( short regindx = aesBG_BLACK ; regindx < maxindx ; ++regindx )
   {
      //* Make foreground visible for black backgrounds.       *
      //* (This assumes that the default foreground is black.) *
      if ( (regindx == aesBG_BLACK) || (regindx == aesBGb_BLACK) )
         this->acSetFg ( aesFG_GREY ) ;
      this->acSetBg ( aeSeq(regindx), false ) ; // set the background color
      wp = this->acWrite ( wp, ColorName[cnindx++], false ) ; // write the text data 
      if ( (regindx == aesBG_BLACK) || (regindx == aesBGb_BLACK) )
         this->acSetFg ( aesFG_DFLT ) ;         // return to default foreground
      this->acFlushOut () ;                     // flush the output stream
   }

   //* Display the synthesized background color attributes *
   short synthRow = baseROW + 7 ;
   cnindx = 8 ;
   wp = { synthRow, synthCOL } ;
   wp = this->acWrite ( wp, L"Synthesized Attributes\n" ) ;
   for ( short regindx = aesBGb_BLACK ; regindx <= aesBGb_GREY ; ++regindx )
   {
      if ( regindx == aesBGb_BLACK )
         this->acSetFg ( aesFG_GREY ) ;
      this->acSetBg ( aeSeq(regindx), true ) ;  // set the background color
      //* Write the text data *
      wp = this->acWrite ( wp, ColorName[cnindx++], false ) ;
      this->acSetBg ( aesFG_DFLT ) ;            // return to default foreground
      if ( regindx == aesBGb_BLACK )
         this->acSetFg ( aesFG_DFLT ) ;
      this->acFlushOut () ;                     // flush the output stream
   }
   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;

   //* Display the AIX intense background color attributes *
   synthRow = baseROW + 7 ;
   cnindx = ZERO ;
   wp = { synthRow, aixCOL } ;
   wp = this->acWrite ( wp, L"AIXterm Background Attributes\n" ) ;

   for ( short regindx = aesAIXBGb_BLACK ; regindx <= aesAIXBGb_GREY ; ++regindx )
   {
      this->acSetBg ( aeSeq(regindx) ) ;        // set the foreground color
      wp = this->acWrite ( wp, AIX_Colors[cnindx++], false ) ;
      this->acSetFg ( aesBG_DFLT ) ;            // return to default foreground
      this->acFlushOut () ;                     // flush the output stream
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   this->acWrite ( baseROW, descCOL, Desc_a ) ;
   this->acWrite ( (baseROW + 12), descCOL, Desc_b ) ;

   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;

}  //* End Test_Bg() *

//*************************
//*    Test_8bitColor     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test fg/bg color selection via the 256-entry color lookup table.             *
//*                                                                              *
//* The target entry is accessed by inserting the index into the ANSI sequence   *
//* templates: ansiSeq[aesFG_INDEX] for foreground and                           *
//*            ansiSeq[aesBG_INDEX] for background.                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_8bitColor ( void )
{  // invoke: C t
   const wchar_t *Desc = 
         L"Eight-bit Color Attributes Test:\n"
         L"------------------------------------------------------\n"
         L"Console color attributes are defined by a standardized\n"
         L"256-element array of attribute registers.\n"
         L"-- The first sixteen(16) registers are reserved for\n"
         L"   the 3-bit and 4-bit color attributes.\n"
         L"-- The next 216 registers define the 8-bit color\n"
         L"   attributes available to console applications.\n"
         L"   These attributes are displayed here for both\n"
         L"   foreground and background.\n"
         L"-- The remaining 24 registers define the available\n"
         L"   greyscale attributes. (see the greyscale test)\n" ;
   const wchar_t *wTemplate = L" %3hhu " ; // formatting template

   const short baseROW = 4 ;
   const short colOFF  = 93 ;

   gString gsOut ;                        // text formattring
   short cnt = ZERO,                      // loop counter
         exitRow = baseROW ;              // cursor row for preparing to exit

   //** Test aesFG_INDEX **
   for ( uint8_t ebIndx = min8BIT ; ebIndx <= max8BIT ; ++ebIndx )
   {
      this->acSet8bitFg ( ebIndx ) ;
      gsOut.compose( wTemplate, &ebIndx ) ;
      this->ttyWrite ( gsOut, false ) ;
      if ( (++cnt % 18) == ZERO )
      {
         this->ttyWrite ( L"\n" ) ;
         ++exitRow ;
      }
   }

   //* Reset to defaut foreground and reset counter *
   this->acSetFg ( aesFG_DFLT ) ;
   this->ttyWrite ( L"\n\n" ) ;
   exitRow += 2 ;
   cnt = ZERO ;

   //** Test aesBG_INDEX **
   for ( short ebIndx = min8BIT ; ebIndx <= max8BIT ; ++ebIndx )
   {
      this->acSet8bitBg ( ebIndx ) ;
      gsOut.compose( wTemplate, &ebIndx ) ;
      this->ttyWrite ( gsOut, false ) ;
      if ( (++cnt % 18) == ZERO )
      {
         this->ttyWrite ( L"\n" ) ;
         ++exitRow ;
      }
   }

   //* Set fg/bg defaults  and flush the output buffer *
   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   //* Write a description of the test *
   this->acWrite ( baseROW, colOFF, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, exitRow, 1 ) ;
   this->acFlushOut () ;            // flush the output stream

}  //* End Test8BitCColor() *

//*************************
//*    Test_Greyscale     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test the terminal's greyscale color attribute support.                       *
//*                                                                              *
//* The target entry is accessed by inserting the index into the ANSI sequence   *
//* templates: ansiSeq[aesFG_INDEX] for foreground and                           *
//*            ansiSeq[aesBG_INDEX] for background.                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Greyscale ( void )
{  // invoke: C g
   const wchar_t *Desc = 
         L"Greyscale Gradient Test:\n"
         L"-----------------------------------------------------------\n"
         L"The last 24 elements of the system color attribute array\n"
         L"(indices 232-255) define the range of greyscale attributes.\n"
         L"These values may be applied to either the foreground text\n"
         L"or to the background.\n"
         L"\n"
         L"Examples:\n"
         L"  acSetGreyscaleFg ( shade )\n"
         L"  acSetGreyscaleBg ( shade )\n"
         L"where: minGSCALE <= shade <= maxGSCALE\n" ;

   const short baseROW = 4 ;
   const short colOFF  = 58 ;
   const short exitROW = 28 ;

   gString gsOut ;                  // text formattring

   //** Test aesFG_INDEX **
   //** Test aesBG_INDEX **
   uint8_t gscIndx = minGSCALE ;
   do
   {
      this->acSetBg ( aesBG_BLUE ) ;            // set contrasting blue background
      this->acSetGreyscaleFg ( gscIndx ) ;      // set greyscale foreground
      gsOut = L"  Greyscale Foreground  " ;     // write some text
      this->ttyWrite ( gsOut, false ) ;

      //* Set blue foreground and default background *
      this->acSetFgBg ( aesFG_BLUE, aesBG_DFLT ) ;

      gsOut.compose( L"  %3hhu  ", &gscIndx ) ; // display the greyscale index
      this->ttyWrite ( gsOut, false ) ;

      this->acSetGreyscaleBg ( gscIndx ) ;      // set greyscale background
      gsOut = L"  Greyscale Background  " ;     // write some text
      this->ttyWrite ( gsOut, false ) ;

      this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ; // set fg/bg defaults
      this->ttyWrite ( gsOut, false ) ;         // write the newline
   }
   while ( gscIndx++ < maxGSCALE ) ;
   // Programmer's Note: The weird loop test is due to the fact that 'gscIndx' 
   // is an 8-bit unsigned integer and could wrap around to zero if we aren't careful.

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ; // set fg/bg defaults
   this->acWrite ( baseROW, colOFF, Desc ) ;    // write the test description

   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;
   this->acFlushOut () ;                        // flush the output stream

}  //* End TestGreyscale() *

//*************************
//*       Test_RGB        *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test color selection via the Red/Green/Blue (RGB) registers.                 *
//*                                                                              *
//* Range for R, G, and B indices:                                               *
//* What the bleep does this mean?                                               *
//* 16-231:  6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b                *
//*          (0 ≤ r, g, b ≤ 5)                                                   *
//*   With a base of 16: 16 <= green <= max.green < min.blue                     *
//*                      max.green < blue <= max.blue < min.red                  *
//*                      max.blue < red <= max.red==231                          *
//* 216 "RGB" registers in a "6x6x6 cube" means 72 registers for each base       *
//* color. If that is true then each 72-register block should be dedicated to    *
//* a color                                                                      *
//*                                                                              *
//* - There appears to be little or no red in 16 through 51.                     *
//* - Red becomes apparent in the purples at index 52-57.                        *
//* - Red tends to dominate in the first 12 indices of each sequential           *
//*   36-register group.                                                         *
//* - The greens seem to be in the central 72 block                              *
//* - This would mean that the 3rd 72-register block s/b dominated by red.       *
//* - With increasing index, the blue transitions to green.                      *
//* - With increasing index, the green transitions to red.                       *
//* - Going above index 231 introduces grey/white into the mix. Most websites    *
//*   that talk about the "6x6x6 cube" explicitly exclue the grey group.         *
//* - What kind of range checking does the system do on the /r/g/b parameters?   *
//*   What substitutions (if any)) are made for out-of-range values?             *
//*   - For "pure" color spectrum, the other two colors may be zero(0).          *
//*     This actually makes no sense because zero is outside the RGB register    *
//*     range of 16-231. While 16 is the logical minimum                         *
//*                                                                              *
//*                                                                              *
//* - This is what we did for the "web-save color chart": hold one value         *
//*   static while exercising the other two values:                              *
//*   - Try holding the green and blue at 16 and cycling the red element         *
//*     through the entire 216-register range.                                   *
//*     Results:                                                                 *
//*   - Try holding the red and blue at 16 and cycling the green element         *
//*     through the entire 216-register range.                                   *
//*     Results:                                                                 *
//*   - Try holding the red and green at 16 and cycling the blue element         *
//*     through the entire 216-register range.                                   *
//*     Results:                                                                 *
//* - Hold two of the three colors static and cycle the 3rd through 216 colors   *
//*   - 16 <= red <= 231   g==16  blue==16                                       *
//*                                                                              *
//*   - r==16  16 <= green <= 231   b==16,                                       *
//*     Approximately the first 20 are saturated black, with green nocicable     *
//*     starting at approximately 36.                                            *
//*     Groups are relatively indistinguishable, so it makes sense to skip       *
//*     x of every y. What are x and y? Groups of 6 are most logical/likely.     *
//*     This would leave 36 shades of green, with the first 3 or 4 tending       *
//*     to be saturated to black.                                                *
//*     - Try setting the static colors to 231 instead of 16 to see what         *
//*       happens.                                                               *
//*     - Try setting the static colors to 255 instead of 16 to see what         * 
//*       happens.                                                               *
//*                                                                              *
//*   - r==16     g==16    16 <= blue <= 231                                     *
//*     Approximately the first 36 are saturated black, with blue nocicable      *
//*     starting at approximately 52.                                            *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  : rgbTest : indicates sub-test:                                       *
//*                    'c' == Step through the 6x6x6 matric of color             *
//*                           combinations. (default)                            *
//*                    'r' == Step Red, Green and Blue static at min/med/max     *
//*                    'g' == Step Green, Red and Blue static at min/med/max     *
//*                    'b' == Step Blue, Red and Green static at min/med/max     *
//*                    'd' == output diagonal (all three colors step forward)    *
//*                                                                              *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_RGB ( wchar_t rgbTest )
{  // invoke: C r [r | g | b | y | w]
   const wchar_t *rgbOptions = 
           L"     RGB Test Options:\n"
           L"---------------------------------------------------\n"
           L"'r'  Red  : Display full range of red colors.\n"
           L"'g'  Green: Display full range of green colors.\n"
           L"'b'  Blue : Display full range of blue colors.\n"
           L"'y'  greY : Display full range of greyscale colors.\n"
           L"'w'  Web  : Display \"web-safe\" colors.\n" ;

   const short baseROW = 1 ;
   const short colOFF  = 32 ;

   gString gsOut ;                  // text formattring
   bool    menu = false ;           // if 'true', display sub-menu

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( baseROW, colOFF, 
                  "  (requires terminal dimensions of 39 rows X 155 columns)\n"
                  "---------------------------------------------------------" ) ; 

   //* Set white foreground so text in the tests will be visible.*
   this->acSetFg ( aesFGb_GREY ) ;

   if ( rgbTest == 'r' )
      this->trgbStepRed () ;
   else if ( rgbTest == 'g' )
      this->trgbStepGreen () ;
   else if ( rgbTest == 'b' )
      this->trgbStepBlue () ;
   else if ( rgbTest == 'y' )
      this->trgbStepGrey () ;
   else if ( rgbTest == 'w' )
   {
      this->acEraseArea ( aesERASE_LINE ) ;
      this->trgbStepWeb () ;
   }
   else
      menu = true ;

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ; // set fg/bg defaults
   if ( menu )                            // if invalid sub-option specified
   {
      this->acSetCursor ( aesCUR_NEXTROW ) ;
      this->ttyWrite ( rgbOptions ) ;
   }
   this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 1, 1 ) ;
   this->acFlushOut () ;                  // flush the output stream

}  //* End Test_RGB() *

//*************************
//*      trgbStepRed      *
//*************************
void AnsiCmd::trgbStepRed ( void )
{  // invoke: C r r
   const wchar_t *Desc = 
         L"Full-spectrum,\n"
         L"8-bit Red Test:\n"
         L"-------------------------\n"
         L"There are 216 values in\n"
         L"the console lookup table\n"
         L"which are reserved for\n"
         L"8-bit color attributes.\n"
         L"This test displays the\n"
         L"full range of Red\n"
         L"color attributes using\n"
         L"minimum values of both\n"
         L"minRGB(16) and ZERO(0).\n" ;

   const short baseROW = 3 ;        // base row for data display
   const short baseCOL = 2 ;        // base column for data display
   const short colOFF  = 130 ;      // column for description text
   const short exitROW = this->termRows - 1 ; // row for exit

   gString gsOut ;
   WinPos wp( baseROW, baseCOL ) ;
   uint8_t r = minRGB,
           g = minRGB,
           b = minRGB ;
   this->acSetCursor ( wp ) ;       // set initial cursor position

   for ( short tst = ZERO ; tst < 2 ; ++tst )
   {
      for ( short i = minRGB ; i <= maxRGB ; ++i )
      {
         #if 1    // Production: Write as background attributes.
         this->acSetRgbBg ( r, g, b ) ;
         #else    // Debugging only: Write as foreground attributes.
         this->acSetRgbFg ( r, g, b ) ;
         #endif   // Bg vs. Fg
         gsOut.compose( " %hhu;%hhu;%hhu ", &r, &g, &b ) ;
         if ( r >= 88 )
            gsOut.padCols( g == minRGB ? 11 : 9 ) ;
         gsOut.append( L'\n' ) ;          // cursor ends at column 1 of next row

         // Programmer's Note: We use the ttyWrite() + acSetCursor() sequence 
         // for speed because it avoids the multiple set-cursor and get-cursor 
         // operations used within the acWrite() method group.
         this->ttyWrite ( gsOut ) ;
         this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column

         ++r ;
         if ( ++wp.row > 38 )             // move to top of next column
         {
            wp = { baseROW, short(wp.col + gsOut.gscols() + 1) } ;
            this->acSetCursor ( wp ) ;
         }
      }

      r = minRGB ;
      //g = b = minRGB ;     // experimental - static colors set to base RGB
      //g = b = maxRGB ;     // experimental - static colors set to maximum (brightest)
      g = b = ZERO ;          // static colors set to minimum
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   this->acWrite ( baseROW, colOFF, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;

}  //* End trgbStepRed() *

//*************************
//*     trgbStepGreen     *
//*************************
void AnsiCmd::trgbStepGreen ( void )
{  // invoke: C r g
   const wchar_t *Desc = 
         L"Full-spectrum,\n"
         L"8-bit Green Test:\n"
         L"-------------------------\n"
         L"There are 216 values in\n"
         L"the console lookup table\n"
         L"which are reserved for\n"
         L"8-bit color attributes.\n"
         L"This test displays the\n"
         L"full range of Green\n"
         L"color attributes using\n"
         L"minimum values of both\n"
         L"minRGB(16) and ZERO(0).\n" ;

   const short baseROW = 3 ;        // base row for data display
   const short baseCOL = 2 ;        // base column for data display
   const short colOFF  = 130 ;      // column for description text
   const short exitROW = this->termRows - 1 ; // row for exit

   gString gsOut ;
   WinPos wp( baseROW, baseCOL ) ;
   uint8_t r = minRGB,
           g = minRGB,
           b = minRGB ;
   this->acSetCursor ( wp ) ;       // set initial cursor position

   for ( short tst = ZERO ; tst < 2 ; ++tst )
   {
      for ( short i = minRGB ; i <= maxRGB ; ++i )
      {
         #if 1    // Production: Write as background attributes.
         this->acSetRgbBg ( r, g, b ) ;
         #else    // Debugging only: Write as foreground attributes.
         this->acSetRgbFg ( r, g, b ) ;
         #endif   // Bg vs. Fg
         gsOut.compose( " %hhu;%hhu;%hhu ", &r, &g, &b ) ;
         if ( g >= 88 )
            gsOut.padCols( r == minRGB ? 11 : 9 ) ;
         gsOut.append( L'\n' ) ;          // cursor ends at column 1 of next row

         // Programmer's Note: We use the ttyWrite() + acSetCursor() sequence 
         // for speed because it avoids the multiple set-cursor and get-cursor 
         // operations used within the acWrite() method group.
         this->ttyWrite ( gsOut ) ;
         this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column

         ++g ;
         if ( ++wp.row > 38 )             // move to top of next column
         {
            wp = { baseROW, short(wp.col + gsOut.gscols() + 1) } ;
            this->acSetCursor ( wp ) ;
         }
      }

      g = minRGB ;
      //r = b = minRGB ;     // experimental - static colorse set to base RGB
      //r = b = maxRGB ;     // experimental - static colors set to maximum (brightest)
      r = b = ZERO ;          // static colors set to minimum
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   this->acWrite ( baseROW, colOFF, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;

}  //* End trgbStepGreen() *

//*************************
//*     trgbStepBlue      *
//*************************
void AnsiCmd::trgbStepBlue ( void )
{  // invoke: C r b
   const wchar_t *Desc = 
         L"Full-spectrum,\n"
         L"8-bit Blue Test:\n"
         L"-------------------------\n"
         L"There are 216 values in\n"
         L"the console lookup table\n"
         L"which are reserved for\n"
         L"8-bit color attributes.\n"
         L"This test displays the\n"
         L"full range of Blue\n"
         L"color attributes using\n"
         L"minimum values of both\n"
         L"minRGB(16) and ZERO(0).\n" ;

   const short baseROW = 3 ;        // base row for data display
   const short baseCOL = 2 ;        // base column for data display
   const short colOFF  = 130 ;      // column for description text
   const short exitROW = this->termRows - 1 ; // row for exit

   gString gsOut ;
   WinPos wp( baseROW, baseCOL ) ;
   uint8_t r = minRGB,
           g = minRGB,
           b = minRGB ;
   this->acSetCursor ( wp ) ;       // set initial cursor position

   for ( short tst = ZERO ; tst < 2 ; ++tst )
   {
      for ( short i = minRGB ; i <= maxRGB ; ++i )
      {
         #if 1    // Production: Write as background attributes.
         this->acSetRgbBg ( r, g, b ) ;
         #else    // Debugging : Write as foreground attributes.
         this->acSetRgbFg ( r, g, b ) ;
         #endif   // Bg vs. Fg
         gsOut.compose( " %hhu;%hhu;%hhu ", &r, &g, &b ) ;
         if ( b >= 88 )
            gsOut.padCols( r == minRGB ? 11 : 9 ) ;
         gsOut.append( L'\n' ) ;          // cursor ends at column 1 of next row

         // Programmer's Note: We use the ttyWrite() + acSetCursor() sequence 
         // for speed because it avoids the multiple set-cursor and get-cursor 
         // operations used within the acWrite() method group.
         this->ttyWrite ( gsOut ) ;
         this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column

         ++b ;
         if ( ++wp.row > 38 )             // move to top of next column
         {
            wp = { baseROW, short(wp.col + gsOut.gscols() + 1) } ;
            this->acSetCursor ( wp ) ;
         }
      }

      b = minRGB ;
      //r = g = minRGB ;     // experimental - static colors set to base RGB
      //r = g = maxRGB ;     // experimental - static colors set to maximum (brightest)
      r = g = ZERO ;       // static colors set to minimum
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   this->acWrite ( baseROW, colOFF, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;

}  //* End trgbStepBlue() *

//*************************
//*     trgbStepGrey     *
//*************************
void AnsiCmd::trgbStepGrey ( void )
{  // invoke: C r y
   #define REPORT_2_MAX (0)   // continue output to top of 256-element array

   const wchar_t *Desc = 
         L"R/G/B Greyscale Test:\n"
         L"--------------------------------------------------\n"
         L"Each of the primary colors, Red/Green/Blue may be \n"
         L"displayed in 216 shades (see other RGB tests).\n"
         L"If the Red, Green and Blue indices step through\n"
         L"the range of elements in unison, a map of 216\n"
         L"greyscale shades is generated.\n"
         L"Note: This is separate from the 24 table elements\n"
         L"reserved specifically for greyscale attributes.\n" ;

   const short baseROW = 3 ;        // base row for data display
   const short baseCOL = 2 ;        // base column for data display
   const short colOFF  = 93 ;       // column for description text
   const short exitROW = this->termRows - 1 ; // row for exit

   gString gsOut ;
   WinPos wp( baseROW, baseCOL ) ;
   uint8_t r = minRGB,
           g = minRGB,
           b = minRGB ;
   this->acSetCursor ( wp ) ;       // set initial cursor position

   #if REPORT_2_MAX != 0   // Terminate at top of register array
   for ( short i = minRGB ; i <= maxLOOKUP ; ++i )
   #else    // Terminate at top of RGB range
   for ( short i = minRGB ; i <= maxRGB ; ++i )
   #endif   // REPORT_2_MAX
   {
      #if 1    // Production: Write as background attributes.
      this->acSetRgbBg ( r, g, b ) ;
      #else    // Debugging only: Write as foreground attributes.
      this->acSetRgbFg ( r, g, b ) ;
      #endif   // Bg vs. Fg
      gsOut.compose( " %hhu;%hhu;%hhu", &r, &g, &b ) ;
      gsOut.padCols( 13 ) ;
      if ( r > maxRGB )
         gsOut.insert( L'|' ) ;
      gsOut.append( L'\n' ) ;          // cursor ends at column 1 of next row

      // Programmer's Note: We use the ttyWrite() + acSetCursor() sequence 
      // for speed because it avoids the multiple set-cursor and get-cursor 
      // operations used within the acWrite() method group.
      this->ttyWrite ( gsOut ) ;
      this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column

      ++r ;
      ++g ;
      ++b ;
      if ( ++wp.row > 38 )
      {
         wp = { baseROW, short(wp.col + gsOut.gscols() + 1) } ;
         this->acSetCursor ( wp ) ;
         if ( r == 160 )
            this->acSetFg ( aesFG_BLACK ) ;
         #if REPORT_2_MAX != 0
         else if ( r == (maxRGB + 1) )
         { this->acWrite ( (baseROW + 11), col, L"Greyscale Registers\n" ) ; }
         #endif   // REPORT_2_MAX
      }
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   //* Write a description of the test *
   this->acWrite ( baseROW, colOFF, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;

}  //* End trgbStepGrey() *

//*************************
//*      trgbStepWeb      *
//*************************
//********************************************************************************
//* Display the RGB color block (minRGB through maxRGB).                         *
//*                                                                              *
//* Display the table of R/G/B color combinations referencing the "web-safe"     *
//* color attributes (one of every six values). This corresponds to the          *
//* "web-safe" HTML/CSS color attributes.                                        *
//* The HTML/CSS values use eight(8) bits for each color to create a 24-bit      *
//* color value, but only one sixth (1/6) of them are considered as "web safe".  *
//* The theory is that all systems and web browsers can accurately render        *
//* these colors. Shades in between may need to be "dithered".                   *
//*                                                                              *
//*                ANSI                                                          *
//*  HTML/CSS   HEX    DEC                                                       *
//*  --------  ------  ---                                                       *
//*   000000   0x0010   16                                                       *
//*   000033   0x0020   22                                                       *
//*   000066   0x0030   28                                                       *
//*   000099   0x0040   34                                                       *
//*   0000CC   0x0050   40                                                       *
//*   0000FF   0x0060   46                                                       *
//*    ...      ...                                                              *
//*   FFFFFF   0x00E7  231                                                       *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::trgbStepWeb ( void )
{  // invoke: C r w
   //* For debugging only. Set to non-zero to  *
   //* render foreground instead of background.*
   #define DEBUG_RGB_WEB (0)

   const wchar_t *Desc = 
         L"R/G/B Web-Safe-Colors Test:\n"
         L"Although each of the primary colors, Red/Green/Blue\n"
         L"may be displayed in 216 shades (see other RGB tests),\n"
         L"many shades are indistinguishable to the eye or are not\n"
         L"rendered uniquely by the software/hardware.\n"
         L"For this reason, the so-called \"web-safe\" color pallete\n"
         L"has been defined to display every sixth shade in a uniform\n"
         L"way across a wide variety of software systems and video\n"
         L"hardware.\n"
         L"\n"
         L"The RGB register indices displayed here represent\n"
         L"respectively the Red, Green and Blue indices for each\n"
         L"of these \"web-safe\" colors.\n" ;
   const short baseROW   = 2 ;      // base row for data display
   const short baseCOL   = 2 ;      // base column for data display
   const short totHUES   = 7 ;      // total number of hues to display

   gString gsOut ;                  // text formatting
   WinPos  wp( baseROW, baseCOL ) ; // text position
   short colWidth = 11 ;            // width of display column
   WebSafeRGB hue ;                 // target color group
   uint8_t shade,                   // color intensity
           webIndex = wsrgbRED ;    // RGB color index

   //* For each color column to be displayed *
   for ( short ihues = ZERO ; ihues < totHUES ; ++ihues )
   {
      //* Initialize values for this iteration *
      wp.row = baseROW ;      // top row of display
      shade = ZERO ;          // minimum intensity
      if ( ihues > ZERO )     // advance start column for display
         wp.col += gsOut.gscols() + 1 ;
      this->acSetCursor ( wp ) ;

      switch ( ihues )
      {
         case 0: hue = wsrgbRED ;      break ;  // Red
         case 1: hue = wsrgbGREEN ;    break ;  // Green
         case 2: hue = wsrgbBLUE ;     break ;  // Blue
         case 3: hue = wsrgbBROWN ; ++colWidth ; break ; // Brown
         case 4: hue = wsrgbMAGENTA ;  break ;  // Magenta
         case 5: hue = wsrgbCYAN ;     break ;  // Cyan
         case 6: hue = wsrgbGREY ;  ++colWidth ; break ;  // Grey
      } ;

      //* Special Case: Black attribute *
      //* Production: Set background attributes.*
      #if DEBUG_ANSICMD == 0 || DEBUG_RGB_WEB == 0
      this->acSetWebBg ( hue, shade ) ;   // set background attribute
      #else    // Set foreground attributes (for debugging only)
      this->acSetWebFg ( hue, shade ) ;
      #endif   // DEBUG_RGB_WEB
      formatRgbString ( gsOut, colWidth, wsrgbBLACK ) ;
      this->ttyWrite ( gsOut ) ;
      this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column

      //* For each shade of the current hue *
      for ( ; shade <= wsrgbSHADE_MAX ; ++shade )
      {
         //* Production: Set background attributes.*
         #if DEBUG_ANSICMD == 0 || DEBUG_RGB_WEB == 0
         this->acSetWebBg ( hue, shade ) ;   // set background attribute
         #else    // Set foreground attributes (for debugging only)
         this->acSetWebFg ( hue, shade ) ;   // set foreground attribute
         #endif   // DEBUG_RGB_WEB
         //* Format the display text *
         formatRgbString ( gsOut, colWidth, webIndex++ ) ;

         // Programmer's Note: We use the ttyWrite() + acSetCursor() sequence 
         // for speed because it avoids the multiple set-cursor and get-cursor 
         // operations used within the acWrite() method group.
         this->ttyWrite ( gsOut ) ;
         this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column
      }
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   wp = { baseROW, short(wp.col + gsOut.gscols() + 3) } ;
   this->acWrite ( wp, Desc ) ;

   #undef DEBUG_RGB_WEB
}  //* End trgbStepWeb() *

//*************************
//*     Test_AltFonts      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test the terminal's support for alternate fonts.                             *
//*                                                                              *
//* Most terminal programs support few if any alternate fonts; however,          *
//* see acSetMod(aesITALIC) to set an italic font.                               *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_AltFonts ( aeSeq fg )
{  // invoke: F
   const wchar_t *Desc = 
         L"Alternate Fonts Test:\n"
          "-------------------------------------------------------\n"
          "The ANSI standard (ANSI X3.64 (ISO/IEC6429)) provides\n"
          "commands to select among ten(10) fonts for use by the\n"
          "terminal. These are the \"Primary\" font and nine\n"
          "\"Alternate\" fonts.\n"
          "For Gnometerm and Konsole the Primary font is specified\n"
          "in the \"Profile\". For Xterm, the Primary font is\n"
          "defined in the \".Xdefaults\" or \".Xresources\" file,\n"
          "but note that under Wayland the file will not be read.\n"
          "\n"
          "When dinosaurs and UNIX geeks roamed the earth and\n"
          "physical terminals were still common, on-the-fly font\n"
          "selection was useful. However, terminal emulation\n"
          "software may not support selection of alternate fonts.\n" ;
   const short baseROW = 4 ;
   const short baseCOL = 1 ;
   const short colOFF  = 48 ;

   gString gsOut ;                  // text formatting

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Write a description of the test *
   this->acWrite ( baseROW, colOFF, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, baseROW, baseCOL ) ;

   //** Test aesPRIMARY_FONT **
   this->acSetFont ( aesPRIMARY_FONT ) ;     // Set primary font
   gsOut = L" Primary Terminal Font. (#00) \n\n" ;
   this->ttyWrite ( gsOut ) ;

   //** Test aesALTERNATE_FONT **
   for ( uint8_t indx = 1 ; indx <= 9 ; ++indx )
   {
      this->acSetFont ( aesALTERNATE_FONT, indx ) ;
      gsOut.compose( " Alternate Font #%hhu\n\n", &indx ) ;
      this->ttyWrite ( gsOut ) ;
   }

   this->acSetFont ( aesPRIMARY_FONT ) ;     // Reset to primary font
   this->acSetFg ( aesFG_DFLT ) ;            // Reset to terminal default foreground color
   this->acFlushOut () ;                     // flush the output stream

}  //* Test_AltFonts() *

//*************************
//*    Test_CursorPos     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test the ANSI escape sequences used for cursor-positioning.                  *
//*                                                                              *
//* We also call acGetTermDimensions() method and report the number of rows      *
//* and columns in the terminal window.                                          *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* -- See note in module header regarding 1-based vs. 0-based cursor            *
//*    positioning.                                                              *
//* -- Using the 'ZERO' argument with aesCUR_ABSCOL works correctly even         *
//*    though the position of the left edge may be interpreted as one-based      *
//*    by some or all of the cursor commands.                                    *
//* -- Using the 'ZERO' arguments with aesCUR_ABSPOS is accepted although the    *
//*    the base row/column position is interpreted as one-based by some or all   *
//*    of the cursor commands AND the 0th row and 1st row are interpreted as     *
//*    THE SAME ROW.                                                             *
//* -- Save and Restore commands come in two flavors, DEC and SCO.               *
//*    For Gnometerm (v:3.40.3), Konsole (v:21.08.3) and Xterm (v:366),          *
//*    the DEC commands appear to be non-functional, while the SCO commands      *
//*    work as expected.                                                         *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

void AnsiCmd::Test_CursorPos ( aeSeq fg )
{  // invoke: P
   const uint16_t tsec = 7 ;        // pause time 0.7 second
   const short msgRow = 4,          // Test-message position
               msgCol = 1,
               cpROW  = 1,          // report cursor position here
               cpCOL  = this->termCols - 20 ;

   gString gsOut,                   // text formatting
           gsErase,                 // for erasing text from message line
           gsCur ;                  // special character for display of cursor position
   WinPos  wpMsg( msgRow, msgCol ),  // message position
           wp ;                     // receives/sets cursor position
   short targetRow,                 // target row
         targetCol ;                // target column

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Construct the special character sequence for     *
   //* character written to the current cursor position.*
   gsCur.compose( "%S$\b%S", 
                  ansiSeq[fg == aesFG_GREEN ? aesFG_RED : aesFG_GREEN],
                  ansiSeq[fg] ) ;

   gsErase.padCols( this->termCols ) ; // construct erasure string

   //* Set terminal configuration for unbuffered input (no echo) *
   this->acBufferedInput ( false, noEcho ) ;

   //* Report the current terminal dimensions *
   WinSize ws = this->acGetTermSize () ;
   gsOut.compose( "term rows: %hu\n"
                  "term cols: %hu\n",
          &ws.ws_row, &ws.ws_col, &ws.ws_xpixel, &ws.ws_ypixel ) ;
   this->acWrite ( (cpROW + 1), cpCOL, gsOut ) ;

   //* Display instructions *
   gsOut = L"Cursor should be at \"home\" position (upper left corner). "
           L"Press any key to continue..." ;
   this->acWrite ( msgRow, msgCol, gsOut ) ;

   //** Test aesCUR_HOME
   //** Test aesCUR_REPORT - (see Test_ReportCurPos())
   this->acSetCursor ( aesCUR_HOME ) ;             // set cursor at home (0,0 or 1,1)
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->acRead () ;                               // get user response
   gsOut.compose( L"%ST%S", ansiSeq[aesFG_DFLT], ansiSeq[fg] ) ;
   this->ttyWrite ( gsOut ) ;                      // restore the title

   //** Test aesCUR_ABSPOS
   targetRow = 11 ; targetCol = 40 ;
   this->acWrite ( wpMsg, gsErase ) ;              // clear the message area
   gsOut.compose( L"Set cursor to an absolute position %hd,%hd. "
                  L"Press any key to continue...",
                  &targetRow, &targetCol ) ;
   this->acWrite ( wpMsg, gsOut ) ;
   //* Set cursor to target position *
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ; // set absolute row,col
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->acRead () ;                               // get user input
   //* Erase the message line and write a new message *
   this->acWrite ( wpMsg, gsErase ) ;              // clear the message area
   gsOut = L"Moving relative to base position, right, left, down and up." ;
   this->acWrite ( wpMsg, gsOut ) ;
   //* Return to target position *
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ; // set absolute row,col
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position

   //** Test aesCUR_COLSRT
   //** Test aesCUR_COLSLT
   //** Test aesCUR_ROWSDN
   //** Test aesCUR_ROWSUP
   this->acSetCursor ( aesCUR_COLSRT, 3 ) ;        // move right relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment
   this->acSetCursor ( aesCUR_ROWSDN, 2 ) ;        // move down relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment
   this->acSetCursor ( aesCUR_COLSLT, 3 ) ;        // move left relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment
   this->acSetCursor ( aesCUR_COLSLT, 3 ) ;        // move left relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment
   this->acSetCursor ( aesCUR_ROWSUP, 2 ) ;        // move up relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment
   this->acSetCursor ( aesCUR_ROWSUP, 2 ) ;        // move up relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment
   this->acSetCursor ( aesCUR_COLSRT, 3 ) ;        // move right relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment
   this->acSetCursor ( aesCUR_COLSRT, 3 ) ;        // move right relative
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment

   //* Prompt for user response *
   this->acSetCursor ( aesCUR_ABSPOS, msgRow, msgCol ) ; // set absolute row,col
   gsOut.append( L" DONE - Press any key to continue..." ) ;
   this->ttyWrite ( gsOut ) ;
   this->acRead () ;                               // get user input

   //** Test aesCUR_ROWSDN
   //** Test aesCUR_ABSCOL
   //** Test aesCUR_NEXTROW
   //** Test aesCUR_PREVROW
   //** Test aesCUR_ROWSUP
   //* Erase the message line and write a new message *
   this->acWrite ( wpMsg, gsErase ) ;
   gsOut = L"Moving relative to base position, down one row, then up one row." ;
   this->acWrite ( wpMsg, gsOut ) ;

   //* Return to target position, then to message position, *
   //* write the message and move back to target position,  *
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ; // return to target position
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec * 2 ) ;                     // wait a moment
   this->acSetCursor ( aesCUR_ROWSDN, 1 ) ;        // move one row downward
   this->acSetCursor ( aesCUR_ABSCOL, 1 ) ;        // left edge of current row
   gsOut = L"  Move to left edge of row below." ;
   this->ttyWrite ( gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ; // return to target position

   //* Go to left edge of next row and write the cursor marker *
   this->acSetCursor ( aesCUR_NEXTROW ) ;          // left edge of next row
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec ) ;                         // wait a moment

   //* Return to target position, then to message position, *
   //* write the message and move back to target position,  *
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ; // set absolute row,col
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec * 2 ) ;                     // wait a moment
   this->acSetCursor ( aesCUR_ROWSUP, 1 ) ;        // move to next row up
   this->acSetCursor ( aesCUR_ABSCOL, 1 ) ;        // left edge of current row
   gsOut = L"  Move to left edge of row above." ;
   this->ttyWrite ( gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ; // set absolute row,col

   //* Go to left edge of previous row and write the cursor marker *
   this->acSetCursor ( aesCUR_PREVROW ) ;          // left edge of previous row
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   this->nsleep ( tsec * 3 ) ;                     // wait a moment

   //** Test aesCUR_ROWDEL
   //** Test aesCUR_ABSCOL
   this->acWrite ( wpMsg, gsErase ) ;              // clear the message area
   gsOut = L"Press any key to delete this row. All lower rows will move upward." ;
   this->acWrite ( wpMsg, gsOut ) ;
   this->acSetCursor ( aesCUR_ABSCOL, 1 ) ;        // left edge of current row
   this->acRead () ;                               // get user input
   //* Delete the message row *
   this->acSetCursor ( aesCUR_ROWDEL ) ;           // remove prompt row
   --targetRow ;                                   // subtract the deleted row
   this->acWrite ( msgRow, msgCol, L"Deleted..." ) ;
   this->nsleep ( tsec * 2 ) ;                     // wait a moment

   //* Clear the test-data area *
   targetRow = 10 ; targetCol = 25 ;
   this->acClearArea ( (targetRow - 3), 1, 7, 50, L'.' ) ;

   //** Test aesCUR_REPORT - see acGetCursor() method **
   //* Return to message line, erase it and write new message *
   this->acWrite ( wpMsg, gsErase ) ;
   gsOut.compose( "Capture the current cursor position. Target row,col: %hd,%hd",
                  &targetRow, &targetCol ) ;
   this->acWrite ( wpMsg, gsOut ) ;
   //* Go to target position and write the cursor marker *
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ;
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
   //* Get the position report and display position *
   wp = this->acGetCursor () ;
   gsOut.append( " - Press any key to continue...\n"
                 "                                   Reported row,col: %hd,%hd",
                 &wp.row, &wp.col ) ;
   this->acWrite ( wpMsg, gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ;
   this->ttyWrite ( gsCur ) ;                      // write the cursor marker
   this->acRead () ;                               // get user input
   this->acWrite ( wpMsg, gsErase ) ;              // clear the message area
   this->acWrite ( (wpMsg.row + 1), wpMsg.col, gsErase ) ;

   //* Test the cursor position save/restore commands.                     *
   //* aesCUR_SAVE/aesCUR_RESTORE, then aesCUR_SAVE_SCO/aesCUR_RESTORE_SCO *
   bool decMsg = true ;
   for ( short loopy = 2 ; loopy > ZERO ; --loopy, decMsg = false )
   {
      //* Erase the message line, and write a new message *
      gsOut.compose( "Saving the current cursor position (%S): row,col: %hd,%hd",
                     (decMsg ? L"DEC" : L"SCO"), &targetRow, &targetCol ) ;
      this->acWrite ( wpMsg, gsErase ) ;
      this->acWrite ( wpMsg, gsOut ) ;

      //** Test aesCUR_SAVE_DEC
      //** Test aesCUR_RESTORE_DEC
      //** Test aesCUR_SAVE_SCO
      //** Test aesCUR_RESTORE_SCO
      //* Set cursor to target coordinates and request *
      //* that terminal save those coordinates.        *
      this->acSetCursor ( aesCUR_ABSPOS, targetRow, targetCol ) ;
      this->acSetCursor ( decMsg ? aesCUR_SAVE_DEC : aesCUR_SAVE_SCO ) ;
      this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
      this->nsleep ( tsec * 4 ) ;                     // wait a moment

      //* Update the message *
      gsOut.compose( "Move to another position, then restore to previous position (%S).",
                     (decMsg ? L"DEC" : L"SCO") ) ;
      this->acWrite ( wpMsg, gsErase ) ;              // clear the message area
      this->acWrite ( wpMsg, gsOut ) ;

      //* Move to another position, write the cursor marker,       *
      //* wait a moment, then restore the previous cursor position.*
      if ( decMsg )
         this->acSetCursor ( aesCUR_ABSPOS, (targetRow - 2), (targetCol - 14) ) ;
      else
         this->acSetCursor ( aesCUR_ABSPOS, (targetRow - 2), (targetCol + 14) ) ;
      this->ttyWrite ( gsCur ) ;                      // write the cursor marker
      this->Test_ReportCurPos ( cpROW, cpCOL ) ;      // report the cursor position
      this->nsleep ( tsec * 4 ) ;                     // wait a moment
      this->acSetCursor ( decMsg ? aesCUR_RESTORE_DEC : aesCUR_RESTORE_SCO ) ;
      this->nsleep ( tsec * 2 ) ;                     // wait a moment
      //* Test whether restore was successful *
      wp = this->acGetCursor () ;
      gsOut.append( (wp.row == targetRow && wp.col == targetCol ? L" Success!" : L" Failed.") ) ;
      this->acWrite ( wpMsg, gsOut ) ;
      this->nsleep ( tsec * 3 ) ;                     // wait a moment

      //* After first loop iteration, erase old cursor marker.*
      if ( decMsg )
         this->acWrite ( (targetRow - 2), (targetCol - 14), L".", false ) ;
   }
   this->nsleep ( tsec * 2 ) ;                        // wait a moment

   this->acSetCursor ( aesCUR_ABSPOS, msgRow, 1 ) ;
   this->acEraseArea ( aesERASE_LINE ) ;
   this->acWrite ( msgRow, msgCol, 
                  L"\n       Cursor positioning tests complete. "
                   "Press any key to exit..." ) ;
   this->acRead () ;                                  // get user input

   //* Prepare to exit *
   this->acSetCursor ( aesCUR_ROWSDN, 11 ) ;
   this->acSetCursor ( aesCUR_ABSCOL, 1 ) ;
   this->acSetMod ( aesBLINK_OFF ) ;   // Disable cursor blinking
   this->acSetFg ( aesFG_DFLT ) ;      // Reset to terminal default foreground color
   this->acRestoreTermInfo () ;        // Restore original terminal configuration
   this->acFlushOut () ;               // flush the output stream

}  //* End Test_CursorPos() *

//*************************
//* Test_UnbufferedInput  *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Enable unbuffered terminal input. Test user responses to various prompts,    *
//* then disable unbuffered input before returning to caller.                    *
//*                                                                              *
//* Test Unbuffered Input:                                                       *
//*  1) wcin.read( wchar_t*, short )  and  cin.read( char*, short )              *
//*  2) wcin.get( void )  and  cin.get( void )                                   *
//*  3) wcin.get( wchar_t& )  and  cin.get( char& )                              *
//*  4) wchar_t acRead ( void ) ;                 handles wide and narrow input  *
//*  5) short acRead ( gString& gsIn, short ) ;   handles wide and narrow input  *
//*  6) wcin >> wchar_t[]  and  cin >> char[]                                    *
//*  7) wchar_t getwchar()  and char getchar()                                   *
//*                                                                              *
//*                                                                              *
//* Input  : fg   : foreground color attribute (member of aeSeq)                 *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_UnbufferedInput ( aeSeq fg )
{  // invoke: T i
   const wchar_t *Desc = 
         L"Unbuffered Input Test:\n"
          "-----------------------------------------------------------\n"
          "The 'stdin' stream ('wcin' or 'cin' in the C++ environment)\n"
          "is buffered by default so that an application must select\n"
          "the standard library input methods with this in mind.\n"
          "By contrast, terminal applications often benefit by using\n"
          "unbuffered data from the input stream, that is, reading a\n"
          "single keycode or a specific number of keycodes from the\n"
          "stream, particularly when the application is sending and\n"
          "receiving ANSI escape sequences, as we are doing here.\n"
          "\n"
          "This test uses several different ways to access the\n"
          "input stream.\n"
          "- The first and last input prompts use the standard\n"
          "  buffered input stream and therefore require the Enter key\n"
          "  to terminate the input.\n"
          "- The other eight(8) prompts use various C++ and C library\n"
          "  calls to capture key input.\n"
          "The data captured by each test are displayed at the right\n"
          "edge of the screen.\n" ;

   const short colOFF  = 54 ; // offset for display of description
   const short exitROW = 34 ; // cursor row when exiting the test

   TermIos tios ;             // structured terminal I/O settings
   tios.c_iflag = ZERO ;      // initialize the target data structure
   tios.c_oflag = ZERO ;
   tios.c_cflag = ZERO ;
   tios.c_lflag = ZERO ;

   gString gsIn, gsOut ;      // text input capture and output formatting
   wchar_t winbuff[64] ;      // receives raw input stream
   wchar_t winchar ;          // receives raw input stream
   char    cinbuff[64] ;      // receives raw input stream
   char    cinchar ;          // receives raw input stream
   short gcnt ;               // input bytes (chars)

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, "  (requires terminal rows/cols of 35/110)\n"
                        "-----------------------------------------" ) ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Create a landing zone *
   const short lzROWS = 10, lzCOLS = 40, lzCOLOFF = (lzCOLS + 37) ;
   short lzr = 4, lzc = 1 ;
   this->acClearArea ( lzr, lzc, lzROWS, lzCOLS, L'.' ) ;
   lzc += 4 ;

   //* Limit width of captured-data display *
   const short outMAXWIDTH = this->termCols - lzCOLOFF + 1 ;


   //* Display the prompt and the test criteria *
   this->acSetMod ( aesBOLD ) ;
   this->acSetMod ( aesUNDERLINE ) ;
   this->acWrite ( (lzr - 1), (lzCOLS + 2), "Type your response to each test prompt:" ) ;

   this->acResetMods () ;           // reset all text modifiers

   gsOut = L"buffered wcin.read(wchar_t*,1)   :\n"
            "unbuffered wcin.read(wchar_t*,1) :\n"
            "unbuffered wcin.get(void)        :\n"
            "unbuffered wcin.get(char_type*)  :\n"
            "unbuffered acRead(void)          :\n"
            "unbuffered acRead(gString&, 1)   :\n"
            "unbuffered acRead(gString&, 3)   :\n"
            "unbuffered wcin >> (cin >>)      :\n"
            "unbuffered getwchar() (getchar()):\n"
            "buffered, Press Enter to exit    :\n" ;
   this->acWrite ( lzr, (lzCOLS + 2), gsOut ) ;

   //* Get the existing terminal settings and display the data *
   gString dtiTitle( "Original Term Settings:" ) ;
   short dumpRow = (lzr + lzROWS + 1), dumpCol = 1 ;
   this->acGetTermInfo ( stdIn, tios ) ;     // get original term settings
   this->acDumpTermInfo ( tios, dumpRow, dumpCol, 2, &dtiTitle ) ;

   //* Display the test description/instructions *
   this->acWrite ( dumpRow, colOFF, Desc ) ;


   //*****************************************
   //* Input buffering enabled:              *
   //* Test wcin.read( char_type*, 1 )       *
   //*   or cin.read( char_type*, 1 )        *
   //*****************************************
   //* Results: Echoes chars. Enter required to terminate. Count=1  *
   //* After Enter detected, returns first char in stdIn buffer,    *
   //* leaving any additional characters in the stdin buffer.       *
   //* Afterward, buffer is cleared to avoid tainting the next test.*
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   if ( this->wideStream )          // get user response (wide)
   { wcin.read( winbuff, 1 ) ; gcnt = wcin.gcount() ; }
   else                             // get user response (narrow)
   { cin.read( cinbuff, 1 ) ; gcnt = cin.gcount() ; }
   this->acFlushIn () ;             // discard abandoned data (incl. newline)
   tuiReport ( gsOut, (this->wideStream ? (void*)winbuff : 
               (void*)cinbuff), gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //* Set terminal configuration for unbuffered input *
   this->acBufferedInput ( false, noEcho ) ;

   //* Retrieve and display the modified settings *
   this->acGetTermInfo ( stdIn, tios ) ;
   dtiTitle = "Retrieved _new_ settings:" ;
   this->acDumpTermInfo ( tios, dumpRow, dumpCol, 2, &dtiTitle ) ;

   //*****************************************
   //* Input buffering disabled:             *
   //* Test wcin.read( char_type*, 1 )       *
   //*   or cin.read( char_type*, 1 )        *
   //*****************************************
   //* Results: Works correctly. count==1, returns keycode *
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   if ( this->wideStream )                      // get user response (wide)
   { wcin.read( winbuff, 1 ) ; gcnt = wcin.gcount() ; }
   else                                         // get user response (narrow)
   { cin.read( cinbuff, 1 ) ;  gcnt = cin.gcount() ;  }
   tuiReport ( gsOut, (this->wideStream ? (void*)winbuff : (void*)cinbuff), gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //*****************************************
   //* Input buffering disabled:             *
   //* Test wcin.get( void )                 *
   //*   or cin.get( void )                  *
   //*****************************************
   //* Results: Works correctly. count==1, returns keycode *
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   if ( this->wideStream )                      // get user response (wide)
   { winchar = wcin.get() ; gcnt = wcin.gcount() ; }
   else                                         // get user response (narrow)
   { winchar = cin.get() ;  gcnt = cin.gcount() ;  }
   winbuff[0] = winchar ; winbuff[1] = NULLCHAR ;
   tuiReport ( gsOut, (void*)winbuff, gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //*****************************************
   //* Input buffering disabled:             *
   //* Test wcin.get( char_type* )           *
   //*   or cin.get( char_type* )            *
   //*****************************************
   //* Results: Works correctly. count==1, returns keycode *
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   if ( this->wideStream )                      // get user response (wide)
   { wcin.get( winchar ) ; gcnt = wcin.gcount() ; winbuff[0] = winchar ; winbuff[1] = NULLCHAR ; }
   else                                         // get user response (narrow)
   { cin.get( cinchar ) ;  gcnt = cin.gcount() ; cinbuff[0] = cinchar ; cinbuff[1] = NULLCHAR ; }
   tuiReport ( gsOut, (this->wideStream ? (void*)winbuff : (void*)cinbuff), gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //*****************************************
   //* Input buffering disabled:             *
   //* Test acRead( void )                   *
   //*****************************************
   //* Results: Works correctly. returns keycode *
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   winchar = this->acRead () ;                  // get user response
   gcnt = 1 ;                                   // number of bytes captured (assumed)
   winbuff[0] = winchar ; winbuff[1] = NULLCHAR ;
   tuiReport ( gsOut, (void*)winbuff, gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //*****************************************
   //* Input buffering disabled:             *
   //* Test acRead( gString&, 1 )            *
   //*****************************************
   //* Results: Works correctly, returns one character *
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   gcnt = this->acRead ( gsIn, 1 ) ;
   tuiReport ( gsOut, (this->wideStream ? (void*)(gsIn.gstr()) : (void*)(gsIn.ustr())), gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //*****************************************
   //* Input buffering disabled:             *
   //* Test acRead( gString&, 3 )            *
   //*****************************************
   //* Results: Works correctly, returns up to three characters. *
   //* The Enter keycode, if encountered, is discarded.          *
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   gcnt = this->acRead ( gsIn, 3 ) ;
   tuiReport ( gsOut, (this->wideStream ? (void*)(gsIn.gstr()) : (void*)(gsIn.ustr())), gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //**************************
   //* Test wcin >> winbuff   *
   //* Test cin  >> cinbuff   *
   //**************************
   //* Results: Works as designed, but the design is grossly inefficient.
   //* (requires character PLUS Enter. Enter is not counted nor returned) *
   // Programmer's Note: For the stream redirection input, wcin.gcount() and
   // cin.gcount() always returns 1, regardless of the number of characters/bytes
   // captured. ALSO, these stream redirectors may not know the size of the target
   // buffer, potentially causing a buffer overflow. For these reasons, we
   // STRONGLY RECOMMEND that stream redirection not be used except with 
   // std::wstring or std::string which use dynamic allocations.
   // Note also that the terminating Enter key is not extracted from the input 
   // buffer and is not counted in wcin.size(). For this reason, we call
   // wcin.getline() or cin.getline() to extract and discard the Enter keycode.
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   if ( this->wideStream )
   {
      std::wstring wstr ;                          // dynamic-allocation input buffer
      wcin >> wstr ;                               // get user response
      gcnt = wstr.size() ;                         // number of characters captured
      gsIn.loadChars( wstr.c_str(), gsALLOCMIN ) ; // copy to main buffer
      wcin.getline( winbuff, gsALLOCMIN ) ;        // clear newline from input stream
   }
   else
   {
      std::string cstr ;                           // dynamic-allocation input buffer
      cin >> cstr ;                                // get user response
      gcnt = cstr.size() ;                         // number of bytes captured
      gsIn.loadChars( cstr.c_str(), gsALLOCMIN ) ; // copy to main buffer
      cin.getline( cinbuff, gsALLOCMIN ) ;         // clear newline from input stream
   }
   tuiReport ( gsOut, (this->wideStream ? (void*)(gsIn.gstr()) : (void*)(gsIn.ustr())), gcnt, this->wideStream ) ;
   if ( (gsOut.gscols()) > outMAXWIDTH )           // limit output width
   {
      gsOut.limitCols( outMAXWIDTH - 3 ) ;
      gsOut.append( L"..." ) ;
   }
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //*****************************************
   //* Input buffering disabled:             *
   //* Test C-style getwchar() -- wide       *
   //*   or C-style getchar()  -- narrow     *
   //*****************************************
   //* Results: Works correctly, returns a single character *
   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   if ( this->wideStream )                      // wide stream
   { winchar = getwchar () ; winbuff[0] = winchar ; winbuff[1] = NULLCHAR ; }
   else                                         // narrow stream
   { cinchar = getchar () ; cinbuff[0] = cinchar ; cinbuff[1] = NULLCHAR ; }
   gcnt = 1 ;                                   // number of bytes captured (assumed)
   tuiReport ( gsOut, (this->wideStream ? (void*)(winbuff) : (void*)(cinbuff)), gcnt, this->wideStream ) ;
   this->acWrite ( lzr++, lzCOLOFF, gsOut ) ;   // display captured input data

   //* Restore original terminal settings *
   this->acRestoreTermInfo () ;

   //* Retrieve a copy of the data we just set *
   this->acGetTermInfo ( stdIn, tios ) ;
   dtiTitle = "Original settings restored:" ;
   this->acDumpTermInfo ( tios, dumpRow, dumpCol, 2, &dtiTitle ) ;

   this->acSetCursor ( aesCUR_ABSPOS, lzr, lzc ) ; // go to landing zone
   this->acRead ( gsIn ) ;                      // get user response

   this->acSetFg ( aesFG_DFLT ) ;      // Reset to terminal default foreground color
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;
   this->acFlushStreams () ;           // Clear input and output buffers

}  //* End Test_UnbufferedInput() *

//********************************************************************************
//* tuiReport:                                                                   *
//* Non-member support method for Test_UnbufferedInput().                        *
//* Format the raw data from the input stream showing keycodes and printing      *
//* characters.                                                                  *
//*                                                                              *
//* Input  : gs      : (by reference) receives formatted report text             *
//*          wbuff   : raw input                                                 *
//*          cnt     : number of characters in wbuff[]                           *
//*          wStream : 'true' if wide input stream, 'false' if narrow stream     *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void tuiReport ( gString& gs, const void *buff, short cnt, bool wStream )
{
   wchar_t wbuff[gsALLOCMIN] ;            // Working copy of raw data
   wchar_t wchar ;                        // character being processed

   //* Copy the raw wide-stream data to the work buffer.*
   if ( wStream )
   {
      gs = (const wchar_t *)(buff) ;
      gs.copy( wbuff, gsALLOCMIN ) ;
   }
   //* Convert narrow-stream data to wide stream for parsing *
   else
   {
      gs = (const char *)(buff) ;
      cnt = gs.gschars() ;
      gs.copy( wbuff, gsALLOCMIN ) ;
   }

   gs.compose( "%hd chars: [", &cnt ) ;
   for ( short i = ZERO ; i < cnt ; ++ i )
   {
      wchar = wbuff[i] ;

      gs.append( "%04X", &wchar ) ;
      if ( (iswprint ( wchar )) )         // printing characters reported directly
         gs.append( "'%C' ", &wchar ) ;

      //* Control characters are converted to plain text.*
      else if ( (iswcntrl ( wchar )) )
      {
         switch ( wchar )                 // ASCII control characters
         {
            case 0x0000:                  // ^0 NUL
               gs.append( "'\\0'" ) ;
               break ;
            case 0x000A:                  // ^J LF (linefeed)
               gs.append( "'\\n'" ) ;
               break ; 
            case 0x000D:                  // ^M CR (carriage return)
               gs.append( "'\\r'" ) ;
               break ; 
            case 0x0009:                  // ^I HT
               gs.append( "'\\t'" ) ;
               break ; 
            case 0x001B:                  // (n/a) ESC
               gs.append( "'esc'" ) ;
               break ;
            case 0x0001:                  // ^A SOH
            case 0x0002:                  // ^B STX
            case 0x0003:                  // ^C ETX
            case 0x0004:                  // ^D EOT
            case 0x0005:                  // ^E ENQ
            case 0x0006:                  // ^F ACK
            case 0x0007:                  // ^G BEL
            case 0x0008:                  // ^H BS
            case 0x000B:                  // ^K VT
            case 0x000C:                  // ^L FF
            case 0x000E:                  // ^N SO
            case 0x000F:                  // ^O SI
            case 0x0010:                  // ^P DLE
            case 0x0011:                  // ^Q DC1
            case 0x0012:                  // ^R DC2
            case 0x0013:                  // ^S DC3
            case 0x0014:                  // ^T DC4
            case 0x0015:                  // ^U NAK
            case 0x0016:                  // ^V SYN
            case 0x0017:                  // ^W ETB
            case 0x0018:                  // ^X CAN
            case 0x0019:                  // ^Y EM
            case 0x001A:                  // ^Z SUB
               wchar += 0x0040 ;
               gs.append( "'^%C'", &wchar ) ;
               break ;
            case 0x001C:                  // ^4 FS
            case 0x001D:                  // ^5 GS
            case 0x001E:                  // ^6 RS
            case 0x001F:                  // ^7 US
               wchar += 0x0018 ;
               gs.append( "'^%C'", &wchar ) ;
               break ;
            default:
               gs.append( "'^?'" ) ;
               break ;
         } ;
      }
      //* Neither a printing character nor a recognized control character.*
      else
      {
         wchar = L'?' ;
         gs.append( "'%C' ", &wchar ) ;
      }
   }
   gs.strip() ;
   gs.append( L"]" ) ;

}  //* End tuiReport() *

//*************************
//*     Test_Erasures     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test the ANSI commands of the aesERASE_xxx group.                            *
//*                                                                              *
//* Input  : fg   : foreground color attribute (member of aeSeq)                 *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Erasures ( aeSeq fg )
{  // invoke: E
   const wchar_t ARROW_UP    = L'↑',         // arrow up
                 ARROW_DOWN  = L'↓' ;        // arrow down

   gString gsOut ;                        // text formatting
   //* Construct the special character sequence for     *
   //* character written to the current cursor position.*
   gString gsCur( "%S$\b%S", 
                  ansiSeq[fg == aesFG_GREEN ? aesFG_RED : aesFG_GREEN],
                  ansiSeq[fg] ) ;
   short targetRow = 2,
         targetCol = this->termCols / 2 ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Set terminal configuration for unbuffered input (echo disabled) *
   this->acBufferedInput ( false, noEcho ) ;

   //* Set the entire window (except first two rows) to a series of characters *
   this->acClearArea ( 3, 1, (this->termRows - 3), this->termCols, L'.' ) ;

   //** Test aesERASE_EOL
   //* Write a message telling user the test being performed *
   //* Set cursor on third line, write the cursor marker.    *
   gsOut = "Erase from cursor position to end of line. Press a key..." ;
   this->acWrite ( targetRow++, (targetCol - ((gsOut.gscols() / 2) - 1)), gsOut ) ;
   //* Set cursor on third line, write the cursor marker *
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, (this->termCols / 2 + 1) ) ;
   this->ttyWrite ( gsCur ) ;
   this->acRead () ;                         // get user response
   this->acEraseArea ( aesERASE_EOL ) ;      // erase to end of row
   targetRow += 2 ;

   //** Test aesERASE_BOL
   //* Write a message telling user the test being performed *
   //* Set cursor on sixth line, write the cursor marker.    *
   gsOut = "Erase from cursor position to start of line. Press a key..." ;
   this->acWrite ( targetRow++, (targetCol - ((gsOut.gscols() / 2) - 1)), gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, (this->termCols / 2 + 1) ) ;
   this->ttyWrite ( gsCur ) ;
   this->acRead () ;                         // get user response
   this->acEraseArea ( aesERASE_BOL ) ;      // erase to beginning of row
   targetRow += 2 ;

   //** Test aesERASE_LINE
   //* Write a message telling user the test being performed *
   //* Set cursor on ninth line, write the cursor marker.    *
   gsOut = "Erase the entire line where cursor sits. Press a key..." ;
   this->acWrite ( targetRow++, (targetCol - ((gsOut.gscols() / 2) - 1)), gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, (this->termCols / 2 + 1) ) ;
   this->ttyWrite ( gsCur ) ;
   this->acRead () ;                         // get user response
   this->acEraseArea ( aesERASE_LINE ) ;     // erase entire row
   targetRow += 2 ;

   //** Test aesERASE_TOW **
   //* Write a message telling user the test being performed *
   //* Set cursor on eleventh line, write the cursor marker. *
   gsOut = "Erase all data from the cursor to the top of the window. Press a key..." ;
   this->acWrite ( targetRow++, (targetCol - ((gsOut.gscols() / 2) - 1)), gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, (this->termCols / 2 + 1) ) ;
   this->ttyWrite ( gsCur ) ;
   this->acRead () ;                         // get user response
   this->acEraseArea ( aesERASE_TOW ) ;      // erase to top of window
   targetRow += 2 ;

   //** Test aesERASE_BOW **
   gsOut = "Erase all data from the cursor to the bottom of the window. Press a key..." ;
   this->acWrite ( targetRow++, (targetCol - ((gsOut.gscols() / 2) - 1)), gsOut ) ;
   this->acSetCursor ( aesCUR_ABSPOS, targetRow, (this->termCols / 2 + 1) ) ;
   this->acRead () ;                         // get user response
   this->acEraseArea ( aesERASE_BOW ) ;      // erase to bottom of window
   this->acClearArea ( (targetRow - 1), ZERO, 1, this->termCols, L'.' ) ;
   targetRow += 2 ;

   //** Test aesERASE_SAVED **
   //* (This erases the history i.e. the lines stored above the    *
   //* window for scroll-back).                                    *
   //* 1) Insert some data into some rows which will be scrolled   *
   //*    off the top of the window and wait for user input.       *
   //* 2) Walk the cursor downward to push the top rows off-screen *
   //* 3) Walk the cursor back to the top of the window.           *
   //* 4) Prompt the user to scroll downward to reveal the hidden  *
   //*    text, then scroll it back out-of-sight.                  *
   //* 5) Erase the text that is _logically_ above the window      *
   //*    (although user may have left it actually visible).       *
   //* This test is made more difficult due to the fact that none  *
   //* of the ANSI sequences cause data in the window to scroll.   *
   const wchar_t *esaveMSG =
     L"  These rows of text will be scrolled upward and out of the window.\n"
     L"  They will then be scrolled back into view to prove they are still present.\n"
     L"  Afterward they will be scrolled out again and will be deleted. %S" ;
   const wchar_t *esavePAK = L"Press a key..." ;

   WinPos savedPos = this->acGetCursor () ; // save the target position
   gsOut.compose( esaveMSG, esavePAK ) ;
   this->acWrite ( (targetRow - 8), 1, gsOut ) ;
   this->acRead () ;                      // get user response
   //* Erase the prompt.                                   *
   // *(Yes, this is much more complicated than necessary, *
   //* but we're exercising the functionality.)            *
   gsOut = esavePAK ;                     // erase the prompt
   this->acSetCursor ( aesCUR_COLSLT, (gsOut.gscols()) ) ;
   this->acEraseArea ( aesERASE_EOL ) ;
   this->acSetCursor ( savedPos ) ;       // return to the target position

   //* This is fancy: Scroll to the bottom of the terminal window *
   //* using a visual cue to show what's happening. Then enhance  *
   //* the visual cue and scroll the window upward, moving the    *
   //* first few rows out the top. These rows are the "saved"     *
   //* data which will be deleted while they are off-screen.      *
   WinPos wp ;
   const uint16_t tsec = 0 ;              // delay (1/10 sec)
   const uint16_t msec = 35 ;             // delay (millisec)
   targetCol = this->termCols / 3 + 1 ;
   this->acSetCursor ( aesCUR_ABSCOL, targetCol ) ;
   short stepRows = this->termRows - targetRow + 1,
         scrollRows = this->termRows - stepRows ;
   gsOut.compose( "%C", &ARROW_DOWN ) ;
   for ( short i = ZERO ; i < stepRows ; ++i )
   {
      this->acSetCursor ( aesCUR_NEXTROW, 1 ) ;
      this->ttyWrite ( gsOut ) ;
      this->nsleep ( tsec, msec ) ;
   }

   //* Add a newline so writing to stdout will scroll.*
   gsOut.append( "%C\n", &ARROW_DOWN ) ;
   this->acSetCursor ( aesCUR_COLSLT, 1 ) ;
   for ( short i = ZERO ; i < scrollRows ; ++i )
   {
      this->ttyWrite ( gsOut ) ;
      this->nsleep ( tsec, msec ) ;
   }
   //* Point upward, and then move upward *
   //* to top of terminal window.         *
   gsOut.compose( "%C%C", &ARROW_UP, &ARROW_UP ) ;
   this->acSetCursor ( aesCUR_COLSRT, 4 ) ;
   for ( short i = scrollRows ; i > ZERO ; --i )
   {
      this->acSetCursor ( aesCUR_ROWSUP, 1 ) ;
      this->acSetCursor ( aesCUR_COLSLT, 2 ) ;
      this->ttyWrite ( gsOut ) ;
      this->nsleep ( tsec, msec ) ;
   }
   gsOut.compose( "%C", &ARROW_UP ) ;
   do
   {
      this->acSetCursor ( aesCUR_ROWSUP, 1 ) ;
      this->acSetCursor ( aesCUR_COLSLT, 1 ) ;
      this->ttyWrite ( gsOut ) ;
      wp = this->acGetCursor () ;
      this->nsleep ( tsec, msec ) ;
   }
   while ( wp.row > 1 ) ;
   this->acSetCursor ( aesCUR_COLSRT, 2 ) ;

   //* This message is written to the firs column of the       *
   //* current row which _should be_ the top row of the window.*
   gsOut = "Use the mouse scroll wheel to scroll the messages back into view,\n"
           "then scroll the lines back out of the window, then press a key.\n" ;
   this->acSetCursor ( aesCUR_ABSCOL, 1 ) ;
   this->ttyWrite ( gsOut ) ;
   this->acRead () ;

   //* Erase the off-screen data *
   this->acEraseArea ( aesERASE_SAVED ) ;

   // Programmer's Note: This message is written at the _current_ cursor position.
   gsOut = "Hidden lines deleted.                                            \n"
           "Use the mouse scroll wheel to scroll the messages back into view.\n"
           "They should no longer be available -- then press a key." ;
   this->acWrite ( 1, 1, gsOut ) ;
   this->acRead () ;                         // get user response

   //** Test aesERASE_WIN **
   //* Write new data to the window, and prompt for a user response  *
   //* Note that the prompt is written in an API-level window in the *
   //* center of the terminal window as a test of that functionality.*
   this->acClearScreen ( L'.' ) ;

   const wchar_t* eraseMsg = L" Erase all data from the window. \n"
                             L"          Press a key...         " ;
   gsOut = eraseMsg ;
   targetRow = this->termRows / 4 + 1 ;
   targetCol = (this->termCols / 2) - ((gsOut.gscols()) / 4) ;

   ACWin acWin( targetRow, targetCol, 4, (((gsOut.gscols()) / 2) + 2),
                ltDUAL,                            // border style
                acAttr(acaFG_GREY | acaBG_BLUE),   // border attributes
                acAttr(acaFG_DFLT | acaBG_DFLT)    // interior attributes
      ) ;
   acWin.OpenWindow ( gsOut.ustr() ) ;
   this->acSetCursor ( aesCUR_COLSLT, 9 ) ;

   this->acRead () ;                      // get user response

   //* Close the window *
   acWin.CloseWindow ()  ;

   this->acBufferedInput ( true, termEcho ) ;// Restore buffered input
   this->acSetFg ( aesFG_DFLT ) ;            // Reset foreground to terminal default
   this->acEraseArea ( aesERASE_WIN ) ;      // erase entire window

   //* Prepare to exit *
   this->acWrite ( 1, 1, L"AnsiCmd - Erasure Tests Complete\n" ) ;
   this->acFlushStreams () ;           // Clear input and output buffers

}  //* End Test_Erasures() *

//*************************
//*     Test_AixFgBg      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test the ANSI commands of the aesAIXFG_xxx and aesAIXBG_xxx groups.          *
//* Note that the AIXTERM commands are not part of the ANSI specification,       *
//* but the intense (bold) foreground and background commands may be supported.  *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note: We default the foreground at the end of each record,      *
//* not because it is necessary, but because one record (probably ecsWHITE)      *
//* will be invisible due to having the same foreground and background colors.   *
//********************************************************************************

void AnsiCmd::Test_AixFgBg ( void )
{  // invoke: A a
   //* Loop control: two groups of 8 color sequences + the default sequence *
   const short recCNT = 16 ;
   const wchar_t *ColorName[recCNT] = 
   {
     L"  90) Intense Black Foreground   \n", L"  91) Intense Red Foreground     \n", 
     L"  92) Intense Green Foreground   \n", L"  93) Intense Brown Foreground   \n", 
     L"  94) Intense Blue Foreground    \n", L"  95) Intense Magenta Foreground \n", 
     L"  96) Intense Cyan Foreground    \n", L"  97) Intense Grey Foreground    \n\n",
     L" 100) Intense Black Background   \n", L" 101) Intense Red Background     \n", 
     L" 102) Intense Green Background   \n", L" 103) Intense Brown Background   \n", 
     L" 104) Intense Blue Background    \n", L" 105) Intense Magenta Background \n", 
     L" 106) Intense Cyan Background    \n", L" 107) Intense Grey Background    \n",
   } ;
   const wchar_t *Desc = 
         L"AIXTERM Extensions for Foreground/Background Colors:\n"
          "--------------------------------------------------------------\n"
          "As part of the IBM AIX operating system, Aixterm includes a\n"
          "semi-proprietary implementation of the ANSI standard escape\n"
          "sequences used for terminal control (ANSI X3.64 (ISO/IEC6429).\n"
          "\n"
          "Although the Aixterm foreground and background color\n"
          "attribute commands are not part of the ANSI standard, they\n"
          "are supported by many Linux-based terminal emulators.\n"
          "\n"
          "Please note that if the foreground and background are the\n"
          "same color, the text will be invisible.\n"
          "See the Intense Grey foreground example which causes the text\n"
          "to be rendered as white. We display it on a grey background to\n"
          "distinguish the text from the (presumed) white background." ;
   const short baseROW = 4 ;     // base display row
   const short exitROW = recCNT + 6 ; // cursor row when preparing to exit
   const short descCOL = 44 ;    // offset for description text

   gString gsOut ;               // text formatting
   short cnindx  = ZERO ;        // 'ColorName' index

   //** Test aesAIXFG_xxxx group **
   for ( short regindx = aesAIXFGb_BLACK ; regindx <= aesAIXFGb_GREY ; ++regindx )
   {
      if ( regindx == aesAIXFGb_GREY )          // set special-case background
         this->acSetBg ( aesBG_GREY ) ;
      this->acSetFg ( aeSeq(regindx) ) ;        // set the foreground color
      this->ttyWrite ( ColorName[cnindx++] ) ;  // write the text data
      this->acSetFg ( aesFG_DFLT ) ;            // return to default foreground
      if ( regindx == aesAIXFGb_GREY )          // reset special-case background
         this->acSetBg ( aesBG_DFLT ) ;
      this->acFlushOut () ;                     // flush the output stream
   }

   //** Test aesAIXBG_xxxx group **
   for ( short regindx = aesAIXBGb_BLACK ; regindx <= aesAIXBGb_GREY ; ++regindx )
   {
      this->acSetBg ( aeSeq(regindx) ) ;        // set the background color
      this->ttyWrite ( ColorName[cnindx++] ) ;  // write the text data
      this->acSetFg ( aesBG_DFLT ) ;            // return to default background
      this->acFlushOut () ;                     // flush the output stream
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   this->acWrite ( baseROW, descCOL, Desc ) ;

   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;

}  //* End Test_AixFgBg() *

//*************************
//*    Test_acaExpand     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Exercise the color-attribute tracking data.                                  *
//* The tracking data are updated whenever the color attributes or text          *
//* modifiers are changed.                                                       *
//* Tracking data are stored in the "attrBits" member which is an acaExpand      *
//* class object.                                                                *
//*                                                                              *
//* There are two major groups of attribute-modification methods:                *
//*  1) Processing the application-level commands, (see enum acAttr).            *
//*     Decoding of data for these commands is integrated into the commands      *
//*     themselves.                                                              *
//*  2) Processing the basic ANSI commands, (see enum aeSeq).                    *
//*     Decoding of data for these commands is in addition to issuing the        *
//*     actual ANSI escape sequences. For this reason, the opportunity for       *
//*     errors is higher, so be aware.                                           *
//*                                                                              *
//* Input  : call : if 'b', use low-level ANSI calls, else use API calls.        *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_acaExpand ( wchar_t call )
{  // invoke C d [a | b]
   //* This method tests the acaExpand data member updates. *
   //* These data members are updated either through the    *
   //* low-level ANSI-escape-sequence calls, OR through the *
   //* higher-level application-level calls.                *
   //* This flag indicates whether the tests are executed   *
   //* through the API-level methods, or the Basic          *
   //* low-level, direct ANSI methods.                      *
   const bool apiCalls = bool(call == L'b' ? false : true) ;

   const short dumpROW = 4 ;        // position of data dump
   const short dumpCOL = 60 ;
   const short exitROW = 36 ;       // exit position
   const short sampCOL = 4 ;        // position for sample text
   const short sampROW = dumpROW + 2 ;
   const short sampHGT = 3 ;        // dimensions of sample-text area
   const short sampWID = 40 ;

   const wchar_t *attrMenu = 
      L"a) Fg Black    j) Bg Black     r) Bold\n"
       "b) Fg Red      k) Bg Red       s) Italic\n"
       "c) Fg Green    l) Bg Green     t) Underline\n"
       "d) Fg Brown    m) Bg Brown     u) Overline\n"
       "e) Fg Blue     n) Bg Blue      v) X-out\n"
       "f) Fg Magenta  o) Bg Magenta   w) Blink\n"
       "g) Fg Cyan     p) Bg Cyan      x) Reversed\n"
       "h) Fg Grey     q) Bg Grey      y) Hidden\n"
       "\n"
      L"A) Fg Black    J) Bg Black     R) Bold Off\n"
       "B) Fg Red      K) Bg Red       S) Italic Off\n"
       "C) Fg Green    L) Bg Green     T) Uline Off\n"
       "D) Fg Brown    M) Bg Brown     U) Oline Off\n"
       "E) Fg Blue     N) Bg Blue      V) X-out Off\n"
       "F) Fg Magenta  O) Bg Magenta   W) Blink Off\n"
       "G) Fg Cyan     P) Bg Cyan      X) Reverse Off\n"
       "H) Fg Grey     Q) Bg Grey      Y) Hidden Off\n"
       "\n"
      L"1) 8-bit Fgnd  4) 8-bit Bgnd   Z) All Mods Off\n"
       "2) RGB Fgnd    5) RGB Bgnd     z) Terminal\n"
       "3) Fg Default  6) Bg Default      Defaults\n"
       
       ;
   const wchar_t *Headers = 
      L"   4-Bit Fg       4-Bit Bg     Text Mods      \n"
       "\n\n\n\n\n\n\n\n"
       "   Bold Fg        Bold Bg      Text Mod Off   \n"
       "\n\n\n\n\n\n\n\n"
       "  8Bit,RGB Fg    8Bit,RGB Bg   Resets         \n" ;
   const wchar_t *Instructions = 
      L"   Sample Text Using Current Attributes\n"
      "\n\n\n\n\n\n"
      "Select an attribute to apply to sample text.\n"
      "           Press ENTER to Exit." ;
   const wchar_t *Description = 
      L"This set of tests exercises the attribute-tracking data configured\n"
       "as bitfields in an acaExpand object which is a member of the AnsiCmd\n"
       "class dataset. Each of the 4-bit foreground and background attributes\n"
       "may be specified, and all text modifiers may be set or reset individually.\n" ;
   const wchar_t *Desc2 = 
      L"The 8-bit attributes are implemented as a pair of tests using\n"
       "representative values: fgnd:%hhu(%02hhXh) lime green, and bgnd:%hhu(%02hhXh) purple.\n" ;
   const wchar_t *Desc3 = 
      L"The RGB attributes are implemented as a pair of tests using\n"
       "representative values: fgnd:%hhu(%02hhXh) cyan and bgnd:%hhu(%02hhXh) green." ;
   const wchar_t *sampText =
      L" Sample text rendered using the current \n"
       "  color attributes and text modifiers.  \n"
       " Where in the World is Carmen SanDiego? " ;

   //* Example 8-bit and web-safe RGB attribute indices *
   //* representing these attribute groups.             *
   const uint8_t fg8BIT = 154 ;                             // lime green
   const uint8_t bg8BIT = 91 ;                              // purple
   const uint8_t fgRGB = wsrgbCYAN + (wsrgbSHADES - 2) ;    // light cyan
   const uint8_t bgRGB = wsrgbGREEN + (wsrgbSHADES / 3) ;   // medium green

   acaExpand dfltBits,              // copy of default settings
             currBits ;             // copy of current settings
   acAttr newAttr ;                 // construct bitfields
   bool   newfgnd, newbgnd, newmods ; // type of change specified
   gString gsOut,                   // text formatting
           gsTitle ;                // dump title
   wchar_t wch ;                    // user input
   WinPos wpBox( short(sampROW - 1), short(sampCOL - 1) ),
          wpTxt( sampROW, sampCOL ),
          wpLZone ;
   bool   goodUser = true ;         // 'true' if user selects a valid option

   //* Set terminal configuration for unbuffered input (no echo) *
   this->acBufferedInput ( false, noEcho ) ;

   //* Set all attributes to default values *
   //* and display the default settings.    *
   this->acReset () ;
   this->acGetAttributes ( dfltBits ) ; // save a copy of the default settings
   gsTitle = L"Default acaExp-object Settings" ;
   this->acaExpandDump ( dfltBits, dumpROW, (dumpCOL + 40), &gsTitle ) ;
   gsTitle = L"  Current acaExp-object Settings  " ;

   //* Visually define the sample-text area and display user instructions.*
   wpLZone = this->acWrite ( dumpROW, wpBox.col, Instructions ) ;
   this->acDrawBox ( wpBox, (sampHGT + 2), (sampWID + 2) ) ;

   //* Display description of this test *
   gsOut.compose( "%S Testing Specified.\n",
                  (apiCalls ? L"API-level (acSetAttributes)" : 
                              L"ANSI-level (acSetFg/acSetBg/acSetMod)") ) ;
   this->acSetMod ( aesITALIC ) ;
   WinPos wp = this->acWrite ( (dumpROW + 13), dumpCOL, gsOut ) ;
   this->acSetMod ( aesITALIC_OFF ) ;
   wp = this->acWrite ( wp, Description ) ;
   gsOut.compose( Desc2, &fg8BIT, &fg8BIT, &bg8BIT, &bg8BIT ) ;
   wp = this->acWrite ( wp, gsOut ) ;
   gsOut.compose( Desc3, &fgRGB, &fgRGB, &bgRGB, &bgRGB ) ;
   wp = this->acWrite ( wp, gsOut ) ;

   //* Display the user menu *
   if ( apiCalls )   //** API-level testing **
   {
      this->acSetAttributes ( acAttr(dfltBits.acaVal | acaUNDERLINE | acaOVERLINE) ) ;
      this->acWrite ( wpLZone.row + 2, wpBox.col, Headers ) ;
      this->acSetAttributes ( acAttr(dfltBits.acaVal | acaCLEAR_MODS | acaUNDERLINE | acaOVERLINE) ) ;
      this->acWrite ( wpLZone.row + 3, wpBox.col, attrMenu ) ;
   }
   else     //** ANSI-level testing **
   {
      this->acSetMod ( aesUNDERLINE ) ;
      this->acSetMod ( aesOVERLINE ) ;
      this->acWrite ( wpLZone.row + 2, wpBox.col, Headers ) ;
      this->acSetMod ( aesUNDERLINE_OFF ) ;
      this->acSetMod ( aesOVERLINE_OFF ) ;
      this->acWrite ( wpLZone.row + 3, wpBox.col, attrMenu ) ;
   }        //** ANSI-level testing **

   //* Get user's selections *
   do
   {
      //* If valid user selection *
      if ( goodUser )
      {
         //* Display some text using the current settings *
         this->acWrite ( wpTxt, sampText ) ;

         //* Display the current attribute settings *
         this->acGetAttributes ( currBits ) ;

         if ( apiCalls )   //** API-level testing **
         {
            this->acSetAttributes ( acaATTR_DFLT, true ) ;  // set default attributes
            this->acaExpandDump ( currBits, dumpROW, dumpCOL, &gsTitle ) ; // display data
            this->acSetAttributes ( currBits.acaVal ) ;     // restore target attributes
         }        //** API-level testing

         else     //** ANSI-level testing **
         {
            this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;    // set default attributes
            this->acResetMods () ;                          // reset all modifiers
            this->acaExpandDump ( currBits, dumpROW, dumpCOL, &gsTitle ) ; // display data
            if ( currBits.bgBits != acaBG_DFLT )            // restore target bgnd
               this->acSetBg ( currBits.aesBgnd ) ;
            if ( currBits.acaFgnd != acaFG_DFLT )           // restore target fgnd
               this->acSetFg ( currBits.aesFgnd ) ;
            if ( currBits.boldMod )       this->acSetMod ( aesBOLD ) ; // restore mods
            if ( currBits.italicMod )     this->acSetMod ( aesITALIC ) ;
            if ( currBits.ulineMod )      this->acSetMod ( aesUNDERLINE ) ;
            if ( currBits.olineMod )      this->acSetMod ( aesOVERLINE ) ;
            if ( currBits.xoutMod )       this->acSetMod ( aesXOUT ) ;
            if ( currBits.blinkMod )      this->acSetMod ( aesBLINK_FAST ) ;
            if ( currBits.revMod )        this->acSetMod ( aesREVERSE ) ;
            if ( currBits.invisMod )      this->acSetMod ( aesCONCEAL ) ;
         }     //** ANSI-level testing **

         this->acSetCursor ( wpLZone ) ;
      }
      goodUser = true ;             // assume user can read (always a risky assumption :-)

      wch = this->acRead () ;       // get user input

      //***********************
      //** API-level testing **
      //***********************
      if ( apiCalls )
      {
         newfgnd = newbgnd = newmods = false ;
         newAttr = acaUSEDFLT ;
         switch ( wch )
         {
            //** 4-Bit Foreground **
            case L'a':  newAttr = acaFG_BLACK ;    newfgnd = true ;  break ;
            case L'b':  newAttr = acaFG_RED ;      newfgnd = true ;  break ;
            case L'c':  newAttr = acaFG_GREEN ;    newfgnd = true ;  break ;
            case L'd':  newAttr = acaFG_BROWN ;    newfgnd = true ;  break ;
            case L'e':  newAttr = acaFG_BLUE ;     newfgnd = true ;  break ;
            case L'f':  newAttr = acaFG_MAGENTA ;  newfgnd = true ;  break ;
            case L'g':  newAttr = acaFG_CYAN ;     newfgnd = true ;  break ;
            case L'h':  newAttr = acaFG_GREY ;     newfgnd = true ;  break ;
            case L'A':  newAttr = acaFGb_BLACK ;   newfgnd = true ;  break ;
            case L'B':  newAttr = acaFGb_RED ;     newfgnd = true ;  break ;
            case L'C':  newAttr = acaFGb_GREEN ;   newfgnd = true ;  break ;
            case L'D':  newAttr = acaFGb_BROWN ;   newfgnd = true ;  break ;
            case L'E':  newAttr = acaFGb_BLUE ;    newfgnd = true ;  break ;
            case L'F':  newAttr = acaFGb_MAGENTA ; newfgnd = true ;  break ;
            case L'G':  newAttr = acaFGb_CYAN ;    newfgnd = true ;  break ;
            case L'H':  newAttr = acaFGb_GREY ;    newfgnd = true ;  break ;
            case L'3':  newAttr = acaFG_DFLT ;     newfgnd = true ;  break ;

            //** 8-Bit Foreground **
            case L'1':
               {
               uint8_t currBgnd = ZERO ;  // placeholder (integration performed below)
               newAttr = this->acComposeAttributes ( fg8BIT, currBgnd, false, false ) ;
               newfgnd = true ;
               }
               break ;

            //** RGB Foreground **
            case L'2':
               {
               uint8_t currBgnd = ZERO ;  // placeholder (integration performed below)
               newAttr = this->acComposeAttributes ( fgRGB, currBgnd, true, false ) ;
               newfgnd = true ;
               }
               break ;

            //** 4-Bit Background **
            case L'j':  newAttr = acaBG_BLACK ;    newbgnd = true ;  break ;
            case L'k':  newAttr = acaBG_RED ;      newbgnd = true ;  break ;
            case L'l':  newAttr = acaBG_GREEN ;    newbgnd = true ;  break ;
            case L'm':  newAttr = acaBG_BROWN ;    newbgnd = true ;  break ;
            case L'n':  newAttr = acaBG_BLUE ;     newbgnd = true ;  break ;
            case L'o':  newAttr = acaBG_MAGENTA ;  newbgnd = true ;  break ;
            case L'p':  newAttr = acaBG_CYAN ;     newbgnd = true ;  break ;
            case L'q':  newAttr = acaBG_GREY ;     newbgnd = true ;  break ;
            case L'J':  newAttr = acaBGb_BLACK ;   newbgnd = true ;  break ;
            case L'K':  newAttr = acaBGb_RED ;     newbgnd = true ;  break ;
            case L'L':  newAttr = acaBGb_GREEN ;   newbgnd = true ;  break ;
            case L'M':  newAttr = acaBGb_BROWN ;   newbgnd = true ;  break ;
            case L'N':  newAttr = acaBGb_BLUE ;    newbgnd = true ;  break ;
            case L'O':  newAttr = acaBGb_MAGENTA ; newbgnd = true ;  break ;
            case L'P':  newAttr = acaBGb_CYAN ;    newbgnd = true ;  break ;
            case L'Q':  newAttr = acaBGb_GREY ;    newbgnd = true ;  break ;
            case L'6':  newAttr = acaBG_DFLT ;     newbgnd = true ;  break ;

            //** 8-Bit Background **
            case L'4':
               {
               uint8_t currFgnd = ZERO ;  // placeholder (integration performed below)
               newAttr = this->acComposeAttributes ( currFgnd, bg8BIT, false, false ) ;
               newbgnd = true ;
               }
               break ;

            //** RGB Background **
            case L'5':
               {
               uint8_t currFgnd = ZERO ;  // placeholder (integration performed below)
               newAttr = this->acComposeAttributes ( currFgnd, bgRGB, false, true ) ;
               newbgnd = true ;
               }
               break ;

            //** Text Modifiers **
            case L'r':  newAttr = acaBOLD ;              break ;
            case L's':  newAttr = acaITALIC ;            break ;
            case L't':  newAttr = acaUNDERLINE ;         break ;
            case L'u':  newAttr = acaOVERLINE ;          break ;
            case L'v':  newAttr = acaXOUT ;              break ;
            case L'w':  newAttr = acaBLINK ;             break ;
            case L'x':  newAttr = acaREVERSE ;           break ;
            case L'y':  newAttr = acaCONCEAL ;           break ;

            case L'R':  newAttr = acAttr(acaCLEAR_MODS | acaBOLD) ;        break ;
            case L'S':  newAttr = acAttr(acaCLEAR_MODS | acaITALIC) ;      break ;
            case L'T':  newAttr = acAttr(acaCLEAR_MODS | acaUNDERLINE) ;   break ;
            case L'U':  newAttr = acAttr(acaCLEAR_MODS | acaOVERLINE) ;    break ;
            case L'V':  newAttr = acAttr(acaCLEAR_MODS | acaXOUT) ;        break ;
            case L'W':  newAttr = acAttr(acaCLEAR_MODS | acaBLINK) ;       break ;
            case L'X':  newAttr = acAttr(acaCLEAR_MODS | acaREVERSE) ;     break ;
            case L'Y':  newAttr = acAttr(acaCLEAR_MODS | acaCONCEAL) ;     break ;
            case L'Z':  newAttr = acaCLEAR_MODS ;                          break ;

            //** Terminal Defaults **
            case L'z':
               newAttr = acAttr(acaCLEAR_MODS | acaATTR_DFLT) ;
               newfgnd = newbgnd = true ;
               break ;

            //** Invalid Selection **
            default:
               if ( wch != NEWLINE )
               { this->acBeep () ; goodUser = false ; }
               break ;
         } ;
         if ( goodUser )
         {
            //* If no change to fgnd/bgnd specified, integrate existing values.*
            if ( ! newfgnd )  newAttr = acAttr(newAttr | currBits.fgBits) ;
            if ( ! newbgnd )  newAttr = acAttr(newAttr | currBits.bgBits) ;
            this->acSetAttributes ( newAttr ) ;
         }
      }        //** API-level testing **

      //************************
      //** ANSI-level testing **
      //************************
      else
      {
         switch ( wch )
         {
            //** 4-Bit Foreground **
            case L'a':  this->acSetFg ( aesFG_BLACK ) ;        break ;
            case L'b':  this->acSetFg ( aesFG_RED ) ;          break ;
            case L'c':  this->acSetFg ( aesFG_GREEN ) ;        break ;
            case L'd':  this->acSetFg ( aesFG_BROWN ) ;        break ;
            case L'e':  this->acSetFg ( aesFG_BLUE ) ;         break ;
            case L'f':  this->acSetFg ( aesFG_MAGENTA ) ;      break ;
            case L'g':  this->acSetFg ( aesFG_CYAN ) ;         break ;
            case L'h':  this->acSetFg ( aesFG_GREY ) ;         break ;
            case L'A':  this->acSetFg ( aesFGb_BLACK ) ;       break ;
            case L'B':  this->acSetFg ( aesFGb_RED ) ;         break ;
            case L'C':  this->acSetFg ( aesFGb_GREEN ) ;       break ;
            case L'D':  this->acSetFg ( aesFGb_BROWN ) ;       break ;
            case L'E':  this->acSetFg ( aesFGb_BLUE ) ;        break ;
            case L'F':  this->acSetFg ( aesFGb_MAGENTA ) ;     break ;
            case L'G':  this->acSetFg ( aesFGb_CYAN ) ;        break ;
            case L'H':  this->acSetFg ( aesFGb_GREY ) ;        break ;
            case L'3':  this->acSetFg ( aesFG_DFLT ) ;         break ;

            //** 8-Bit Foreground **
            case L'1':  this->acBeep ( 2, 2 ) ; goodUser = false ;   break ;

            //** RGB Foreground **
            case L'2':  this->acBeep ( 2, 2 ) ; goodUser = false ;   break ;

            //** 4-Bit Background **
            case L'j':  this->acSetBg ( aesBG_BLACK ) ;        break ;
            case L'k':  this->acSetBg ( aesBG_RED ) ;          break ;
            case L'l':  this->acSetBg ( aesBG_GREEN ) ;        break ;
            case L'm':  this->acSetBg ( aesBG_BROWN ) ;        break ;
            case L'n':  this->acSetBg ( aesBG_BLUE ) ;         break ;
            case L'o':  this->acSetBg ( aesBG_MAGENTA ) ;      break ;
            case L'p':  this->acSetBg ( aesBG_CYAN ) ;         break ;
            case L'q':  this->acSetBg ( aesBG_GREY ) ;         break ;
            case L'J':  this->acSetBg ( aesBGb_BLACK ) ;       break ;
            case L'K':  this->acSetBg ( aesBGb_RED ) ;         break ;
            case L'L':  this->acSetBg ( aesBGb_GREEN ) ;       break ;
            case L'M':  this->acSetBg ( aesBGb_BROWN ) ;       break ;
            case L'N':  this->acSetBg ( aesBGb_BLUE ) ;        break ;
            case L'O':  this->acSetBg ( aesBGb_MAGENTA ) ;     break ;
            case L'P':  this->acSetBg ( aesBGb_CYAN ) ;        break ;
            case L'Q':  this->acSetBg ( aesBGb_GREY ) ;        break ;
            case L'6':  this->acSetBg ( aesBG_DFLT ) ;         break ;

            //** 8-Bit Background **
            case L'4':  this->acBeep ( 2, 2 ) ; goodUser = false ;   break ;

            //** RGB Background **
            case L'5':  this->acBeep ( 2, 2 ) ; goodUser = false ;   break ;

            //** Text Modifiersd **
            case L'r':  this->acSetMod ( aesBOLD ) ;           break ;
            case L's':  this->acSetMod ( aesITALIC ) ;         break ;
            case L't':  this->acSetMod ( aesUNDERLINE ) ;      break ;
            case L'u':  this->acSetMod ( aesOVERLINE ) ;       break ;
            case L'v':  this->acSetMod ( aesXOUT ) ;           break ;
            case L'w':  this->acSetMod ( aesBLINK_FAST ) ;     break ;
            case L'x':  this->acSetMod ( aesREVERSE ) ;        break ;
            case L'y':  this->acSetMod ( aesCONCEAL ) ;        break ;

            case L'R':  this->acSetMod ( aesBOLD_OFF ) ;       break ;
            case L'S':  this->acSetMod ( aesITALIC_OFF ) ;     break ;
            case L'T':  this->acSetMod ( aesUNDERLINE_OFF ) ;  break ;
            case L'U':  this->acSetMod ( aesOVERLINE_OFF ) ;   break ;
            case L'V':  this->acSetMod ( aesXOUT_OFF ) ;       break ;
            case L'W':  this->acSetMod ( aesBLINK_OFF ) ;      break ;
            case L'X':  this->acSetMod ( aesREVERSE_OFF ) ;    break ;
            case L'Y':  this->acSetMod ( aesCONCEAL_OFF ) ;    break ;
            case L'Z':  this->acResetMods () ;                 break ;

            //** Terminal Defaults **
            case L'z':  this->acReset () ;                     break ;

            //** Invalid Selection **
            default:
               if ( wch != NEWLINE )
               { this->acBeep () ; goodUser = false ; }
         } ;
      }     //** ANSI-level testing **
   }
   while ( wch != NEWLINE ) ;

   //* Prettify the sample area *
   this->acReset () ;      // set all color attributes and mods to defaults
   this->acClearArea ( sampROW, sampCOL, sampHGT, sampWID, L'-' ) ;

   //* Prepare to exit *
   this->acBufferedInput ( true, termEcho ) ;         // restore buffered input
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;  // set cursor to exit position

}  //* End Test_acaExpand() *

//*************************
//*  Test_acaExpandSwap   *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//*                                                                              *
//* This method exercises the acaExpand functionality for creating 4-bit, 8-bit  *
//* and RGB bitfields, and then swapping foreground and background.              *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_acaExpandSwap ( void )
{  // invoke: C s
   const short winROW = 4 ;
   const short winCOL = 46 ;
   const short winHGT = 12 ;
   const short winWID = 48 ;
   const short curOFF = 266 ;
   const short padCNT = 50 ;
   const short tstCNT = 15 ;
   const char* outTemplate = "%hd/%hd) %s" ;
   const char* testLabel[tstCNT] = 
   {
      "Fg/Bg Defaults",
      "Fg 4-bit/Bg Dflt",
      "Fg Dflt/Bg 4-bit (swap)",
      "Fg 4-bit/Bg Dflt (swap)",
      "Fg 4-bit/Bg 4-bit",
      "Fg 4-bit/Bg 4-bit (swap)",
      "Fg 8-bit(value)/Bg 8-bit(build8Bit)",
      "Fg 8-bit/Bg 8-bit (swap)",
      "Fg 8-bit(build8Bit)/Bg 8-bit(build8Bit)",
      "Fg 8-bit/Bg 8-bit (swap)",
      "Fg 8-bit(value)/Bg RGB(buildRgb)",
      "Fg RGB/Bg 8-bit (swap)",
      "Fg RGB(buildRgb)/Bg RGB(buildRgb)",
      "Fg RGB/Bg RGB (swap)",
      "Fg/Bg Defaults"
   } ;
   const wchar_t* bitfieldMap = 
      L"     acaExpand Bitfield Map\n"
       "     ======================\n"
       "     00-07 foreground index\n"
       "     08-15 background index\n"
       "     16    mod: bold\n"
       "     17    mod: italic\n"
       "     18    mod: underline\n"
       "     19    mod: overline\n"
       "     20    mod: xout\n"
       "     21    mod: blink\n"
       "     22    mod: conceal\n"
       "     23    mod: reverse\n"
       "     24    clear_mods\n"
       "     25    fgnd_default\n"
       "     26    fgnd_rgb\n"
       "     27    bgnd_default\n"
       "     28    bgnd_rgb\n"
       "     29-30 reserved\n"
       "     31    use_default" ;
   gString gsOut = 
        //123456789-123456789-123456789-123456789-12345|
         "The acaExpand class manages color attributes\n"
         "and text modifiers for the ACWin class, and\n"
         "the skForm/skField user-interface class.\n"
         "The old and new bitfields are displayed after\n"
         "each test. See the acaExpand-class 'compose'\n"
         "method for an explanation of the bitfields.\n"
         "  Press any key to continue (or 'q' to quit)\n"
         "---------------------------------------------\n"
         "**old:" ;
   acaExpand aca( acaATTR_DFLT ) ;
   uint32_t oldaca = ZERO ;
   acAttr   tmpaca ;
   short ix ;
   gString gs, gsMsg ;
   WinPos wpMsg( short(winROW + winHGT), short(winCOL + 2) ) ;
   short exitRow = winROW + winHGT + 6 ;  // cursor row for exit

   this->acSetCursor ( (winROW - 1), 1 ) ;
   this->ttyWrite ( bitfieldMap ) ;       // explanation of bitfields

   this->acWrite ( 1, winCOL + 5, 
            L"Verify acaExpand-class utility methods:\n"
             "     'swap' 'build8Bit' 'buildRgb'     \n"
             "---------------------------------------" ) ;

   ACWin *win = new ACWin ( winROW, winCOL, winHGT, winWID ) ;
   win->SetFieldData ( ZERO, gsOut, aca.acaVal, ZERO ) ;
   win->OpenWindow () ;
   win->acSetEchoOption ( noEcho ) ;

   for ( short t = ZERO, x = 1 ; t < tstCNT ; ++t, ++x )
   {
      switch ( t )
      {
         case 1:     // one default
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 4-bit/Bg Dflt" ) ;
            aca.decode( acAttr(acaFG_CYAN | acaBG_DFLT) ) ;
            break ;
         case 2:     // swap default
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg Dflt/Bg 4-bit (swap)" ) ;
            aca.swap() ;
            break ;
         case 3:     // swap default (backward)
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 4-bit/Bg Dflt (swap)", &x, &tstCNT ) ;
            aca.swap() ;
            break ;
         case 4:     // 4-bit/4-bit
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 4-bit/Bg 4-bit", &x, &tstCNT ) ;
            aca.decode( acAttr(acaFG_CYAN | acaBG_RED ) ) ;
            break ;
         case 5:     // swap 4-bit
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 4-bit/Bg 4-bit (swap)", &x, &tstCNT ) ;
            aca.swap() ;
            break ;
         case 6:     // 8-bit/8-bit (build Bg)
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 8-bit(value)/Bg 8-bit(build8Bit)", &x, &tstCNT ) ;
            tmpaca = aca.build8Bit( false, min8BIT, true, 129 ) ;
            aca.decode( acAttr(250 | tmpaca) ) ;
            break ;
         case 7:     // swap 8-bit/8-bit
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 8-bit/Bg 8-bit (swap)" ) ;
            aca.swap() ;
            break ;
         case 8:     // build 8-bit Fg/Bg
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 8-bit(build8Bit)/Bg 8-bit(build8Bit)" ) ;
            tmpaca = aca.build8Bit( true, 190, true, 21 ) ;
            aca.decode( acAttr(tmpaca) ) ;
            break ;
         case 9:     // swap 8-bit/8-bit
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 8-bit/Bg 8-bit (swap)" ) ;
            aca.swap() ;
            break ;
         case 10:    // 8-bit/RGB (build RGB Bg)
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg 8-bit(value)/Bg RGB(buildRgb)" ) ; gsMsg.padCols( padCNT ) ;
            tmpaca = aca.buildRgb ( false, wsrgbMIN, 
                                    true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MED) ) ;
            aca.decode( acAttr(250 | tmpaca) ) ;
            break ;
         case 11:    // 8-bit/RGB (build RGB Bg)
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg RGB/Bg 8-bit (swap)" ) ; gsMsg.padCols( padCNT ) ;
            aca.swap() ;
            break ;
         case 12:    // Fg RGB(build)/Bg RGB(build)
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg RGB(buildRgb)/Bg RGB(buildRgb)" ) ;
            tmpaca = aca.buildRgb ( true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MAX), 
                                    true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MED / 2) ) ;
            aca.decode( acAttr(tmpaca) ) ;
            break ;
         case 13:    // Fg RGB(build)/Bg RGB(build) (swap)
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg RGB/Bg RGB(swap)" ) ;
            aca.swap() ;
            break ;
         default:
            //gsMsg.compose( outTemplate, &x, &tstCNT, "Fg/Bg Defaults" ) ;
            aca.decode( acaATTR_DFLT ) ;
            break ;
      }
      gsMsg.compose( outTemplate, &x, &tstCNT, testLabel[t] ) ;
      gsMsg.padCols( padCNT ) ;
      this->acSetCursor ( wpMsg ) ;
      this->ttyWrite ( gsMsg ) ;
      gs.compose( L"**old:%_B\n**new:%_B", &oldaca, &aca.acaVal ) ;
      ix = gsOut.find( L"**old:" ) ;
      gsOut.limitChars( ix ) ;
      gsOut.append( gs.gstr() ) ;
      win->SetFieldData ( ZERO, gsOut, aca.acaVal, curOFF ) ;
      if ( (win->acRead ()) == L'q' ) break ;
      oldaca = aca.acaVal ;
   }

   win->CloseWindow ( false ) ;

   //* Prepare to exit *
   this->acRestoreTermInfo () ;        // Restore original terminal settings
   this->acSetCursor ( aesCUR_ABSPOS, exitRow, 1 ) ; // set cursor
   this->acFlushStreams () ;           // Clear input and output buffers

}  //* End Test_acaExpandSwap() *

//*************************
//*    Test_AsciiCodes    *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Exercise the ASCII control codes Ctrl+n and Alt+n key combinations.          *
//* Note that some of these will be captured by the system, and others will      *
//* simply be ignored.                                                           *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_AsciiCodes ( aeSeq fg )
{  // invoke: A
   const wchar_t *Desc_a = 
   L"ASCII Control Codes, CTRL+n, SHIFT+n and ALT+n Test:\n"
    "-------------------------------------------------------------------------------"
    "----------------------------------------------\n"
    "The ASCII (American Standard Code for Information) Exchange) control codes are "
    "the first 32 characters of the ASCII table.\n"
    "These are non-printing characters, that is, they will not be visible on the display, "
    "OR they will produce garbage.\n"
    "Many of these codes were designed to control printers, teletype machines, "
    "paper-tape punches and so on, and no longer used.\n"
    "-- These codes are generated by a combination of the CONTROL key and one of: "
    "a-z, 4, 5, 6, 7.  \n" ;
   const wchar_t *Desc_b = 
   L"-- By default, the terminal software will capture and discard most of these "
       "codes; however, a few codes are still in common\n"
    "   use within console applications. The most common are bell(07h \\a), "
       "backspace(08h \\b), delete(7Fh), horizontal tab(09h \\t),\n"
    "   vertical tab(0Bh \\v), linefeed/newline(0Ah \\n), carriage return(0Dh \\r), and of course escape(1Bh \\e).\n"
    "-- In addition, certain system signals are assigned to some key combinations: "
       "Ctrl+C == Break, Ctrl+Z == Quit,\n"
    "   Ctrl+S == Suspend output, Ctrl+Q == resume output, Ctrl+J == newline, Ctrl+M == Enter key, and Ctrl+4 == take a dump.\n" ;
   const wchar_t *Desc_c = 
   L"-- The AnsiCmd library captures these signal-key combinations and converts them "
       "to ordinary keycodes for use as command keys\n"
    "   by the host application. (Ctrl+4 is captured, but still terminates the application.)\n"
    "-- The Function keys: F01-F12, Shift+F01-ShiftF12 and Ctrl+F01-Ctrl+F12 are reported.\n"
    "    (Ctrl+Shift+Fnn combinations are silently converted to Ctrl+Fnn equivalents.)\n"
    "-- The so-called GUI keys are the keycodes generated by a combination of the "
       "Left ALT key and one of:\n"
    "               a-z ! \" # $ % & ' ( ) * + , - . / : ; < = > ? @ \\ ] ^ _ { | }\n"
    "   Note that ALT+[a-z] and ALT+[A-Z] are equivalent, and that "
       "ALT+[0-9] are encoded as their shifted counterparts.\n"
    "-- The Shift+[Up|Down|Right|Left] group are also reported.\n" ;
   const wchar_t *Prompt = 
   L" Press any of the key combinations listed, or SPACEBAR to exit." ;
   const wchar_t UNICODE_MIN_CONV = 0x002190 ;     // (this is also wcsLARROW)
   const wchar_t UNICODE_MAX_CONV = 0x002290 ;     // (this is also wcsALT_RB)

   const short minROWS = 37 ;                   // minimum terminal window height
   const short minCOLS = 132 ;                  // minimum terminal window width
   const short baseROW = 4 ;                    // base display row
   const short baseCOL = 2 ;                    // base display column
   const short lzROWS = 14 ;                    // height of landing zone
   const short colWID = 15 ;                    // output column width
   const short lzCOLS = colWID * 8 ;            // width of landing zone
   const short descROW = baseROW + lzROWS + 1 ; // row for description text
   const short descCOL = baseCOL ;              // column for description text

   WinPos wp( descROW, descCOL ) ;              // cursor position
   gString gsOut ;                              // text formatting
   wchar_t wch ;                                // user input
   wchar_t wdisp, wdisp2 ;                      // calculated wchar_t value(s)
   short lzCol = baseCOL,                       // output column
         exitRow ;                              // cursor row for exit

   //* Warn about minimum terminal window dimensions.*
   gsOut.compose( "  (requires terminal dimensions of %hd rows X %hd columns)", 
                  &minROWS, &minCOLS ) ;
   this->acWrite ( 1, 28, gsOut ) ;

   //* Write a description of the test *
   wp = this->acWrite ( wp, Desc_a ) ;
   wp = this->acWrite ( wp, Desc_b ) ;
   wp = this->acWrite ( wp, Desc_c ) ;
   exitRow = wp.row ;
   this->acSetMod ( aesUNDERLINE ) ;
   this->acSetMod ( aesBOLD ) ;
   this->acWrite ( descROW, 55, Prompt ) ;
   this->acSetMod ( aesBOLD_OFF ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Set unbuffered, non-blocking input (no echo) *
   this->acBufferedInput ( false, noEcho, false ) ;
   //* Capture the signals *
   this->acCaptureBreakSignal ( true, cchtCopy ) ;
   this->acCaptureSuspendSignal ( true ) ;

   //** Get user input **
   wp = { baseROW, lzCol } ;
   this->acSetCursor ( wp ) ;
   while ( (wch = this->acRead ()) != wcsSPACE )
   {
      if ( (wch >= wcsALT_A) && (wch <= wcsALT_Z) )
      {
         wdisp = L'Z' - (wcsALT_Z - wch) ;
         gsOut.compose( "%4X wcsALT_%C\n", &wch, &wdisp ) ;
      }
      else if ( (wch > wcsALT_Z) && (wch <= wcsALT_RB) )
      {
         switch ( wch )
         {
            case wcsALT_0:    wdisp = L'0' ; wdisp2 = L' ' ;   break ;  // [0 | )]
            case wcsALT_1:    wdisp = L'1' ; wdisp2 = L' ' ;   break ;  // [1 | !]
            case wcsALT_2:    wdisp = L'2' ; wdisp2 = L' ' ;   break ;  // [2 | @]
            case wcsALT_3:    wdisp = L'3' ; wdisp2 = L' ' ;   break ;  // [3 | #]
            case wcsALT_4:    wdisp = L'4' ; wdisp2 = L' ' ;   break ;  // [4 | $]
            case wcsALT_5:    wdisp = L'5' ; wdisp2 = L' ' ;   break ;  // [5 | %]
            case wcsALT_6:    wdisp = L'6' ; wdisp2 = L' ' ;   break ;  // [6 | ^]
            case wcsALT_7:    wdisp = L'7' ; wdisp2 = L' ' ;   break ;  // [7 | &]
            case wcsALT_8:    wdisp = L'8' ; wdisp2 = L' ' ;   break ;  // [8 | *]
            case wcsALT_9:    wdisp = L'9' ; wdisp2 = L' ' ;   break ;  // [9 | (]
            case wcsALT_DQ:   wdisp = L'D' ; wdisp2 = L'Q' ;   break ;  // '"'
            case wcsALT_SQ:   wdisp = L'S' ; wdisp2 = L'Q' ;   break ;  // '''
            case wcsALT_PL:   wdisp = L'P' ; wdisp2 = L'L' ;   break ;  // '+'
            case wcsALT_CO:   wdisp = L'C' ; wdisp2 = L'O' ;   break ;  // ','
            case wcsALT_MI:   wdisp = L'M' ; wdisp2 = L'I' ;   break ;  // '-'
            case wcsALT_ST:   wdisp = L'S' ; wdisp2 = L'T' ;   break ;  // '.'
            case wcsALT_FS:   wdisp = L'F' ; wdisp2 = L'S' ;   break ;  // '/'
            case wcsALT_CN:   wdisp = L'C' ; wdisp2 = L'N' ;   break ;  // ':'
            case wcsALT_SC:   wdisp = L'S' ; wdisp2 = L'C' ;   break ;  // ';'
            case wcsALT_LT:   wdisp = L'L' ; wdisp2 = L'T' ;   break ;  // '<'
            case wcsALT_EQ:   wdisp = L'E' ; wdisp2 = L'Q' ;   break ;  // '='
            case wcsALT_GT:   wdisp = L'G' ; wdisp2 = L'T' ;   break ;  // '>'
            case wcsALT_QU:   wdisp = L'Q' ; wdisp2 = L'U' ;   break ;  // '?'
            case wcsALT_LS:   wdisp = L'L' ; wdisp2 = L'S' ;   break ;  // '['
            case wcsALT_BS:   wdisp = L'B' ; wdisp2 = L'S' ;   break ;  // '\'
            case wcsALT_RS:   wdisp = L'R' ; wdisp2 = L'S' ;   break ;  // ']'
            case wcsALT_US:   wdisp = L'U' ; wdisp2 = L'S' ;   break ;  // '_'
            case wcsALT_LB:   wdisp = L'L' ; wdisp2 = L'B' ;   break ;  // '{'
            case wcsALT_VB:   wdisp = L'V' ; wdisp2 = L'B' ;   break ;  // '|'
            case wcsALT_RB:   wdisp = L'R' ; wdisp2 = L'B' ;   break ;  // '}'
            default:          wdisp = L'-' ; wdisp2 = L'-' ;   break ;  // (unlikely)
         } ;
         gsOut.compose( "%4X wcsALT_%C%C\n", &wch, &wdisp, &wdisp2 ) ;
      }
      else if ( (wch >= wcsCTL_A) && (wch <= wcsCTL_Z) )
      {
         wdisp = L'Z' - (wcsCTL_Z - wch) ;
         gsOut.compose( "%4X wcsCTL_%C\n", &wch, &wdisp ) ;
      }
      else if ( (wch > wcsCTL_Z) && (wch <= wcsCTL_7) )
      {
         switch ( wch )
         {
            case wcsCTL_4:    wdisp = L'4' ;    break ;
            case wcsCTL_5:    wdisp = L'5' ;    break ;
            case wcsCTL_6:    wdisp = L'6' ;    break ;
            case wcsCTL_7:    wdisp = L'7' ;    break ;
            default:          wdisp = L'-' ;    break ; // (unlikely)
         }
         gsOut.compose( "%4X wcsCTL_%C\n", &wch, &wdisp ) ;
      }

      else if ( (wch >= wcsF01) && (wch <= wcsCF12) )
      {
         wdisp = L'0' ;                   // most likely value
         switch ( wch )
         {
            case wcsF01: case wcsSF01: case wcsCF01: wdisp2 = L'1' ;    break ;
            case wcsF02: case wcsSF02: case wcsCF02: wdisp2 = L'2' ;    break ;
            case wcsF03: case wcsSF03: case wcsCF03: wdisp2 = L'3' ;    break ;
            case wcsF04: case wcsSF04: case wcsCF04: wdisp2 = L'4' ;    break ;
            case wcsF05: case wcsSF05: case wcsCF05: wdisp2 = L'5' ;    break ;
            case wcsF06: case wcsSF06: case wcsCF06: wdisp2 = L'6' ;    break ;
            case wcsF07: case wcsSF07: case wcsCF07: wdisp2 = L'7' ;    break ;
            case wcsF08: case wcsSF08: case wcsCF08: wdisp2 = L'8' ;    break ;
            case wcsF09: case wcsSF09: case wcsCF09: wdisp2 = L'9' ;    break ;
            case wcsF10: case wcsSF10: case wcsCF10: wdisp2 = L'0' ; wdisp = L'1' ; break ;
            case wcsF11: case wcsSF11: case wcsCF11: wdisp2 = L'1' ; wdisp = L'1' ; break ;
            case wcsF12: case wcsSF12: case wcsCF12: wdisp2 = L'2' ; wdisp = L'1' ; break ;
            default: break ; // (unlikely)
         }
         if ( (wch >= wcsF01) && (wch <= wcsF12) )
            gsOut.compose( "%4X wcsF%C%C\n", &wch, &wdisp, &wdisp2 ) ;
         else if ( (wch >= wcsSF01) && (wch <= wcsSF12) )
            gsOut.compose( "%4X wcsSF%C%C\n", &wch, &wdisp, &wdisp2 ) ;
         else // ( (wch >= wcsCF01) && (wch <= wcsCF12) )
            gsOut.compose( "%4X wcsCF%C%C\n", &wch, &wdisp, &wdisp2 ) ;
      }

      else
      {
         wdisp = wch ;     // make a backup copy in case a test corrupts it

         if ( (this->isSpecialKey ( wdisp )) )
         {
            gsOut.compose( "%4X wcs", &wch ) ;
            switch ( wch )
            {
               case wcsUARROW:   gsOut.append( "UARROW" ) ;    break ;
               case wcsDARROW:   gsOut.append( "DARROW" ) ;    break ;
               case wcsRARROW:   gsOut.append( "RARROW" ) ;    break ;
               case wcsLARROW:   gsOut.append( "LARROW" ) ;    break ;
               case wcsHOME:     gsOut.append( "HOME" ) ;      break ;
               case wcsEND:      gsOut.append( "END" ) ;       break ;
               case wcsPGUP:     gsOut.append( "PGUP" ) ;      break ;
               case wcsPGDN:     gsOut.append( "PGDN" ) ;      break ;
               case wcsBKSP:     gsOut.append( "BKSP" ) ;      break ;
               case wcsTAB:      gsOut.append( "TAB" ) ;       break ;
               case wcsSTAB:     gsOut.append( "STAB" ) ;      break ;
               case wcsDELETE:   gsOut.append( "DELETE" ) ;    break ;
               case wcsINSERT:   gsOut.append( "INSERT" ) ;    break ;
               case wcsAENTER:   gsOut.append( "AENTER" ) ;    break ;
               case wcsSUP:      gsOut.append( "SUP" ) ;       break ;
               case wcsSDOWN:    gsOut.append( "SDOWN" ) ;     break ;
               case wcsSRIGHT:   gsOut.append( "SRIGHT" ) ;    break ;
               case wcsSLEFT:    gsOut.append( "SLEFT" ) ;     break ;
               case wcsCHOME:    gsOut.append( "CHOME" ) ;     break ;
               case wcsCEND:     gsOut.append( "CEND" ) ;      break ;

               default:          gsOut.append( "???" ) ;       break ;  // (unlikely)
            } ;
            gsOut.append( L'\n' ) ;
         }

         //* Miscellaneous converted key combinations *
         else if ( (wch >= UNICODE_MIN_CONV) && (wch <= UNICODE_MAX_CONV) )
         {
            gsOut.compose( "%4X wcs", &wch ) ;
            switch ( wch )
            {
               case wcsCUP:      gsOut.append( "CUP" ) ;       break ;
               case wcsCDOWN:    gsOut.append( "CDOWN" ) ;     break ;
               case wcsCRIGHT:   gsOut.append( "CRIGHT" ) ;    break ;
               case wcsCLEFT:    gsOut.append( "CLEFT" ) ;     break ;
               case wcsCHOME:    gsOut.append( "CHOME" ) ;     break ;
               case wcsCEND:     gsOut.append( "CEND" ) ;      break ;
               case wcsAUP:      gsOut.append( "AUP" ) ;       break ;
               case wcsADOWN:    gsOut.append( "ADOWN" ) ;     break ;
               case wcsARIGHT:   gsOut.append( "ARIGHT" ) ;    break ;
               case wcsALEFT:    gsOut.append( "ALEFT" ) ;     break ;
               case wcsAHOME:    gsOut.append( "AHOME" ) ;     break ;
               case wcsAEND:     gsOut.append( "AEND" ) ;      break ;
               case wcsCPGUP:    gsOut.append( "CPGUP" ) ;     break ;
               case wcsCPGDN:    gsOut.append( "CPGDN" ) ;     break ;
               case wcsAPGUP:    gsOut.append( "APGUP" ) ;     break ;
               case wcsAPGDN:    gsOut.append( "APGDN" ) ;     break ;
               case wcsCAPGUP:   gsOut.append( "CAPGUP" ) ;    break ;
               case wcsCAPGDN:   gsOut.append( "CAPGDN" ) ;    break ;
               case wcsSDEL:     gsOut.append( "SDEL" ) ;      break ;
               case wcsADEL:     gsOut.append( "ADEL" ) ;      break ;
               case wcsASDEL:    gsOut.append( "ASDEL" ) ;     break ;
               case wcsCDEL:     gsOut.append( "CDEL" ) ;      break ;
               case wcsCSDEL:    gsOut.append( "CSDEL" ) ;     break ;
               case wcsCASDEL:   gsOut.append( "CASDEL" ) ;    break ;
               case wcsESCAPE:   gsOut.append( "ESCAPE" ) ;    break ;
               default:          gsOut.append( "???" ) ;       break ;
            }
            gsOut.append( L'\n' ) ;
         }
   
         else if ( ((iswprint ( wch )) != ZERO) )
         {
            gsOut.compose( "%4X '%C'\n", &wch, &wch ) ;
         }

         else
         {
            switch ( wch )
            {
               case NEWLINE: gsOut.compose( "  %02X NEWLINE\n", &wch ) ;   break ;
               default:      gsOut.compose( "%4X -unknown-\n", &wch ) ; break ;
            } ;
         }
      }
      wp = this->acWrite ( wp, gsOut ) ;

      if ( wp.row >= (baseROW + lzROWS) )
      {
         wp = { baseROW, short(lzCol += colWID) } ;
         if ( wp.col > (colWID * 8) )  // if work area filled, erase it
         {
            this->acClearArea ( baseROW, baseCOL, lzROWS, lzCOLS ) ;
            wp = { baseROW, baseCOL } ;
            lzCol = baseCOL ;
         }
         this->acSetCursor ( wp ) ;
      }
   }     // while()

   //* Prepare to exit *
   this->acRestoreTermInfo () ;        // Restore original terminal settings
   this->acSetCursor ( aesCUR_ABSPOS, exitRow, 1 ) ; // set cursor
   this->acSetFg ( aesFG_DFLT ) ;      // Reset to terminal default foreground color
   this->acFlushStreams () ;           // Clear input and output buffers

}  //* End Test_AsciiCodes() *

//*************************
//*     Test_Ideogram     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test for system support for the Ideogram group of ANSI escape sequences.     *
//*                                                                              *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Ideogram ( aeSeq fg )
{
   const short grpSIZE = 6 ;
   const short loopCNT = grpSIZE + 2 ;
   const wchar_t *TestName[loopCNT] = 
   {
      L"Plain Text",
      L"aesIDEO_UNDER   - Underline",
      L"aesIDEO_UDOUBLE - Double Underline",
      L"aesIDEO_OVER    - Overline",
      L"aesIDEO_ODOUBLE - Double Overline",
      L"aesIDEO_STRESS  - Stress Marking",
      L"aesIDEO_OFF     - Ideogram Attributes Off",
      L"Plain Text",
   } ;
   const wchar_t *Desc = 
         L"Ideogram Test:\n"
          "-------------------------------------------------\n"
          "The ANSI standard (ANSI X3.64 (ISO/IEC6429))\n"
          "includes six(6) escape sequences for \"ideograms\".\n"
          "These were apparently intended to provide\n"
          "highlighting or separation of text blocks.\n"
          "\n"
          "These commands are a holdover from early versions\n"
          "of the standard, and it is unlikely that they\n"
          "are supported by terminal emulation software.\n" ;
   aeSeq aesGroup[loopCNT - 2] =
   {
      aesIDEO_UNDER, aesIDEO_UDOUBLE, aesIDEO_OVER, aesIDEO_ODOUBLE, 
      aesIDEO_STRESS, aesIDEO_OFF
   } ;
   const short baseROW = 4 ;     // base display row
   const short baseCOL = 2 ;     // base display column
   const short colOFF  = 50 ;    // offset for description text

   gString gsOut ;               // text formatting
   short   row = baseROW,        // output row
           idea = ZERO ;         // index into aesGroup

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Write a description of the test *
   this->acWrite ( baseROW, colOFF, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, baseROW, baseCOL ) ;

   for ( short i = ZERO ; i < loopCNT ; ++i )
   {
      if ( (i >= 1) && (i < grpSIZE) )
         this->acSetIdeogram ( aesGroup[idea++] ) ;

      this->acWrite ( row, baseCOL, TestName[i] ) ;
      row += 2 ;
   }

   this->acSetCursor ( aesCUR_ABSPOS, row, 1 ) ; // prepare to exit
   this->acSetFg ( aesFG_DFLT ) ;      // Reset to terminal default foreground color
   this->acFlushStreams () ;           // Clear input and output buffers

}  //* End Test_Ideogram() *

//*************************
//*    Test_TermFlags     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest_Menu().                             *
//* Test tracking flags for:                                                     *
//*  1) tset->inpBuf  ---------------------- input buffering flag                *
//*  2) tset->inpEch  ---------------------- input echo option                   *
//*  3) tset->inpBlk  ---------------------- input blocking-read flag            *
//*  4) tset->cchType ---------------------- break key capture                   *
//*  5) tset->curType ---------------------- cursor type                         *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_TermFlags ( aeSeq fg )
{  // invoke: T t
   const short baseROW  = 7 ;       // base display row for flags tests
   const short baseCOL1 = 1 ;       // base column for display column 1
   const short baseCOL2 = 80 ;      // base column for display column 2
   const short baseROW2 = 16 ;      // base display row for CTRL+C test
   const short prOFF1   = 45 ;      // user-input prompt position 1 (column)
   const short prOFF2   = 45 ;      // user-input prompt position 2 (column)
   gString gsOut ;                        // text formatting
   gString gsIn ;                         // capture user input
   TermIos tios ;                         // dummy object
   short row = baseROW, col = baseCOL1 ;  // output position

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, 
         L"  (requires terminal dimensions of 39 rows X 155 columns)\n"
          "---------------------------------------------------------" ) ; 

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* If break-signal captured, disable it *
   if ( this->tset->cchType != cchtTerm )
      this->acCaptureBreakSignal ( false ) ;
   //* Set cursor style to default *
   this->acSetCursorStyle ( csDflt ) ;

   //* Display title and column headings *
   gsOut = L" Validate Terminal Setup Flags \n"
            "-------------------------------" ;
   col = (this->termCols / 2) - (gsOut.gscols() / 4) ;
   this->acWrite ( (baseROW - 4), col, gsOut ) ;
   col = baseCOL1 ;

   this->acWrite ( (baseROW - 2), baseCOL1,
         L"           Input Buffering and Echo Options (dflt cursor shape)\n"
          "----------------------------------------------------------------------------\n" ) ;
   this->acWrite ( (baseROW - 2), baseCOL2,
         L"                            Cursor Shape Tests\n"
          "--------------------------------------------------------------------------\n" ) ;

   //********************************
   //********************************
   //**  Buffering and Echo Tests  **
   //********************************
   //********************************

   //* Display initial flag settings *
   gsOut = "Initial Settings:" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;

   this->tfPrompt ( row++, prOFF1, 1 ) ;     // user prompt

   //************************************************
   //* Set unbuffered input (terminal echo enabled) *
   //************************************************
   this->acBufferedInput ( false, termEcho ) ;

   //* Display the modified flags *
   gsOut = "Unbuffered, with term echo:" ;
   this->acDumpTermInfo ( tios, row += 4, col, 3, &gsOut ) ;

   this->tfPrompt ( row++, prOFF1, 1 ) ;     // user prompt

   //**********************************************
   //* Set unbuffered input (with soft echo, All) *
   //**********************************************
   this->acBufferedInput ( false, softEchoA ) ;

   //* Display the modified flags *
   gsOut = "Unbuffered, with soft echo (all):" ;
   this->acDumpTermInfo ( tios, row += 4, col, 3, &gsOut ) ;

   this->tfPrompt ( row++, prOFF1, 1 ) ;     // user prompt

   //*************************************************************
   //* Set unbuffered input, non-blocking, (with soft echo, All) *
   //*************************************************************
   this->acBufferedInput ( false, softEchoA, false ) ;

   //* Display the modified flags *
   gsOut = "Nonblocking, with soft echo (all):" ;
   this->acDumpTermInfo ( tios, row += 4, col, 3, &gsOut ) ;

   this->tfPrompt ( row++, prOFF1, 4 ) ;     // user prompt

   //***************************************
   //* Set unbuffered input (with no echo) *
   //***************************************
   this->acBufferedInput ( false, noEcho ) ;

   //* Display the modified flags *
   gsOut = "Unbuffered, with no echo:" ;
   this->acDumpTermInfo ( tios, row += 4, col, 3, &gsOut ) ;

   this->tfPrompt ( row++, prOFF1, 1 ) ;     // user prompt

   //*********************************************
   //* Restore buffered input with blocking read *
   //*********************************************
   this->acRestoreTermInfo () ;

   //* Display the modified flags *
   gsOut = "Restore Original Settings:" ;
   this->acDumpTermInfo ( tios, row += 4, col, 3, &gsOut ) ;

   //* Prompt user to test the restored settings *
   //* presumably buffered with terminal echo.   *
   this->tfPrompt ( row++, prOFF1, 1 ) ;

   //***************************
   //***************************
   //**  Cursor -Shape Tests  **
   //***************************
   //***************************

   //* Set unbuffered input with terminal echo *
   this->acBufferedInput ( false, termEcho ) ;

   row = baseROW ;                     // establish the second display column
   col = baseCOL2 ;

   //* Cursor - Blinking Block *
   this->acSetCursorStyle ( csBblock ) ;
   gsOut = "Cursor Shape: Blinking Block" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row, (col + prOFF2), 2, L"Blinking Block       " ) ;

   //* Cursor - Default Cursor Shape *
   this->acSetCursorStyle ( csDflt ) ;
   gsOut = "Cursor Shape: Default" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row, (col + prOFF2), 2, L"Default Cursor Shape " ) ;

   //* Cursor - Steady Block *
   this->acSetCursorStyle ( csSblock ) ;
   gsOut = "Cursor Shape: Steady Block" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row, (col + prOFF2), 2, L"Steady Block         " ) ;

   //* Cursor - Blinking Underline *
   this->acSetCursorStyle ( csBuline ) ;
   gsOut = "Cursor Shape: Blinking Underline" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row, (col + prOFF2), 2, L"Blinking Underline   " ) ;

   //* Cursor - Steady Underline *
   this->acSetCursorStyle ( csSuline ) ;
   gsOut = "Cursor Shape: Steady Underline" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row, (col + prOFF2), 2, L"Steady Underline     " ) ;

   //* Cursor - Blinking Vertical Bar *
   this->acSetCursorStyle ( csBvbar ) ;
   gsOut = "Cursor Shape: Blinking Vertical" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row, (col + prOFF2), 2, L"Blinking Vertical Bar" ) ;

   //* Cursor - Steady Vertical Bar *
   this->acSetCursorStyle ( csSvbar ) ;
   gsOut = "Cursor Shape: Steady Vertical" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row, (col + prOFF2), 2, L"Steady Vertical Bar  " ) ;

   //* Return to default cursor style *
   this->acSetCursorStyle ( csDflt ) ;


   //***********************************
   //***********************************
   //**  CTRL+C Signal Capture Tests  **
   //***********************************
   //***********************************

   //* Display the column heading *
   row = baseROW2 ;
   col = baseCOL2 ;
   this->acWrite ( (baseROW2 - 2), baseCOL2,
    L"                       CTRL+C (break signal) Capture\n"
     "--------------------------------------------------------------------------\n" ) ;

   //* This test uses unbuffered input, with  *
   //* soft-echo and "special" keys disabled. *
   this->acBufferedInput ( false, softEchoD ) ;

   //* Capture break signal using default *
   //* handler breakSignalHandler(), and  *
   //* default handler type: Ignore ('i'),*
   //* (with audible alert).              *
   //* Display the terminal settings, then*
   //* prompt user to press CTRL+C.       *
   this->acCaptureBreakSignal ( true, cchtIgnore, true ) ;

   gsOut = "Ctrl+C 'Ignore'" ;
   this->acDumpTermInfo ( tios, row, col, 3, &gsOut ) ;
   this->tfPrompt ( row++, col + prOFF2, 3, L"Signal Handler Active\n"
                                           "Ctrl+C : \"Ignored\"," ) ;

   //* Change handler type to User ('u'). *
   //* Display the terminal settings, then*
   //* prompt user to press CTRL+C.       *
   this->acCaptureBreakSignal ( true, cchtUser ) ;
   gsOut = "Ctrl+C 'User Prompt'" ;
   this->acDumpTermInfo ( tios, row += 4, col, 3, &gsOut ) ;
   this->tfPrompt ( row++, col + prOFF2, 3, L"Signal Handler Active\n"
                                           "Ctrl+C : \"User Prompt\"," ) ;

   //* Change handler type to Exit ('e'). *
   //* Display the terminal settings, then*
   //* prompt user to press CTRL+C.       *
   this->acCaptureBreakSignal ( true, cchtExit ) ;
   gsOut = "Ctrl+C 'Exit'" ;
   this->acDumpTermInfo ( tios, row += 4, col, 3, &gsOut ) ;
   this->tfPrompt ( row++, col + prOFF2, 3, L"Signal Handler Active\n"
                                           "Ctrl+C : \"Exit\"," ) ;

   //* If user did not exit the application via CTRL+C, *
   //* or (gasp!), there was a program bug, say bye-bye.*
   this->acWrite ( (row + 4), baseCOL2, 
                  L"Ctrl+C Exit failed, OR Ctrl+C not seen.\n"
                   "Press Any Key To Exit..." ) ;
   this->acRead () ;

   //* In case the user did not exit the application *
   //* using the CTRL+C signal, prepare to exit.     *
   this->acBufferedInput ( true, termEcho ) ;   // Re-enable buffered input
   this->acSetCursor ( aesCUR_ABSPOS, (this->termRows - 2), 1 ) ; // Set cursor position
   this->acCaptureBreakSignal ( false ) ; // disable break-signal capture
   this->acSetFg ( aesFG_DFLT ) ;      // Reset to terminal default foreground color
   this->acFlushStreams () ;           // Clear input and output buffers

}  //* End Test_TermFlags() *

//*************************
//*       tfPrompt        *
//*************************
//********************************************************************************
//* Called by Test_TermFlags() method.                                           *
//* Prompt the user for some key input, capture it and display the captured      *
//* data.                                                                        *
//*                                                                              *
//* Note: If one of the soft-echo options is active, only printing characters    *
//*       (and a select few control characters) are returned from acRead()       *
//*       unmodified.                                                            *
//*                                                                              *
//* Input  : baseRow : base row for prompt                                       *
//*          baseCol : base column for prompt                                    *
//*          prompt  : specifies which prompt to use                             *
//*          p2      : (optional, null pointer by default)                       *
//*                    When 'prompt' arg == 2, this is the supplimentary         *
//*                    text for the user prompt                                  *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::tfPrompt ( short baseRow, short baseCol, short prompt, const wchar_t *p2 )
{
   const wchar_t *userPrompt1 = L"Write some text, then press Enter:\n"
                                "->" ;
   const wchar_t *userPrompt2 = L"%S\n"
                                 "Write some text,\n"
                                 "then press Enter..." ;
   const wchar_t *userPrompt3 = L"%S\n"
                                 "Press Ctrl+C: " ;
   const short promptRESP1 = 2 ;       // position for echo of captured input
   gString gsOut,                      // text formatting
           gsIn ;                      // capture user responses

   //* First series of tests: buffering states and echo options.  *
   //* Note: Break key is under terminal control for these tests, *
   //*       so Ctrl+C will kill the application.                 *
   if ( prompt == 1 )
   {
      //* Display the prompt *
      this->acWrite ( baseRow++, baseCol, userPrompt1 ) ;

      //* Get user response *
      this->acRead ( gsIn )  ;

      //* Dispay the captured data *
      this->acWrite ( ++baseRow, (baseCol + promptRESP1), gsIn ) ;
      this->acSetCursor ( aesCUR_NEXTROW ) ;
   }  // (prompt==1)

   //* Second set of tests: Cursor shape *
   else if ( prompt == 2 )
   {
      const short prOFF2   = 19 ;      // user-input prompt position
      gString gsIn ;                   // receives user input
      gsOut.compose( userPrompt2, p2 ) ;
      this->acWrite ( baseRow, baseCol, gsOut ) ;
      this->acRead ( gsIn, 15 ) ;
      this->acSetCursor ( aesCUR_ABSPOS, (baseRow + 2), (baseCol + prOFF2) ) ;
      this->acEraseArea ( aesERASE_EOL ) ;
   }  // (prompt==2)

   //* Third set of tests: Ctrl+C signal capture.*
   else if ( prompt == 3 )
   {
      gsOut.compose ( userPrompt3, p2 ) ;
      this->acWrite ( baseRow++, baseCol, gsOut ) ;

      //* Get user response *
      this->acRead ( gsIn )  ;

   }  // (prompt==3)
   //* Same as prompt #1, except with non-blocking read *
   else if ( prompt == 4 )
   {
      //* Display the prompt *
      this->acWrite ( baseRow++, baseCol, userPrompt1 ) ;

      this->acFlushIn () ;          // clear the input buffer

      //* Get user response: (Yes, this is complicated, but it's fun.)  *
      //* Call acReadNB() in a loop, until Enter keycode is received.   *
      //* The printing characters are echoed to the terminal by the     *
      //* soft-echo algorithm, while the encoded "special" keycodes are *
      //* returned to us for local processing.                          *
      //* -- This loop actually prints the special keys as verification *
      //*    that they were properly captured and encoded.              *
      //* -- When the non-blocking read returns without capturing a     *
      //*    keycode, we print a full-stop ('.') to indicate that we    *
      //*    are waiting for user input. Then, when user key input is   *
      //*    received, we backspace over the '.' characters and echo    *
      //*    the captured character to the terminal.                    *
      //*                          Fun!!                                *
      gString gstmp ;
      wchar_t wchar ;
      short   points = ZERO ;
      gsIn.clear() ;
      gsOut.clear() ;

      do
      {
         //* Read and echo printing characters (special keys not echoed).*
         if ( (this->acReadNB ( wchar, 10, 100, false )) )
         {
            while ( points-- > ZERO )
               this->ttyWrite ( "\b \b", false ) ;
            points = ZERO ;
            if ( (isprint ( wchar, *this->ioLocale )) )
            {
               if ( !(this->isSpecialKey ( wchar )) )
                  this->ttyWrite ( "\b \b", false ) ; // erase the echoed character
               this->ttyWrite ( wchar ) ;
               gsIn.append( "%C", &wchar ) ;
            }
            else
            { /* Do Nothing */ }
         }
         else
         {
            this->ttyWrite ( L'.' ) ;
            ++points ;
         }
      }
      while ( wchar != NEWLINE ) ;

      //* Dispay the captured data *
      this->acSetCursor ( ++baseRow, (baseCol + promptRESP1) ) ;
      this->ttyWrite ( gsIn ) ;
      this->acSetCursor ( aesCUR_NEXTROW ) ;
   }  // (prompt==4)

}  //* End tfPrompt() *

//*************************
//*     Test_ModFlags     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test text modifiers and the associated tracking flags:                       *
//*  1) intenseMod   : Bold and Faint                                            *
//*  2) italicMod    : Italic and Fractur                                        *
//*  3) ulineMod     : Underline                                                 *
//*  4) blinkMod     : Blink-slow and Blink-fast                                 *
//*  5) reverseMod   : Reversed fg/bg                                            *
//*  6) concealMod   : Concealed (invisible) text                                *
//*  7) xoutMod      : Xout (crossed-out) text                                   *
//*  8) frameMod     : Framed text and Encircled text                            *
//*  9) olineMod     : Overlined text                                            *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_ModFlags ( aeSeq fg )
{  // invoke: T m
  const wchar_t *Desc = 
  L"Text-attribute Modifiers Test:\n"
   "---------------------------------------------------------------------------\n"
   "In addition to setting the foreground/background color attributes for text,\n"
   "several other text modifiers are available to draw the user's attention.\n"
   "This test exercises each of the text modifiers defined by the ANSI standard\n"
   "(ANSI X3.64 (ISO/IEC6429)). Support for these modifiers is system dependent,\n"
   "but Gnometerm, Konsole and XTerm all support the common options.\n"
   "The internal tracking flags for these modifiers are also displayed.\n"
   "\n"
   "A few of the options in the original ANSI specification, such as \"Framed\"\n"
   "and \"Encircled\" are seldom if ever supported by modern terminal programs.\n" ;

   const short baseROW = 1 ;
   const short colOFF  = 28 ;
   const short descROW = 27 ;
   const short descCOL = 78 ;
   short row = 4,                         // display row
         col = 1,                         // display column
         drow = 3,                        // flag-report row
         dcol = 30 ;                      // flag-report column

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( baseROW, colOFF, 
                  "  (requires terminal dimensions of 39 rows X 155 columns)\n"
                  "---------------------------------------------------------" ) ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Write a description of the test *
   this->acWrite ( descROW, descCOL, Desc ) ;
   this->acSetCursor ( aesCUR_ABSPOS, baseROW, 1 ) ;

   //* Reset all mods to default values *
   this->acResetMods () ;
   this->acWrite ( row++, col, L"0) Plain Text" ) ;
   this->acDumpMods ( drow, dcol, L"0)Reset All" ) ; dcol += 16 ;

   //* Test Bold *
   this->acSetMod ( aesBOLD ) ;
   this->acWrite ( row++, col, L"1) Bold Text" ) ;
   this->acDumpMods ( drow, dcol, L"1)Bold On" ) ; dcol += 16 ;

   //* Add Underline to the Bold *
   this->acSetMod ( aesUNDERLINE ) ;
   this->acWrite ( row, col, L"2) Bold, Underlined Text" ) ;   row += 2 ;
   this->acDumpMods ( drow, dcol, L"2)UnderlineOn" ) ; dcol += 16 ;

   //* Clear Bold and Underline *
   this->acSetMod ( aesBOLD_OFF ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;
   this->acWrite ( row++, col, L"3) Plain Text" ) ;
   this->acDumpMods ( drow, dcol, L"3)Plain Text" ) ; dcol += 16 ;

   //* Faint text *
   this->acSetMod ( aesFAINT ) ;
   this->acWrite ( row++, col, L"4) Faint Text" ) ;
   this->acDumpMods ( drow, dcol, L"4)Faint Text" ) ; dcol += 16 ;

   //* Back to plain text *
   this->acSetMod ( aesBOLD_OFF ) ;
   this->acWrite ( row++, col, L"5) Plain Text" ) ;
   this->acDumpMods ( drow, dcol, L"5)Plain Text" ) ; dcol += 16 ;

   //* Overline on *
   this->acSetMod ( aesOVERLINE ) ;
   this->acWrite ( row, col, L"6) Overline On" ) ;    row += 2 ;
   this->acDumpMods ( drow, dcol, L"6)Overline On" ) ; dcol += 16 ;

   //* Add slow-blinking to overline
   this->acSetMod ( aesBLINK_SLOW ) ;
   this->acWrite ( row, col, L"7) Slow Blink" ) ;     row += 2 ;
   this->acDumpMods ( drow, dcol, L"7)Slow Blink" ) ; dcol += 16 ;

   drow += 12 ; dcol = 30 ;               // next report row

   //* Fast blink *
   this->acSetMod ( aesBLINK_FAST ) ;
   this->acWrite ( row, col, L"8) Fast Blink" ) ;     row += 2 ;
   this->acDumpMods ( drow, dcol, L"8)Fast Blink" ) ; dcol += 16 ;

   //* Reset blink and Overline *
   this->acSetMod ( aesBLINK_OFF ) ;
   this->acSetMod ( aesOVERLINE_OFF ) ;
   this->acDumpMods ( drow, dcol, L"9)Plain Text" ) ; dcol += 16 ;
   this->acWrite ( row, col, L"9) Plain Text" ) ;     row += 2 ;

   //* Set Italic text *
   this->acSetMod ( aesITALIC ) ;
   this->acDumpMods ( drow, dcol, L"10)Italic" ) ; dcol += 16 ;
   this->acWrite ( row, col, L"10)Italic Text" ) ;    row += 2 ;

   //* Add Reverse fg/bg to the Italic *
   this->acSetMod ( aesREVERSE ) ;
   this->acDumpMods ( drow, dcol, L"11)Reverse" ) ; dcol += 16 ;
   this->acWrite ( row, col, L"11) Italic/Reversed Text" ) ;   row += 2 ;

   //* Reset Italic and Reversed fg/bg, set X-out (strikethrough) *
   this->acSetMod ( aesITALIC_OFF ) ;
   this->acSetMod ( aesREVERSE_OFF ) ;
   this->acSetMod ( aesXOUT ) ;
   this->acDumpMods ( drow, dcol, L"12)Xout" ) ; dcol += 16 ;
   this->acWrite ( row++, col, L"12) Xout Text" ) ;

   //* Point to concealed text, then conceal the text *
   this->acSetMod ( aesXOUT_OFF ) ;
   this->acWrite ( row++, col, L"| Concealed Text Below |" ) ;
   this->acSetMod ( aesCONCEAL ) ;
   this->acDumpMods ( drow, dcol, L"13)Conceal" ) ; dcol += 16 ;
   this->acWrite ( row++, col, L"13) Concealed Text" ) ;

   //* Reset concealed text *
   this->acSetMod ( aesCONCEAL_OFF ) ;
   this->acDumpMods ( drow, dcol, L"14)Plain Text" ) ; dcol += 16 ;
   this->acWrite ( row++, col, L"14) Plain Text" ) ;

   //* Test "Fractur" (italicMod). (In practice, this is rendered as italic.)*
   this->acSetMod ( aesFRACTUR ) ;
   this->acDumpMods ( drow, dcol, L"15)Fractur" ) ; dcol += 16 ;
   this->acWrite ( row++, col, L"15) Fractur Text" ) ;

   drow += 12 ; dcol = 30 ;               // next report row

   //* Reset Fractur (italicMod), then: test "Framed". (frameMod) *
   this->acSetMod ( aesITALIC_OFF ) ;
   this->acSetMod ( aesFRAMED ) ;
   this->acDumpMods ( drow, dcol, L"16)Framed" ) ; dcol += 16 ;
   this->acWrite ( row++, col, L"16) Framed Text" ) ;

   //* Test "Encircle". (frameMod flag) *
   this->acSetMod ( aesFRM_ENC_OFF ) ;
   this->acSetMod ( aesENCIRCLE ) ;
   this->acWrite ( row, col, L"17) Encircled Text" ) ;  row += 2 ;
   this->acDumpMods ( drow, dcol, L"17)Encircle" ) ; dcol += 16 ;

   //* Return all mods to default values *
   this->acResetMods () ;
   this->acWrite ( row, col, L"18) Plain Text - Exit" ) ;
   this->acDumpMods ( drow, dcol, L"18)Reset All" ) ;

   //* Prepare to exit *
   this->acSetCursor ( aesCUR_ABSPOS, (this->termRows - 1), 1 ) ;
   this->acSetFg ( aesFG_DFLT ) ;

}  //* End Test_ModFlags() *

//*************************
//*     Test_NonBlock     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Non-breaking read from stdin:                                                *
//* =============================                                                *
//*  Our testing is done with input buffering disabled via terminal settings     *
//*  and non-blocking input stream set through the (virtual) file descriptor.    *
//*                                                                              *
//* 1) Library calls cannot directly be made non-blocking, so first, the         *
//*    file descriptor (actually virtual file descriptor) must be set as         *
//*    non-blocking.                                                             *
//*                                                                              *
//* 2) When the input stream is set for non-blocking read, the std::wcin.peek()  *
//*    method is able to query the input stream.                                 *
//*    The documentation says that the return value is:                          *
//*       If good() == true, peek() returns the next character in the stream.    *
//*       Otherwise, it returns Traits::eof(). (WEOF==-1)                        *
//*                                                                              *
//* 3) Although a low-level C function, poll() can be used to query the stream,  *
//*    we prefer to remain (as much as possible) in the land of C++ because      *
//*    the dinosaurs and UNIX programmers in C-land make us nervous.             *
//*    Unfortunately, we need to dip our toe in the C-esspool for the            *
//*    fcntl() function in order to set or reset the non-blocking read flag.     *
//*                                                                              *
//* 4) fcntl() function:                                                         *
//*    int fd = 0 ;     // zero (0) identifies the 'stdin' input stream          *
//*    fcntl(fd, F_GETFL, 0)                                                     *
//*    F_GETFL  This macro requests the current flag settings: The file          *
//*             access mode and the status flags are returned.                   *
//*             (Note that the third argument of the call is    )                *
//*             (ignored, but we set it to zero just to be safe.)                *
//*                                                                              *
//*    F_SETFL  This macro sets the flags for the specified stream:              *
//*       From the man page:                                                     *
//*       Set  the file status flags to the value specified by arg.              *
//*       File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation        *
//*       flags (i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored.   *
//*       On Linux, this command can change only the O_APPEND, O_ASYNC,          *
//*       O_DIRECT, O_NOAT‐IME, and O_NONBLOCK flags.                            *
//*       It is not possible to change the O_DSYNC and O_SYNC flags.             *
//*                                                                              *
//*       The O_NONBLOCK flag is the flag of interest here.                      *
//*       #include <fcntl.h>                                                     *
//*       int fd = 0 ;                            // 0 == stdin stream           *
//*       int flags = fcntl(fd, F_GETFL, 0);      // get current flags           *
//*       fcntl(fd, F_SETFL, flags | O_NONBLOCK); // set the O_NONBLOCK flag     *
//*                                                                              *
//********************************************************************************

void AnsiCmd::Test_NonBlock ( aeSeq fg )
{  // invoke: T b
   #define CALL_NONBLK (1) // Select between direct access and call to AnsiCmd method
   #if CALL_NONBLK != 0
   uint16_t msDelay = 100,                // delay in mSec for acReadNB(): 0.1 sec
            rptCnt = 8 ;                  // polling repeat count for acReadNB()
   bool     status ;                      // return value from acReadNB()
   #else // (CALL_NONBLK==0)
   short    tsec = 5 ;                    // sleep interval (tenths of a second)
   wchar_t  pv2 ;                         // peek-value 2
   #endif   // CALL_NONBLK

   const short baseROW = 4 ;              // first output row
   gString gsOut ;                        // text formatting
   WinPos wp( baseROW, 1 ) ;               // initial output position
   uint32_t pval ;                        // returned value from peek()
   wchar_t wch ;                          // returned value from wcin()
   uint32_t iost ;                       // ios_base::iostate (istream flags)
   bool good, fail, bad, eof ;             // input-stream status flags

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* User Prompt *
   wp = this->acWrite ( wp, 
         L"Test non-blocking read of input stream, 'stdin'.\n"
          "Type a few characters, then wait a moment and type a few more.\n"
          "The value returned by peek() reports whether data are available\n"
          "and if so, get() captures the data.  " ) ;
   this->acSetMod ( aesUNDERLINE ) ;
   this->acSetMod ( aesBOLD ) ;
   this->ttyWrite ( L"Press ENTER to exit:\n" ) ;
   this->acSetMod ( aesBOLD_OFF ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   if ( (ofsdbg.is_open()) )
   {
      gsdbg.compose( "Test_Nonblock1 ( inpBuf:%hhd inpEch:%hd inpBlk:%hhd cchType:%hd )", 
                     &this->tset->inpBuf, &this->tset->inpEch, 
                     &this->tset->inpBlk, &this->tset->cchType ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

   //* Set stdin stream for non-blocking read  *
   //* (As a side-effect, input buffering is ) *
   //* (disabled, and echo is set to 'noEcho') *
   this->acBlockingInput ( false ) ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   if ( (ofsdbg.is_open()) )
   {
      gsdbg.compose( "Test_Nonblock2 ( inpBuf:%hhd inpEch:%hd inpBlk:%hhd cchType:%hd )", 
                     &this->tset->inpBuf, &this->tset->inpEch, 
                     &this->tset->inpBlk, &this->tset->cchType ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

   do
   {
      #if CALL_NONBLK != 0              // Call AnsiCmd method
      //**************************
      //* Query the input stream *
      //**************************
      //* Note: 'silent' parameter for this call is set to 'false'. *
      //* This is done so that any "special" keys will be decoded.  *
      //* However, the 'noEcho' option set above overrides, so the  *
      //* call will not write to display.                           *
      status = this->acReadNB ( wch, rptCnt, msDelay, false, &iost ) ;
      good = bool(iost & std::wistream::goodbit) ;
      fail = bool(iost & std::wistream::failbit) ;
      bad  = bool(iost & std::wistream::badbit) ;
      eof  = bool(iost & std::wistream::eofbit) ;

      //* Don't print non-printing characters *
      if ( (isprint ( wch, *this->ioLocale )) ) pval = wch ;
      else                                      pval = L'-' ; 
      gsOut.compose( "[%04X '%C' g:%hhd,f:%hhd,b:%hhd,e:%hhd]\n", 
                    &wch, &pval, &good, &fail, &bad, &eof ) ;
      this->ttyWrite ( gsOut ) ;

      if ( status != false )               // if valid character data
      {
         if ( (isprint ( wch, *this->ioLocale )) )
            gsOut.compose( "[%04X] '%C'\n", &wch, &wch ) ;
         else
            gsOut.compose( "[%04X] '-'\n", &wch ) ;
         this->ttyWrite ( gsOut ) ;
         this->nsleep ( 1 ) ;             // pause for 1/10 second
      }

      // ------------------------------------------------------------------
      // ------------------------------------------------------------------
      #else                             // Call library methods directly
      this->nsleep ( tsec ) ;             // wait a moment for input

      //**************************
      //* Query the input stream *
      //**************************
      if ( this->wideStream )             // wide-stream input
      {
         pval = std::wcin.peek () ;
         iost = std::wcin.rdstate() ;
         good = bool(iost & std::wistream::goodbit) ;
         fail = bool(iost & std::wistream::failbit) ;
         bad  = bool(iost & std::wistream::badbit) ;
         eof  = bool(iost & std::wistream::eofbit) ;
      }
      else                              // narrow-stream input
      {
         pval = std::cin.peek () ;
         good = bool(iost & std::istream::goodbit) ;
         fail = bool(iost & std::istream::failbit) ;
         bad  = bool(iost & std::istream::badbit) ;
         eof  = bool(iost & std::istream::eofbit) ;
      }

      //* Do not echo the WEOF or control characters *
      // Programmer's Note: The "special" keys are not decoded in this 
      // direct-access test, so partial escape sequences will appear as 
      // garbage in the output. See how the asReadNB() method is used in the 
      // production-worthy test above to eliminate this garbage data.
      if ( (isprint ( wch, *this->ioLocale )) && (pval != L'\n') ) pv2 = pval ;
      else                                      pv2 = L'-' ;
      gsOut.compose( "[%04X '%C' g:%hhd,f:%hhd,b:%hhd,e:%hhd]\n", 
                    &pval,  &pv2, &good, &fail, &bad, &eof ) ;
      this->ttyWrite ( gsOut ) ;
      tsec = eof ? 5 : 1 ;                // adjust pause interval

      //*****************************************
      //* If data available, get next character *
      //* from the input stream.                *
      //*****************************************
      if ( pval != WEOF )
      {
         if ( this->wideStream )             // wide-stream input
            wch = std::wcin.get() ;
         else                              // narrow-stream input
            wch = wchar_t(std::cin.get()) ;
         if ( (isprint ( wch, *this->ioLocale )) )
            gsOut.compose( "[%04X] '%C'\n", &wch, &wch ) ;
         else
            gsOut.compose( "[%04X] '-'\n", &wch, &wch ) ;
         this->ttyWrite ( gsOut ) ;
         this->nsleep ( 1 ) ;             // pause for 1/10 second
      }
      //* If peek() returned an error i.e. no data available, *
      //* clear the error code and continue.                  *
      else
      {
         if ( this->wideStream )           // wide-stream input
            std::wcin.clear() ;
         else                              // narrow-stream input
            std::cin.clear() ;
      }
      #endif   // CALL_NONBLK
   }
   while ( wch != NEWLINE ) ;

   //* Say Good Night, Gracie *
   this->ttyWrite ( L"----- Bye-bye! -----\n\n" ) ;

   //* Return terminal to buffered input with   *
   //* terminal echo AND blocking read of input.*
   this->acBufferedInput ( true, termEcho ) ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   if ( (ofsdbg.is_open()) )
   {
      gsdbg.compose( "Test_Nonblock3 ( inpBuf:%hhd inpEch:%hd inpBlk:%hhd cchType:%hd )", 
                     &this->tset->inpBuf, &this->tset->inpEch, 
                     &this->tset->inpBlk, &this->tset->cchType ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

}  //* End Test_NonBlock() *

//*************************
//*     Test_RawInput     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_RawInput ( aeSeq fg )
{  // invoke: T r
   uint16_t msDelay = 100,                // delay in mSec for acReadNB(): 0.1 sec
            rptCnt = 8 ;                  // polling repeat count for acReadNB()
   const short baseROW = 4 ;              // first output row
   const short maxROWS = 24 ;             // height of output column
   gString gsOut ;                        // text formatting
   WinPos wp( baseROW, 1 ) ;              // output position
   wchar_t wch, wchp ;                    // input value
   short   baseCol = 1,                   // initial output column
           outRow ;                       // top output row

   this->acSetCursor ( wp ) ;
   wp = this->acWrite ( wp, 
         L"Report raw data from the input stream, 'stdin'.\n" ) ;
   this->acSetMod ( aesUNDERLINE ) ;
   this->acSetMod ( aesBOLD ) ;
   wp = this->acWrite ( wp, L"Press SPACEBAR to exit:\n\n" ) ;
   this->acSetMod ( aesBOLD_OFF ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;
   outRow = wp.row ;                      // remember top row

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   this->acBufferedInput ( false, noEcho, false ) ;       // Non-blocking read
   this->acCaptureBreakSignal ( true, cchtCopy, false ) ; // capture and convert break signal

   do
   {
      if ( (this->acReadNB ( wch, rptCnt, msDelay, true )) )
      {
         if ( wch != wcsSPACE )
         {
            wchp = wcsSPACE ;       // printable character
            if ( (iswprint ( wch )) )
               wchp = wch ;
            gsOut.compose( "%02X %C\n", &wch, &wchp ) ;
            wp = this->acWrite ( wp, gsOut ) ;

            if ( wp.row > maxROWS )
            {
               wp.row = outRow ;
               wp.col = baseCol += 8 ;
               this->acSetCursor ( wp ) ;
            }
         }
      }
   }
   while ( wch != wcsSPACE ) ;

   this->acCaptureBreakSignal ( false ) ;                // terminal break handler
   this->acBufferedInput ( true, termEcho, true ) ;      // terminal read
   this->acSetCursor ( (baseROW + maxROWS - 3), 1 ) ;

   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

}  //* End Test_RawInput() *

//*************************
//*   Test_OutputStream   *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Exercise the output stream using the ttyWrite() group of methods.            *
//*                                                                              *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* There are several overloads for the ttyWrite() method with specific types    *
//* of source data:                                                              *
//*   a) gString object: for wide stream (wcout), write wchar_t data, and        *
//*                      for narrow stream (cout), write UTF-8 data.             *
//*   b) wchar_t*:       for wide stream, write to wcout                         *
//*                      for narrow stream, write to cout                        *
//*   c) wchar_t character: convert character to string and redirect to          *
//*                         wchar_t* overload                                    *
//*   d) char*: for wide stream (wcout), convert data to wchar_t,                *
//*             then write to wcout.                                             *
//*             for narrow stream, write directly to cout.                       *
//*   e) char character: convert character to string and redirect to             *
//*                      char* overload                                          *
//*   f) uint8_t*: redirect to char* overload                                    *
//*                                                                              *
//* -- The basic test is to simply write some wide text and some narrow text.    *
//*    If this text is displayed correctly, then it is safe to perform the more  *
//*    complex tests.                                                            *
//*                                                                              *
//* -- This method includes a special test of 'ttyWrite' which converts UTF-8    *
//*    data to UTF-32 before writing it to stdout.                               *
//*    This conversion is necessary because multi-byte characters are not handled*
//*    correctly when written to the wide output stream. The test simply sends   *
//*    a long string of UTF-8 characters with mixed English and Chinese.         *
//*                                                                              *
//* -- To test the narrow (byte-oriented output stream, it is necessary to       *
//*    invoke the application using the "--term=W:n" option.                     *
//*    Note: If narrow stream, a pointer in hex is printed before the mixed-width*
//*          data. It appears to be artifacts from transmitting escape sequences.*
//*          It is not clear whether this is just an artifact or an actual bug.  *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*    See the AnsiCmd ttyWrite method for more information.                     *
//********************************************************************************

void AnsiCmd::Test_OutputStream ( aeSeq fg )
{  // invoke: T o
   const wchar_t * mixedData = 
      L"Abcde成吉思汗Fghij,Abcde成吉思汗Fghij,Abcde成吉思汗Fghij,Abcde成吉思汗Fghij,"
       "Abcde成吉思汗Fghij,Abcde成吉思汗Fghij,Abcde成吉思汗Fghij,Abcde成吉思汗Fghij,\n" ;

   const short baseROW = 3 ;                    // first output row
   const short exitROW = this->termRows - 3 ;   // target row for exit
   const short dataROW = baseROW + 4 ;          // data output position
   const short colOFF  = 28 ;
   gString gsOut ;                              // text formatting
   uint8_t *ubuff = new uint8_t[gsALLOCMED * 2] ;  // output buffer (8Kb)
   wchar_t  userChar ;                          // user response
   short    ubytes ;                            // data bytes in ubuff
   bool     done = false ;                      // loop control

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, colOFF, 
                  L"  (requires terminal dimensions of 36 rows X 152 columns)\n"
                   "---------------------------------------------------------" ) ; 
   //* Set the foreground color *
   this->acSetFg ( fg ) ;
   //* Report whether wide or narrow output stream is active.*
   gsOut.compose( " %S output stream ", (wchar_t*)(this->wideStream ? L"wide" : L"narrow") ) ;
   this->acSetMod ( aesREVERSE ) ;
   this->ttyWrite ( gsOut ) ;
   this->acSetMod ( aesREVERSE_OFF ) ;

   this->acBufferedInput ( false, noEcho, false ) ;       // Non-blocking read
   this->acCaptureBreakSignal ( true, cchtExit, false ) ; // capture break signal
   this->acSetCursorStyle ( csBblock ) ;

   const wchar_t *Prompt = L"Select a test: " ;
   while ( ! done )
   {
      //* Display instructions (incl. note for "narrow" stream) *
      this->acSetCursor ( aesCUR_ABSPOS, baseROW, ZERO ) ;
      gsOut.compose( "%S\n"
                     "   a) Long UTF-8 source data\n"
                     "   q) Quit\n", Prompt ) ;
      this->ttyWrite ( gsOut ) ;
      this->acSetCursor ( aesCUR_ABSPOS, baseROW, ZERO ) ;
      gsOut.compose( "%S", Prompt ) ;
      this->ttyWrite ( gsOut ) ;
      this->acSetCursorStyle ( csShow ) ;
      userChar = this->acRead () ;        // get user selection
      this->acSetCursorStyle ( csHide ) ;

      switch ( userChar )
      {
         case L'a': case L'A':
            this->acSetCursor ( aesCUR_ABSPOS, dataROW, ZERO ) ;
            this->acEraseArea ( aesERASE_BOW ) ;
            gsOut = mixedData ;
            while ( ubytes < 4600 )
            {
               gsOut.copy( &ubuff[ubytes], gsOut.utfbytes() ) ;
               ubytes += gsOut.utfbytes() - 1 ;
            }
            ubuff[ubytes] = NULLCHAR ; // ensure a null terminator
            this->ttyWrite ( ubuff ) ;
            break ;
         case L'q': case L'Q':
            done = true ;
            break ;
         default:
            this->acBeep () ;
            break ;
      }
   }  // while()

   //* Prepare to exit *
   this->acSetCursorStyle ( csShow ) ;                   // make cursor visible
   this->acSetCursorStyle ( csDflt ) ;                   // return to default cursor style
   if ( ubuff != NULL ) { delete [] ubuff ; }            // release dynamic allocation
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, ZERO ) ;
   this->acEraseArea ( aesERASE_BOW ) ;
   this->ttyWrite ( L"Test of output stream complete.\n" ) ;

   this->acCaptureBreakSignal ( false ) ;                // terminal break handler
   this->acBufferedInput ( true, termEcho, true ) ;      // terminal read

   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

}  //* End Test_OutputStream() *

//*************************
//*     Test_SoftEcho     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//*                                                                              *
//* Input  : eOpt: Input-echo option for unbuffered input.                       *
//*                'a' All special keys translations active.                     *
//*                'b' Backspace and other edit keys translation active.         *
//*                'c' Cursor-movement keys translation active.                  *
//*                'd' Disable special-key translation.                          *
//*                't' Terminal echo                                             *
//*                'n' No echo                                                   *
//*                'r' Report                                                    *
//*          fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note: For tests which use direct read of the input stream, the  *
//* stream is explicitly set to unbuffered input for the specified test.         *
//* For tests which gather input through the ACWin user interface, buffering     *
//* configuration is handled automatically.                                      *
//********************************************************************************

static uint8_t TSE_SHHH = 0xFE ; // "secret" code to execution of test method from application level

void AnsiCmd::Test_SoftEcho ( wchar_t eOpt, aeSeq fg )
{  // invoke: T e [a|b|c|d|e|r|t]
   const short baseROW = 7 ;              // base output row
   const short baseCOL = 3 ;              // base output column
   const short instROW = 4 ;              // position for instructions text
   const short instCOL = 3 ;
   const short dumpCOL = 78 ;
   const short dumpOPT = 4 ;              // verbose term-setup dump
   const short colOFF  = 32 ;             // offset for title extension
         short exitROW = baseROW + 22 ;   // target row for exit
   TermIos tios ;                         // for reporting terminal settings
   gString gsOut ;                        // text formatting
   WinPos wp,                             // cursor position
          wpDump ;                        // Position for term-info dump
   wchar_t wch ;

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, colOFF, 
                  L"  (requires terminal dimensions of 36 rows X 155 columns)\n"
                   "---------------------------------------------------------" ) ; 

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Set terminal configuration for unbuffered input and *
   //* specified soft echo, term-echo or no-echo option.   *
   EchoOpt ech ;           // echo option from caller
   switch ( eOpt )
   {
      case 'a':                           // All special keys exercised
      case 'r':                           // Report all special keys
         ech = softEchoA ; break ;
      case 'b': ech = softEchoB ; break ; // Disable (discard) special keys
      case 'c': ech = softEchoC ; break ; // Cursor group of special keys
      case 'd': ech = softEchoD ; break ; // Disable (discard) special keys
      case 'e': ech = softEchoE ; break ; // Edit group of special keys: Bksp/Del/Ins
      case 'n': ech = noEcho ;    break ; // No echo of key input
      case 't': ech = termEcho ;  break ; // Terminal echo of key input
   } ;

   //* Display the names of the "special" keys *
   //* which are active for the current test.  *
   if ( (ech == softEchoA) && (eOpt != L'r') )
   {  // T e a    (windowed test)
      gsOut = L"Special Keys: LeftArrow, RightArrow, UpArrow, DownArrow, Home, End,\n"
               "PageUp, PageDown, Tab, Shift+Tab, Alt+Enter, Backspace, Delete, Insert.\n"
               "Shift Key plus: Up | Down | Right | Left (select)\n"
               "Ctrl Key plus : A (select/deslect all), C (copy), X (cut), V (paste)\n" ;
   }
   else if ( ech == softEchoA )
   {  // T e r    (report All special keys)
      gsOut = L"Special Keys: LeftArrow, RightArrow, UpArrow, DownArrow, Home, End,\n"
               "PageUp, PageDown, Tab, Shift+Tab, Alt+Enter, Backspace, Delete, Insert.\n"
               "Shift Key plus: Up | Down | Right | Left --- plus Ctrl+[A-Z] Alt+[A-Z]\n" 
               "Ctrl Key plus : Up | Down | Right | Left | Home | End\n" 
               "Alt Key plus  : Up | Down | Right | Left | Home | End | PgUp | PgDown\n" ;
   }
   else if ( ech == softEchoB )
   {  // T e b    (report special keys Excluding 'selection' group)
      gsOut = L"Special Keys: LeftArrow, RightArrow, UpArrow, DownArrow, Home, End,\n"
               "PageUp, PageDown, Tab, Shift+Tab, Alt+Enter, Backspace, Delete, Insert.\n" ;
   }
   else if ( ech == softEchoC )
   {  // T e c    (cursor movement keys only)
      gsOut = L"Special Keys: LeftArrow, RightArrow, UpArrow, DownArrow, Home, End,\n"
               "Ctrl+Home, Ctrl+End, PageUp, PageDown, Tab, Shift+Tab.\n" ;
   }
   else if ( ech == softEchoD )
   {  // T e d    (disable all special keycodes, printing chars and GUI keys are reported)
      gsOut = L"Special Keys: (all special keys discarded)\n" ;
   }
   else if ( ech == softEchoE )
   {  // T e e
      gsOut = L"Special Keys: Backspace, Delete, Insert, Tab, Shift+Tab,\n"
               "              PageUp, PageDown, Alt+Enter\n" ;
   }
   else if ( ech == noEcho )
   {  // T e n
      gsOut = L"Special Keys: All special keys, GUI keycodes and printing characters\n"
               "are received and decoded, but are not echoed to the display.\n" ;
   }
   gsOut.append( L"-----------------------------------------------------------------------\n" ) ;
   wpDump = this->acWrite ( instROW, dumpCOL, gsOut ) ;

   //* If not a windowed interface, write instructions to terminal window *
   if ( (ech != softEchoA) || ((ech == softEchoA) && (eOpt == L'r')) )
   {
      gsOut.compose( "Key Input Echo Option: %S\n",
                    (ech == softEchoA) ? L"softEchoA (All Special keys translated)" :
                    (ech == softEchoB) ? L"softEchoB (Basic set, exclude selection keys)" :
                    (ech == softEchoC) ? L"softEchoC (Cursor movement keys only)" :
                    (ech == softEchoD) ? L"softEchoD (Special Key translations discarded)" :
                    (ech == softEchoE) ? L"softEchoE (Edit: Bksp/Del/Ins/selection group)" :
                    (ech == termEcho)  ? L"Terminal Echo of Key Input\n"
                                          "(\"special\" keys will display as escape sequences)" : 
                    (ech == noEcho)    ? L"No Echo of Key Input\n"
                                          "(a beep indicates that keycode was received)"
                                       : L"Error!" ) ;
      gsOut.append( L"       " ) ;
      wp = this->acWrite ( instROW, instCOL, gsOut ) ;
      this->acSetMod ( aesUNDERLINE ) ;
      this->acSetMod ( aesBOLD ) ;
      this->acWrite ( wp, L"Press SPACEBAR to exit." ) ;
      this->acSetMod ( aesBOLD_OFF ) ;
      this->acSetMod ( aesUNDERLINE_OFF ) ;
   }

   //********************************************************
   //* softEchoA: This test creates an ACWin window object  *
   //* with an embedded skForm object containing 5 fields.  *
   //* 0) single-row field (floShift)                       *
   //* 1) multi-row field using basic line breaks (floToken)*
   //* 2) multi-row field using text-editor mode (floShift) *
   //* 3) a read-only field containing a copy of current    *
   //*    clipboard contents.                               *
   //* 4) a read-only field that reports the current state  *
   //*    of the Insert/Overstrike mode.                    *
   //*                                                      *
   //* -- This test includes user text selection and local  *
   //*    clipboard access. (Access to the system clipboard *
   //*    is not yet integrated into the AnsiCmd library).  *
   //********************************************************
   if ( (ech == softEchoA) && (eOpt != L'r') )
   {  // invoke: T e a
      const char* Instructions = 
            "Select or deselect text, copy/paste to/from the local clipboard.\n"
            "Shift+RightArrow or Shift+LeftArrow to select text incrementally.\n"
            "Ctrl+A = All data in field selected (or deselected).\n"
            "Ctrl+C = Copy to clipboard, Ctrl+X = Cut to clipboard,\n"
            "Ctrl+V = Paste from clipboard. Enter = line break." ;
      const short winROW = 3 ;
      const short winCOL = 2 ;
      const short winHEIGHT = 32 ;        // window dimensions
      const short winWIDTH  = 71 ;
      const short fldCLIP   = 3 ;         // index of clipboard reporting field
      //* Color Attributes:                              *
      //* Border and interior inherit terminal defaults. *
      //* First two Textboxes inherit window defaults.   *
      //* Third Textbox receives brown fg, inherits bg.  *
      const acAttr bdrAttr = acAttr(acaFG_DFLT | acaBG_DFLT), // border attributes
                   intAttr = acAttr(acaFG_DFLT | acaBG_DFLT), // interior attributes
                   tb2Attr = acAttr(acaFG_BROWN | acaBG_DFLT), // ftTB index 2 attributes
                   ioIns = acAttr(acaFGb_GREY | acaBG_GREEN), // insert mode field attribute
                   ioOvr = acAttr(acaFG_BLUE  | acaBG_GREY),  // overstrike mode field attribute
                   clipAttr = ioOvr,                        // clipboard textbox attributes
                   clipHdr = acAttr(acaFG_GREEN | acaBG_DFLT | acaREVERSE) ; // heading attributefor clipboard display field
      short f = ZERO ;                    // field index for initialization
      WinPos wpul( 1, 2 ),                // initialize field origin
             wpIns( 2, 63 ) ;             // offset of ins/ovr report

      //* Construct an skForm-class object and initialize it.     *
      //* The initialized object is passed as a parameter to the  *
      //* ACWin-class constructor where validation is performed.  *
      //* Defined Fields:                                         *
      //*   0) Single-row text field                              *
      //*   1) Multi-row text field (word-processing mode)
      //*   2) Multi-row text field (text-editor mode)            *
      //*   3) Clipboard display text field (read only)           *
      //*   4) Insert/Overstrike radio-button field (read only)   *
      //* -- This tests is used to validate the copy-and-paste    *
      //*    functionality of the local (inter-field) clipboard,  *
      //*    and the system clipboard (if connected).             *
      //* Initialize an array of skField objects.                 *
      //* Items left unspecified will receive default values.     *
      const short FLD_COUNT = 5 ;

      skForm *skfPtr = new skForm
      (
         FLD_COUNT,        // number of skField objects in array
         new skField[FLD_COUNT]
         {
            //* Field index 0: ftTB (Textbox) *
            {
               ftTB,                         // field type
               1,                            // field height (in rows)
               winWIDTH - 6,                 // field width (in columns)
               wpul.row, wpul.col,           // Y/X offset in window
               "Scroll rightward and leftward to test horizontal " // initial text data
               "scrolling within the single-row field.",
               acaUSEDFLT,                   // color attributes (window default fg/bg)
               floShift,                     // text formatting option (text-editor mode)
               ZERO,                         // insertion point (cursor offset)
               false,                        // user accessible (not read-only)
               true,                         // field outline enabled
               false,                        // field is visible (not hidden)
               true,                         // access lock enabled
               false,                        // binary state not applicable to ftTB
               NULLCHAR,                     // no hotkey specified
               noGROUP                       // not a group member
            },
   
            //* Field index 1: ftTB (Textbox) *
            {
               ftTB,                         // field type
               5,                            // field height (in rows)
               winWIDTH - 6,                 // field width (in columns)
               short(wpul.row += 3),         // Y/X offset in window
               wpul.col,
               // initial text data (either 2 or 3 pages of data)
               "Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu \n"
               "Vv Ww Xx Yy Zz. 12345 67890 12345 67890 12345 67890 12345 67890. \n"
               "The Up, Down, Right, and Left arrow keys, Home, End, PageUp, \n"
               "PageDown, Tab, and Shift+Tab navigate through the text and among \n"
               "the fields. The Insert key toggles Insert/Overstrike input mode. \n"
               "There once was a man from Kirkuk, \n"
               "Who was known far and wide for his luck, \n"
               "Till while jogging one day, he looked the wrong way, \n"
               #if 1    // 2 pages
               "Whereupon he was hit by a truck.",
               #else    // 3 pages
               "Whereupon he was hit by a truck.\n"
               "There once was a bloke from Iran, \n"
               "Who while jogging as fast ere he can, \n"
               "Got some dust in his eye, \n"
               "Which did cause him to cry, \n"
               "Whereupon he was hit by a van. ",
               #endif   // 3 pages
               acAttr(acaFG_DFLT| acaBG_DFLT),// color attributes (window default fg/bg)
               floToken,                     // text formatting option (text-editor mode)
               ZERO,                         // insertion point (cursor offset)
               false,                        // user accessible (not read-only)
               true,                         // field outline enabled
               false,                        // field is visible (not hidden)
               true,                         // access lock enabled
               false,                        // binary state not applicable to ftTB
               NULLCHAR,                     // no hotkey specified
               noGROUP                       // not a group member
            },
   
            //* Field index 2: ftTB (Textbox) *
            {
               ftTB,                         // field type
               3,                            // field height (in rows)
               winWIDTH - 14,                // field width (in columns)
               short(wpul.row += 7),         // Y/X offset in window
               short(wpul.col += 4),
               "//* This field is configured for text-editor mode. *\n"
               "/* 此字段即配置为文本编辑器模式。 */ short 插入 = 4, 抵消 = 6 ;\n"
               "trgHeight = maxHeight - 插入 - (抵消 >= ZERO ? 抵消 : ZERO);\n"
               "trgWidth  = (dlgWid > ZERO) ? (dlgWid - 插入) : ((maxWidth / 2) - 插入);\n"
               "if ( trgWidth > (availwid - 插入) ) {trgWidth = availwid - 插入;}\n"
               "this->FormatParagraph ( gsStatic, trg即Height, trgWidth, false, false, &truncindx );\n"
               "this->ParagraphSize ( gsStatic, msgHeight, msgWidth );\n"
               "dlgWid = msgWidth + 插入 ; // new dialog width (text plus 即padding)",
               tb2Attr, // color attributes (brown fg and window default bg)
               floShift,                     // text formatting option (text-editor mode)
               ZERO,                         // insertion point (cursor offset)
               false,                        // user accessible (not read-only)
               true,                         // field outline enabled
               //false,                      // field is visible (not hidden) (default)
               //true,                       // access lock enabled (default)
               //false,                      // binary state not applicable to ftTB (default)
               //NULLCHAR,                   // no hotkey specified (default)
               //noGROUP                     // not a group member (default)
            },

            //* Field index 3: ftTB (Textbox - Report Clipboard Contents) *
            {
               ftTB,                         // field type
               6,                            // field height (in rows)
               short(winWIDTH - 6),          // field width (in columns)
               short(wpul.row += 7),         // Y/X offset in window
               short(wpul.col - 4),
               "(empty)",                    // initial text
               clipAttr,                     // color attribute
               floChar,                      // text formatting option (brain-dead auto-format)
               ZERO,                         // insertion point (cursor offset)
               true,                         // field is read-only (can't receive input focus)
               true,                         // field outline enabled
               //false,                      // field is visible (not hidden) (default)
               //true,                       // access lock enabled (default)
               //false,                      // binary state not applicable to ftTB (default)
               //NULLCHAR,                   // no hotkey specified (default)
               //noGROUP                     // not a group member (default)
            },
   
            //* Field index 4: ftPB (Pushbutton - Report Insert/Overstrike Mode) *
            {
               ftPB,                         // field type
               1,                            // field height (in rows)
               5,                            // field width (in columns)
               short(winHEIGHT - 3),         // Y/X offset in window
               short(winWIDTH - wpul.col - 3),
               " INS ",                      // initial text data
               ioIns,                        // color attribute
               floShift,                     // text formatting option (n/a)
               ZERO,                         // insertion point (cursor offset)
               true,                         // read-only field
               //false,                      // field outline disabled (default) (receives outline below)
               //false,                      // field is visible (not hidden) (default)
               //true,                       // access lock enabled (default)
               //false,                      // binary state not applicable to ftTB (default)
               //NULLCHAR,                   // no hotkey specified (default)
               //noGROUP                     // not a group member (default)
            }
         }     // (skField array)

         //* Initialize remaining skForm parameters.       *
         //* Optional arguments are set to default values. *
         //ZERO,           // index of field with initial focus
         //acaUSEDFLT,     // default color attributes for control with focus
         //true,           // beep on unhandled keycode received
         //true,           // initial state of Insert/Overstrike flag
         //true,           // user access to Insert/Overstrike mode
         //false,          // enable cursor shape change for Insert/Overstrike
         //csBblock,       // cursor shape when Insert mode
         //csBuline        // cursor shape when Overstrike mode
         ) ;                                    


      //**************************
      //* Define an ACWin object *
      //**************************
      ACWin *win = new ACWin
            ( winROW, winCOL,                // position (upper-left corner) 
              winHEIGHT, winWIDTH,           // dimensions (height, width)
              ltSINGLE,                      // border style
              bdrAttr,                       // border attributes
              intAttr,                       // interior attributes
              " Text Selection and Clipboard Access ", // window title
              skfPtr
            ) ;

      //* Note: Opening the window disables input buffering AND *
      //*       sets the echo option to 'softEchoA'.            *
      win->OpenWindow () ;

      //* Delete the local skForm object and reference   *
      //* the fully-initialized object within the window.*
      delete skfPtr ;                  // delete the local skForm object
      const skForm *skfp = win->GetForm () ;
      cbPtr = win ;                    // give callback method access to ACWin object

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      win->acGetTermInfo ( stdIn, tios ) ;
      win->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      wpul = { short(skfp->fld[fldCLIP].orig.row - 2), 
               skfp->fld[fldCLIP].orig.col } ;
      win->Write ( wpul, " Contents Of Local Clipboard ", clipHdr ) ;
      wpul.row += skfp->fld[fldCLIP].hgt + 3 ;
      win->Write ( wpul, Instructions, acAttr(acaFG_BROWN | acaBG_DFLT) ) ;

      win->EnableFootnotes ( true ) ;  // enable diagnostic footnotes
      #if 0    // Exercise the options of Footnote()
      gString gsDMsg( " This dog ain't gonna hunt! " ) ;
      win->Footnote ( gsDMsg.gstr(), 30 ) ;
      win->Footnote ( gsDMsg.ustr(), 30, acaUSEDFLT, true ) ;
      win->Footnote ( gsDMsg, 30, acAttr(acaFGb_GREEN | acaBG_BLACK) ) ;
      win->Footnote ( gsDMsg, 30, acAttr(acaFGb_BLACK | acaBGb_CYAN), true ) ;
      #endif   // Exercise the options of Footnote()
      win->Footnote ( L" Press CTRL+Q to exit. ", ZERO, acaUSEDFLT, true ) ;

      //* Establish a callback method to monitor user input.  *
      //* When the CTRL+Q key is received, EditForm() returns.*
      win->EstablishCallback ( &tseA_Callback ) ;

      //***************************
      //* Interact with the user. *
      //***************************
      win->EditForm ( f ) ;

      exitROW = winROW + winHEIGHT ;      // target row for exit

      //* Note: Closing the window re-enables input buffering *
      //*       AND resets the echo option to 'termEcho'.     *
      //* Note: For this test, window display is not erased.  *
      win->CloseWindow ( false ) ;
      cbPtr = NULL ;                      // reset the external pointer

      delete ( win ) ;        // release the allocated resourses

   }     // softEchoA && eOpt != 'r'

   else if ( ech == termEcho )
   {  // invoke: T e t
      short curCol = baseCOL ;   // base display column

      //* Set unbuffered input using the specified echo option.*
      this->acBufferedInput ( false, ech, true ) ;

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      this->acGetTermInfo ( stdIn, tios ) ;
      this->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      wp = { baseROW + 1, curCol } ;
      this->acSetCursor ( wp ) ;
      this->ttyWrite ( L">>>" ) ;

      do
      {
         if ( wp.row >= (exitROW - 2) )
         {
            wp = { short(baseROW + 1), short(curCol += 17) } ;
            this->acSetCursor ( wp ) ;
         }

         wch = this->acRead () ;

         if ( wch == NEWLINE )
            this->acSetCursor ( wp.row, (wp.col + 1) ) ;

         gsOut.compose( "  (0x%04X)\n", &wch ) ;
         this->ttyWrite ( gsOut ) ;
         if ( (wch == SPACE) && (curCol > baseCOL) )
            wp = { short(exitROW - 2), 1 } ;
         else
            wp = { short(wp.row + 1), curCol } ;
         this->acSetCursor ( wp ) ;
      }
      while ( wch != wcsSPACE ) ;
   }  // (ech==termEcho)

   else if ( ech == noEcho )
   {
      //* Set unbuffered input using the specified echo option,  *
      //* and capture the signals which interfere with key input.*
      this->acCaptureSignals ( ech ) ;

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      this->acGetTermInfo ( stdIn, tios ) ;
      this->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      wp = { short(baseROW + 1), baseCOL } ;
      this->acSetCursor ( wp ) ;
      this->ttyWrite ( L">>>" ) ;
      while ( (wch = this->acRead ()) != wcsSPACE )
         this->acBeep () ;
   }  // (ech==noEcho)

   //**************************************************************
   //* These non-windowed tests share an output format.           *
   //* The primary differences are the specified soft-echo        *
   //* option and the user instructions.                          *
   //* ---------------------------------------------------------- *
   //* Note that GUI keys are not included as part of any         *
   //* special-key group, _nor_ are they printing keycodes;       *
   //* They are decoded and passed to the application layer to    *
   //* be interpred or discarded, as desired.                     *
   //**************************************************************
   //* softEchoA: Capture and report individual keypress events.  *
   //* All special keys are active, but are reported only,        *
   //* not processed. GUI keycodes _are_ reported.                *
   //* ---------------------------------------------------------- *
   //* softEchoB: Same group of keycodes as softEchoA, except     *
   //* that the text 'selection' group is excluded:               *
   //*  Shift+[Up|Down|Left|Right], selet-all(Ctrl+A),            *
   //*  Copy(Ctrl+C), Cut(Ctrl+X), Paste(ctrl+V), Undo(Ctrl+Z).   *
   //* ---------------------------------------------------------- *
   //* softEchoC: This tests the special keys defined for         *
   //* cursor movement. Other special keys are filtered out.      *
   //* GUI keycodes _are_ reported.                               *
   //* ---------------------------------------------------------- *
   //* softEchoD: Exercise the disable option.                    *
   //* Special keys should be discarded (set to NULLCHAR by the   *
   //* call to acSpecialKeyTest()), and should not be available   *
   //* to the application. GUI keycodes _are_ reported.           *
   //* ---------------------------------------------------------- *
   //* softEchoE: Exercise the edit keys. This tests the          *
   //* Backspace/Delete/Insert/Tab/ShiftTab keys. Other special   *
   //* keys are filtered out. GUI keycodes _are_ reported.        *
   //**************************************************************
   else
   {  // T e [b | c | d | e | r]
      wchar_t altwch ;
      bool prow = true ;            // indicates first write to display

      //* Set unbuffered input using the specified echo option,  *
      //* and capture the signals which interfere with key input.*
      this->acCaptureSignals ( ech ) ;

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      this->acGetTermInfo ( stdIn, tios ) ;
      this->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      wp = { baseROW, baseCOL } ;
      this->acSetCursor ( wp ) ;
      this->ttyWrite ( L">>>" ) ;
      this->acSetCursor ( wp ) ;

      do
      {
         if ( wp.row >= (exitROW - 2) )
         {
            wp = { baseROW, short(wp.col + 10) } ;
            this->acSetCursor ( wp ) ;
         }
         wch = this->acRead () ;

         altwch = wch ; // This call may modify wch, so use alternate variable
         if ( (this->isSpecialKey ( altwch )) )
         {
            gsOut.compose( "%C 0x%04X\n", &wch, &wch ) ;
            wp = this->acWrite ( wp, gsOut ) ;
         }
         else
         {
            //* If the key pressed _was_ a special key, *
            //* wch contains rejected special keycode.  *
            if ( altwch == ZERO )
               gsOut = L" ------\n" ;
            else
               gsOut.compose( " 0x%04X\n", &wch ) ;

            //* If on prompt row and if nullchar (rejected special key), or  *
            //* newline, then character was not written, so erase the prompt.*
            if ( prow )
            {
               if ( (wch == NULLCHAR) || (wch == NEWLINE) )
                  this->ttyWrite ( L" \b" ) ;
               prow = false ;
            }

            if ( wch != wcsSPACE )
            {
               wp = this->acWrite ( wp.row, ++wp.col, gsOut ) ;
               --wp.col ;
               this->acSetCursor ( wp ) ;
            }
         }
      }
      while ( wch != wcsSPACE ) ;
   }  // T e [b | c | d | e | r]

#if 0    // OBSOLETE AND REDUNDANT TEST
   //********************************************************
   //* softEchoB: This test uses an "invisible" ACWin window*
   //* with an embedded skForm object for these tests.      *
   //* ACWin objects use the softEchoA option, but certain  *
   //* keycodes are interpreted according to the specific   *
   //* type of field under edit.                            *
   //********************************************************
   else if ( ech == softEchoB )
   {  // invoke: T e b
      const short winROW = 3 ;
      const short winCOL = 2 ;
      const short winHEIGHT = 15 ;        // window dimensions
      const short winWIDTH  = 71 ;
      const short instCOL   = 3 ;
      const short rptROW    = 18 ;        // position for stats report
      const short rptCOL    = instCOL ;
      const short FLD_COUNT = 4 ;

      WinPos wpul( 4, 1 ),                // initialize field origin
             wpRpt( rptROW, rptCOL ),     // position stats report
             wpIns( 2, 63 ) ;             // offset of ins/ovr report
       bool insFlag = true ;              // track current state of ins/ovr flag

      //* Construct an skForm-class object and initialize it.     *
      //* The initialized object is passed as a parameter to the  *
      //* ACWin-class constructor.                                *
      //* -- The window border and interior both use the terminal *
      //*    default color attributes.                            *
      //* -- The window border is invisible to give the impression*
      //*    that the fields stand alone in the terminal window,  *
      //*    BUT they do not.                                     *
      //* -- Note that one of the fields is initialized with      *
      //*    multi-column characters to stress test cursor        *
      //*    movements.                                           *
      //* - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
      //* Note that many members have default values, but for     *
      //* clarity, they are explicity initialized here. See skForm*
      //* and skField initialization constructors for details     *
      skForm *skfPtr = new skForm
      (
         FLD_COUNT,
         new skField[FLD_COUNT]
         {
            //* Field index 0: ftTB (Textbox) *
            {
            ftTB,                      // field type
            1,                         // field height (in rows)
            short(winWIDTH - 4),       // field width (in columns)
            wpul.row, wpul.col,        // Y/X offset in window
            "Use the Arrow keys and Home/End keys to navigate within a field.",
            acaUSEDFLT,                // color attributes for text
            floShift,                  // text formatting option (text-editor mode)
            ZERO,                      // insertion point (cursor offset)
            false,                     // user accessible (not read-only)
            false,                     // field outline disabled
            false,                     // field is visible (not hidden)
            true,                      // access lock enabled
            false,                     // binary state not applicable to ftTB
            NULLCHAR,                  // no hotkey specified
            noGROUP                    // not a group member
            },

            //* Field index 1: ftTB (Textbox) *
            {
            ftTB,                      // field type
            1,                         // field height (in rows)
            short(winWIDTH - 4),       // field width (in columns)
            short(wpul.row += 2),      // Y offset in window
            wpul.col,                  // X offset in window
            "Use the Backspace, .....x..... Delete and Insert Keys to edit text.",
            acaUSEDFLT,                // color attributes for text
            floChar,                   // text formatting option (simple)
            24,                        // insertion point (cursor offset)
            false,                     // user accessible (not read-only)
            false,                     // field outline disabled
            false,                     // field is visible (not hidden)
            true,                      // access lock enabled
            false,                     // binary state not applicable to ftTB
            NULLCHAR,                  // no hotkey specified
            noGROUP                    // not a group member
            },

            //* Field index 2: ftTB (Textbox) *
            {
            ftTB,                      // field type
            3,                         // field height (in rows)
            short(winWIDTH - 4),       // field width (in columns)
            short(wpul.row += 2),      // Y offset in window
            wpul.col,                  // X offset in window
            "Use the Tab and Shift+Tab keys .....x..... to move among fields.\n"
            "Note: Within the edit method, PgDown/DownArrow and PgUp/UpArrow\n"
            "are automatically converted to Tab and Shift+Tab, respectively.",
            acaUSEDFLT,                // color attributes for text
            floChar,                   // text formatting option (simple)
            36,                        // insertion point (cursor offset)
            false,                     // user accessible (not read-only)
            false,                     // field outline disabled
            false,                     // field is visible (not hidden)
            true,                      // access lock enabled
            false,                     // binary state not applicable to ftTB
            NULLCHAR,                  // no hotkey specified
            noGROUP                    // not a group member
            },

            //* Field index 3: ftTB (Textbox) *
            {
            ftTB,                      // field type
            1,                         // field height (in rows)
            short(winWIDTH - 4),       // field width (in columns)
            short(wpul.row += 4),      // Y offset in window
            wpul.col,                  // X offset in window
            "道是生命的本质　。。。。。三。。。。。　还是巧克力？",
            acaUSEDFLT,                // color attributes for text
            floChar,                   // text formatting option (simple)
            13,                        // insertion point (cursor offset)
            false,                     // user accessible (not read-only)
            true,                      // field outline enabled
            false,                     // field is visible (not hidden)
            true,                      // access lock enabled
            false,                     // binary state not applicable to ftTB
            NULLCHAR,                  // no hotkey specified
            noGROUP                    // not a group member
            }
         },
         //* Optional skFormarguments *
         ZERO,       // 'fldIndex'  index of field with initial focus (the Pushbutton)
         acaUSEDFLT, // 'fldAttr'   color attributes for control with focus
         true,       // 'noisy'     beep on unhandled keycode received
         insFlag,    // 'ioFlag'    initial state of Insert/Overstrike flag
         true,       // 'ioToggle'  user access to Insert/Overstrike mode
         true,       // 'ioCursor'  enable cursor shape change for Insert/Overstrike
         csBblock,   // 'insCursor' cursor shape when Insert mode
         csBuline    // 'ovrCursor' cursor shape when Overstrike mode
      ) ;

      //* For testing only: optionally make border visible and change     *
      //* colors. For production, border is invisible with default colors.*
      wchar_t border = L'i', color = L'd' ;


      //**************************
      //* Define an ACWin object *
      //**************************
      ACWin *win = new ACWin
            ( winROW, winCOL,                      // position (upper-left corner) 
              winHEIGHT, winWIDTH,                 // dimensions (height, width)
              (border=='d' ? ltDUAL : border=='s' ? ltSINGLE : ltHORIZ),
              (color=='d' ? acAttr(acaFG_DFLT | acaBG_DFLT) : acAttr(acaFGb_BROWN | acaBG_BLUE)),
              acAttr(acaFG_DFLT | acaBG_DFLT),     // interior attributes
              NULL,
              skfPtr
            ) ;

      //* Note: Opening the window disables input buffering AND *
      //*       sets the echo option to 'softEchoA'.            *
      win->OpenWindow(
         "   Key Input Echo Option: softEchoB - (within an skForm object)\n"
         "   (special keys interpreted according to the field under edit)\n"
         "       Press Enter To Exit                                     <INS>\n\n"
         "[                                                                   ]\n\n"
         "[                                                                   ]\n"
         "┌                                                                   ┐\n"
         "│                                                                   │\n"
         "│                                                                   │\n"
         "│                                                                   │\n"
         "└                                                                   ┘\n"
         "[                                                                   ]" ) ;
      //* Highlight the user instructions.*
      wpul = { wpIns.row, 7 } ;
      win->Write ( wpul, L"Press Enter To Exit", acAttr(acaATTR_DFLT | acaBOLD) ) ;

      //* Delete the local skForm object and reference   *
      //* the fully-initialized object within the window.*
      delete skfPtr ;                  // delete the local skForm object
      short fIndx = ZERO ;             // index of field with focus
      const skForm *skfp = win->GetForm () ;
      tseBPtr = win ;                  // give callback method access to ACWin object
      tseAnsiPtr = this ;              // give callback method access to AnsiCmd object

      win->acSetEchoOption ( softEchoB ) ;

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      win->acGetTermInfo ( stdIn, tios ) ;
      win->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      //* Report the data members of each field *
      this->tseReport ( *skfp, wpRpt, TSE_SHHH ) ;

      //* Establish a callback method to monitor user input.  *
      //* When the ENTER key is received, EditForm() returns. *
      win->EstablishCallback ( &tseB_Callback ) ;

      //* Interact with the user. The callback method will  *
      //* insert the termination character into the stream  *
      //* when the ENTER key is received.                   *
      win->EditForm ( fIndx ) ;

      //* Note: Closing the window re-enables input buffering *
      //*       AND resets the echo option to 'termEcho'.     *
      //* Note: For this test, window display is not erased.  *
      win->CloseWindow ( false ) ;
      tseBPtr = NULL ;                    // reset the external pointer
      tseAnsiPtr = NULL ;                 // reset the external pointer

      delete ( win ) ;        // release the allocated resourses

   }  // (ech==softEchoB)

   //************************************************************
   //* softEchoA: Capture and report individual keypress events.*
   //* All special keys are active, but are reported only,      *
   //* not processed.                                           *
   //* -------------------------------------------------------- *
   //* softEchoD: Exercise the disable option.                  *
   //* Special keys should be discarded (set to NULLCHAR by the *
   //* call to acSpecialKeyTest()), and should not be available *
   //* to the application.                                      *
   //************************************************************
   else if ( (ech == softEchoA) || (ech == softEchoD) )
   {  // invoke: T e [d | r]
      wchar_t altwch ;
      bool prow = true ;            // indicates first write to display

      //* Set unbuffered input using the specified echo option,  *
      //* and capture the signals which interfere with key input.*
      this->acCaptureSignals ( ech ) ;

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      this->acGetTermInfo ( stdIn, tios ) ;
      this->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      wp = { baseROW, baseCOL } ;
      this->acSetCursor ( wp ) ;
      this->ttyWrite ( L">>>" ) ;
      this->acSetCursor ( wp ) ;
      do
      {
         if ( wp.row >= (exitROW - 2) )
         {
            wp = { baseROW, short(wp.col + 10) } ;
            this->acSetCursor ( wp ) ;
         }
         wch = this->acRead () ;

         altwch = wch ; // This call may modify wch, so use alternate variable
         if ( (this->isSpecialKey ( altwch )) )
         {
            gsOut.compose( "%C 0x%04X\n", &wch, &wch ) ;
            wp = this->acWrite ( wp, gsOut ) ;
         }
         else
         {
            //* If the key pressed _was_ a special key, *
            //* wch contains rejected special keycode.  *
            if ( altwch == ZERO )
               gsOut = L" ------\n" ;
            else
               gsOut.compose( " 0x%04X\n", &wch ) ;

            //* If on prompt row and if nullchar (rejected special key), or  *
            //* newline, then character was not written, so erase the prompt.*
            if ( prow )
            {
               if ( (wch == NULLCHAR) || (wch == NEWLINE) )
                  this->ttyWrite ( L" \b" ) ;
               prow = false ;
            }

            if ( wch != wcsSPACE )
            {
               wp = this->acWrite ( wp.row, ++wp.col, gsOut ) ;
               --wp.col ;
               this->acSetCursor ( wp ) ;
            }
         }
      }
      while ( wch != wcsSPACE ) ;

   }  // (ech==softEchoA || ech==softEchoD)

   //********************************************************
   //* softEchoC: This test uses the special keys defined   *
   //* for cursor movement.                                 *
   //* Note that an "invisible" ACWin window is used to     *
   //* define the skForm used for testing. By default, the  *
   //* ACWin object uses a customized set of special keys   *
   //* with certain keycodes converted to accomodate the    *
   //* specific type of field under edit.                   *
   //* This test overrides that customized set of keycodes. *
   //********************************************************
   else if ( ech == softEchoC )
   {  // invoke: T e c
      const short winROW = 7 ;            // window position
      const short winCOL = 8 ;
      const short winHEIGHT  = 12 ;       // window dimensions
      const short winWIDTH   = 48 ;
      const short rptROW = 20 ;           // position for stats report
      const short rptCOL = winCOL ; 

      WinPos wpul( 3, 1 ),                // initialize field origin
             wpRpt( rptROW, rptCOL ) ;    // position stats report

      //* Construct an skForm-class object and initialize it.     *
      //* The initialized object is passed as a parameter to the  *
      //* ACWin-class constructor.                                *
      //* -- The window border and interior both use the terminal *
      //*    default color attributes.                            *
      //* -- The window border is invisible to give the impression*
      //*    that the field stands alone in the terminal window,  *
      //*    BUT it does not.                                     *
      skForm *skfPtr = new skForm
      (
         2,
         new skField[2]
         {
            //* Field index 0: ftTB (Textbox) *
            {
            ftTB,                            // field type
            short(winHEIGHT - 4),            // field height (in rows)
            short(winWIDTH - 2),             // field width (in columns)
            ZERO, ZERO,                      // Y/X offset in window
            "..............................................\n"
            "............Exercise the cursor keys:.........\n"
            "..LeftArrow, RightArrow, UpArrow, DownArrow,..\n"
            "..........Home, End, Tab, Shift+Tab,..........\n"
            "...............PageUp, PageDown...............\n"
            ".............Press Enter to Exit..............\n"
            "..............................................\n"
            "..............................................",
            // Remaining members receive default values.
            //acaUSEDFLT,                    // color attributes
            //floChar,                       // text formatting option
            //ZERO,                          // insertion point (cursor offset)
            //false,                         // read-only flag is 'reset'
            //false,                         // field outline disabled
            //false,                         // hidden flag is 'reset'
            //true,                          // access lock enabled
            //false,                         // binary state: 'reset'
            //NULLCHAR,                      // no hotkey specified
            //noGROUP                        // not a group member
            },

            //* Field index 1: ftTB (Textbox)
            {
            ftTB,                            // field type
            1,                               // field height (in rows)
            short(winWIDTH - 18),            // field width (in columns)
            9, 8,                            // Y/X offset in window
            "         Hello World!",         // field text
            acaUSEDFLT,                    // color attributes
            floShift,                      // text formatting option
            8,                             // insertion point (cursor offset)
            // Remaining members receive default values.
            //false,                         // read-only flag is 'reset'
            //false,                         // field outline disabled
            //false,                         // hidden flag is 'reset'
            //true,                          // access lock enabled
            //false,                         // binary state: 'reset'
            //NULLCHAR,                      // no hotkey specified
            //noGROUP                        // not a group member
            },
         }
         //* Initialize remaining skForm parameters to default values.*
         //ZERO,           // index of field with initial focus
         //acaUSEDFLT,     // default color attributes for control with focus
         //true,           // beep on unhandled keycode received
         //true,           // initial state of Insert/Overstrike flag
         //true,           // user access to Insert/Overstrike mode
         //false,          // enable cursor shape change for Insert/Overstrike
         //csBblock,       // cursor shape when Insert mode
         //csBuline        // cursor shape when Overstrike mode
      ) ;

      //* For testing only: optionally make border visible and change     *
      //* colors. For production, border is invisible with default colors.*
      wchar_t border = L'i', color = L'd' ;


      //**************************
      //* Define an ACWin object *
      //**************************
      ACWin *win = new ACWin
            ( winROW, winCOL,                      // position (upper-left corner) 
              winHEIGHT, winWIDTH,                 // dimensions (height, width)
              (border=='d' ? ltDUAL : border=='s' ? ltSINGLE : ltHORIZ),
              (color=='d' ? acAttr(acaFG_DFLT | acaBG_DFLT) : acAttr(acaFGb_BROWN | acaBG_BLUE)),
              acAttr(acaFG_DFLT | acaBG_DFLT),      // interior attributes
              NULL,
              skfPtr
            ) ;

      //* Note: Opening the window disables input buffering AND *
      //*       set the echo option to 'softEchoA'.             *
      win->OpenWindow( "\n\n\n\n\n\n\n\n\n"
                       "       [                              ]" ) ;

      //* Delete the local skForm object and reference   *
      //* the fully-initialized object within the window.*
      delete skfPtr ;                  // delete the local skForm object
      short fIndx = ZERO ;             // index of field with focus
      const skForm *skfp = win->GetForm () ;
      tseCPtr = win ;                  // give callback method access to ACWin object
      tseAnsiPtr = this ;              // give callback method access to AnsiCmd object

      //* For this test only: Override the window's soft-echo option.*
      //*          Don't try this at home! See note above.           *
      win->acSetEchoOption ( softEchoC ) ;

      //* Set cursor to most noticeable style. *
      this->acSetCursorStyle ( csBblock ) ;

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      win->acGetTermInfo ( stdIn, tios ) ;
      win->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      //* Report the data members of each field *
      this->tseReport ( *skfp, wpRpt, TSE_SHHH ) ;

      //* Establish a callback method to monitor user input.  *
      //* When the ENTER key is received, EditForm() returns. *
      win->EstablishCallback ( &tseC_Callback ) ;

      //* Interact with the user. The callback method will  *
      //* insert the termination character into the stream  *
      //* when the ENTER key is received.                   *
      win->EditForm ( fIndx ) ;

      //* Return cursor to default shape.*
      this->acSetCursorStyle ( csDflt ) ;

      //* Note: Closing the window re-enables input buffering *
      //*       AND resets the echo option to 'termEcho'.     *
      //* Note: For this test, window display is not erased.  *
      win->CloseWindow ( false ) ;
      tseCPtr = NULL ;                    // reset the external pointer
      tseAnsiPtr = NULL ;                 // reset the external pointer

      delete ( win ) ;        // release the allocated resourses

   }  // (ech==softEchoC)

   //****************************************************************
   //* softEchoE: Exercise the edit keys.                           *
   //* This test uses the Backspace/Delete/Insert/Tab/ShiftTab keys.*
   //* Note that an "invisible" ACWin window is used to define the  *
   //* skForm object used for testing.                              *
   //****************************************************************
   else if ( ech == softEchoE ) 
   {  // invoke: T e e
      const short winROW = 3 ;
      const short winCOL = 2 ;
      const short winHEIGHT  = 11 ;       // window dimensions
      const short winWIDTH   = 71 ;
      const short fldCOUNT = 3 ;          // number of fields in window
      const short instCOL = 3 ;
      const short rptROW = 14 ;           // position for stats report
      const short rptCOL = instCOL ;

      WinPos wpul( 3, 1 ),                // initialize field origin
             wpRpt( rptROW, rptCOL ),      // position stats report
             wpIns( 1, 63 ) ;             // offset of ins/ovr report
       bool insFlag = true ;              // track current state of ins/ovr flag

      //* Construct an skForm-class object and initialize it.     *
      //* The initialized object is passed as a parameter to the  *
      //* ACWin-class constructor.                                *
      //* -- The window border and interior both use the terminal *
      //*    default color attributes.                            *
      //* -- The window border is invisible to give the impression*
      //*    that the fields stand alone in the terminal window,  *
      //*    BUT they do not.                                     *
      //* -- Note that one of the fields is initialized with      *
      //*    multi-column chars to stress test cursor movements.  *
      skForm *skfPtr = new skForm
      (
         fldCOUNT,
         new skField[fldCOUNT]
         {
            //* Field index 0: ftTB (Textbox) *
            {
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(winWIDTH - 4),          // field width (in columns)
            wpul.row, wpul.col,           // Y/X offset in window
// This may be true, but this is not a softEchoE test.
            "For the \"softEchoE\" option, the cursor keys are disabled.",
            acaUSEDFLT,                   // color attributes
            floShift,                     // text formatting option
            // note: The specified IP is an invalid value and s/b auto-corrected
            winWIDTH,                     // insertion point (cursor offset)
            // Remaining members receive default values.
            //false,                        // read-only flag is 'reset'
            //false,                        // field outline disabled
            //false,                        // hidden flag is 'reset'
            //true,                         // access lock enabled
            //false,                        // binary state: 'reset'
            //NULLCHAR,                     // no hotkey specified
            //noGROUP                       // not a group member
            },

            //* Field index 1: ftTB (Textbox) *
            {
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(winWIDTH - 4),          // field width (in columns)
            short(wpul.row += 2),         // Y/X offset in window
            wpul.col,
            "Use the Backspace, .....x..... Delete and Insert Keys to edit text.",
            acaUSEDFLT,                   // color attributes
            floShift,                     // text formatting option
            24,                           // insertion point (cursor offset)
            // Remaining members receive default values.
            //false,                        // read-only flag is 'reset'
            //false,                        // field outline disabled
            //false,                        // hidden flag is 'reset'
            //true,                         // access lock enabled
            //false,                        // binary state: 'reset'
            //NULLCHAR,                     // no hotkey specified
            //noGROUP                       // not a group member
            },

            //* Field index 2: ftTB (Textbox) *
            {
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(winWIDTH - 4),          // field width (in columns)
            short(wpul.row += 2),         // Y/X offset in window
            wpul.col,
            "Use the Tab and Shift+Tab keys .....x..... to move among fields.\n",
            acaUSEDFLT,                   // color attributes
            floShift,                     // text formatting option
            36,                           // insertion point (cursor offset)
            // Remaining members receive default values.
            //false,                        // read-only flag is 'reset'
            //false,                        // field outline disabled
            //false,                        // hidden flag is 'reset'
            //true,                         // access lock enabled
            //false,                        // binary state: 'reset'
            //NULLCHAR,                     // no hotkey specified
            //noGROUP                       // not a group member
            },
         },
         //* Initialize remaining skForm parameters to default values.*
         ZERO,           // index of field with initial focus
         acaUSEDFLT,     // default color attributes for control with focus
         true,           // beep on unhandled keycode received
         insFlag         // initial state of Insert/Overstrike flag
         //true,           // user access to Insert/Overstrike mode
         //false,          // enable cursor shape change for Insert/Overstrike
         //csBblock,       // cursor shape when Insert mode
         //csBuline        // cursor shape when Overstrike mode
      ) ;

      //* For testing only: optionally make border visible and change     *
      //* colors. For production, border is invisible with default colors.*
      wchar_t border = L'i', color = L'd' ;


      //**************************
      //* Define an ACWin object *
      //**************************
      ACWin *win = new ACWin
            ( winROW, winCOL,                      // position (upper-left corner) 
              winHEIGHT, winWIDTH,                 // dimensions (height, width)
              (border=='d' ? ltDUAL : border=='s' ? ltSINGLE : ltHORIZ),
              (color=='d' ? acAttr(acaFG_DFLT | acaBG_DFLT) : 
                            acAttr(acaFGb_BROWN | acaBG_BLUE)),
              acAttr(acaFG_DFLT | acaBG_DFLT),      // interior attributes
              NULL,
              skfPtr
            ) ;

      //* Note: Opening the window disables input buffering AND *
      //*       sets the echo option to 'softEchoA'.            *
      win->OpenWindow(
         "   Key Input Echo Option: softEchoE (Text Edit keys translated)\n"
         "       Press Enter To Exit\n\n"
         "[                                                                   ]\n\n"
         "[                                                                   ]\n\n"
         "[                                                                   ]" ) ;

      //* Delete the local skForm object and reference   *
      //* the fully-initialized object within the window.*
      delete skfPtr ;                  // delete the local skForm object
      short fIndx = ZERO ;             // index of field with focus
      const skForm *skfp = win->GetForm () ;
      tseBPtr = win ;                  // give callback method access to ACWin object
      tseAnsiPtr = this ;              // give callback method access to AnsiCmd object

      //* For this test only: Override the window's soft-echo option.*
      //*          Don't try this at home! See note above.           *
      win->acSetEchoOption ( softEchoE ) ;

      //* Get terminal settings and report them *
      gsOut = L"Term Setup for Input Echo" ;
      win->acGetTermInfo ( stdIn, tios ) ;
      win->acDumpTermInfo ( tios, wpDump.row, wpDump.col, dumpOPT, &gsOut ) ;

      //* Report the data members of each field *
      this->tseReport ( *skfp, wpRpt, TSE_SHHH ) ;

      //* Establish a callback method to monitor user input.  *
      //* When the ENTER key is received, EditForm() returns. *
      win->EstablishCallback ( &tseB_Callback ) ;

      //* Interact with the user.                           *
      //* Interact with the user. The callback method will  *
      //* insert the termination character into the stream  *
      //* when the ENTER key is received.                   *
      win->EditForm ( fIndx ) ;

      //* Note: Closing the window re-enables input buffering *
      //*       AND resets the echo option to 'termEcho'.     *
      //* Note: For this test, window display is not erased.  *
      win->CloseWindow ( false ) ;
      tseBPtr = NULL ;                    // reset the external pointer
      tseAnsiPtr = NULL ;                 // reset the external pointer

      delete ( win ) ;        // release the allocated resourses

   }  // (ech==softEchoE)
#endif   // OBSOLETE AND REDUNDANT TEST

   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

   //* Restore original terminal settings and prepare to exit.*
   this->acRestoreTermInfo () ;
   this->acSetCursor ( aesCUR_ABSPOS, exitROW, 1 ) ;  // set cursor position

}  //* End Test_SoftEcho() *

//*************************
//*     tseA_Callback     *
//*************************
//********************************************************************************
//* Non-member function:                                                         *
//* --------------------                                                         *
//* Callback function for Test_SoftEcho() above.                                 *
//* Handle the callbacks from ACWin method: EditForm().                          *
//* The static pointer 'cbPtr' gives this method access to the ACWin object      *
//* created within Test_SoftEcho().                                              *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: 'wkey' is returned.        *
//********************************************************************************
//*                                                                              *
//********************************************************************************

static wchar_t tseA_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   const char* insText = " INS " ;
   const char* ovrText = " OVR " ;
   const short fldMULT = 1 ;
   const short fldEDIT = 2 ;
   const short fldCLIP = 3 ;
   const short fldMODE = 4 ;              // index of Insert/Overstrike field
   const acAttr ioIns = acAttr(acaFGb_GREY | acaBG_GREEN), // insert mode field attribute
                ioOvr = acAttr(acaFG_BLUE  | acaBG_GREY) ; // overstrike mode field attribute
   static FloType f1Flow = floToken,      // formatting type for field 1
                  f2Flow = floShift ;     // formatting type for field 2
   static bool insFlag ;                  // most recent state of Insert/Overstrike flag

   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   {
      insFlag = cbPtr->isInsertMode () ;// current state if Insert/Overstrike flag
      if ( insFlag )                      // set field to match the flag
         cbPtr->SetFieldData ( fldMODE, insText, ioIns, ZERO ) ;
      else
         cbPtr->SetFieldData ( fldMODE, ovrText, ioOvr, ZERO ) ;
      return wkey ;
   }

   //* Update the Insert/Overstrike field. Note that the *
   //* skForm 'ins' flag WILL BE toggled by caller. This *
   //* is why the if/else seems backward.                *
   if ( wkey == wcsINSERT )
   {
      if ( (insFlag = bool(insFlag ? false : true)) )
         cbPtr->SetFieldData ( fldMODE, insText, ioIns, ZERO ) ;
      else
         cbPtr->SetFieldData ( fldMODE, ovrText, ioOvr, ZERO ) ;
   }
   //* If the Copy or Cut command has been executed,     *
   //* update the field that displays clipboard contents.*
   else if ( (wkey == wcsCOPY) || (wkey == wcsCUT) )
   {
      gString gsClip ;
      cbPtr->acClipboardGetText ( gsClip ) ;         // get a copy of clipboard text
      #if 0    // Make trailing newline visible - (testing only)
      if ( (gsClip.findlast( NEWLINE )) == (gsClip.gschars() - 2) )
         gsClip.append( wcsCEND ) ;
      //gsClip.replace( L' ', L'`', ZERO, false, true ) ;
      #endif   // Make trailing newline visible
      cbPtr->SetFieldText ( fldCLIP, gsClip ) ;      // display clipboard text
      //* Returning the null character should not be necessary because *
      //* the clipboard keys have already been processed, but be safe. *
      wkey = NULLCHAR ;                   // keycode has been handled
   }
   else if ( wkey == wcsCTL_R )           // reformat the field
   {
      //* Test the SetFieldFlow() method.                  *
      //* Note: Do not reformat the field which has focus  *
      //*       because it is currently being edited.      *
      //* a) For field #1 (multi-row, flowing text),       *
      //*    toggle between floToken and floChar.          *
      //* b) For field #2 (multi-row, fixed-format text),  *
      //*    toggle between floShift and floToken.         *
      short   target_field = (ctrlIndex == 1 ? 2 : 1) ;
      FloType new_flow ;
      if ( target_field == 1 )
      { new_flow = f1Flow = (f1Flow == floToken ? floChar : floToken) ; }
      else
      { new_flow = f2Flow = (f2Flow == floShift ? floToken : floShift) ; }
      cbPtr->ReformatFieldText ( target_field, new_flow ) ;
      wkey = NULLCHAR ;                   // keycode has been handled
   }
   //* Test of direct scrolling in multi-row text fields *
   //* under application control.                        *
   else if ( ((ctrlIndex == fldMULT) || (ctrlIndex == fldEDIT)) && 
             ((wkey == wcsALT_D) || (wkey == wcsALT_U) || (wkey == wcsALT_T)) )
   {
      short steps = ZERO ;                            // move to top of text
      if ( wkey == wcsALT_D )      { steps = 1 ; }    // move one row down
      else if ( wkey == wcsALT_U ) { steps = -1 ; }   // move one row up
      if ( ! (cbPtr->ScrollFieldText ( ctrlIndex, steps )) )
         cbPtr->acBeep () ;
      wkey = NULLCHAR ;                // keycode handled
   }

   else if ( (wkey == wcsCTL_Q) || (wkey == wcsALT_Q) )
      wkey = wcsCTL_4 ;

   return wkey ;

}  //* End tseA_Callback() *

#if 0    // POTENTIALLY OBSOLETE
//*************************
//*     tseB_Callback     *
//*************************
//********************************************************************************
//* Non-member function:                                                         *
//* --------------------                                                         *
//* Callback function for Test_SoftEcho() above.                                 *
//* Handle the callbacks from ACWin method: EditForm() for the softEchoB and     *
//* softEchoE options.                                                           *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: value of 'wkey' parameter  *
//********************************************************************************

static wchar_t tseB_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   const wchar_t* insText = L"<INS>" ;
   const wchar_t* ovrText = L"<OVR>" ;
   const acAttr ioIns = acAttr(acaFG_GREEN | acaBG_DFLT),  // insert mode field attribute
                ioOvr = acAttr(acaFG_BLUE  | acaBG_DFLT) ; // overstrike mode field attribute
   static WinPos wpIns( 1, 63 ) ;         // offset of ins/ovr report
   static WinPos wpRpt( 14, 3 ) ;         // position for stats report
   static bool insFlag ;                  // most recent state of flag
   winInfo winnie ;                       // copy of internal window data
   tseBPtr->WinInfo ( winnie ) ;

   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   {
      //* Determine which test window is active,         *
      //* (test for softEchoB has 4 fields), and adjust  *
      //* output positions for Ins/Ovr and tseReport().  *
      if ( winnie.skf->fCnt == 4) { ++wpIns.row ; wpRpt.row = 18 ; }

      insFlag = tseBPtr->isInsertMode () ;// current state if Insert/Overstrike flag
      //* Set message to match state of flag. *
      tseBPtr->Write ( wpIns, (insFlag ? insText : ovrText), (insFlag ? ioIns : ioOvr) ) ;
      return wkey ;
   }

   //* Set cursor to invisible for update. The refresh makes it visible again.*
   tseBPtr->acSetCursorStyle ( csHide ) ;

   //* Update the Insert/Overstrike field. Note that the *
   //* skForm 'ins' flag WILL BE toggled by caller. This *
   //* is why the if/else logic seems backward.          *
   if ( wkey == wcsINSERT )
   {
      insFlag = bool(insFlag ? false : true) ;
      tseBPtr->Write ( wpIns, (insFlag ? insText : ovrText), (insFlag ? ioIns : ioOvr) ) ;
      tseBPtr->RefreshField ( (tseBPtr->GetInputFocus ()) ) ; // return cursor to previous position
   }

   else if ( wkey == wcsENTER )           // exit EditForm method
      wkey = wcsCTL_4 ;

   //* Report the data members of each field *
   tseAnsiPtr->tseReport ( *winnie.skf, wpRpt, TSE_SHHH ) ;
   tseBPtr->RefreshField () ;             // return visible cursor to previous position

   return wkey ;

}  //* End tseB_Callback() *

//*************************
//*     tseC_Callback     *
//*************************
//********************************************************************************
//* Non-member function:                                                         *
//* --------------------                                                         *
//* Callback function for Test_SoftEcho() above.                                 *
//* Handle the callbacks from ACWin method: EditForm() for the softEchoC option. *
//* The static pointer 'tseCPtr' gives this method access to the ACWin object    *
//* created within Test_SoftEcho().                                              *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: 'wkey' is returned.        *
//********************************************************************************

static wchar_t tseC_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   static WinPos wpRpt( 20, 8 ) ;         // position for stats report

   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   {
      return wkey ;  // no initialization required
   }

   //* Get a copy of window data *
   winInfo winnie ;
   tseCPtr->WinInfo ( winnie ) ;

   //* Set cursor to invisible for update. The refresh makes it visible again.*
   tseCPtr->acSetCursorStyle ( csHide ) ;

   if ( wkey == wcsENTER )                // exit EditForm method
      wkey = wcsCTL_4 ;

   //* Report the data members of each field *
   tseAnsiPtr->tseReport ( *winnie.skf, wpRpt, TSE_SHHH ) ;
   tseCPtr->RefreshField () ;             // return visible cursor to previous position

   return wkey ;


}  //* End tseC_Callback() *
#endif   // POTENTIALLY OBSOLETE

//*************************
//*       tseReport       *
//*************************
//********************************************************************************
//* PUBLIC METHOD _BUT_ USED ONLY AS A PRIVATE METHOD                            *
//* Called by Test_SoftEcho() and its callback functions to report user input    *
//* and keycode processing.                                                      *
//*                                                                              *
//* Input  : skf  : (by reference) skForm object containing data to be reported  *
//*          wpRpt: (by reference) position for report                           *
//*          secret_code: defensive programming to prevent application from      *
//*                       executing the code in this method                      *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::tseReport ( const skForm& skf, const WinPos& wpRpt, uint8_t secret_code )
{
   if ( secret_code == TSE_SHHH )
   {
      WinPos wp, wpcur ;
      WinPos base( 99, 99 ) ; short seli = 99 ;
      wchar_t pKey = L'?' ;   bool foot = false, dlg = false ;
      skf.skfProtectedMembers ( base, pKey, seli, foot, dlg ) ;
      gString gsOut( "skf: base:%02hd/%02hd fi:%02hd ins:%hhd pKey:%C %04X\n", 
                     &base.row, &base.col, &skf.fi, &skf.ins, 
                     &pKey, &pKey, &skf.togg, &skf.beep ) ;
      wp = this->acWrite ( wpRpt, gsOut ) ;

      for ( short f = ZERO ; f < skf.fCnt ; ++f )
      {
         gsOut.compose( "f:%-2hd orig:%02hd/%02hd cur:%02hd/%02hd ip:%-3hd\n", &f, 
                        &skf.fld[f].orig.row, &skf.fld[f].orig.col, 
                        &skf.fld[f].cur.row,  &skf.fld[f].cur.col,
                        &skf.fld[f].ip ) ;
         wp = this->acWrite ( wp, gsOut ) ;
      }
      wp = this->acWrite ( wp, L"Note: report update may lag by one keystroke." ) ;
   }

}  //* End tseReport() *

//*************************
//*      Test_Window      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Create an ACWin object and ask the user to make adjustments to the           *
//* parameters.                                                                  *
//*                                                                              *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                [currently unused]                                            *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Window ( aeSeq fg )
{  // invoke: W w
   #define DEBUG_TestWindow (0)    // FOR DEBUGGING ONLY

   const wchar_t* promptText = 
      L"Specify Window Configuration and Press Enter:\n\n\n" ;
   const char* bdTitle = "Border Attributes" ;
   const char* tdTitle = "Interior Attributes" ;
   const char* fldText = "------------Window refreshed.-------------" ;

   const short winROW     = 4 ;           // window position
   const short winCOL     = 6 ;
   const short altROW     = 3 ;
   const short altCOL     = 1 ;
   const short winHEIGHT  = 10 ;          // window dimensions
   const short winWIDTH   = 48 ;
   const short dumpROW    = winROW - 1 ;            // attribute dump
   const short tdumpCOL   = winCOL + winWIDTH + 4 ; // interior attribute dump
   const short bdumpCOL   = tdumpCOL + 40 ;         // border attribute dump
   const short promptRow  = 16 ;          // first row of prompt/menu area
   const short promptCol  = 6 ;           // left column of prompt/menu area
   const short promptHeight = 20 ;        // rows of prompt/menu area
   const short promptWidth  = 48 ;        // columns of prompt/menu area
   const short menuRow = promptRow + 2;   // row position for menu display
   const short menuCol = promptCol ;      // column position for menu display
   const short ccRow   = promptRow + 1 ;  // row position for color chart
   const short ccCol   = bdumpCOL - 7 ;   // column position for color chart
   const short rptRows = 20 ;             // operation-report rows
   const short rptCols = 26 ;             // operation-report columns

   gString gsOut,                         // text formatting
           gsIn ;                         // user input
   acaExpand txtExp, bdrExp ;             // local copy of window attributes
   WinPos winBase( winROW, winCOL ) ;     // window origin
   WinPos wpos ;                          // cursor offset into window object
   WinPos ppos( promptRow, promptCol ) ;  // prompt position
   WinPos mpos( menuRow, menuCol ) ;      // menu position
   WinPos cpos( ccRow, ccCol ) ;          // color-chart position
   WinPos wpDbg( ccRow, tdumpCOL ) ;      // position for debugging output
   #if 0    // For testing only - rely on defaults for production build.
   TermConfig termConf ;                  // ACWin constructor configures terminal
      termConf.termStream = stdIn ;
      termConf.echoOption = softEchoA ;
      termConf.bufferInput = false ;
      termConf.bcType = cchtCopy ;
   #endif   // For testing only


   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, "  (requires terminal rows/cols of 37/142)\n"
                        "-----------------------------------------" ) ; 

   //* Set terminal configuration for unbuffered input *
   this->acBufferedInput ( false, softEchoA ) ;

   //* Display the color chart *
   this->twColorChart ( cpos ) ;

   //* Set attributes to specified terminal foreground value *
   this->acSetFgBg ( fg, aesBG_DFLT ) ;

   //* Define a single, one-row field with a read-only message. *
   //* Note that user is not offered a chance to edit the field.*
   //* Note also that the field is obscured by text written to  *
   //* the window EXCEPT for when the window is refreshed.      *
   skForm *skfPtr = new skForm
   (
      1,
      new skField[1]
      {
         //* Field index 3: ftTB (Textbox: fldMULTI) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         short(winWIDTH - 6),             // field width (in columns)
         4, 2,                            // Y/X offset in window
         "",                              // field text (empty string)
         acaUSEDFLT,                      // color attributes
         floChar,                         // text formatting option
         ZERO,                            // insertion point (cursor offset)
         true,                            // read-only flag is 'set'
         // Remaining members receive default values.
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },
      }
      //* Initialize remaining skForm parameters to default values.*
      //ZERO,           // index of field with initial focus
      //acaUSEDFLT,     // default color attributes for control with focus
      //true,           // beep on unhandled keycode received
      //true,           // initial state of Insert/Overstrike flag
      //true,           // user access to Insert/Overstrike mode
      //false,          // enable cursor shape change for Insert/Overstrike
      //csBblock,       // cursor shape when Insert mode
      //csBuline        // cursor shape when Overstrike mode
   ) ;


   //**************************
   //* Define an ACWin object *
   //**************************
   ACWin *win = new ACWin
         ( winROW, winCOL,                      // position (upper-left corner) 
           winHEIGHT, winWIDTH,                 // dimensions (height, width)
           ltSINGLE,                            // border style
           #if 1     // Initial Colors
           acAttr(acaFGb_BROWN | acaBG_BLUE),   // border attributes
           acAttr(acaFG_RED | acaBG_GREY),      // interior attributes
           #else     // Alternate Colors
           acAttr(acaFGb_GREEN | acaBG_BLUE),   // border attributes
           acAttr(acaFG_RED | acaBG_GREY),      // interior attributes
           #endif   // Initial Colors
           " Where In the World Is Carmen San Diego ", // title text
           skfPtr
           // , &termConf // Testing Only
         ) ;
   delete skfPtr ;                  // release the dynamic allocation

   //* Get a copy of the window attributes and display them.*
   win->GetWinAttributes ( txtExp, bdrExp ) ;

   gsOut = tdTitle ;
   this->acaExpandDump ( txtExp, dumpROW, tdumpCOL, &gsOut ) ;
   gsOut = bdTitle ;
   this->acaExpandDump ( bdrExp, dumpROW, bdumpCOL, &gsOut ) ;

   //*************************************
   //* Open the window (make it visible) *
   //*************************************
   wpos = win->OpenWindow ( "Master thief Carmen San Diego is meeting with\n"
                            "the hench-persons of her gang, V.I.L.E.\n"
                          ) ;
   //wpos = win->OpenWindow ( "----------1---------2---------3---------4----z\n"
   //                         "012345678901234567890123456789012345678\n"
   //                       ) ;
   short rows = winHEIGHT - 2, cols = winWIDTH  - 2 ;
   gString gsDbg( "rows/cols: %02hd / %02hd\n"
                  "wpos     : %2hd / %2hd\n",
                  &rows, &cols, &wpos.row, &wpos.col ) ;

   //* By default, window objects are opened with non-blocking read *
   //* of the input stream, but since we interacting directly with  *
   //* the user for this test, re-enable blocking read.             *
   win->acBlockingInput ( true ) ;

   //* Enter some additional text at the current offset to verify *
   //* cursor position and box's interior color attributes.       *
   //* (Multi-column text characters test position parsing.)      *
   wpos = win->Write ( wpos, L">(i.e. Villains International League of Evil)<\n" ) ;
   gsDbg.append( "wpos     : %02hd / %02hd\n", &wpos.row, &wpos.col ) ;

   wpos.col += 10 ;
   gsDbg.append( "wpos     : %02hd /*%02hd\n", &wpos.row, &wpos.col ) ;

   //* Chinese translation of "Villains International League of Evil" *
   wpos = win->Write ( wpos, L">（即反派国际邪恶联盟）<" ) ;
   gsDbg.append( "wpos     : %02hd / %02hd\n", &wpos.row, &wpos.col ) ;

   //* Add text at an arbitrary offset and in a different color.*
   wpos.row += 2 ;
   wpos.col = 11 ;
   wpos = win->Write ( wpos, "Who is Carmen, really?!\n"
                             "Stay tuned to find out.",
                       acAttr(acaFGb_BLUE | acaBG_GREY) ) ;

   gsDbg.append( "wpos     : %02hd / %02hd\n", &wpos.row, &wpos.col ) ;
   wpDbg = this->acWrite ( wpDbg, gsDbg ) ;

   //**-----------------------------------**
   //** Initial window setup is complete. **
   //**-----------------------------------**

   //* Clear the prompt/menu area and display main option menu.*
   this->acClearArea ( promptRow, promptCol, promptHeight, promptWidth ) ;
   this->acSetFgBg ( fg, aesBG_DFLT ) ;
   this->acSetMod ( aesUNDERLINE ) ;
   this->acWrite ( mpos.row, (mpos.col + 4),
                   L"(enclose arguments in single quotes)\n" ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;
   this->acWrite ( (mpos.row + 1), mpos.col,
                   L"'c' (clear)   : erase window text\n"
                    "    Optional 'n' indicates row, else clear all\n"
                    "    Example: clear all: c  clear line: c='4'\n"
                    "'p' (position): specify text insertion\n"
                    "    point. Example: p='0,14'\n"
                    "'t' (addtext) : add text to window\n"
                    "    (caret: '^'==newline) (special case:'verb')\n"
                    "    Example: t='Hello World!^How are You?'\n"
                    "'T' (Title)[a]: specify new window title\n"
                    "    Example: T='ANSI World' or Ta='ANSI World'\n"
                    "'a' (txtattr) : set text fg/bg and update text\n"
                    "    Example: a='4,11' (a-h for RGB: a='c,12')\n"
                    "'b' (bdrattr) : set border fg/bg, update border\n"
                    "    Example: b='8,3' (a-h for RGB: b='b,e')\n"
                    "'d' (draw)[d] : lines and boxes\n"
                    "'h' (hide)    : hide (erase) the window\n"
                    "'r' (refresh) : redraw the window\n"
                    "'m' (move)    : move window to alt position\n"
                    "'x' (exit)    : exit the test\n"
                 ) ;

   #if 0    // Verify Attribute "Type"
   short frgbType  = aesFG_RGB,   brgbType   = aesBG_RGB,
         f4bitType = aesFG_DFLT,  b4bitType  = aesBG_DFLT,
         f8bitType = aesFG_INDEX, b8bitType = aesBG_INDEX ;
   gsOut.compose( "-----aes Types-----\n"
                  "4bitFg:%hX 4bitBg:%hX\n"
                  "8bitFg:%hX 8bitBg:%hX\n"
                  " rgbFg:%hX  rgbBg:%hX\n",
                  &f4bitType, &b4bitType, &f8bitType, 
                  &b8bitType, &frgbType, &brgbType ) ;
   this->acWrite ( 3, 135, gsOut ) ;
   #endif   // Verify Attribute "Type"

   //**********************
   //* Display the prompt *
   //**********************
   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;
   this->acSetMod ( aesBOLD ) ;
   gsOut.clear() ;
   gsOut.padCols( promptWidth, wcsHORIZs ) ;
   this->acWrite ( (promptRow + 1), promptCol, gsOut ) ;
   this->acWrite ( (promptRow - 1), promptCol, promptText ) ;
   this->acSetMod ( aesBOLD_OFF ) ;

   //********************
   //* User interaction *
   //********************
   WinPos   userIP( 0, 0 ) ;              // text insertion point (offset)
   short    userRow,                      // row offset
            cindx,                        // character index
            newAttrSteps = 3 ;            // for setting text/border attributes
   bool     done = false ;                // loop control

   while ( ! done )
   {
      //* Clear the prompt area *
      this->acClearArea ( promptRow, promptCol, 1, promptWidth ) ;
      this->acSetCursor ( aesCUR_ABSPOS, promptRow, promptCol ) ;

      //******************
      //* Get user input *
      //******************
      this->acRead ( gsIn, promptWidth ) ;

      #if 1    // Debugging Only
      //* Used for repeated use of the same input *
      if ( *gsIn.gstr() == L'z' )
      {
         if ( newAttrSteps == 3 )         gsIn = L"a='c,b'" ;
         else if ( newAttrSteps == 2 )    gsIn = L"a='h,f'" ;
         else if ( newAttrSteps == 1 )    gsIn = L"a='f,c'" ;
      }
      #endif   // Debugging Only

      //* If the operation-report area is full, clear the area *
      if ( wpDbg.row >= (ccRow + rptRows) )
      {
         this->acClearArea (ccRow, tdumpCOL, rptRows, rptCols ) ;
         wpDbg = {ccRow, tdumpCOL} ;
      }

      //*---------------------------*
      //* Clear one row or all rows *
      //*---------------------------*
      if ( ((gsIn.find( L'c' )) == ZERO) || ((gsIn.find( L"clear" )) == ZERO) )
      {
         //* Clear a single row *
         if ( (cindx = gsIn.after( L"='" )) > ZERO )
         {
            gsIn.shiftChars( -(cindx) ) ;
            if ( (gsIn.gscanf( "%hd", &userRow )) != 1 ) // syntax error
               userRow = -1 ;
            if ( userRow >= (winHEIGHT - 2) )            // range check
               userRow = -1 ;
            if ( userRow >= ZERO )
               userIP = win->ClearRow ( userRow ) ;
            gsDbg.compose( "clear    : %hd\n", &userRow ) ;
            wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
         }
         //* Clear entire window interior *
         else
         {
            userIP = win->ClearWin () ;
            gsDbg = "clear    : win\n" ;
            wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
         }
      }

      //*------------------------------------------------*
      //* Specify text insertion point (cursor position) *
      //*------------------------------------------------*
      else if ( ((gsIn.find( L'p' )) == ZERO) || ((gsIn.find( L"position" )) == ZERO) )
      {
         //* Note: This position will be used for the subsequent write *
         //*       of text into the window.                            *
         if ( (cindx = gsIn.after( L"='" )) > ZERO )
         {
            gsIn.shiftChars( -(cindx) ) ;
            if ( ((gsIn.gscanf( "%hd,%hd", &userIP.row, &userIP.col )) != 2) ||
                 (userIP.row < ZERO) || (userIP.row >= (winHEIGHT - 2)) ||
                 (userIP.col < ZERO) || (userIP.col >= (winWIDTH - 2)) )
            {
               userIP.row = userIP.col = -1 ;
               gsDbg.compose( "position : %02hd / %02hd (error)\n", &userIP.row, &userIP.col ) ;
            }
            else
               gsDbg.compose( "position : %02hd / %02hd (saved)\n", &userIP.row, &userIP.col ) ;
         }
         else
            gsDbg = "position error\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

      //*-----------------------------------------------*
      //* Add text to window at current insertion point *
      //*-----------------------------------------------*
      else if ( ((gsIn.find( L't', ZERO, true )) == ZERO) || 
                ((gsIn.find( L"addtext" )) == ZERO) )
      {
         //* This text DOES NOT include line breaks and therefore may be used *
         //* to test automatic line-wrap functionality of the Write() method. *
         const wchar_t *verboseText = 
            L"When in the Course of human events it becomes necessary for one "
             "people to dissolve the political bands which have connected them "
             "with another, and to assume among the Powers of the earth, the "
             "separate and equal station to which the Laws of Nature and of "
             "Nature's God entitle them, a decent respect to the opinions of "
             "mankind requires that they should declare the causes which impel "
             "them to the separation." ;

         if ( (cindx = gsIn.after( L"='" )) > ZERO )
         {
            gsIn.shiftChars( -(cindx) ) ;
            if ( (gsIn.findlast( L'\'' )) == (gsIn.gschars() - 2) )
               gsIn.limitChars( (gsIn.gschars() - 2) ) ;
            gsIn.replace( '^', '\n', ZERO, false, true ) ;
            gsDbg.compose( "add text : %C%C...\n", 
                           &gsIn.gstr()[0], &gsIn.gstr()[1] ) ;

            //* Special Case: Use pre-defined verbose text.*
            if ( (gsIn.compare( L"verb", true, 4 )) == ZERO )
               gsIn = verboseText ;

            //* Write the text and save the new position *
            userIP = win->Write ( userIP, gsIn ) ;
         }
         else
            gsDbg = "error\n" ;
         if ( (gsIn.findlast( L'\n' )) != (gsIn.gschars() - 2) )
            gsDbg.append( "\n" ) ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

      //*---------------------------*
      //* Set text fg/bg attributes *
      //*---------------------------*
      else if ( ((gsIn.find( L'a' )) == ZERO) || ((gsIn.find( L"txtattr" )) == ZERO) )
      {
         const wchar_t *NewAttrText = L"New text attributes have been specified.\n" ;

         acAttr newAttr ;           // encoded attributes
         short   fgVal,  bgVal ;    // for color index
         bool isRgbFg, isRgbBg ;    // RGB indicators

         gsDbg = gsIn ;             // copy source data
         if ( (twDecodeFgBg ( gsDbg, fgVal, bgVal, isRgbFg, isRgbBg )) )
         {
            //* Construct the attribute bitmap *
            newAttr = win->acComposeAttributes ( fgVal, bgVal, isRgbFg, isRgbBg ) ;
            wpos = { 2, 3 } ;       // test text output position

            //* Set the new attributes according to specified method.*
            //* These methods exercise different parts of the code.  *
            switch ( newAttrSteps )
            {
               //* Perform the update in three discrete steps *
               case 3:
                  win->SetTextAttr ( newAttr ) ;              // set new attributes
                  win->ClearWin () ;                          // erase window interior
                  userIP = win->Write ( wpos, NewAttrText ) ; // write the text
                  break ;

               //* Perform the update in two steps *
               case 2:
                  win->ClearWin ( newAttr ) ; // set new attributes and clear window
                  userIP = win->Write ( wpos, NewAttrText ) ; // write the text
                  break ;

               //* Perform the update in a single step.  *
               //* Note that setting attributes during   *
               //* a Write() call is for that write only.*
               case 1:
                  //* Set attributes, clear window interior and write text *
                  gsOut.compose( "%S         (for this write only)          \n", 
                                 NewAttrText ) ;
                  userIP = win->Write ( wpos, gsOut, newAttr, true ) ;
                  break ;
            }

            //* Display new attribute configuration *
            win->GetWinAttributes ( txtExp, bdrExp ) ;
            gsOut = tdTitle ;
            this->acaExpandDump ( txtExp, dumpROW, tdumpCOL, &gsOut ) ;
         }
         else
            gsDbg = "txtattr  : error\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }  // text attributes

      //*-----------------------------*
      //* Set border fg/bg attributes *
      //*-----------------------------*
      else if ( ((gsIn.find( L'b' )) == ZERO) || ((gsIn.find( L"bdrattr" )) == ZERO) )
      {
         acAttr newAttr ;           // encoded attributes
         short   fgVal,  bgVal ;    // for color index
         bool isRgbFg, isRgbBg,     // RGB indicators
              goodUser = false ;    // 'true' if good user input

         gsDbg = gsIn ;             // copy source data
         if ( (goodUser = twDecodeFgBg ( gsDbg, fgVal, bgVal, isRgbFg, isRgbBg )) )
         {
            //* Construct the attribute bitmap *
            newAttr = win->acComposeAttributes ( fgVal, bgVal, isRgbFg, isRgbBg ) ;

            win->SetBorderAttr ( newAttr ) ;

            //* Display new attribute configuration *
            win->GetWinAttributes ( txtExp, bdrExp ) ;
            gsOut = bdTitle ;
            this->acaExpandDump ( bdrExp, dumpROW, bdumpCOL, &gsOut ) ;
         }
         else
            gsDbg = "bdrattr  : error\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }     // border attributes

      //*------------------*
      //* Set window title *
      //*------------------*
      else if ( ((gsIn.find( L'T', ZERO, true )) == ZERO) || ((gsIn.find( L"Title" )) == ZERO) )
      {
         if ( (cindx = gsIn.after( L"='" )) > ZERO )
         {
            bool altAttr = false ;              // assume border color attributes
            if ( (gsIn.find( L'a' )) == cindx - 3 )
               altAttr = true ;                 // alternate text colors
            gsIn.shiftChars( -(cindx) ) ;
            if ( (gsIn.findlast( L'\'' )) == (gsIn.gschars() - 2) )
               gsIn.limitChars( (gsIn.gschars() - 2) ) ;
            gsDbg.compose( "Title    : %S\n", gsIn.gstr() ) ;
            if ( ! altAttr )
               win->SetTitle ( gsIn.ustr() ) ;
            else
            {
               gsDbg.replace( L"Title   ", L"TitleAlt" ) ;
               win->SetTitle ( gsIn.ustr(), acAttr(acaFG_BLUE | acaBGb_GREEN) ) ;
            }
         }
         else
            gsDbg = "Title error\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

      //*----------------------*
      //* Draw lines and boxes *
      //*----------------------*
      else if ( ((gsIn.find( L'd' )) == ZERO) || ((gsIn.find( L"draw" )) == ZERO) )
      {
         const acAttr boxCOLORS = acAttr(0x0000340A) ;
         const acAttr altATTR   = acAttr(0x0000340E) ;
         const short boxHEIGHT = 4 ;
         const short boxWIDTH = 26 ;
         LineType bdrStyle = ((gsIn.gstr()[1] == L'd') ? ltDUAL : ltSINGLE) ;
         //* Set the window title *
         win->SetTitle ( " Line-drawing Tests ", acaUSEDFLT, bdrStyle ) ;
         //* Clear window and set color attributes *
         win->ClearWin ( boxCOLORS ) ;

         //* Draw a box *
         wpos = { 4, 10 } ;
         win->DrawBox ( wpos, boxHEIGHT, boxWIDTH, ltDUAL, altATTR ) ;
         //* Draw horizontal interior lines (no endpoints) *
         wpos.row += 2 ;
         wpos.col += 5 ;
         win->DrawLine ( wpos, boxWIDTH - 8, false, ltSINGLE, L'\0', L'\0', altATTR ) ;
         ++wpos.row ;
         win->DrawLine ( wpos, boxWIDTH - 8, false, ltDUAL, L'\0', L'\0', altATTR ) ;

         //* Draw vertical interior lines (no endpoints) *
         --wpos.row ;
         wpos.col -= 2 ;
         win->DrawLine ( wpos, boxHEIGHT - 2, true, ltDUAL, L'\0', L'\0', altATTR ) ;
         wpos.col += boxWIDTH - 5 ;
         win->DrawLine ( wpos, boxHEIGHT - 2, true, ltSINGLE, L'\0', L'\0', altATTR ) ;

         //* Draw horizontal lines the full width of window.*
         wpos = { 1, 0 } ;
         win->DrawLine ( wpos, winWIDTH, false, ltSINGLE ) ;
         wpos.row += 3 ;
         win->DrawLine (wpos, winWIDTH, false, ltDUAL ) ;

         //* Draw vertical lines the full height of window.*
         //* These intersect not only with the border, but *
         //* also with the full-width horizontal lines.    *
         wpos = { 0, 4 } ;
         wpos = win->DrawLine ( wpos, 2, true, ltSINGLE, L'\0', wcsINSECT ) ;
         wpos = win->DrawLine ( wpos, 3, true, ltSINGLE, L'\0', wcsINSECTdh ) ;
         win->DrawLine ( wpos, 5, true, ltSINGLE ) ;
         wpos = { 0, short(winWIDTH - 5) } ;
         wpos = win->DrawLine ( wpos, 2, true, ltDUAL, L'\0', wcsINSECTdv ) ;
         wpos = win->DrawLine ( wpos, 3, true, ltDUAL, L'\0', wcsINSECTd ) ;
         win->DrawLine ( wpos, 5, true, ltDUAL ) ;

         #if 1    // Production
         //* Draw interior lines with appropriate endpoints.*
         wpos = { 2, 12 } ;
         win->DrawLine ( wpos, 24, false, ltSINGLE, wcsLTEE, wcsRTEE ) ;
         ++wpos.row ;
         --wpos.col ;
         win->DrawLine ( wpos, 26, false, ltDUAL, wcsLTEEd, wcsRTEEd ) ;
         wpos = { 5, 7 } ;
         win->DrawLine ( wpos, 4, true, ltSINGLE, wcsTTEE, wcsBTEE ) ;
         wpos.col = winWIDTH - 8 ;
         win->DrawLine ( wpos, 4, true, ltDUAL, wcsTTEEd, wcsBTEEd ) ;
         #else    // Debugging only: Test length==2 (this is a special case)
         WinPos wpt = { 2, 0 } ;
         wpt = win->DrawLine ( wpt, 2, false, ltSINGLE, L'\0', L'x' ) ;
         wpt = win->DrawLine ( wpt, 44, false, ltSINGLE, L'y' ) ;
         wpt = win->DrawLine ( wpt, 2, false, ltSINGLE, L'z' ) ;
         wpt = { 0, 6 } ;
         wpt = win->DrawLine ( wpt, 2, true, ltSINGLE, L'\0', L'x' ) ;
         wpt = win->DrawLine ( wpt, 6, true, ltSINGLE, L'y' ) ;
         wpt = win->DrawLine ( wpt, 2, true, ltSINGLE, L'z' ) ;
         #endif   // Debugging only

         gsDbg = "draw     : OK\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

      //*-----------------*
      //* Hide the window *
      //*-----------------*
      else if ( ((gsIn.find( L'h' )) == ZERO) || ((gsIn.find( L"hide" )) == ZERO) )
      {
         acAttr hideAttr = acaUSEDFLT ;
         if ( gsIn.gstr()[1] != NULLCHAR )   // test alternate clear attribute
            hideAttr = acAttr(acaFG_BLUE | acaBG_CYAN) ;
         win->HideWin ( hideAttr ) ;
         gsDbg = "hide     : OK\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

      //*------------------------------*
      //* Refresh (re-draw) the window *
      //*------------------------------*
      else if ( ((gsIn.find( L'r' )) == ZERO) || ((gsIn.find( L"refresh" )) == ZERO) )
      {
         win->RefreshWin () ;
         win->SetFieldData ( ZERO, fldText, acaBG_GREEN ) ;
         win->nsleep ( 25 ) ;
         win->SetFieldData ( ZERO, L"", acAttr(acaFG_RED | acaBG_GREY) ) ;
         gsDbg = "refresh  : OK\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

      //*-------------------------------------------*
      //* Move the window to the alternate position *
      //*-------------------------------------------*
      else if ( ((gsIn.find( L'm' )) == ZERO) || ((gsIn.find( L"move" )) == ZERO) )
      {
         if ( winBase.row == winROW )                 // alternate window position
            winBase = {altROW, altCOL} ;
         else                                         // original window position
            winBase = {winROW, winCOL} ;
         win->MoveWin ( winBase ) ;
         gsDbg = "move     : OK\n" ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

      //*------------------------------------*
      //* Exit the test and return to caller *
      //*------------------------------------*
      else if ( ((gsIn.find( L'x' )) == ZERO) || ((gsIn.find( L"exit" )) == ZERO) )
      {
         done = true ;
      }

      //* Options to specify which text attribute modification test *
      //* to perform. Not included in the user menu. (see above)    *
      else if ( ((gsIn.find( L'1' )) == ZERO) ||
                ((gsIn.find( L'2' )) == ZERO) ||
                ((gsIn.find( L'3' )) == ZERO) )
      {
         gsIn.gscanf( "%hd", &newAttrSteps ) ;
         gsDbg.compose( "attrSteps: %hd\n", &newAttrSteps ) ;
         wpDbg = this->acWrite ( wpDbg, gsDbg ) ;     // report
      }

   }  // while()

   //* Close the window (but do not erase it) *
   win->CloseWindow ( false ) ;

   //* Delete the window object *
   delete win ;

   //* Restore original terminal settings *
   this->acRestoreTermInfo () ;

   //* Prepare to Exit *
   this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 1, 1 ) ;

}  //* End Test_Window() *

//*************************
//*     twDecodeFgBg      *
//*************************
//********************************************************************************
//* Non-member method: Called by Test_Window() to decode fg/bg user input.       *
//* ------------------                                                           *
//* Raw Input Format: a='x,y'  or  b='x,y'                                       *
//*  a) Indexed values are integers between zero and max8BIT.                    *
//*  b) RGB colors are indicated by alpha characters between 'a' and 'h'.        *
//*                                                                              *
//* Input  : gsIn   : (by reference) user input string                           *
//*          fgVal  : (by reference) receives decoded foreground index           *
//*          bgVal  : (by reference) receives decoded background index           *
//*          isRgbFg: (by reference) 'true' if fgnd is RGB index                 *
//*                                  'false' if fgnd is table index              *
//*          isRgbBg: (by reference) 'true' if bgnd is RGB index                 *
//*                                  'false' if bgnd is table index              *
//*                                                                              *
//* Returns: 'true' if data validated, else 'false'                              *
//*          On return, gsIn contains the report message.                        *
//********************************************************************************

static bool twDecodeFgBg ( gString& gsIn, short& fgVal, short& bgVal, 
                           bool& isRgbFg, bool& isRgbBg )
{
   //* Reporting formats *
   const char *rr = "txtattr  :  %C /  %C\n" ;
   const char *ii = "txtattr  : %02hd / %02hd\n" ;
   const char *ir = "txtattr  : %02hd /  %C\n" ;
   const char *ri = "txtattr  :  %C / %02hd\n" ;

   wchar_t fgChar = L'\0',                // alpha characters
           bgChar = L'\0' ;
   short indexed = ZERO,                  // number of indexed values scanned
         rgbeed  = ZERO,                  // number of RGB alpha characters scanned
         cindx ;                          // source index
   bool border   = false,                 // true if border, false if text
        goodUser = false ;                // return value

   fgVal = bgVal = 256 ;                  // initialize indices
   isRgbFg = isRgbBg = false ;            // initialize flags

   //* Determine whether target area is text (interior) or border *
   if ( gsIn.gstr()[0] == L'b' )
      border = true ;

   //* Strip the enclosure leaving only the parameters seperated by a comma.*
   if ( (cindx = gsIn.after( L"='" )) > ZERO )
   {
      gsIn.shiftChars( -(cindx) ) ;
      if ( (gsIn.findlast( L'\'' )) == (gsIn.gschars() - 2) )
         gsIn.limitChars( (gsIn.gschars() - 2) ) ;

      if ( (indexed = gsIn.gscanf( "%hd,%hd", &fgVal, &bgVal )) == ZERO )
      {
         if ( (rgbeed  = gsIn.gscanf( "%C,%C", &fgChar, &bgChar )) > ZERO )
         {
            if ( (bgChar >= L'0') && (bgChar <= L'9') )
            { bgChar = L'\0' ; --rgbeed ; }
         }
      }

      //* If at exactly one parameter scanned, *
      //* scan for the remaining parameter.    *
      if ( ((indexed + rgbeed) == 1) && ((cindx = gsIn.find( L',' )) > ZERO) )
      {
         if ( indexed == 1 )                 // indexed fg found, scan for rgb bg
            rgbeed += gsIn.gscanf( cindx, ",%C", &bgChar ) ;
         else                                // rgb fg found, scan for indexed bg
            indexed += gsIn.gscanf( cindx, ",%hd", &bgVal ) ;
      }

      //* If two parameters were captured *
      if ( (indexed + rgbeed) == 2 )
      {
         bool goodFg = false, goodBg = false ;
         if ( (fgChar >= L'a') && (fgChar <= L'h') )  // convert rgb fg to index
         {
            //* Convert alpha user input to web-safe RGB indices.*
            switch ( fgChar )
            {
               case L'a':  fgVal = 0 ;    break ;
               case L'b':  fgVal = 36 ;   break ;
               case L'c':  fgVal = 72 ;   break ;
               case L'd':  fgVal = 108 ;  break ;
               case L'e':  fgVal = 144 ;  break ;
               case L'f':  fgVal = 180 ;  break ;
               case L'g':  fgVal = 216 ;  break ;
               case L'h':  fgVal = 252 ;  break ;
            }
            goodFg = isRgbFg = true ;
         }
         if ( (bgChar >= L'a') && (bgChar <= L'h') )  // convert rgb bg to index
         {
            switch ( bgChar )
            {
               case L'a':  bgVal = 0 ;    break ;
               case L'b':  bgVal = 36 ;   break ;
               case L'c':  bgVal = 72 ;   break ;
               case L'd':  bgVal = 108 ;  break ;
               case L'e':  bgVal = 144 ;  break ;
               case L'f':  bgVal = 180 ;  break ;
               case L'g':  bgVal = 216 ;  break ;
               case L'h':  bgVal = 252 ;  break ;
            }
            goodBg = isRgbBg = true ;
         }

         //* If fg != valid rgb *
         if ( ! goodFg && ((fgVal >= ZERO) && (fgVal <= max8BIT)) )
         {
              goodFg = true ;
         }

         //* If bg != valid rgb *
         if ( ! goodBg && ((bgVal >= ZERO) && (bgVal <= max8BIT)) )
         {
            goodBg = true ;
         }

         if ( goodFg && goodBg )       // fg and bg captured and range checked
         {
            goodUser = true ;
   
            //* Format the caller's report *
            if ( !isRgbFg && !isRgbBg )
               gsIn.compose( ii, &fgVal, &bgVal) ;
            else if ( isRgbFg  && isRgbBg )
               gsIn.compose( rr, &fgChar, &bgChar) ;
            else if ( !isRgbFg && isRgbBg )
               gsIn.compose( ir, &fgVal, &bgChar) ;
            else if ( isRgbFg && !isRgbBg )
               gsIn.compose( ri, &fgChar, &bgVal) ;
            if ( border )
               gsIn.replace( L"txt", L"bdr" ) ;
         }
      }
   }     // strip
   return goodUser ;

}  //* End twDecodeFgBg() *

//*************************
//*       Test_Form       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Create an ACWin object and exercise access to the functionality of the       *
//* skForm object within an ACWin window.                                        *
//*                                                                              *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Form ( aeSeq fg )
{  // Invocation: W f
   #define DEBUG_GFD (0)      // debugging only - test the GetFieldData() method
   #define DEBUG_TFBDR (0)    // debugging only - test Radiobutton field outline

   const char* bdTitle = "Border Attributes" ;
   const char* tdTitle = "Interior Attributes" ;
   const char* skTitle = "Settings: skForm Object" ;
   const acAttr bgAttr = acaBGb_CYAN ;    // window interior background attribute
   const short winROW     = 4 ;           // window position
   const short winCOL     = 2 ;
   const short winHEIGHT  = 28 ;          // window dimensions
   const short winWIDTH   = 48 ;
   const short dumpROW    = 1 ;                     // attribute dump
   const short bdumpCOL   = winCOL + winWIDTH + 4 ; // border attribute dump
   const short tdumpCOL   = bdumpCOL + 40 ;         // interior attribute dump
   const short skfROW     = dumpROW + 13 ;          // skForm dump
   const short skfCOL     = bdumpCOL - 2 ;
   enum fld : short                       // field indices
   {
      fldMBAR  = ZERO,                    // menubar anchors the group
      fldMENU1,                           // sub-menu for menu group
      fldMENU2,                           // sub-menu for menu group
      fldMULTI,                           // multi-row text field
      fldEMPTY,                           // empty field
      fldVIET,                            // Vietnamese field
      fldZHONG,                           // Chinese field
      fldRB_b,                            // Radiobutton for 'blue' border color
      fldRB_y,                            // Radiobutton for 'yellow' border color
      fldRB_g,                            // Radiobutton for 'green' border color
      fldRB_INT,                          // Radiobutton for alternate window interior color
      fldPB_BOX,                          // Pushbutton for field outlines
      fldRB_INS,                          // Radiobutton for Insert/Overstrike
      fldSB_BOX,                          // Scrollbox field
      fldPB_EXIT,                         // Exit Pushbutton
      fldCNT,                             // number of skField objects in window
   } ;

   //* Note that Gnometerm interprets the 'bold' foregound bit to mean   *
   //* that the number of pixels used to render text should be doubled,  *
   //* i.e. Fat Text rather than Intense Text (bright colors). For this  *
   //* reason we specify RGB and 8-bit attributes to produce bright text.*
   acaExpand acaBld ;
   const acAttr rgbBorder =               // window border: bright yellow on blue
         acaBld.buildRgb( true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX),
                          true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MAX * 0.65) ) ;
   const acAttr rgbInterior =             // window interior: red on bright cyan
         acaBld.buildRgb( true, WebSafeRGB(wsrgbRED),
                          true, WebSafeRGB(wsrgbCYAN + wsrgbSHADE_MAX) ) ;
   const acAttr mnAttr = 
         acaBld.buildRgb( true, WebSafeRGB(wsrgbGREY + wsrgbSHADE_MAX), 
                          true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MED) ) ;
   const acAttr pbAttr = acaBld.build8Bit( true, 160, true, 82 ) ;
   const acAttr sbAttr = acaBld.build8Bit( true, 16, true, 219 ) ;

   gString gsOut,                         // text formatting
           gsIn ;                         // user input
   acaExpand txtExp, bdrExp ;             // local copy of window attributes
   WinPos wpos ;                          // cursor offset into window object

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, "  (rows/cols of 37/155)\n"
                          "-----------------------" ) ; 

   //* -------------------------------------------------------- *
   //* Important Note: The call to OpenWindow() sets unbuffered *
   //* input with non-blocking read and echo option 'softEchoA'.*
   //*  (actually 'noEcho' which has the same keycode group.)   *
   //* -------------------------------------------------------- *

   //* Set attributes to specified terminal foreground value *
   this->acSetFgBg ( fg, aesBG_DFLT ) ;

   //* -------------------------------------------------------- *
   //* Define the input fields (skForm object).                 *
   //* -One field is three(3) rows in height, and the other     *
   //*  fields have a height of one row.                        *
   //* -Field width varies: 2 are wide, 3 are narrow.           *
   //* - Field color attributes set to terminal defaults during *
   //*   instantiation, and then automagically set to window    *
   //*   interior attributes by ACWin constructor UNLESS        *
   //*   explicitly specified in the skForm initialization.     *
   //* -Position of field 'orig' is relative to window origin.  *
   //* -'ip' indicates initial cursor position (insertion       *
   //*   point. 'cur' value is calculated based on 'ip'.        *
   //* -Field 'type' defaults to ftTB (Textbox).                *
   //*  4 Textbox controls (ftTB)                               *
   //*  4 Radiobutton controls (ftRB) (one is read-only)        *
   //*  1 Pushbutton control                                    *
   //* -Field 'flow' defaults to floChar (wrap at character)    *
   //*  Note that 'flow' applies ONLY to ftTB controls.         *
   //*                                                          *
   //* -------------------------------------------------------- *
   const wchar_t mbGrpId = L'm' ;               // menu group ID
   const wchar_t rbGrpId = L'a' ;               // radiobutton group ID
   const short widA = winWIDTH - 6,             // width of field 0 and 3
               widB = widA / 2 - 1 ;            // width of field 1, 2, 4
   short fldIndx ;                              // index of field with focus
   WinPos wpul( 0, 1 ) ;                        // field origin

   //* Create an object with fldCNT fields     *
   //* and initialize each field.              *
   //* See also Insert key configuration below.*
   skForm *skfPtr = new skForm
   (
      fldCNT,
      new skField[fldCNT]
      {
         //* Field index 0: ftMB (Menu: fldMBAR) *
         {
         ftMB,                            // field type
         1,                               // field height (in rows)
         short(winWIDTH - 4),             // field width (in columns)
         wpul.row, wpul.col,              // Y/X offset in window
         "  Animals(ALT+A)   Vegetables(ALT+V)", // field text
         mnAttr,                          // color attributes
         floChar,                         // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only flag is 'reset'
         false,                           // field outline disabled
         false,                           // field is visible (not hidden)
         true,                            // access lock enabled
         false,                           // initial binary state: 'reset'
         wcsF02,                          // menubar hotkey
         mbGrpId,                         // menubar group
         },

         //* Field index 1: ftMB (Menu: fldMENU1) *
         {
         ftMB,                            // field type
         6,                               // field height (in rows)
         16,                              // field width (in columns)
         short(wpul.row += 1),            // Y/X offset in window
         short(wpul.col += 2),
         "Item #1 Cat\n"
         "Item #2 Dog\n"
         "Item #3 Turtle\n"
         "Item #4 Parrot\n"
         "Item #5 Python\n",
         mnAttr,                          // color attributes
         floChar,                         // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only flag is 'reset'
         false,                           // field outline disabled
         true,                            // hidden flag is 'set'
         true,                            // access lock enabled
         false,                           // binary state: 'reset'
         wcsALT_A,                        // menu hotkey
         mbGrpId,                         // menubar group
         },

         //* Field index 2: ftMB (Menu: fldMENU2) *
         {
         ftMB,                            // field type
         7,                               // field height (in rows)
         18,                              // field width (in columns)
         wpul.row,                        // Y/X offset in window
         short(wpul.col += 17),
         "Item #1 Tomato\n"               // fieldtext
         "Item #2 Cucumber\n"
         "Item #3 Radish\n"
         "Item #4 Pepper\n"
         "Item #5 Avocado\n"
         "Item #6 Onion\n",
         mnAttr,                          // color attributes
         floChar,                         // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only flag is 'reset'
         false,                           // field outline disabled
         true,                            // hidden flag is 'set'
         true,                            // access lock enabled
         false,                           // binary state: 'reset'
         wcsALT_V,                        // menu hotkey
         mbGrpId,                         // menubar group
         },

         //* Field index 3: ftTB (Textbox: fldMULTI) *
         {
         ftTB,                            // field type
         3,                               // field height (in rows)
         widA,                            // field width (in columns)
         short(wpul.row += 4),            // Y/X offset in window
         short(wpul.col = 2),
         #if 1    // Production
         "Edit keys are: Left, Right, Home, End, \n"  // field text
         "Tab, Shift+Tab, PageUp, PageDown, plus \n"
         "Backspace, Delete and Insert.",
         #else    // TEST: mixed-width-characters
         // (Although our wife is far away, she is always with us.)
         "We are happy to present 我们的妻子苏菲。"
         "Although 她在远方， we feel that "
         "她总是和我们在一起。Happy feeling!",
         #endif   // TEST: mixed-width-character test
         acAttr(acaFG_BLUE | acaBG_GREY), // color attributes
         floChar,                         // text formatting option
         56,                              // insertion point (cursor offset)
         //false,                           // read-only flag is 'reset'
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 4: ftTB (Textbox: fldEMPTY) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         short(wpul.row += 5), wpul.col,  // Y/X offset in window
         "",                              // field text (empty string)
         acAttr(acaFG_GREEN | acaBG_GREY),// color attributes
         floShift,                        // text formatting option
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // read-only flag is 'reset'
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 5: ftTB (Textbox: fldVIET) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         wpul.row,                        // Y/X offset in window
         short(wpul.col += widB + 2),
         "Gặp tôi ở Sai Gon.",            // field text
         acAttr(acaFG_RED | acaBG_GREY),  // color attributes
         floShift,                        // text formatting option
         10,                              // insertion point (cursor offset)
         //false,                           // read-only flag is 'reset'
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 6: ftTB (Textbox: fldZHONG) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         widA,                            // field width (in columns)
         short(wpul.row += 3),            // Y/X offset in window
         short(wpul.col -= widB + 2),
         "这他妈的并不像看起来那么容易。",  // "It's not as fucking easy as it looks."
         acAttr(acaFG_MAGENTA | acaBG_GREY), // color attributes
         floShift,                        // text formatting option
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // read-only flag is 'reset'
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 7: ftRB (Radiobutton: fldRB_b) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         short(wpul.row += 2), wpul.col,  // Y/X offset in window
         "[ ] ^Blue Border",              // field text with Hotkey'^B'
         acAttr(acaFG_BLUE | bgAttr),     // color attributes
         floChar,                         // text formatting option (n/a)
         ZERO,                            // IP is dynamically set
         false,                           // read-only flag is 'reset'
         false,                           // field outline disabled
         false,                           // hidden flag is 'reset'
         true,                            // access lock enabled
         true,                            // binary state: 'set'
         NULLCHAR,                        // hotkey is embedded in text data
         rbGrpId,                         // radiobutton group
         },

         //* Field index 8: ftRB (Radiobutton: fldRB_y) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         short(wpul.row += 1), wpul.col,  // Y/X offset in window
         "[ ] ^Yellow Border",            // field text with Hotkey '^Y'
         acAttr(acaFG_BLUE | bgAttr),     // color attributes
         floChar,                         // text formatting option (n/a)
         ZERO,                            // IP is dynamically set
         false,                           // read-only flag is 'reset'
         false,                           // field outline disabled
         false,                           // hidden flag is 'reset'
         true,                            // access lock enabled
         false,                           // binary state: 'reset'
         NULLCHAR,                        // hotkey is embedded in text data
         rbGrpId,                         // radiobutton group
         },

         //* Field index 9: ftRB (Radiobutton: fldRB_g) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         short(wpul.row += 1), wpul.col,  // Y/X offset in window
         "[ ] ^Green Border",             // field text with Hotkey '^G'
         acAttr(acaFG_BLUE | bgAttr),     // color attributes
         floChar,                         // text formatting option (n/a)
         ZERO,                            // IP is dynamically set
         false,                           // read-only flag is 'reset'
         false,                           // field outline disabled
         false,                           // hidden flag is 'reset'
         true,                            // access lock enabled
         false,                           // binary state: 'reset'
         NULLCHAR,                        // hotkey is embedded in text data
         rbGrpId,                         // radiobutton group
         },

         //* Field index 10: ftRB (Radiobutton: fldRB_INT) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         short(wpul.row -= 2),            // Y/X offset in window
         short(wpul.col += widB + 2),
         "[ ] Alt^. Interior",            // field text with Hotkey '^.' (full-stop)
         acAttr(acaFG_BLUE | bgAttr),     // color attributes
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // IP is dynamically set
         //false,                           // read-only flag is 'reset'
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // hotkey is embedded in text data
         //noGROUP                          // not a group member
         },

         //* Field index 10: ftPB (Pushbutton: fldPB_BOX) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         short(wpul.row += 1), wpul.col,  // Y/X offset in window
         " Alternate ^Outlines ",         // field text with Hotkey '^O'
         pbAttr,                          // color attributes
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // insertion point (cursor offset) (n/a)
         //false,                           // read-only flag is 'reset'
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // hotkey is embedded in text data
         //noGROUP                          // not a group member
         },

         //* Field index 11: ftRB (Radiobutton: fldRB_INS) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         widB,                            // field width (in columns)
         // (width will be set to actual value during initialization)
         #if DEBUG_TFBDR == 0    // Production
         short(wpul.row += 1), wpul.col,  // Y/X offset in window
         #else                   // test ftRB outline
         short(wpul.row += 2),
         short(wpul.col += 4),
         #endif                  // test ftRB outline
         "[ ] Ins/Ovr Key ",              // field text
         acAttr(acaFG_GREY | bgAttr),     // color attributes
         floChar,                         // text formatting option (n/a)
         ZERO,                            // IP is dynamically set
         true,                            // read-only flag is 'set'
         false,                           // field outline disabled
         false,                           // hidden flag is 'reset'
         true,                            // access lock enabled
         true,                            // initial binary state 'set' (see explicit setting below)
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 12: ftSB (Scrollbox: fldSB_BOX) *
         {
         ftSB,                            // field type
         3,                               // field height (in rows)
         short(widA - 7),                 // field width (in columns)
         short(wpul.row += 2),            // Y/X offset in window
         #if DEBUG_TFBDR == 0    // Production
         short(wpul.col = 6),
         #else                   // test ftRB outline
         short(wpul.col = 2),
         #endif                  // test ftRB outline
         "There once was a man from Kirkuk,\n"  // field text
         "Known far and wide for his luck,\n"
         "Till while jogging one day,\n"
         "he looked the wrong way,\n"
         "Whereupon he was hit by a truck.\n"
         " ------------------------------\n"
         "There once was a bloke from Iran,\n"
         "While jogging as fast ere he can,\n"
         "Got some dust in his eye,\n"
         "Which did cause him to cry,\n"
         "Whereupon he was hit by a van.\n",
         sbAttr,                          // color attributes
         floToken,                        // text formatting option
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // read-only flag is 'reset'
         //false,                           // field outline disabled (will be set below)
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // initial binary state
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 13: ftPB (Pushbutton: fldPB_EXIT) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         short(widB + 2),                 // field width (in columns)
         short(winHEIGHT - 4),            // Y/X offset in window
         short((winWIDTH - 2) / 2 - (widB + 2) / 2),
         " E^XIT - 出口 - Salida ",         // field text with Hotkey '^X'
         pbAttr,                          // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (default)
         //ZERO,                            // insertion point (cursor offset) (default)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },
      },

      //* Initialize remaining skForm parameters.*
      fldMULTI          // index of field with initial focus
      // Remaining parameters receive default values.
      //acaUSEDFLT,     // default color attributes for control with focus
      //true,           // beep on unhandled keycode received
      //true,           // initial state of Insert/Overstrike flag
      //true,           // user access to Insert/Overstrike mode
      //false,          // enable cursor shape change for Insert/Overstrike
      //csBblock,       // cursor shape when Insert mode
      //csBuline        // cursor shape when Overstrike mode
   ) ;


   //**************************
   //* Define an ACWin object *
   //**************************
   ACWin *win = new ACWin
         ( winROW, winCOL,                        // position (upper-left corner) 
           winHEIGHT, winWIDTH,                   // dimensions (height, width)
           ltSINGLE,                              // border style
           rgbBorder,                             // border attributes
           rgbInterior,                           // interior attributes
           "  Window Formatted With \"skForm\" ", // title text
           skfPtr                                 // partially-initialized skForm object
         ) ;

   //* Get a copy of the window attributes and display them.*
   win->GetWinAttributes ( txtExp, bdrExp ) ;
   gsOut = bdTitle ;
   this->acaExpandDump ( bdrExp, dumpROW, bdumpCOL, &gsOut ) ;
   gsOut = tdTitle ;
   this->acaExpandDump ( txtExp, dumpROW, tdumpCOL, &gsOut ) ;

   //*************************************
   //* Open the window (make it visible) *
   //*************************************
   //* Note that we create borders around the defined fields.   *
   //* Important Note: The call to OpenWindow() sets unbuffered *
   //* input with blocking read and echo option 'softEchoA'.    *
   wpos = win->OpenWindow ( "\n\n"
                            " Tab among fields. Edit the fields as desired.\n"
                            " \"Hotkeys\" == ALT key + underlined character." ) ;
   cbPtr = win ;                    // give callback method access to ACWin object

   //* Configure the functionality of the "special" Insert key *
   win->InsertKey ( true, true, true ) ;

   //* Delete the local skForm object and reference   *
   //* the fully-initialized object within the window.*
   delete skfPtr ;
   gsOut = skTitle ;
   const skForm *skfp = win->GetForm () ;
   this->skFormDump ( *skfp, skfROW, skfCOL, &gsOut ) ;

   //* Draw boxes around the text fields and apply labels to the those fields.*
   //* Note that for some fields, the label and outline are set seperately,   *
   //* and for others the outline and label are set in a single call.         *
   //* This exercises the various configuration methods.                      *
   //* A: Set outline, then set label. *
   win->SetFieldOutline ( fldMULTI, ltSINGLE, acAttr(acaFG_BLUE | bgAttr) ) ;
   win->SetFieldLabel   ( fldMULTI, "^Multi-row Field", acAttr(acaFG_BLUE | bgAttr) ) ;
   //* B: Set outline and label together. *
   win->SetFieldLabel ( fldEMPTY, "^Empty Field", acAttr(acaFG_GREEN | bgAttr),
                        false, true, ltSINGLE ) ;
   win->SetFieldLabel   ( fldVIET, "^TiengViet Field", acAttr(acaFG_RED | bgAttr),
                          true, true, ltSINGLE ) ; // (label is centered)
   win->SetFieldLabel   ( fldZHONG, "^Zhongwen Field", acAttr(acaFG_MAGENTA | bgAttr),
                          false, true, ltSINGLE ) ;
   //* C: Set outline, then set label of a different color. *
   win->SetFieldOutline ( fldSB_BOX, ltDUAL, acAttr(acaFGb_GREEN | acaBGb_MAGENTA) ) ;
   win->SetFieldLabel   ( fldSB_BOX, "Sc^rolling - View Only", 
                          acAttr(acaFG_BLUE | acaBGb_MAGENTA), true ) ;
   //* D: Set outline only, (no external label for Pushbutton controls). *
   win->SetFieldOutline ( fldPB_EXIT, ltDUAL, acAttr(acaFG_MAGENTA | bgAttr) ) ;

   #if DEBUG_TFBDR != 0 // Test ftRB Outline
   win->SetFieldOutline ( 9, ltSINGLE, acAttr(acaFGb_GREY | bgAttr) ) ;
   #endif   // DEBUG_TFBDR

   // Debugging only - validate GetFieldData()
   // Also requires debug code in called method.
   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_FDUMP != 0 && DEBUG_GFD != 0
   skField sk ;
   win->GetFieldData ( fldMENU1, sk ) ;
   win->GetFieldData ( fldVIET, sk ) ;
   win->GetFieldData ( fldRB_b, sk ) ;
   win->GetFieldData ( fldPB_EXIT, sk ) ;
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_FDUMP && DEBUG_GFD

   //* Establish a callback method to monitor user input.   *
   //* When Pushbutton control selected, EditForm() returns.*
   win->EstablishCallback ( &tfCallback ) ;

   //******************
   //* Get User Input *
   //******************
   win->EditForm ( fldIndx ) ;

   //* Close the window and delete the object.*
   //* Important Note: The call to CloseWindow() sets buffered *
   //* input with blocking read and echo option 'termEcho'.    *
   win->CloseWindow () ;
   delete win ;
   cbPtr = NULL ;                         // reset the external pointer
   this->acWrite ( winROW, winCOL, L"Window closed and ACWin object deleted." ) ;

   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

   //* Restore original terminal settings *
   this->acRestoreTermInfo () ;

   //* Prepare to Exit *
   this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 1, 1 ) ;

   #undef DEBUG_TFBDR
   #undef DEBUG_GFD
}  //* End Test_Form() *

//*************************
//*      tfCallback       *
//*************************
//********************************************************************************
//* Non-member method:                                                           *
//* ------------------                                                           *
//* Callback method for Test_Form() above. Handle the callbacks from EditForm()  *
//*                                                                              *
//* As with all callback methods, some intimate assumptions about the member     *
//* method's data are made here. If something changes in Test_Form(), ALWAYS     *
//* check here to determine whether adjustments are needed.                      *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: value of 'wkey' parameter  *
//********************************************************************************
//* It would be useful to update the dump of border attributes when changing     *
//* the border colors; however, the acaExpandDump() method is declared private.  *
//* It would only be practical if the callback was a member method.              *
//* Think about this...                                                          *
//*                                                                              *
//* const short bdumpROW   = 3 ;           // border attribute dump              *
//* const short bdumpCOL   = 58 ;          // border attribute dump              *
//* const char* bdTitle = "Border Attributes" ;                                  *
//* acaExpand txtExp, bdrExp ;    // local copy of window attributes             *
//*   save attributes                                                            *
//*   set terminal defaults                                                      *
//* cbPtr->GetWinAttributes ( txtExp, bdrExp ) ;                                 *
//* gString gsOut( bdTitle ) ;                                                   *
//* this->acaExpandDump ( bdrExp, bdumpROW, bdumpCOL, &gsOut ) ;                 *
//*   restore attributes                                                         *
//********************************************************************************

static wchar_t tfCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   enum fld : short                       // field indices
   {
      fldMBAR  = ZERO,                    // menubar anchors the group
      fldMENU1,                           // sub-menu for menu group
      fldMENU2,                           // sub-menu for menu group
      fldMULTI,                           // multi-row text field
      fldEMPTY,                           // empty field
      fldVIET,                            // Vietnamese field
      fldZHONG,                           // Chinese field
      fldRB_b,                            // Radiobutton for 'blue' border color
      fldRB_y,                            // Radiobutton for 'yellow' border color
      fldRB_g,                            // Radiobutton for 'green' border color
      fldRB_INT,                          // Radiobutton for alternate window interior color
      fldPB_BOX,                          // Pushbutton for field outlines
      fldRB_INS,                          // Radiobutton for Insert/Overstrike
      fldSB_BOX,                          // Scrollbox field
      fldPB_EXIT,                         // Exit Pushbutton
   } ;
   static acAttr bdrB, bdrY, bdrG ;       // border color attributes
   static acAttr intStd, intAlt, intCur ; // window interior color attributes
   static LineType outline = ltSINGLE ;   // field outline type
   static bool insFlag ;                  // state of Ins/Ovr flag

   //* First call *
   if ( firstTime )
   {
      acaExpand bld ;                     // calculate border and interior colors
      bdrB = bld.buildRgb( true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX), // yellow-on-blue
                           true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MAX * 0.65) ) ;
      bdrY = bld.buildRgb( true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MAX * 0.65), // blue-on-yellow
                           true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX) ) ;
      bdrG = bld.buildRgb( true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MAX * 0.65), // magenta-on-green
                           true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MAX) ) ;
      intStd = bld.buildRgb( true, WebSafeRGB(wsrgbRED),    // red-on-cyan
                             true, WebSafeRGB(wsrgbCYAN + wsrgbSHADE_MAX) ) ;
      intAlt = bld.buildRgb( true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MAX), // green-on-black
                             true, WebSafeRGB(wsrgbBLACK) ) ;
      intCur = intStd ;
      insFlag = cbPtr->isInsertMode () ;
      return wkey ;
   }

   //* Update the Insert/Overstrike Radiobutton.                 *
   //* Note that the skForm 'ins' flag is set/reset by caller.   *
   //* the fldRB_INS field is read-only, so we must explicitly   *
   //* update the field when the state of the 'ins' flag changes.*
   if ( wkey == wcsINSERT )
   {
      insFlag = insFlag ? false : true ;
      cbPtr->SetFieldState ( fldRB_INS, insFlag, true ) ;
      cbPtr->RefreshField () ;      // return visible cursor to field with focus
   }

   //* Enter Key:                                                 *
   //* a) If member of Radiobutton group, execute the associated  *
   //*    task. Note that cursor may need to be returned to its   *
   //*    position afterward.                                     *
   //*    Please note that if wkey==SPACE, it is not modified     *
   //*    unless it activates a Radiobutton.                      *
   //* b) If 'exit; Pushbutton, signal to close the window.       *
   else if ( (wkey == wcsENTER) || (wkey == SPACE) )
   {
      switch ( ctrlIndex )
      {
         case fldRB_b:                    // Radiobutton XOR group: window border to blue
         case fldRB_y:                    // Radiobutton XOR group: window border to yellow
         case fldRB_g:                    // Radiobutton XOR group: window border to green
            {
            acAttr attr = acAttr(ctrlIndex == fldRB_b ? bdrB :
                          ctrlIndex == fldRB_y ? bdrY : bdrG) ;
            cbPtr->SetBorderAttr ( attr, (ctrlIndex == fldRB_g ? ltDUAL : ltSINGLE) ) ;
            cbPtr->RefreshField () ;
            }
            break ;
         case fldRB_INT:                  // set/reset alternate window interior attributes
            intCur = intCur == intStd ? intAlt : intStd ;
            cbPtr->SetTextAttr ( intCur ) ;
            cbPtr->RefreshWin () ;
            // Programmer's Note: The attributes for Radiobutton fields and 
            // external labels are not updated, but then again, no sane 
            // application developer would ever do what this test does.
            break ;
         case fldPB_BOX:                  // toggle field outline style and attributes
            outline = outline == ltSINGLE ? ltDUAL : ltSINGLE ;
            cbPtr->SetFieldOutline ( fldMULTI, outline, acaUSEDFLT ) ;
            cbPtr->SetFieldOutline ( fldEMPTY, outline, acaUSEDFLT ) ;
            cbPtr->SetFieldOutline ( fldVIET,  outline, acaUSEDFLT ) ;
            cbPtr->SetFieldOutline ( fldZHONG, outline, acaUSEDFLT ) ;
            cbPtr->SetFieldOutline ( fldPB_EXIT, 
                                     (outline==ltDUAL ? ltSINGLE : ltDUAL), 
                                     acAttr(acaFG_MAGENTA | (intCur & acaBG_MASK)) ) ;
            // Programmer's Note: The attributes for the text field labels 
            // are not updated, but ibid.
            break ;
         case fldPB_EXIT:                 // Exit pushbutton
            wkey = wcsCTL_4 ;
            break ;
         default:                         // not valid for single-row text fields
            break ;                       // caller will convert to wcsTAB
      }  // switch()
   }

   return wkey ;

}  //* End tfCallback() *

//*************************
//*    Test_WinDefault    *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Create an ACWin object using the default constructor.                        *
//* Exercise various window-configuration methods.                               *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_WinDefault ( aeSeq fg )
{  // invoke: W c
   const acAttr Text1Attr   = acAttr(acaFG_BLUE | acaBG_CYAN) ;
   const acAttr Text2Attr   = acAttr(acaFG_DFLT | acaBG_DFLT) ;
   const acAttr Title1Attr  = acAttr(acaFGb_GREEN | acaBG_BROWN) ;
   const acAttr Border1Attr = acAttr(acaFG_GREEN | acaBG_BROWN) ;
   const acAttr Border2Attr = acAttr(acaFG_DFLT | acaBG_DFLT) ;
   const acAttr Field1Attr  = acAttr(acaFGb_GREEN | acaBG_BLACK) ;
   const acAttr Field2Attr  = acAttr(acaFG_DFLT | acaBG_DFLT) ;
   const acAttr CloseAttr   = acAttr(acaFG_DFLT | acaBG_GREY) ;
   const char  *Title1Text  = "  Default ACWin Constructor  " ;
   const char  *Title2Text  = "" ;

   gString gsOut, gsIns, gsTxt ;       // text formatting
   WinPos  wpos ;                      // window offset in terminal window
   WinPos  wpTxt( 0, 0 ) ;             // offset into window's text area
   const skForm *skfp ;                // pointer to window's skForm object
   acAttr  txtAttr = Text2Attr ;       // current text (window interior) attributes
   acAttr  bdrAttr = Border2Attr ;     // current border attributes
   acAttr  fldAttr = Field2Attr ;      // input field color attributes
   wchar_t wkey ;                      // user input
   short   whgt,                       // window height (rows)
           wwid,                       // window width (columns)
           fIndx = ZERO ;              // receives field index on return from EditForm()
   bool    title = false ;             // set if title currently displayed

   //* Set unbuffered read for initial interaction.            *
   //* Window will take control of buffering when it is opened.*
   this->acBufferedInput ( false, noEcho ) ;

   //* Instantiate the ACWin object using the _default_ constructor.     *
   //* All parameters are set to the default values for that constructor.*
   ACWin *win = new ACWin ;

   //* Get window dimensions and position, *
   //* and compose user instructions.      *
   wpos  = win->GetWinConfig ( whgt, wwid ) ;
   skfp  = win->GetForm () ;
   gsIns.compose( "pos:%hd:%hd rows:%hd cols:%hd fields:%hd\n"
                  "focus:%hd ins:%hhd togg:%hhd beep:%hhd\n"
                  "'t' text color   'b' border color\n"
                  "'f' field color  'e' edit field\n"
                  "'T' set title    'c' cursor move\n"
                  "                 'q' to quit.", 
                  &wpos.row, &wpos.col, &whgt, &wwid, &skfp->fCnt,
                  &skfp->fi, &skfp->ins, &skfp->togg, &skfp->beep ) ;
   gsTxt = " Hello World! " ;
   gsTxt.padCols( wwid - 4, L'-', true ) ;
   gsTxt.insert( "\n\n<" ) ;
   gsTxt.append( ">\n   (press any key to continue)" ) ;

   //* Open the window and write some text into the text area.*
   win->OpenWindow () ;
   WinPos wpx( 0, 0 ) ;
   win->Write ( wpx, gsTxt ) ;
   win->SetCursorPos ( 4, (wwid / 2 - 2) ) ;
   win->acRead () ;           // wait for user response
   win->acSetEchoOption ( noEcho ) ;

   //* Initialize the (only) field with instructions *
   //* on how to use the test.                       *
   win->SetFieldText ( fIndx, gsIns ) ;
   win->acSetCursorStyle ( csHide ) ;

   while ( (wkey = win->acRead ()) != L'q' )
   {
      switch ( wkey )
      {
         case L't':              // Text color (window interior)
            txtAttr = txtAttr == Text2Attr ? Text1Attr : Text2Attr ;
            win->SetTextAttr ( txtAttr ) ;
            win->Write ( wpTxt, gsTxt, acaUSEDFLT, true ) ;
            win->acRead () ;     // wait for user response
            win->RefreshField ( fIndx ) ;
            break ;
         case L'b':              // Border color
            bdrAttr = bdrAttr == Border2Attr ? Border1Attr : Border2Attr ;
            win->SetBorderAttr ( bdrAttr ) ;
            if ( title )
               win->SetTitle ( Title1Text, Title1Attr ) ;
            break ;
         case L'f':              // Field text color
            fldAttr = fldAttr == Field2Attr ? Field1Attr : Field2Attr ;
            win->SetFieldAttr ( fIndx, fldAttr ) ;
            break ;
         case L'e':              // Edit text
            fldAttr = fldAttr == Field2Attr ? Field1Attr : Field2Attr ;
            win->SetFieldAttr ( fIndx, fldAttr ) ;
            win->acSetCursorStyle ( csShow ) ;
            win->EditForm ( fIndx, -1, L'q' ) ;
            win->acSetCursorStyle ( csHide ) ;
            #if 0    // Test GetFieldText()
            {
            win->GetFieldText ( fIndx, gsOut ) ;
            WinPos wp( short(wpos.row + whgt + 1), short(wpos.col + 1) ) ;
            this->acSetFg ( fg ) ;
            this->acSetMod ( aesUNDERLINE ) ;
            wp = this->acWrite ( wp, L"Copy of Field Text Data           \n" ) ;
            this->acSetMod ( aesUNDERLINE_OFF ) ;
            this->acWrite ( wp, gsOut ) ;
            this->acSetFg ( aesFG_DFLT ) ;
            }
            #endif   // Test GetFieldText()
            win->SetFieldText ( fIndx, gsIns ) ;   // restore instructions
            fldAttr = fldAttr == Field2Attr ? Field1Attr : Field2Attr ;
            win->SetFieldAttr ( fIndx, fldAttr ) ;
            break ;
         case L'T':              // Title
            if ( ! title )
            { win->SetTitle ( Title1Text, Title1Attr ) ; title = true ; }
            else
            { win->SetTitle ( Title2Text, bdrAttr ) ; title = false ; }
            break ;
         case L'c':              // Cursor movement within field
            win->acSetCursorStyle ( csShow ) ;
            win->SetFieldIP ( fIndx, 9 ) ;   win->acBeep () ;
            this->nsleep ( 16 ) ;
            win->SetFieldIP ( fIndx, 79 ) ;  win->acBeep () ;
            this->nsleep ( 16 ) ;
            win->SetFieldIP ( fIndx, gsALLOCMIN ) ; win->acBeep () ;
            this->nsleep ( 16 ) ;
            win->SetFieldIP ( fIndx, ZERO ) ; win->acBeep () ;
            this->nsleep ( 16 ) ;
            win->acSetCursorStyle ( csHide ) ;
            break ;
         default:
            this->acBeep () ;
      }
   }

   win->acSetCursorStyle ( csShow ) ;     // make cursor visible

   //* Close the window and erase the display area using   *
   //* a grey background.                                  *
   //* Note: Closing the window re-enables input buffering *
   //*       AND resets the echo option to 'termEcho'.     *
   win->CloseWindow ( true, CloseAttr ) ;

   delete win ;                           // delete the ACWin object

   //* Say bye-bye and prepare to exit.               *
   //* (Note the newlines used to set exit position.) *
   this->acSetFg ( fg ) ;
   this->acSetCursor ( wpos.row + 2, wpos.col + 1 ) ;
   this->ttyWrite ( "Default-constructor test complete.\n\n\n\n\n\n" ) ;
   this->acSetFg ( aesFG_DFLT ) ;

}  //* End Test_WinDefault() *

//*************************
//*       Test_Wrap       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test automagic word wrap within the ACWin text area.                         *
//* a) An unformatted block of text is written. Automatic formatting will be     *
//*    performed.                                                                *
//* b) A sequence of text blocks will be written, each starting where the        *
//*    previous block finished.                                                  *
//* c) A block of source text that fills the window is written.                  *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Wrap ( aeSeq fg )
{  // major:'W', minor:'t'
   const char* mainTitle = "  Test of Automatic Word Wrap  " ;
   const char* pakTitle  = "  Press A Key...  " ;
   const wchar_t *Menu = L" Select the Formatting Test:                   \n"
                          " a) Format and display a paragraph of text.    \n"
                          " b) Format and display a series of paragraphs. \n"
                          " c) Format and display a full page of text.    \n"
                          " x) Exit                                       " ;
   // This paragraph is an excerpt from a short story written by the author.
   // (c) Copyright 2014 The Software Samurai (all rights reserved)
   const wchar_t * chuck = 
      L"Despite his manly name, Chuck was seldom asked to join the games of the "
       "other boys. First, he was rather small for his age and had the upper body "
       "strength of a 5-year-old, even though he was nearly nine. The continual "
       "asthma attacks and the prepubescent OCD contributed something as well.——"
       "Yes, Chuck was a nerd, but he had a burning desire to express "
       "himself and an overwhelming need to create something that would "
       "be of lasting value to someone, though his peers would probably "
       "still steal his lunch, and he was sure that his family would "
       "just continue to stare at him in slack-jawed incomprehension. " ;

   // The Aladdin story was written to cheer up a friend in the hospital.
   // It is in the public domain. It's not very good, but feel free to use it.
   const wchar_t *aladdin_a = 
     L"Since he was 13 yeard old, motorcycles have been Alphonse's own personal "
      "flying carpet. Like Aladdin, his flying carpet takes him to "
      "“A whole new world,” with “a new fantastic point of view.” Unfortunately, "
      "during his years in the loony bin, he had been without his motorcycle. "
      "So in addition to being locked away inside the grey walls of the hospital "
      "day-after-day, without his bike, Alphonse felt chained to the ground like "
      "a dog chained to a tree on a cold, rainy day." ;
   const wchar_t *aladdin_b = 
     L"That is why, when he was released from the hospital, his first "
      "project was to retrieve the motorcycle from storage and begin the work of "
      "bringing it back to life after years of neglect. Alphonse is however a "
      "terrible mechanic. He found it tedious getting the parts disconnected, especially "
      "with his still-fragile brain reboot. And when he did get the parts disassembled, "
      "he tended to either drop them them or break them. He sat in a puddle of oil, "
      "spilled de-greaser on his shoes, cut himself more than once, and lost several "
      "of the smaller parts." ;
   const wchar_t *aladdin_c = 
     L"No, Alphonse is not the sharpest chopstick in the drawer—but like most people "
      "with OCD, his super-power is stubbornness; and after two LooooonG weeks, the "
      "old Triumph roared to life like a bear waking from hibernation. "
      "The first ten kilometers to the petrol station and back broke the chains and lifted "
      "his heart with the firm-but-gentle reminder that life is more than everyday "
      "work and worry. —— Dr. Triumph Bonneville, IV, PhD." ;

   // This paragraph is an example of a story without adjectives, written by the
   // author as an example for ESL students.
   // (c) Copyright 2008 The Software Samurai (all rights reserved)
   // (This is unformatted text which is formatted for display in the target window.)
   const wchar_t *tony = 
     L"Tony Fixes the Car: "
      "Tony began to work. Alice and the children watched him. Tony unlatched the hood. "
      "He grasped a wrench and approached the engine. A filter covered and obscured the "
      "carburetor. A bolt secured the filter. Tony loosened and removed the bolt. "
      "He lifted and removed the filter. The bolt fell. Tony looked and looked, but "
      "could not find the bolt. Tony forgot the bolt and continued to work. "
      "Tony could see the carburetor. Dirt covered the carburetor. "
      "The carburetor needed cleaning, and Tony had suspected this. He got a towel "
      "and wiped the venturis. This helped, but dirt remained. Tony removed the jets "
      "and cleaned them. Tony dropped the jets. Tony sighed. He reached for the jets "
      "and the family heard a thump. Tony said “Ouch!” and stood up. A welt appeared "
      "and he massaged it. Alice grimaced and said nothing, but a foot began to tap. "
      "The tapping expressed emotion. Tony saw the foot; he saw the tapping; he "
      "understood the emotion. Tony retrieved the jets and reinstalled them. "
      "This took time. Tony glanced at the clock. Alice glanced at the clock. "
      "Tony cranked the engine but it would not start. Tony said a word. "
      "The children gasped, because they could not use the word. Mother had forbidden "
      "the word. Alice frowned and Tony said, “Sorry.” Alice continued to frown. "
      "The foot continued to tap. Tony saw a spring. The spring held the butterfly. "
      "He removed the spring and the butterfly and cleaned them. He replaced the "
      "butterfly, but dropped the spring. It disappeared; the carburetor had swallowed "
      "it. Bolts held the carburetor. Tony heard the tapping, felt the frown. "
      "Tony muttered something, but Alice did not hear it. Tony got the toolbox. "
      "He tried not to notice the tapping. Tony selected a wrench, selected a bolt, "
      "and applied pressure. Nothing happened. Tony increased the pressure. "
      "Tony yelled a word. Alice shouted, “Tony! the children!” The wrench thumped "
      "and clanged; it hit the floor. Blood began to flow. Tears appeared. "
      "Tony wrapped the wound. He used the towel. Tony turned and passed the family. "
      "Tony glared. Alice glared. She replaced the tapping with a stomp. Tony left "
      "the garage. The children wondered, and asked Tony questions. They had not seen "
      "the blood. Although Tony did not answer the questions, the children guessed "
      "that the trip had been canceled. Tony stood in the yard. Tony approached "
      "a tree. The tree shaded Chuckles. Chuckles napped. Chuckles liked naps. "
      "The footsteps awakened Chuckles. A tongue lolled; a tail wagged. "
      "Tony ignored Chuckles. Tony shouted words. Tony repeated the words and kicked "
      "the tree. Chuckles ran." ;

   const short winRow = 2 ;                        // window origin
   const short winCol = 4 ;
   const short winHgt = 30 ;                       // window dimensions
   const short winWid = 98 ;
   LineType winBdr = ltSINGLE ;                    // border style
   acAttr winBdrAttr = acAttr(acaFGb_GREY | acaBG_BLUE) ;   // border attributes
   acAttr winTxtAttr = acAttr(acaFG_MAGENTA | acaBG_DFLT) ; // text attributes
   acAttr pakTitAttr = acAttr(winBdrAttr | acaBLINK) ;      // press-a-key attriutes
   gString gs ;                                    // text formatting
   WinPos wpMenu( 0, 0 ) ;                         // menu position
   WinPos xpos( short(winRow + winHgt + 2), 1 ) ;  // exit position for cursor
   WinPos paraPos ;                                // output position
   wchar_t wch ;                                   // user input
   short txtHgt, txtWid ;                          // dimensions of text

   //* If a non-default foreground attribute specified,        *
   //* convert it to acAttr format and update dialog attribute.*
   if ( fg != aesFG_DFLT )
   {
      bool flag ;
      acaExpand acax ;
      acAttr tmpAttr = acax.aeseq2bits( fg, flag ) ;
      winTxtAttr = acAttr(tmpAttr | acaBG_DFLT) ;
   }
   acAttr acMenu = acAttr(winTxtAttr | acaREVERSE) ;

   //* Define the window object *
   ACWin *win = new ACWin ( winRow, winCol, winHgt, winWid, 
                           winBdr, winBdrAttr, winTxtAttr, 
                           mainTitle, NULL, NULL ) ;

   //* Clear the terminal window *
   win->acClearScreen ( L' ', acaATTR_DFLT ) ;

   //* Open the application window and reference the active skForm object *
   win->OpenWindow () ;

   //* Set input echo option from the default 'softEchoA' to 'noEcho' *
   win->acSetEchoOption ( noEcho ) ;

   win->acSetCursorStyle ( csHide ) ;     // make cursor invisible

   //* Center the menu horizontally *
   gs = Menu ;
   win->ParagraphSize ( gs, txtHgt, txtWid ) ;
   wpMenu.col = winWid / 2 - txtWid / 2 ;

   do
   {
      win->ClearWin () ;                     // clear the window interior
      win->Write ( wpMenu, Menu, acMenu ) ;  // display the menu
      wch = win->acRead () ;                 // get user selection
      wch = tolower ( wch, std::locale() ) ; // lower-case input

      if ( wch == L'a' )                  // write a single paragraph
      {
         paraPos = { 8, 0 } ;
         win->Write ( paraPos, chuck ) ;

         win->SetTitle ( pakTitle, pakTitAttr ) ;
         win->acRead () ;
         win->SetTitle ( mainTitle ) ;
      }
      else if ( wch == L'b' )             // write a sequence of paragraphs
      {
         paraPos = { 7, 0 } ;
         paraPos = win->Write ( paraPos, aladdin_a ) ;
         ++paraPos.row ;
         paraPos = win->Write ( paraPos, aladdin_b ) ;
         ++paraPos.row ;
         win->Write ( paraPos, aladdin_c ) ;

         win->SetTitle ( pakTitle, pakTitAttr ) ;
         win->acRead () ;
         win->SetTitle ( mainTitle ) ;
      }
      else if ( wch == L'c' )             // write a full page of data
      {
         win->ClearWin () ;
         paraPos = { 0, 0 } ;
         win->Write ( paraPos, tony ) ;

         win->SetTitle ( pakTitle, pakTitAttr ) ;
         win->acRead () ;
         win->SetTitle ( mainTitle ) ;
      }
      else if ( wch != L'x' )             // exit the test loop
         win->acBeep () ;
   }
   while ( wch != L'x' ) ;

   win->acSetCursorStyle ( csShow ) ;     // make cursor invisible

   //* Close the application window, but keep it visible. *
   //* Delete the ACWin object.                           *
   win->CloseWindow ( false ) ;
   delete win ;

   //* Say bye-bye and prepare to exit. *
   this->acSetFg ( fg ) ;
   this->acSetCursor ( xpos ) ;
   this->ttyWrite ( "   Word-wrap test complete.\n" ) ;
   this->acSetFg ( aesFG_DFLT ) ;

}  //* End Test_Wrap() *

//*************************
//*      Test_Dialog      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Open an ACWin object, then create various dialog objects within the window.  *
//* Exercise position and sizing of the dialog, as well as automatic formatting  *
//* of the text to be displayed.                                                 *
//* Test positioning of the user-interface controls.                             *
//*                                                                              *
//* Input  : group: specifies dialog testin "group" 'q', 'x', 'z', '0'           *
//*                 converted to member of enum testNumber, below                *
//*          test : test within the specified 'group'                            *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Dialog ( wchar_t group, wchar_t test )
{  // invoke: D [ q | d | s | 0 ]

   //* Test Menu *
   const wchar_t *MenuTop = 
       L"Test Menu for Test_Dialog: Group + Test.\n" ;
   const wchar_t *MenuBrief = 
       L" q) QuickDialog Dq[1 | e | c | v | s | t | p | w | h | r]  Example: Dqe\n"
        " d) Full Dialog Dd[a|b|c|d|e|f| t | w | h | i | v | u | z] Example: Dda\n" ;
   const wchar_t *MenuQ = 
       L" QuickDialog\n"
        " ---------------------------------\n"
        " q-1) one control.                q-t) automatic-timeout (no controls)\n"
        " q-e) two controls. (English)     q-p) pre-formatted text, auto-size\n"
        " q-c) two controls. (Chinese)     q-w) auto-formatted text, fixed width\n"
        " q-v) two controls. (Vietnamese)  q-h) auto-formatted text, fixed height\n"
        " q-s) two controls. (Spanish)     q-r) automatic repositioning\n"
        " \n\n"
        " d) Full Dialog: Dd[a | b | c | d | e | f | t | w | h | i | v | u | z]\n" ;
   const wchar_t *MenuX = 
       L" Full-Initialization Dialog\n"
        " ---------------------------------\n"
        " ----dlgHgt-dlgWid-vOff-hOff-tv-th-fCnt---Description-------------\n"
        " d-a)   0      0    -1   -1   0  1  2   Formatted text V/H auto   \n"
        " d-b)   0     42    -1   -1   0  1  2   Formatted text V auto  H fixed\n"
        " d-c)   8      0    -1   -1   0  1  2   Formatted text V fixed H auto\n"
        " d-d)   0      0    -1   -1   0  1  2   Unformatted    V/H auto   \n"
        " d-e)   0     42     2   48   0  1  2   Unformatted    V auto  H fixed\n"
        " d-f)   6      0     6   34   0  1  2   Unformatted    V fixed H auto\n"
        " d-t)   8     38    -1   -1   0  1  2   Fixed Size, truncated text\n"
        " d-w)   8     42     4   -1   0  1  2   Fixed Size and VOffset, truncated\n"
        " d-h)   9     47     7   22   0  1  2   Horiz. auto-config. Formatted\n"
        " d-i)   9     47     7   22   0  1  2   Horiz. auto-config. Unformatted\n"
        " d-v)   0     49     8   -1   0  1  2   Vertical auto-config, Formatted\n"
        " d-u)   0     49     8   -1   0  1  2   Vertical auto-config, Unformatted\n"
        " d-z)  20      0     0   -1   0  1  7   Dialog Control Testing.   \n"
        " q) QuickDialog: Dq[1 | e | c | v | s | t | p | w | h | r]\n" ;
   const wchar_t *MenuEnd = 
       L" s) Sidebar Test (not a dialog test)\n"
        " 0) Display dialog test menu.\n"
        "    [To exit a test, press Space key, or for looping tests, select 'Cancel']\n"
        "    [Note that for these tests, the parent window is not refreshed on exit.]" ;

     const wchar_t* Chuck = 
     // 123456789-123456789-123456789-123456789-123456789-123456789-123456789-
      L"Yes, Chuck was a nerd, but he had a burning desire to express \n"
       "himself and an overwhelming need to create something that would \n"
       "be of lasting value to someone, though his peers would probably \n"
       "still steal his lunch, and he was sure that his family would \n"
       "just continue to stare at him in slack-jawed incomprehension." ;
   const wchar_t* Charles = 
      L"Despite his manly name, Chuck was seldom asked to join \n"
       "the games of the other boys. First, he was rather small \n"
       "for his age and had the upper body strength of a \n"
       "5-year-old, even though he was nearly nine. \n"
       "The continual asthma attacks and the prepubescent OCD \n"
       "contributed something as well.——Yes, Chuck was a nerd, \n"
       "but he had a burning desire to express himself and an \n"
       "overwhelming need to create something that would be of \n"
       "lasting value to someone, though his peers would \n"
       "probably still steal his lunch, and he was sure that his \n"
       "family would just continue to stare at him in slack-jawed \n"
       "incomprehension."
       #if 0    // OVERFLOW HEIGHT (if default width)
       "It is true that at the age of 32 Chuck is still a nerd, "
       "but at work they just call him \"Sir\"."
       #endif   // OVERFLOW HEIGHT
       ;

   const short winRow = 2 ;
   const short winCol = 4 ;
   const short winWid = 80 ;
         short winHgt = 24 ;
   const short zBUMP  = 7 ;
   LineType winBdr = ltSINGLE ;
   acAttr   winBdrAttr = acAttr(acaFGb_GREY | acaBG_BLUE) ;
   acAttr   winTxtAttr = acAttr(acaFG_MAGENTA | acaBG_DFLT) ;
   acAttr   dlgBdrAttr = acAttr(acaFG_BROWN | acaBG_CYAN) ;
   gString gs ;                           // dialog display text
   WinPos xpos( short(winRow + winHgt + 2), 1 ) ; // exit position for cursor
   WinPos wp ;                            // position for direct writes
   short dHeight = ZERO,                  // QuickDialog auto height
         dWidth  = ZERO,                  // QuickDialog auto width
         vOffset = ZERO,                  // dialog vertical offset
         hOffset = -1,                    // dialog horizontal offset
         exIndex,                         // return value from call to dialog
         menuOpt = 1 ;                    // menu display option (see below)

   if ( (group == '?') && (test == '?') )
   { group = '0' ; menuOpt = 1 ; }  // MenuTop + MenuBrief + MenuEnd
   else if ( (group == 'q') && (test == '?') )
   { group = '0'; menuOpt = 2 ; }   // MenuTop + MenuQ + MenuEnd
   else if ( (group == 'd') && ( test == '?') )
   { group = '0' ; menuOpt = 3 ; }  // MenuTop + MenuX + MenuEnd

   //* Range check the 'group' parameter. Also, if *
   //* test option not provided, display test menu.*
   if ( ! ((group == 'q') || (group == 'd') || (group == 's')) )
      group = L'0' ;
   else if ( (test == '?') && (group != 's') )
   {
      if ( group == 'p' ) menuOpt = 2 ;
      if ( group == 'd' ) menuOpt = 3 ;
      group = L'0' ;
   }
   //* Full dialog, test 'z' requires a taller window. *
   if ( (group == 'd') && (test == 'z') )
   { winHgt += zBUMP ; xpos.row += zBUMP ; }

   //* Define the window object *
   gs.compose( " AcTest (act) v:%s (c) 2023-2025  The Software Samurai ", this->acVersion () ) ;
   ACWin *win = new ACWin ( winRow, winCol, winHgt, winWid, 
                           winBdr, winBdrAttr, winTxtAttr, 
                           gs.ustr(), NULL, NULL ) ;
   win->AlertEnable ( true ) ;            // enable alert 

   //* Clear the terminal window *
   win->acClearScreen ( L' ', acaATTR_DFLT ) ;

   //* Open the application window and reference the active skForm object *
   //* -- Note that cursor is invisible by default, EXCEPT when a textbox *
   //*    has the input focus.                                            *
   //* -- "noEcho" option is set explicitly by each dialog call.          *
   win->OpenWindow () ;

   win->EnableFootnotes ( true ) ;        // enable diagnostic footnotes
   gs.compose( "test:D %C %C", &group, &test ) ;  // report the test parameters
   win->Footnote ( gs.gstr(), 0 ) ;

   //*****************
   //* Quick dialogs *
   //*****************
   if ( group == 'q' )
   {
      if ( test == '1' )
      {
         gString gs( 
              L"This is a basic test of the auto-generated dialog. \n"
               "The text consists of two rows, 51 columns wide." ) ;
               win->QuickDialog ( gs, false, NULL, dHeight, dWidth, 
                                  vOffset, hOffset, ZERO, true ) ;
      }
      else if ( (test == 'e') || (test == 'c') || (test == 'v') || (test == 's') )
      {
         gString gs( 
              L"This is a basic test of the auto-generated dialog using \n"
               "pre-formatted text. The \"locale\" is set according to the \n"
               "specified test. This triggers automatic invocation of the \n"
               "associated language for the dialog controls. \n"
               "Locale: " ) ;
         if ( test == 'e' )
            win->acSetLocale ( "en_US.UTF-8" ) ;   // English  (U.S.)
         else if ( test == 'c' )
            win->acSetLocale ( "zh_CN.utf8" ) ;    // Zhongwen (China mainland)
         else if ( test == 'v' )
            win->acSetLocale ( "vi_VN.utf8" ) ;    // TiengViet(Vietnam)
         else if ( test == 's' )
            win->acSetLocale ( "es_MX.utf8" ) ;    // Espanol  (Mexico)
         gs.append( "'%s'", win->acGetLocale () ) ;// append the locale name
         win->QuickDialog ( gs, true, NULL, dHeight, dWidth, 
                            vOffset, hOffset, ZERO, true ) ;
      }

      //* Display a dialog with automatic timeout. *
      else if ( test == 't' )
      {
         gString gs( 
              "This dialog is displayed with an automatic timeout. \n"
              "The timeout is specified in seconds (4 in this case).\n"
              "No user-interface controls are displayed. \n"
              "The dialog closes automatically at the end of the \n"
              "timeout period, or when any key is pressed." ) ;
         win->QuickDialog ( gs, false, NULL, 0, 0, 0, -1, 4 ) ;
      }

      //* Preformatted text and automatic vertical and horizontal dimensions. *
      else if ( test == 'p' )
      {
         gString gs( Chuck ) ;
         wp = { 10, 10 } ;
         win->Write ( wp, L"Text preformatted and dialog automatically sized to fit." ) ;
         win->QuickDialog ( gs, true, NULL, dHeight, dWidth, 
                            vOffset, hOffset, ZERO, true ) ;
      }

      //* Automatically-formatted text, with *
      //* fixed width and automatic height.  *
      else if ( test == 'w' )
      {
         gString gs( Chuck ) ;
         gs.erase( NEWLINE, ZERO, false, true ) ;
         dWidth = 52 ;
         wp = { 12, 11 } ;
         win->Write ( wp, L"Text automatically reformatted to fit dialog width of 52." ) ;
         win->QuickDialog ( gs, true, NULL, dHeight, dWidth, 
                            vOffset, hOffset, ZERO, true ) ;
      }

      //* Automatically-formatted text, with *
      //* fixed height and automatic width.  *
      else if ( test == 'h' )
      {
         gString gs( Chuck ) ;
         gs.erase( NEWLINE, ZERO, false, true ) ;
         dHeight = 8 ;
         wp = {  9, 11 } ;
         win->Write ( wp, L"Text automatically reformatted to fit dialog height of 8." ) ;
         win->QuickDialog ( gs, true, NULL, dHeight, dWidth, 
                            vOffset, hOffset, ZERO, true ) ;
      }

      //* Repositioning and position limiting test    *
      //* Note that this test exercises the optional  *
      //* dialog title.                               *
      else if ( test == 'r' )
      {
         gString gs( L"Test range-checking of dialog positioning. \n"
                      "Dialog may not overwrite the borders of \n"
                      "the parent window.   (7 rows x 47 columns)" ) ;
         dHeight = 7 ;
         dWidth  = 47 ;
         vOffset = 5 ;
         hOffset = 10 ;

         for ( ; vOffset < 17 ; ++vOffset )
         {
            exIndex = win->QuickDialog ( gs, true, " Positioning Test ", 
                                         dHeight, dWidth, 
                                         vOffset, hOffset, ZERO, true ) ;
            hOffset += 3 ;
            if ( ! exIndex )
               break ;
            if ( vOffset < 16 )
               win->RefreshWin () ;
         }
      }
      else              // invalid test
      { group = '0' ; menuOpt = 2 ; }

   }  // "quick" dialog group

   //*******************************
   //* Full-initialization dialogs *
   //*******************************
   else if ( group == 'd' )
   { // invoke: D d -
      //* Create an skForm object with (at least) two controls. *
      short dlgHeight = ZERO, dlgWidth = ZERO,     // msgHeight, msgWidth,
            fldCount = (group == 'd' && test == 'z') ? 9 : 2, // number of controls
            tbWidth = 59 ;                         // textbox width
      short VOff  = (fldCount == 2) ? -1 : -4,     // control's vertical offset
            HOff  = (fldCount == 2) ? ZERO : -20 ; // control's horizontal offset

      // Note that some test require different offsets for the controls.
      skForm* skfp = NULL ;

      //* If the test requires only the two Pushbuttons *
      if ( fldCount == 2 )
      {
         skfp = new skForm
         (
            fldCount,
            new skField[fldCount]
            {
               //* Field index 0: ftPB (Pushbutton) *
               {
                  ftPB,                         // field type
                  1,                            // field height (in rows)
                  8,                            // field width (in columns)
                  VOff, short(HOff - 8),        // Y/X offset in window
                  "   ^OK   ",                  // field text with Hotkey '^O'
                  acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
                  //floChar,                    // text formatting option
                  //ZERO,                       // insertion point (cursor offset)
                  //false,                      // user accessible (not read-only)
                  //false,                      // field outline disabled
                  //true,                       // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },
   
               //* Field index 1: ftPB (Pushbutton) *
               {
                  ftPB,                         // field type
                  1,                            // field height (in rows)
                  10,                           // field width (in columns)
                  VOff, short(HOff + 1),        // Y/X offset in window
                  "  ^CANCEL  ",                // field text with Hotkey '^O'
                  acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
                  //floChar,                    // text formatting option
                  //ZERO,                       // insertion point (cursor offset)
                  //false,                      // user accessible (not read-only)
                  //false,                      // field outline disabled
                  //true,                       // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },
            }
            //* Initialize remaining skForm parameters to default values.*
            //ZERO,           // index of field with initial focus
            //acaUSEDFLT,     // default color attributes for control with focus
            //true,           // beep on unhandled keycode received
            //true,           // initial state of Insert/Overstrike flag
            //true,           // user access to Insert/Overstrike mode
            //false,          // enable cursor shape change for Insert/Overstrike
            //csBblock,       // cursor shape when Insert mode
            //csBuline        // cursor shape when Overstrike mode
         ) ;
      }
      else
      {
         //* Adjust initial offsets *
         VOff = -2 ;                      // 4 rows up from the bottom
         HOff = -12 ;                     // 21 columns left of center

         skfp = new skForm
         (
            fldCount,
            new skField[fldCount]
            {
               //* Field index 0: ftPB (Pushbutton) *
               {
                  ftPB,                         // field type
                  1,                            // field height (in rows)
                  8,                            // field width (in columns)
                  VOff, short(HOff - 8),        // Y/X offset in window
                  "   ^OK   ",                  // field text with Hotkey '^O'
                  acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
                  //floChar,                    // text formatting option
                  //ZERO,                       // insertion point (cursor offset)
                  //false,                      // user accessible (not read-only)
                  //false,                      // field outline disabled
                  //true,                       // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },
   
               //* Field index 1: ftPB (Pushbutton) *
               {
                  ftPB,                         // field type
                  1,                            // field height (in rows)
                  18,                           // field width (in columns)
                  VOff, short(HOff += 3),       // Y/X offset in window
                  " ^Auto-Scroll Test ",        // field text with Hotkey '^A'
                  acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
                  //floChar,                    // text formatting option
                  //ZERO,                       // insertion point (cursor offset)
                  //false,                      // user accessible (not read-only)
                  //false,                      // field outline disabled
                  //true,                       // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },

               //* Field index 2: ftPB (Pushbutton) *
               {
                  ftPB,                         // field type
                  1,                            // field height (in rows)
                  10,                           // field width (in columns)
                  VOff, short(HOff + 20),       // Y/X offset in window
                  "  ^CANCEL  ",                // field text with Hotkey '^O'
                  acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
                  //floChar,                    // text formatting option
                  //ZERO,                       // insertion point (cursor offset)
                  //false,                      // user accessible (not read-only)
                  //false,                      // field outline disabled
                  //true,                       // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },

               //* Field index 3: ftTB (Textbox) *
               {
                  ftTB,                         // field type
                  1,                            // field height (in rows)
                  tbWidth,                      // field width (in columns)
                  short(VOff = 2),              // Y/X offset in window
                  short(HOff = -(tbWidth / 2)),
                  "Edit the text in this field.", // field text
                  acAttr(acaFG_BLACK | acaBG_GREY), // color attributes
                  floShift,                     // text formatting option
                  //ZERO,                       // insertion point (cursor offset)
                  //false,                      // user accessible (not read-only)
                  //false,                      // field outline disabled
                  //true,                       // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },

               //* Field index 4: ftRB (Radiobutton) *
               {
                  ftRB,                         // field type
                  1,                            // field height (in rows)
                  15,                           // field width (in columns)
                  short(VOff += 5),             // Y/X offset in window
                  short(HOff += 2),
                  "[ ] ^Mathematics",           // field text
                  acAttr(acaFGb_RED | acaBG_DFLT), // color attributes
                  floChar,                      // text formatting option
                  ZERO,                         // insertion point (cursor offset)
                  false,                        // user accessible (not read-only)
                  true,                         // field outline enabled
                  //true,                       // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 'reset'
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },

               //* Field index 5: ftRB (Radiobutton) *
               {
                  ftRB,                         // field type
                  1,                            // field height (in rows)
                  11,                           // field width (in columns)
                  VOff, short(HOff += 19),      // Y/X offset in window
                  "[ ] ^Physics",               // field text
                  acAttr(acaFGb_RED | acaBG_DFLT), // color attributes
                  floChar,                      // text formatting option
                  ZERO,                         // insertion point (cursor offset)
                  false,                        // user accessible (not read-only)
                  true,                         // field outline enabled
                  false,                        // field is visible (not hidden)
                  true,                         // access lock enabled
                  true,                         // binary state 'set'
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },

               //* Field index 6: ftRB (Radiobutton) *
               {
                  ftRB,                         // field type
                  1,                            // field height (in rows)
                  20,                           // field width (in columns)
                  VOff, short(HOff += 15),      // Y/X offset in window
                  "[ ] Comp^uter Science",      // field text
                  acAttr(acaFG_BROWN | acaBG_DFLT), // color attributes
                  floChar,                      // text formatting option
                  ZERO,                         // insertion point (cursor offset)
                  false,                        // user accessible (not read-only)
                  true,                         // field outline enabled
                  false,                        // field is visible (not hidden)
                  true,                         // access lock enabled
                  false,                        // binary state 'reset'
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },

               //* Field index 7: ftSB (Scrollbox) *
               {
                  ftSB,                         // field type
                  3,                            // field height (in rows)
                  42,                           // field width (in columns)
                  short(VOff = 13),             // Y/X offset in window
                  short(HOff = -28),
                  "There once was a man from Kirkuk, \n" // field text
                  "Who was known far and wide for his luck,\n"
                  "Till while jogging one day, \n"
                  "He looked the wrong way, \n"
                  "Whereupon he was hit by a truck.",
                  acAttr(acaFG_BROWN | acaBG_DFLT), // color attributes
                  floChar,                      // text formatting option
                  ZERO,                         // insertion point (cursor offset)
                  false,                        // user accessible (not read-only)
                  true,                         // field outline enabled
                  //false,                      // field is visible (not hidden)
                  //true,                       // access lock enabled
                  //false,                      // binary state 'reset'
                  //NULLCHAR,                   // no hotkey specified (embedded in text)
                  //noGROUP                     // not a group member
               },

               //* Field index 8: ftMB (Menu) *
               {
                  ftMB,                         // field type
                  6,                            // field height (in rows)
                  14,                           // field width (in columns)
                  short(VOff - 1),              // Y/X offset in window
                  short(HOff + 44),
                  "Pigs ^Grunt  \n"             // field text
                  "Birds ^Fly   \n"
                  "Cows ^Moo    \n"
                  "Goats ^Bleat \n",
                  //* Build menu attributes from 8-bit values.*
                  //* Note: Alternate border style set below. *
                  (this->acComposeAttributes ( 16, 87, false, false )),
                  floShift,                     // text formatting option
                  ZERO,                         // insertion point (cursor offset)
                  false,                        // user accessible (not read-only)
                  false,                        // field outline disabled
                  false,                        // field is set as visible
                  true,                         // access lock enabled (always visible)
                  //false,                      // binary state 'reset'
                  //NULLCHAR,                   // no hotkey specified
                  //noGROUP                     // not a group member
               },
            }
            //* Initialize remaining skForm parameters to default values.*
            //ZERO,           // index of field with initial focus
            //acaUSEDFLT,     // default color attributes for control with focus
            //true,           // beep on unhandled keycode received
            //true,           // initial state of Insert/Overstrike flag
            //true,           // user access to Insert/Overstrike mode
            //false,          // enable cursor shape change for Insert/Overstrike
            //csBblock,       // cursor shape when Insert mode
            //csBuline        // cursor shape when Overstrike mode
         ) ;
      }

      gs = Charles ;                      // load the static text

      //* Centered, auto-dimension, formatted text *
      if ( test == 'a' ) 
      {  // invoke: D d a
         wp = {  short(winHgt - 4), 17 } ;
         win->Write ( wp, L"Dialog centered and automatically sized to \n"
                           "dimensions of preformatted text." ) ;
         win->Dialog ( 0, 0, skfp, gs.ustr(), NULL, -1, -1, ltDUAL,
                       dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Centered, auto-height, fixed width, formatted text *
      else if ( test == 'b' ) 
      {  // invoke: D d b
         #if 0    // test overrun of maximum control offsets
         skfp->fld[0].orig.row = skfp->fld[0].orig.col = 99 ;
         #endif   // test overrun of maximum control offsets

         wp = {  short(winHgt - 6), 0 } ;
         win->Write ( wp, L"Centered,\nFixed width of 42,\nand auto-height." ) ;
         dlgWidth = 42 ;                  // specific dialog width
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, -1, -1, ltDUAL,
                       dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Centered, fixed height, auto-width, formatted text *
      else if ( test == 'c' ) 
      {  // invoke: D d c
         gs = Chuck ;
         wp = {  short(winHgt - 5), 1 } ;
         win->Write ( wp, L"Centered, auto-width, and fixed height of 8 \n"
                           "(height overriden to avoid truncation of text)." ) ;
         dlgHeight = 8 ;                  // specific dialog height
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, -1, -1, 
                       ltDUAL, dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Centered, unformatted text, auto-dimensions *
      else if ( test == 'd' ) 
      {  // invoke: D d d
         gs = Chuck ;
         wp = {  2, 1 } ;
         win->Write ( wp, L"Unformatted text \n"
                           "with automatic\n"
                           "dialog sizing.\n"
                           "Centered in the\n"
                           "parent window." ) ;
         gs.erase( NEWLINE, ZERO, false, true ) ;  // strip newlines from the text
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, -1, -1, 
                       ltDUAL, dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Horiz. offset, unformatted text, fixed-horizontal, auto-vertical *
      else if ( test == 'e' ) 
      {  // invoke: D d e
         wp = {  2, 1 } ;
         win->Write ( wp, L"Unformatted text.\n"
                           "Vertical Offset  : 2\n"
                           "Horizontal width : 42 \n"
                           "Horizontal offset of 48 places part of \n"
                           "dialog beyond right border, so:\n"
                           "a) Width is limited to space between \n"
                           "   horiz. offset and right border.\n"
                           "b) Height is extended from vertical offset\n"
                           "   to bottom border.\n"
                           "Note that the adjustments do not provide\n"
                           "enough space, so text is truncated." ) ;
         dlgHeight = ZERO ;
         dlgWidth = 42 ;
         vOffset  = 2 ;
         hOffset  = 48 ;
         gs.erase( NEWLINE, ZERO, false, true ) ;  // strip newlines from the text
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, vOffset, hOffset, 
                       ltDUAL, dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Vertical offset, unformatted text, fixed-vertical, auto-horizontal *
      else if ( test == 'f' ) 
      {  // invoke: D d f
         wp = {  1, 1 } ;
         win->Write ( wp, L"Unformatted text.\n"
                           "Vertical Offset : 6\n"
                           "Dialog Height: 6 and Dialog Width: auto \n"
                           "a) Horizontal offset of 34 means that \n"
                           "   width is limited to space \n"
                           "   between horiz. offset and \n"
                           "   right border.\n"
                           "b) Height is extended from \n"
                           "   vertical offset to bottom \n"
                           "   border.\n"
                           "Note that automatic adjustments\n"
                           "do not provide sufficient space,\n"
                           "so text is truncated." ) ;
         dlgHeight = 6 ;
         dlgWidth = ZERO ;
         vOffset  =  6 ;
         hOffset  = 34 ;
         gs.erase( NEWLINE, ZERO, false, true ) ;  // strip newlines from the text
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, vOffset, hOffset, 
                       ltDUAL, dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Centered, fixed height and width (auto-truncate) *
      else if ( test == 't' ) 
      {  // invoke: D d t
         wp = {  2, 1 } ;
         win->Write ( wp, L"Centered, with \n"
                           "fixed dimensions.\n"
                           "(specified height\n"
                           " overriden, but\n"
                           " truncation could\n"
                           " not be avoided)." ) ;
         dlgHeight = 8 ;                  // specific dialog height
         dlgWidth = 38 ;                  // specific dialog width
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, -1, -1, ltDUAL,
                       dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Centered, fixed width and vertical offset (auto-truncate) *
      else if ( test == 'w' ) 
      {  // invoke: D d w
         wp = {  1, 1 } ;
         win->Write ( wp, L"Centered, with fixed dimensions and fixed vertical offset.\n"
                           "(specified height overriden, but truncation could not be avoided)." ) ;
         dlgHeight = 8 ;                  // specific dialog height
         dlgWidth = 42 ;                  // specific dialog width
         vOffset  = 4 ;                   // specific vertical offset
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, vOffset, -1, ltDUAL,
                       dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      //* Horizontal offset in increments *
      else if ( test == 'h' || test == 'i' )
      {  // invoke: D d [h | i]
         gs = L"Test horizontal positioning with automatic \n"
               "resizing of dialog at window boundaries. \n"
               "Specified horizontal offset is honored up \n"
               "to the minimum dialog width. Truncation of \n"
               "text may occur at bottom border. \n"
               "Initial dialog size: 9 rows by 47 columns." ;
         //* For test 'i' (unformated-text test),  *
         //* remove formatting from source text.   *
         if ( test == 'i' )
            gs.erase( NEWLINE, ZERO, false, true ) ;

         short maxHOff = 52 ;
         dlgHeight = 9 ;
         dlgWidth  = 47 ;
         vOffset = 7 ;
         hOffset = 22 ;

         for ( ; hOffset < maxHOff ; ++hOffset )
         {
            exIndex = win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, 
                                    vOffset, hOffset, ltSINGLE, 
                                    dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;
            if ( exIndex > ZERO )
               break ;
            if ( hOffset < (maxHOff - 1) )
               win->RefreshWin ( false ) ;
            if ( hOffset < 28 )
               ++hOffset ;
         }

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }
   
      //* Vertical offset in increments *
      else if ( test == 'v' || test == 'u' )
      {  // invoke: D d [v | u]
         gs = L"Test vertical positioning with automatic \n"
               "resizing of the dialog at window boundaries. \n"
               "Displayed text will be truncated \n"
               "if necessary to stay within window. \n"
               "Initial dialog size: 8 rows by 49 columns." ;
         //* For test 'u' (unformated-text test),  *
         //* remove formatting from source text.   *
         if ( test == 'u' )
            gs.erase( NEWLINE, ZERO, false, true ) ;

         short maxVOff = 20 ;
         dlgHeight = 0 ;
         dlgWidth  = 45 + dwPAD ;
         vOffset   = 8 ;
         hOffset   = -1 ;  // (centered)
   
         for ( ; vOffset < maxVOff ; ++vOffset )
         {
            exIndex = win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), NULL, 
                                    vOffset, hOffset, ltSINGLE, 
                                    dlgBdrAttr, acaUSEDFLT, 0, 1, NULL, true ) ;
            if ( exIndex != ZERO )  // Cancel button or invalid setup
               break ;
            if ( vOffset < (maxVOff - 1) )
               win->RefreshWin ( false ) ;
         }

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      else if ( test == 'z' )
      {  // invoke: D d z
         // For clarity, define an enum for controls:
         enum zCtrl : short
         {
            zcPB_OK = ZERO,
            zcPB_AUTO,
            zcPB_CANCEL,
            zcTB_TEXT,
            zcRB_MATH,
            zcRB_PHYSICS,
            zcRB_COMPSCI,
            zcSB_POEM,
            zcMB_CONTEXT
         } ;
         //* Initialize local clipboard with sample text. *
         gString gsClip( "Dialog(z) test data." ) ;
         //gs = "Dialog(z) test data." ;
         win->acClipboardSetText ( gsClip ) ;

         //* Dialog Static Text.                   *
         gs = L"This dialog contains samples of each valid control type.\n"
               "The Textbox field may be edited, (incl. clipboard data).\n"
               "\n\n"
               "The Radiobutton controls may be set or reset using the \n"
               "Space or Enter keys, or via \"hotkey\".\n\n"
               "\n\n"
               "         Scrollbox controls display          Menu controls \n"
               "         text which may be scrolled          display lists.\n"
               "         vertically, but not edited.          ------------ \n"
               "\n\n\n\n\n\n"
               "The Pushbutton controls return control to the application \n"
               "after saving the data of the other controls. The return \n"
               "value is the index of the control which had the input \n"
               "focus when the dialog was closed.\n"
               "         XXXXXXXX   XXXXXXXXXXXXXXXXXX   XXXXXXXX\n"
               "Use Tab and Shift+Tab or \"hotkeys\" to move among controls." ;
         dlgHeight = 26 ;                 // specific dialog height
         dlgWidth = ZERO ;                // specific dialog width (from text width)
         vOffset  = ZERO ;                // specific vertical offset

         //* Special Test: Set menu control outline style and *
         //*               label text (attribute is automatic)*
         skfp->fld[zcMB_CONTEXT].outlineConfig ( ltDUAL ) ; // set menu outline line style
         skfp->fld[zcMB_CONTEXT].labelConfig ( "You^r Choice:", acAttr(acaFG_BLUE | acaBGb_GREY), true ) ;

         // Report Dialog Setup data. Can be compared with Dialog() method's 
         // debugging output which is done _after_ adjustments.
         WinPos wp( 2, 86 ) ;
         wp = this->acWrite ( wp, L"Dump Dialog Form:\n=================\n" ) ;
         gString gsDump( "dlgHeight:%02hd dlgWidth:%02hd vOffset:%02hd hOffset:-1\n"
                         "FLD  HGT  WID  Y  :X  \n"
                         "---  ---  ---  ---:---\n",
                         &dlgHeight, &dlgWidth, &vOffset ) ;
         wp = this->acWrite ( wp, gsDump ) ;
         for ( short f = ZERO ; f < fldCount ; ++f )
         {
            gsDump.compose( "%2hd) %3hd  %3hd  %3hd:%3hd\n", &f,
                            &skfp->fld[f].hgt, &skfp->fld[f].wid, 
                            &skfp->fld[f].orig.row, &skfp->fld[f].orig.col ) ;
            wp = this->acWrite ( wp, gsDump ) ;
         }

         #if 0    // Development Test
         //* Test per-row color attributes for Menu and Scrollbox. *
         //* This test is disabled by default because it look ugly,*
         //* but it is a valid test of colorConfig().              *
         //* Scrollbox has five(5) text rows,  *
         //* (but pad with an extra attribute).*
         acAttr altAttr = acAttr( acaFGb_CYAN | acaBG_DFLT ) ;
         acAttr rowAttr[6] =
         { acaUSEDFLT, altAttr, acaUSEDFLT, altAttr, acaUSEDFLT, acaUSEDFLT } ;
         short rowCnt = 5 ;
         skfp->fld[zcSB_POEM].colorConfig ( rowAttr, rowCnt ) ;
         //* Menu has four(4) items: multi-color Fg with existing Bg *
         rowCnt = 4 ;
         rowAttr[1] = this->acComposeAttributes ( 28, 87, false, false ) ;
         rowAttr[2] = this->acComposeAttributes (160, 87, false, false ) ;
         rowAttr[3] = this->acComposeAttributes (208, 87, false, false ) ;
         skfp->fld[zcMB_CONTEXT].colorConfig ( rowAttr, rowCnt ) ;
         #endif   // Test per-row color attributes for Menu and Scrollbox
         #if 0    // Development Test - Alternate Radiobutton label config
         skfp->fld[zcRB_COMPSCI].labelConfig ( acAttr(acaFG_GREEN | acaBG_DFLT), true ) ;
         #endif   // Alternate Radiobutton label config

         cbPtr = win ;                    // give callback method access to ACWin object
         short focusPb = 
         win->Dialog ( dlgHeight, dlgWidth, skfp, gs.ustr(), 
                       " Full-Initialization Dialog ", vOffset, -1, ltDUAL,
                       dlgBdrAttr, acaUSEDFLT, 0, 1, ddzCallback, true ) ;
         cbPtr = NULL ;                   // reset the external pointer
         wp = { short(winHgt - 5), 1 } ;

         // Notes:
         // a) Menu controls (ftMB) and Scrollbox controls (ftSB) contain 
         //    individual items which may indicate an action to be performed 
         //    when the dialog closes.
         // b) Menus (ftMB) always contain selectable items (Duh!).
         // c) Scrollbox controls may contain either plain paragraph text or 
         //    "selectable" items.
         // d) If a Menu (ftMB) or Scrollbox (ftSB) was defined for the dialog, 
         //    the control item referenced when dialog was closed may be retrieved 
         //    via the skField-class getItem() method. Alternatively, the 
         //    previously "selected" menu item (if any) may be reported. Either 
         //    option is available below under the conditional compile directive.
         gString menuItemTxt ; short menuItem ;
         gString sboxItemTxt ; short sboxItem ;
         #if 1    // Report the "selected" item (if any)
         skfp->fld[zcMB_CONTEXT].getItem ( menuItemTxt, menuItem, true ) ;
         skfp->fld[zcSB_POEM].getItem ( sboxItemTxt, sboxItem, true ) ;
         #else    // Report highlighted menu item
         skfp->fld[zcMB_CONTEXT].getItem ( menuItemTxt, menuItem ) ;
         skfp->fld[zcSB_POEM].getItem ( sboxItemTxt, sboxItem ) ;
         #endif   // Report item

         gs.compose( 
            "Returned Values: focus:%hd Math:%hhd Phys:%hhd Comp:%hhd Menu:%hd-'%S'\n"
            "  Sbox: %hd-'%S'\n"
            "  Text: '%S'", 
            &focusPb, &skfp->fld[zcRB_MATH].state, &skfp->fld[zcRB_PHYSICS].state,
            &skfp->fld[zcRB_COMPSCI].state, 
            &menuItem, menuItemTxt.gstr(),
            &sboxItem, sboxItemTxt.gstr(),
            skfp->fld[zcTB_TEXT].txt.gstr() ) ;
         win->Write ( wp, gs ) ;

         //* Release the dynamic allocation *
         if ( skfp != NULL ) { delete skfp ; skfp = NULL ; }
      }

      else              // invalid test
      { group = '0' ; menuOpt = 3 ; }

   }  // "expanded" i.e. full-initialization dialog group

   //******************************************************************
   //* Test Sidebar functionality. Note that this test is not related *
   //* to dialogs; we just didn't know where else to put it.          *
   //******************************************************************
   else if ( group == 's' )
   {
      tdSidebar ( win, winHgt, winWid ) ;
   }

   //* Display the test menu. *
   if ( group == '0' )
   {
      WinPos wp( 0, 1 ) ;
      win->ClearWin () ;
      wp = win->Write ( wp, MenuTop ) ;
      if ( menuOpt == 1 )
         wp = win->Write ( wp, MenuBrief ) ;
      else if ( menuOpt == 2 )
         wp = win->Write ( wp, MenuQ ) ;
      else if ( menuOpt == 3 )
         wp = win->Write ( wp, MenuX ) ;
      win->Write ( wp, MenuEnd ) ;
   }

   //* Close the application window, but keep it visible. *
   win->CloseWindow ( false ) ;

   //* Say bye-bye and prepare to exit. *
   this->acSetFg ( aesFG_DFLT ) ;
   this->acSetCursor ( xpos ) ;
   this->ttyWrite ( "   Dialog sub-window test complete.\n" ) ;
   this->acSetCursorStyle ( csShow ) ;

}  //* End Test_Dialog() *

//*************************
//*      ddzCallback      *
//*************************
//********************************************************************************
//* Non-member function:                                                         *
//* --------------------                                                         *
//* Callback function for Test_Dialog( option: Ddz ) above.                      *
//* Handle the callbacks from ACWin method: EditDialog().                        *
//* The static pointer 'cbPtr' gives this method access to the ACWin object      *
//* created within Test_Dialog().                                                *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditDialog() should   *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: 'wkey' is returned.        *
//********************************************************************************
//* Notes:                                                                       *
//* -- This callback is designed primarily to handle auto-scrolling within       *
//*    Textbox, Scrollbox and Context Menu controls. Auto-scrolling is defined   *
//*    as cursor/highlight movement under program control, rather than in        *
//*    response to user keyboard input.                                          *
//* -- Auto-scrolling may have limited usefulness in the real world; however,    *
//*    it may be useful in drawing the user's attention to a specific operational*
//*    choice.                                                                   *
//* -- Auto-scrolling is available for both ACWin objects and ACDlg objects.     *
//*    This is a dialog callback, so Dialog_Scroll() is used here.               *
//*    For ACWin auto-scrolling, see ScrollFieldText() in tseA_Callback().       *
//*                                                                              *
//********************************************************************************

static wchar_t ddzCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   #define DEBUG_TEXTBOX (0)              // for debugging only

   //* This is a copy of the enum defined for this *
   //* dialog in Test_Dialog(). Keep it synched.   *
   enum zCtrl : short
   {
      zcPB_OK = ZERO,
      zcPB_AUTO,
      zcPB_CANCEL,
      zcTB_TEXT,
      zcRB_MATH,
      zcRB_PHYSICS,
      zcRB_COMPSCI,
      zcSB_POEM,
      zcMB_CONTEXT
   } ;
   static int step = 1 ;                     // number of steps to scroll
   #if DEBUG_TEXTBOX == 0                    // production (textbox test disabled)
   static short trg = zcSB_POEM ;            // scrolling target
   #else    // for debugging only - enable Textbox scroll test
   static short trg = zcTB_TEXT ;
   static bool first_tb = true ;             // set if first auto-update of textbox
   #endif   // for debugging only - enable Textbox scroll test
   static bool head = false, tail = false ;  // flags
   static bool first_auto = true ;           // set if first press of zcPB_AUTO
   static const short msgY = 17, msgX = 2 ;  // message offset
   acAttr msgAttr = acAttr(acaFG_MAGENTA | acaBG_DFLT | acaBOLD | acaUNDERLINE) ;
   const int asMsgWid = 41 ;                 // columns for auto-scroll message
   gString gs ;                              // text management
   int tmpstep = step ;                      // protect the static 'step' value
   short item ;                              // currently hilighted item

   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   { /* nothing to do at this time */ }

   if ( ((wkey == wcsENTER) || (wkey == wcsSPACE)) )
   {
      if ( ctrlIndex == zcPB_AUTO )
      {
         //* If this is the first time this button was pressed, *
         //* set item for Scrollbox and Context Menu.           *
         if ( first_auto )
         {
            if ( trg != zcTB_TEXT )
            {
               if ( (cbPtr->Dialog_GetItem ( zcSB_POEM, gs, item, false )) )
                  cbPtr->Dialog_SetItem( zcSB_POEM, item ) ;
               if ( (cbPtr->Dialog_GetItem ( zcMB_CONTEXT, gs, item, false )) )
                  cbPtr->Dialog_SetItem( zcMB_CONTEXT, item ) ;
               cbPtr->nsleep ( 4 ) ;
            }
            first_auto = false ;
         }

         if ( trg == zcSB_POEM )
         {
            if ( !(head && tail) &&
                 (cbPtr->Dialog_Scroll ( zcSB_POEM, tmpstep, head, tail )) )
            {
               if ( (cbPtr->Dialog_GetItem ( zcSB_POEM, gs, item, false )) )
               {
                  cbPtr->Dialog_SetItem( zcSB_POEM, item ) ;
                  gs.compose( "▷ ScrollboxItem: %02hd", &item ) ;
                  gs.padCols( asMsgWid ) ;
                  cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr  ) ;
               }
            }
            else
            {
               if ( head && tail )
               {
                  //* Scrollbox text complete; *
                  //* switch to context menu   *
                  trg = zcMB_CONTEXT ;
                  step = 1 ;
                  head = tail = false ;

                  cbPtr->nsleep ( 4 ) ;
                  tmpstep = step ;
                  if ( (cbPtr->Dialog_Scroll ( zcMB_CONTEXT, tmpstep, head, tail )))
                  {
                     if ( (cbPtr->Dialog_GetItem ( zcMB_CONTEXT, gs, item, false )) )
                     {
                        cbPtr->Dialog_SetItem ( zcMB_CONTEXT, item ) ;
                        gs.compose( " Context Menu Item: %02hd", &item ) ;
                        gs.padCols( asMsgWid ) ;
                        cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr ) ;
                     }
                     wkey = NULLCHAR ;
                  }
               }
               else if ( step > ZERO )
               {
                  step = -1 ;
                  if ( ! tail )
                  {
                     //* After changing 'step' direction, call the scroll method. *
                     tmpstep = step ;
                     if ( (cbPtr->Dialog_Scroll ( zcSB_POEM, tmpstep, head, tail )) )
                     {
                        if ( (cbPtr->Dialog_GetItem ( zcSB_POEM, gs, item, false )) )
                        {
                           cbPtr->Dialog_SetItem( zcSB_POEM, item ) ;
                           gs.compose( "▷ ScrollboxItem: %02hd", &item ) ;
                           gs.padCols( asMsgWid ) ;
                           cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr  ) ;
                        }
                     }
                  }
                  if ( tail )
                  {
                     head = true ;
                     //* After setting 'head', call the scroll method. *
                     //* 'head' parameter should take precedence.      *
                     tmpstep = step ;
                     if ( (cbPtr->Dialog_Scroll ( zcSB_POEM, tmpstep, head, tail )) )
                     {
                        if ( (cbPtr->Dialog_GetItem ( zcSB_POEM, gs, item, false )) )
                        {
                           cbPtr->Dialog_SetItem( zcSB_POEM, item ) ;
                           gs.compose( "▷ ScrollboxItem: %02hd", &item ) ;
                           gs.padCols( asMsgWid ) ;
                           cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr  ) ;
                        }
                     }
                  }
               }
               else if ( step < ZERO )
               {
                  step = 1 ; tail = true ;
                  //* After setting 'tail', call the scroll method. *
                  //* 'tail' parameter should take precedence.      *
                  tmpstep = step ;
                  if ( (cbPtr->Dialog_Scroll ( zcSB_POEM, tmpstep, head, tail )) )
                  {
                     if ( (cbPtr->Dialog_GetItem ( zcSB_POEM, gs, item, false )) )
                     {
                        cbPtr->Dialog_SetItem( zcSB_POEM, item ) ;
                        gs.compose( "▷ ScrollboxItem: %02hd", &item ) ;
                        gs.padCols( asMsgWid ) ;
                        cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr  ) ;
                     }
                  }
               }
            }
         }
         else if ( trg == zcMB_CONTEXT )
         {
            if ( (cbPtr->Dialog_Scroll ( zcMB_CONTEXT, tmpstep, head, tail )))
            {
               if ( (cbPtr->Dialog_GetItem ( zcMB_CONTEXT, gs, item, false )) )
               {
                  cbPtr->Dialog_SetItem ( zcMB_CONTEXT, item ) ;
                  gs.compose( " Context Menu Item: %02hd", &item ) ;
                  gs.padCols( asMsgWid ) ;
                  cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr ) ;
               }
            }
            else
            {
               if ( step > ZERO )
               {
                  step = -1 ;
                  if ( ! tail )
                  {
                     //* After changing 'step' direction, call the scroll method. *
                     tmpstep = step ;
                     if ( (cbPtr->Dialog_Scroll ( zcMB_CONTEXT, tmpstep, head, tail )) )
                     {
                        if ( (cbPtr->Dialog_GetItem ( zcMB_CONTEXT, gs, item, false )) )
                        {
                           cbPtr->Dialog_SetItem( zcMB_CONTEXT, item ) ;
                           gs.compose( " Context Menu Item: %02hd", &item ) ;
                           gs.padCols( asMsgWid ) ;
                           cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr  ) ;
                        }
                     }
                  }
                  if ( tail )
                  {
                     head = true ;
                     tmpstep = step ;
                     if ( (cbPtr->Dialog_Scroll ( zcMB_CONTEXT, tmpstep, head, tail )) )
                     {
                        if ( (cbPtr->Dialog_GetItem ( zcMB_CONTEXT, gs, item, false )) )
                        {
                           cbPtr->Dialog_SetItem( zcMB_CONTEXT, item ) ;
                           gs.compose( " Context Menu Item: %02hd", &item ) ;
                           gs.padCols( asMsgWid ) ;
                           cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr  ) ;
                        }
                     }

                     //* Scrollbox/Context Menu auto-scroll test is complete.*
                     cbPtr->nsleep ( 8 ) ;
                     gs.compose( "Auto-scroll test is complete." ) ;
                     gs.padCols( asMsgWid ) ;
                     cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr ) ;
   
                     //* Set Textbox as target; this disables the auto-scroll test.*
                     trg = zcTB_TEXT ;
                     step = 1 ;
                     head = tail = false ;
                  }
               }
               else if ( step < ZERO )
               {
                  step = 1 ;
                  tail = true ;
                  tmpstep = step ;
                  if ( (cbPtr->Dialog_Scroll ( zcMB_CONTEXT, tmpstep, head, tail )) )
                  {
                     if ( (cbPtr->Dialog_GetItem ( zcMB_CONTEXT, gs, item, false )) )
                     {
                        cbPtr->Dialog_SetItem( zcMB_CONTEXT, item ) ;
                        gs.compose( " Context Menu Item: %02hd", &item ) ;
                        gs.padCols( asMsgWid ) ;
                        cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr  ) ;
                     }
                  }
               }
            }
         }
         #if DEBUG_TEXTBOX != 0  // enable Textbox scroll test
         else if ( trg == zcTB_TEXT )
         {
            // Programmer's Note: If user has already set Textbox IP to our target IP, 
            // then the value reported in 'tmpstep will be inaccurate.
            // In that case, we must explicitly request the position
            if ( first_tb )               // if first time:
            {
               //* Set Textbox IP to head of text (IP will be zero).       *
               //* (If already at head, then returned value not accurate.) *
               if ( !(cbPtr->Dialog_Scroll ( zcTB_TEXT, tmpstep, true, false )) )
               {
                  tmpstep = ZERO ;        // request position report
                  cbPtr->Dialog_Scroll ( zcTB_TEXT, tmpstep, false, false ) ;
               }
               gs.compose( "Verify that Textbox IP set to %02d.", &tmpstep ) ;
               gs.padCols( asMsgWid ) ;
               cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr ) ;

               step = 8 ;                 // set parms for next iteration
               head = tail = false ;
               first_tb = false ;
            }
            else
            {
               if ( !(cbPtr->Dialog_Scroll ( zcTB_TEXT, tmpstep, head, tail )) )
               {
                  tmpstep = ZERO ;        // request position report
                  cbPtr->Dialog_Scroll ( zcTB_TEXT, tmpstep, false, false ) ;
               }
               gs.compose( "Verify that Textbox IP set to %02d.", &tmpstep ) ;
               gs.padCols( asMsgWid ) ;
               cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr ) ;

               //* If forward step has reached end of text, *
               //* reverse directions.                      *
               cbPtr->Dialog_GetText ( zcTB_TEXT, gs ) ;
               if ( ! tail )
               {
                  if ( tmpstep == (gs.gschars() - 1) )
                     step = -(step) ;
                  else if ( (tmpstep == ZERO) )
                     tail = true ;
               }
               else  // (tail == end of test sequence)
               {
                  cbPtr->nsleep( 25 ) ;
                  gs.compose( "Textbox auto-scroll test is complete." ) ;
                  gs.padCols( asMsgWid ) ;
                  cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, msgAttr ) ;
               }
            }
         }
         #endif                  // enable Textbox scroll test
         else     // Test has been completed
         { cbPtr->acBeep () ; }

         wkey = NULLCHAR ;
      }
      else if ( (ctrlIndex == zcPB_OK) || (ctrlIndex == zcPB_CANCEL) )
      {
         gs.clear() ;                     // erase scroll-test message
         gs.padCols( asMsgWid ) ;
         cbPtr->Dialog_Write ( gs.gstr(), msgY, msgX, false, acaUSEDFLT ) ;
         wkey = wcsCTL_4 ;                // signal dialog exit
      }
   }

   return wkey ;

   #undef DEBUG_TEXTBOX
}  //* End ddzCallback() *

//*************************
//*       tdSidebar       *
//*************************
//********************************************************************************
//* Non-member method. Called by TestDialog() to exercise Sidebar functionality. *
//*                                                                              *
//* Input  : win   : pointer to open ACWin window                                *
//*          winHgt: window height (rows)                                        *
//*          winWid: window width (columns)                                      *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void tdSidebar ( ACWin* win, short winHgt, short winWid )
{
   WinPos wpsb(0,0) ;

   //* Draw a grid for visual verification of positioning. *
   #if 1    // 22 window rows (standard window height)
   win->Write ( wpsb, L"1\n2\n3\n4\n5\n=\n-\n-\n-\n-\n-\n-\n-\n-\n-\n-\n=\n1\n2\n3\n4\n5" ) ;
   wpsb.col = winWid - 3 ;
   win->Write ( wpsb, L"1\n2\n3\n4\n5\n=\n-\n-\n-\n-\n-\n-\n-\n-\n-\n=\n1\n2\n3\n4\n5\n6" ) ;
   #else    // 21 window rows (instantiate window with height==winHgt-1)
   win->Write ( wpsb, L"1\n2\n3\n4\n=\n-\n-\n-\n-\n-\n-\n-\n-\n-\n-\n=\n1\n2\n3\n4\n5" ) ;
   wpsb.col = winWid - 3 ;
   win->Write ( wpsb, L"1\n2\n3\n4\n5\n=\n-\n-\n-\n-\n-\n-\n-\n-\n-\n=\n1\n2\n3\n4\n5" ) ;
   #endif   // window rows

   //* Automatic vertical centering of messages.       *
   //* First message has an even number of characters. *
   //* Second message has an odd number of characters. *
   win->Sidebar ( L"Hello World!", true, acAttr(acaFG_BROWN | acaBGb_GREEN), 0 ) ; // even
   win->Sidebar ( L"Hello World", false, acAttr(acaFG_BROWN | acaBGb_GREEN), 0 ) ; // odd

   wpsb = { short(winHgt - 5), 2 } ;
   wpsb = win->Write ( wpsb, L"<----------------------- Test of Sidebar is Active ---------------------->\n" ) ;
   win->Write ( wpsb, L"<------- Centered Sidebar Messages. Press a key for next test...--------->" ) ;
   win->acSetCursorStyle ( csHide ) ;     // set cursor invisible

   win->acRead () ;                       // get user response
   win->Sidebar ( L"", true ) ;           // clear the border
   win->nsleep ( 15 ) ;                   // pause to verify clear operation

   //* Maximum negative and positive offsets.                 *
   //* Greater offsets should activate automatic adjustments. *
   win->Sidebar ( L"Hello World!", true, acAttr(acaFG_BROWN | acaBGb_GREEN), -4 ) ;
   win->Sidebar ( L"Hello World", false, acAttr(acaFG_BROWN | acaBGb_GREEN), 5 ) ;
   win->Write ( wpsb, L"<---------- Offset Sidebar Messages. Press any key to exit...------------>" ) ;
   win->acSetCursorStyle ( csHide ) ;     // set cursor invisible
   win->acRead () ;                       // get user response

   win->acSetCursorStyle ( csShow ) ;     // set cursor visible

}  //* End tdSidebar() *

//*************************
//*       Test_Fifo       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test initialization/store/retrieve of records written to the BlockFifo       *
//* member of the ACWin class.                                                   *
//*                                                                              *
//* The BlockFifo-class object is used to store data written directly to the     *
//* window interior via the Write() method, (outside of any defined field).      *
//* The stored data may then be retrieved for display when the window's display  *
//* is refreshed (see RefreshWin()).                                             *
//*                                                                              *
//* 1) The test window is defined with a few fields, and with one initial        *
//*    block of text written directly to the window interior via the call        *
//*   to OpenWindow().                                                           *
//* 2) Additional text blocks are then written to the window at various          *
//*    positions.                                                                *
//* 3) For each of these writes, the contents of the FIFO are written to the     *
//*    log file (if log file is open and DEBUG_FIFO declaration is non-zero).    *
//* 4) A summary of the FIFO contents is written to the terminal window          *
//*    (outside the test window).                                                *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Fifo ( aeSeq fg )
{  // invoke: W s
   const short winHEIGHT = 16 ;              // window height
   const short winWIDTH  = 70 ;              // window width
   const short winROW    = 3 ;               // window origin row
   const short winCOL    = 8 ;               // window origin column
   const short exitROW   = 32 ;              // exit row
   const short rptROW    = winROW + winHEIGHT + 1 ; // base row of reporting area
   const short rptHEIGHT = 12 ;              // height of reporting area
   const short rptWIDTH  = 100 ;             // width of reporting area
   enum fldINDEX : short
   {
      fldTEXT = ZERO,      // index of ftTB (textbox) field
      fldALTB,             // index of ftRB (radiobutton) field
      fldNEXT,             // index of ftPB (pushbutton) field
      fldCLEAR,            // index of ftPB (pushbutton) field
      fldEXIT,             // index of ftPB (pushbutton) field
      fldCOUNT             // number of defined fields
   } ;
   const char* OpenTxt = 
         " This is a test of the ACWin-class FIFO object which stores text \n"
         " written directly to the interior of the window. (log file: db.log)" ;
   const char* TxtData = "Tab among controls, Space or Enter to select a control." ;
   const char* AllDone = " ALL TESTS COMPLETE - Alternate Bgnd Toggle Still Active" ;
   const char* ByeBye  = "----------------- ALL TESTS COMPLETE!! -----------------" ;
   const char* PakClr  = "     Window cleared. Press any key to continue...       " ;
   acaExpand acaBld ;
   const acAttr txtBkgnd = 
         acaBld.buildRgb( false, wsrgbMIN, true, WebSafeRGB(wsrgbCYAN + wsrgbSHADE_MAX) ) ;
   const acAttr rgbBorder =                  // window border
         acaBld.buildRgb( true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX),
                          true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MAX * 0.65) ) ;
   const acAttr rgbStdInt =                  // standard window interior
         acAttr(acaBld.buildRgb(true, WebSafeRGB(wsrgbRED)) | txtBkgnd) ;
   const acAttr rgbAltInt =                  // alternate window interior
         acaBld.buildRgb( true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MAX),
                          true, WebSafeRGB(wsrgbBLACK) ) ;
   const acAttr ctrlAttr = acaBld.build8Bit( true, 160, true, 82 ) ;
   const char* testTemplate = "* Test #%02hd *" ;  // template for test text
   acAttr testFgnd[17] =      // color attributes for each test string
   {
      acaATTR_DFLT,     // (dummy value)
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbRED   +  9)) | txtBkgnd    | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbRED   + wsrgbSHADE_MED))   | txtBkgnd),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbRED   + 27)) | txtBkgnd    | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbRED   + wsrgbSHADE_MAX))   | txtBkgnd),

      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbGREEN +  9)) | txtBkgnd    | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MED))   | txtBkgnd),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbGREEN + 27)) | txtBkgnd    | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MAX))   | txtBkgnd),

      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbBLUE  +  9)) | txtBkgnd    | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbBLUE  + wsrgbSHADE_MED))   | txtBkgnd),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbBLUE  + 27)) | txtBkgnd    | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbBLUE  + wsrgbSHADE_MAX))   | txtBkgnd),

      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbMAGENTA +  9)) | txtBkgnd  | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MED)) | txtBkgnd),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbMAGENTA + 27)) | txtBkgnd  | acaBOLD),
      acAttr(acaBld.buildRgb( true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MAX)) | txtBkgnd),
   } ;

   WinPos rptOrig( rptROW, ZERO ) ;       // base of reporting area
   WinPos wp( 3, 6 ) ;                    // control positioning and write positioning
   WinPos wp_rpt( rptROW, ZERO ) ;
   short testNum = 1,                     // test number
         fi,                              // focus index
         bi = -1 ;                        // block index (note the negative initialization)
   gString gsOut( testTemplate, &testNum ) ; // text formatting
   gString gsRpt ;                        // diagnostic messages
   wchar_t wch ;                          // user keystrokes
   bool    rbState = false,               // state of Radiobutton
           hotKey = false,                // set if hotkey received
           done = false ;                 // loop control

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, "  (requires terminal dimensions of 32 rows X 100 columns)\n"
                          "---------------------------------------------------------" ) ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   this->acBufferedInput ( false, noEcho ) ;    // set unbuffered input

   //* Clear the reporting area *
   this->acClearArea ( rptOrig, rptHEIGHT, rptWIDTH, L'.' ) ;


   //* Construct an skForm-class object and initialize it.     *
   //* The initialized object is passed as a parameter to the  *
   //* ACWin-class constructor.                                *
   //* Fields: Single-row text field: (read only)              *
   //*         Textbox    : (read-only, report test results)   *
   //*         Radiobutton: (toggle alternate background)      *
   //*         Pushbutton : (execute next test)                *
   //*         Pushbutton : (clear window)                     *
   //*         Pushbutton : (exit test window)                 *
   skForm *skfPtr = new skForm
   (
      fldCOUNT,
      new skField[fldCOUNT]
      {
         //* Field index 0: ftTB (Textbox: fldTEXT) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         short(winWIDTH - 14),            // field width (in columns)
         wp.row, wp.col,                  // Y/X offset in window
         TxtData,                         // field text
         acAttr(acaFG_BLUE | acaBG_DFLT), // color attributes
         floChar,                         // text formatting option (default)
         ZERO,                            // insertion point (cursor offset) (default)
         true,                            // read-only field
         // Remaining parameters receive default values.
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 1: ftRB (Radiobutton: fldALTB) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         1,                               // field width (dynamically calculated)
         short(wp.row = winHEIGHT - 6),   // Y/X offset in window
         wp.col,
         "[ ] ^Alternate Background",     // field text
         acAttr(ctrlAttr | acaBOLD),      // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (default)
         //ZERO,                            // insertion point (cursor offset) (default)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 2: ftPB (Pushbutton: fldNEXT) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         21,                              // field width (in columns)
         short(wp.row += 2), wp.col,      // Y/X offset in window
         "  ^Execute Next Test  ",        // field text with Hotkey '^E'
         acAttr(ctrlAttr | acaBOLD),      // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 3: ftPB (Pushbutton: fldCLEAR) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         16,                              // field width (in columns)
         wp.row, short(wp.col += 21 + 2), // Y/X offset in window
         "  ^Clear Window  ",             // field text with Hotkey '^C'
         acAttr(ctrlAttr | acaBOLD),      // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (default)
         //ZERO,                            // insertion point (cursor offset) (default)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 4: ftPB (Pushbutton: fldEXIT) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         10,                              // field width (in columns)
         wp.row, short(wp.col += 16 + 2), // Y/X offset in window
         "   E^XIT   ",                   // field text with Hotkey '^X'
         acAttr(ctrlAttr | acaBOLD),      // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },
      },
      //* Initialize remaining skForm parameters.*
      fldNEXT           // index of field with initial focus
      // Remaining parameters receive default values.
      //acaUSEDFLT,     // default color attributes for control with focus
      //true,           // beep on unhandled keycode received
      //true,           // initial state of Insert/Overstrike flag
      //true,           // user access to Insert/Overstrike mode
      //false,          // enable cursor shape change for Insert/Overstrike
      //csBblock,       // cursor shape when Insert mode
      //csBuline        // cursor shape when Overstrike mode
   ) ;


   //**************************
   //* Define an ACWin object *
   //**************************
   ACWin *win = new ACWin
         ( winROW, winCOL,                      // position (upper-left corner) 
           winHEIGHT, winWIDTH,                 // dimensions (height, width)
           ltSINGLE,                            // border style
           rgbBorder,                           // border attributes
           rgbStdInt,                           // interior attributes
           " Test FIFO Storage and Retrieval ", // window title
           skfPtr
         ) ;

   //* Note: Opening the window disables input buffering AND *
   //*       sets the echo option to 'softEchoA'.            *
   win->OpenWindow ( OpenTxt ) ;

   //* Delete the local skForm object and reference   *
   //* the fully-initialized object within the window.*
   delete skfPtr ;                  // delete the local skForm object
   const skForm *skfp = win->GetForm () ;       // pointer to internal data
   const BlockFifo *bfifo = win->GetFifo () ;   // pointer to internal data
   //* Initial position for direct window writes. *
   wp = { short(skfp->fld[fldTEXT].orig.row + 2), skfp->fld[fldTEXT].orig.col } ;

   win->SetFieldOutline ( fldTEXT, ltSINGLE, acAttr(acaFG_DFLT | acaBG_DFLT) ) ;

   //* Report the text written to window interior by OpenWindow(). *
   this->dumpFifoRec ( bfifo, bi, wp_rpt, fg, rptROW, rptHEIGHT, rptWIDTH ) ;
   win->RefreshField ( skfp->fi ) ;

   while ( ! done )              // get loopy
   {
      wch = win->acRead () ;     // talk with the user (filthy creatures)

      //** Hotkey Group - Move to indicated control and set "activate" keycode. **
      //** Note: either Ctrl+n or Alt+n are accepted as hotkeys for this test.  **
      hotKey = true ;            // assume hotkey
      switch ( wch )
      {
         case wcsALT_A:          // hotkey: Alternate Background
         case wcsCTL_A: win->SetInputFocus ( fldALTB ) ; break ;
         case wcsALT_E:          // hotkey: Execute Next Test
         case wcsCTL_E: win->SetInputFocus ( fldNEXT ) ; break ;
         case wcsALT_C:          // hotkey: Clear Window
         case wcsCTL_C: win->SetInputFocus ( fldCLEAR ) ; break ;
         case wcsALT_X:          // hotkey: Exit
         case wcsCTL_X: win->SetInputFocus ( fldEXIT ) ; break ;
         default: hotKey = false ; break ;
      }
      if ( hotKey ) { wch = wcsENTER ; }

      switch ( wch )
      {
         case SPACE:             // activate control with focus
         case wcsENTER:
            switch ( (fi  = win->GetInputFocus ()) )
            {
               case fldALTB:     // set/reset alternate background (invokes RefreshWin())
                  rbState = win->GetFieldState ( fldALTB ) ;   // get current state
                  rbState = rbState ? false : true ;           // toggle the state
                  rbState = win->SetFieldState ( fldALTB, rbState ) ;
                  win->SetTextAttr ( rbState ? rgbAltInt : rgbStdInt ) ;
                  win->RefreshWin () ;                         // make changes visible
                  break ;

               case fldNEXT:     // execute next test
                  win->Write ( wp, gsOut, testFgnd[testNum] ) ;
                  ++testNum ;
                  gsOut.compose( testTemplate, &testNum ) ;
                  if ( testNum <= 4 )        { ++wp.row ; }
                  else if ( testNum == 5 )   { wp.row -= 3 ; wp.col += gsOut.gscols() + 1 ; }
                  else if ( testNum <= 8 )   { ++wp.row ; }
                  else if ( testNum == 9 )   { wp.row -= 3 ; wp.col += gsOut.gscols() + 1 ; }
                  else if ( testNum <= 12 )  { ++wp.row ; }
                  else if ( testNum == 13 )  { wp.row -= 3 ; wp.col += gsOut.gscols() + 1 ; }
                  else if ( testNum <= 16 )  { ++wp.row ; }
                  win->RefreshField ( fldNEXT ) ;              // return cursor to control
                  if ( testNum == 17 )
                  {
                     win->Focus2NextField () ;
                     win->SetReadOnly ( fldNEXT, true ) ;
                     win->SetFieldAttr ( fldNEXT, acAttr(acaFG_BLACK | acaBG_GREY) ) ;
                     win->SetFieldData ( fldTEXT, AllDone, acAttr(acaFGb_GREY | acaBG_BLACK) ) ;
                  }
                  this->dumpFifoRec ( bfifo, bi, wp_rpt, fg, rptROW, rptHEIGHT, rptWIDTH ) ;
                  win->RefreshField ( skfp->fi ) ;
                  break ;

               case fldCLEAR:    // clear window and reset FIFO
                  win->ClearWin() ;       // clear the entire window (including field display)
                  win->SetFieldData ( fldTEXT, PakClr, 
                                      acAttr(acaFGb_GREY | acaBG_RED | acaBLINK) ) ;
                  win->SetReadOnly ( fldNEXT, false ) ;
                  win->SetFieldAttr ( fldNEXT, acAttr(ctrlAttr | acaBOLD) ) ;
                  win->acBeep ( 2, 2 ) ;
                  wp_rpt = { rptROW, ZERO } ;// reinitialize reporting position
                  this->acClearArea ( rptOrig, rptHEIGHT, rptWIDTH, L'.' ) ;
                  bi = -1 ;                  // reinitialize reporting index
                  this->dumpFifoRec ( bfifo, bi, wp_rpt, fg, rptROW, rptHEIGHT, rptWIDTH ) ;
                  win->RefreshField ( skfp->fCnt ) ; // refresh all fields
                  --bi ;                     // adjust reporting index
                  wp = { short(skfp->fld[fldTEXT].orig.row + 2), skfp->fld[fldTEXT].orig.col } ;
                  win->acRead () ;           // wait for user response
                  win->SetFieldData ( fldTEXT, TxtData, acAttr(acaFG_BLUE | acaBG_DFLT) ) ;
                  testNum = 1 ;              // reinitialize test sequence
                  gsOut.compose( testTemplate, &testNum ) ;
                  win->RefreshWin () ;       // redraw everything
                  wp = { ZERO, ZERO } ;      // write the instructions
                  win->Write ( wp, OpenTxt, rgbStdInt ) ;
                  this->dumpFifoRec ( bfifo, bi, wp_rpt, fg, rptROW, rptHEIGHT, rptWIDTH ) ;
                  wp = { short(skfp->fld[fldTEXT].orig.row + 2), skfp->fld[fldTEXT].orig.col } ;
                  win->Focus2PrevField () ;  // return to 'Execute' control
                  break ;

               case fldEXIT:     // exit the window
                  win->SetFieldData ( fldTEXT, ByeBye, acAttr(acaFGb_GREY | acaBG_BLACK) ) ;
                  done = true ;
                  break ;

               default:          // (this should not happen)
                  win->acBeep () ;
                  break ;
            }
            break ;

         case wcsTAB:            // move to next field
            win->Focus2NextField () ;
            break ;

         case wcsSTAB:           // move to previous field
            win->Focus2PrevField () ;
            break ;

#if 0    // FUNCTIONAL
         //** Hotkey Group **
         case wcsALT_A:          // hotkey: Alternate Background
         case wcsCTL_A:
            win->SetInputFocus ( fldALTB ) ;
            break ;
         case wcsALT_E:          // hotkey: Execute Next Test
         case wcsCTL_E:
            win->SetInputFocus ( fldNEXT ) ;
            break ;
         case wcsALT_C:          // hotkey: Clear Window
         case wcsCTL_C:
            win->SetInputFocus ( fldCLEAR ) ;
            break ;
         case wcsALT_X:          // hotkey: Exit
         case wcsCTL_X:
            win->SetInputFocus ( fldEXIT ) ;
            done = true ;
            break ;
#endif   // FUNCTIONAL
         default:
            win->acBeep () ;
            break ;
      }
   }

   //* Note: Closing the window re-enables input buffering *
   //*       AND resets the echo option to 'termEcho'.     *
   //* Note: For this test, window display is not erased.  *
   win->CloseWindow ( false ) ;
   delete ( win ) ;        // release the allocated resourses

   //* Restore buffered input and prepare to exit.*
   this->acBufferedInput ( true, termEcho ) ;
   this->acSetFg ( aesFG_DFLT ) ;
   this->acSetCursor ( exitROW, ZERO ) ;

}  //* End Test_Fifo() *

//*************************
//*      dumpFifoRec      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY Test_Fifo().                                 *
//* Format and display a record from the BlockFifo member.                       *
//*                                                                              *
//*                                                                              *
//* Input  : vfifo  : pointer to the BlockFifo member                            *
//*                   Note: This is cast as a void pointer because the AnsiCmd   *
//*                         class does not have access to this class, so the     *
//*                         method prototype demands the cast to const void*.    *
//*          bi     : (by reference) index into array of records                 *
//*                   Note: This value is pre-incremented to reference the record*
//*          wpRpt  : cursor position at which to display the record             *
//*          fg     : text color attribute for output                            *
//*          baseRow: top row of reporting area                                  *
//*          areaHgt: number of rows in reporting area                           *
//*          areaWid: number of columns in reporting area                        *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::dumpFifoRec ( const void* vfifo, short& bi, WinPos& wpRpt, 
                            aeSeq fg, short baseRow, short areaHgt, short areaWid )
{
   const char* rptTemplate = 
      "  top:%02hd next:%02hd block%02hd) wp:%02hd/%02hd aca:%08X act:%hhd %s" ;
   gString gsRpt ;
   const BlockFifo* bfifo = (const BlockFifo*)vfifo ; // convert void pointer

   this->acSetFgBg ( fg, aesBG_DFLT ) ;
   if ( ++bi >= DB_MAX )
      bi = ZERO ;

   gsRpt.compose( rptTemplate, &bfifo->top, &bfifo->next, &bi, 
                  &bfifo->block[bi].wp.row, &bfifo->block[bi].wp.col,
                  &bfifo->block[bi].aca.acaVal, &bfifo->block[bi].active,
                  bfifo->block[bi].txt ) ;
   gsRpt.erase( NEWLINE, ZERO, false, true ) ;
   gsRpt.limitCols( areaWid ) ;
   this->acClearArea ( wpRpt, 1, areaWid ) ;
   this->acSetCursor ( wpRpt ) ;
   this->ttyWrite ( gsRpt ) ;
   if ( ++wpRpt.row >= (baseRow + areaHgt) )
      wpRpt.row = baseRow ;

}  //* End dumpFifoRec() *

//*************************
//*      Test_Menu       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Open an ACWin window with various menu fields.                               *
//*                                                                              *
//* Fields: Pushbutton : exit the test                                           *
//*         Menubar    : anchor the menu group                                   *
//*         Member Menu: (File)                                                  *
//*         Member Menu: (Edit)                                                  *
//*         Member Menu: (Help)                                                  *
//*         ContextMenu: (stand-alone)                                           *
//*         Scrollbox  : (read-only text)                                        *
//*         Textbox    : (view-only selection report)                            *
//*         Pushbutton : Activate Menubar                                        *
//*         Pushbutton : Activate Context Menu                                   *
//*         Pushbutton : Exit (close the window)                                 *
//*                                                                              *
//* Notes on initialization:                                                     *
//* 1) Many initialization parameters for the skForm and skField classes have    *
//*    default values; however, for this test these parameters are initialized   *
//*    explicitly, OR the default value is shown for reference purposes.         *
//* 2) In some cases, parameters may be overriden during instantiation of the    *
//*    window object to normalize the data structures.                           *
//*    See documentation for descriptions of the individual field types.         *
//* 3) For Menubox (ftMB) controls ONLY: Specify the associated hotkey character *
//*    for each menu control. These hotkeys are set _explicitly_ because ftMB    *
//*    controls do not have labels from which a hotkey may be extracted.         *
//* 4) A group identifier is assigned to the menubar and its three associated    *
//*    sub-menus. Other fields are not members of a group.                       *
//* 5) Custom (per-row) color attributes for Menus (ftMB) and Scrollbox (ftSB)   *
//*    controls are tested here. Although custom attributes SHOULD always be     *
//*    specified AFTER the ACWin object is created, but BEFORE the window is     *
//*    opened, the MULTICOLOR_PREOPEN directive is provided so that              *
//*    initialization may be tested either before or after the window is opened. *
//*    This is done to give us a warm-fuzzy about the underlying code.           *
//*    See SetFieldAttr() method for additional details.                         *
//*                                                                              *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Menu ( aeSeq fg )
{  // invoke: W m
   const short winHEIGHT = 20 ;              // window height
   const short winWIDTH  = 70 ;              // window width
   const short winROW    = 3 ;               // window origin row
   const short winCOL    = 8 ;               // window origin column
   const short TB_WIDTH  = 30 ;              // width of read-only Textbox
   const short SB_WIDTH  = 33 ;              // width of Scrollbox
   const short exitROW = winROW + winHEIGHT ; // exit row
   enum fldIndex : short
   {
      fldPB_EXIT = ZERO,                  // ftPB (Exit)
      fldPB_CX,                           // ftPB (toggle ConteXt menu)
      fldMENU0,                           // ftMB (Menubar)
      fldMENUF,                           // ftMB (File menu)
      fldMENUE,                           // ftMB (Edit menu)
      fldMENUH,                           // ftMB (Help menu)
      fldMENUC,                           // ftMB (stand-alone, Context menu)
      fldS_BOX,                           // ftSB Scrollbox (view, but no edit)
      fldTB_RE,                           // ftTB Textbox (report menu selection)
      fldPB_MB,                           // ftPB (toggle MenuBar)
      fldCOUNT                            // number of fields
   } ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   this->acBufferedInput ( false, noEcho ) ;    // set unbuffered input

   acaExpand acaBld ;                     // construct color attributes
   const acAttr acaBorder =               // window border: bright yellow on blue
         acaBld.buildRgb( true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX),
                          true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MAX * 0.65) ) ;
   const acAttr acaInterior =             // window interior, black on pale green)
         acaBld.buildRgb( true, WebSafeRGB(wsrgbBLACK),
                          true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MAX) ) ;
   const acAttr pbAttr = acaBld.build8Bit( true, 160, true, 82 ) ;
   const acAttr mwAttr = acaBld.build8Bit( true, 195, true, 129 ) ;
   const acAttr mbAttr = acAttr(mwAttr | acaUNDERLINE) ;
   const acAttr cxAttr = acaBld.build8Bit( true, 82, true, 88 ) ;
   const acAttr tbAttr = acaBld.build8Bit( true, 16, true, 123 ) ;
   const wchar_t grpId = L'a' ;     // menubar group ID
   const short  mbarROW = 1 ;       // menubar vertical offset
   const short  mbarCOL = 4 ;       // menubar horizontal offset
   short f = ZERO ;                 // control index on return from edit
   gString gs ;                     // text analysis
   WinPos wpul( short(winHEIGHT - 4), short((winWIDTH / 2) - 6) ) ; // field positioning

   //* Construct an skForm-class object and initialize it.     *
   //* The initialized object is passed as a parameter to the  *
   //* ACWin-class constructor.                                *
   skForm *skfPtr = new skForm
   (
      fldCOUNT,
      new skField[fldCOUNT]
      {
         //* Field index 0: ftPB (Pushbutton: fldPB_EXIT) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         10,                              // field width (in columns)
         wpul.row, wpul.col,              // Y/X offset in window
         "   E^xit   ",                   // field text with Hotkey '^x'
         pbAttr,                          // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 1: ftPB (Pushbutton: fldPB_CX) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         23,                              // field width (in columns)
         wpul.row,                        // Y/X offset in window
         short( wpul.col += 10 + 1),
         " Activate ^Context Menu ",      // field text with Hotkey '^C'
         pbAttr,                          // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 2: ftMB (Menu: fldMENU0 - menubar) *
         {
         ftMB,                            // field type
         1,                               // field height (in rows)
         59,                              // field width (in columns)
         short(wpul.row = mbarROW),      // Y/X offset in window
         short(wpul.col = mbarCOL),
         "  File               Edit                   Help           ",
         mbAttr,                          // color attributes
         floShift,                        // text formatting option (n/a)
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         false,                           // field outline disabled (n/a)
         true,                            // field is initially invisible
         false,                           // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         wcsF02,                          // hotkey character
         grpId                            // menubar group
         },

         //* Field index 3: ftMB (Menu: fldMENUF) *
         {
         ftMB,                            // field type
         5,                               // field height (in rows)
         18,                              // field width (in columns)
         short(wpul.row += 1),            // Y/X offset in window
         short(wpul.col += 1),
         "^Open a file\n"                 // field text
         "Sa^ve file\n"
         "^Close file\n"
         "E^xit Application\n",  // (test with trailing newline)
         mwAttr,                          // color attributes
         floShift,                        // text formatting option (n/a)
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         false,                           // field outline disabled (n/a)
         true,                            // field is initially invisible
         false,                           // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         wcsALT_F,                        // hotkey character
         grpId                            // menubar group
         },

         //* Field index 4: ftMB (Menu: fldMENUE) *
         {
         ftMB,                            // field type
         6,                               // field height (in rows)
         22,                              // field width (in columns)
         wpul.row,                        // Y/X offset in window
         short(wpul.col += 18 + 1),
         "Select ^All\n"  // set field text: height == 6, width == 22
         "^Copy Selection\n"
         "Cu^t Selection\n"
         "^Paste from Clipboard\n"
         "^Undo Paste Operation",  // (test without trailing newline)
         mwAttr,                          // color attributes
         floShift,                        // text formatting option (n/a)
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         false,                           // field outline disabled (n/a)
         true,                            // field is initially invisible
         false,                           // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         wcsALT_E,                        // hotkey character
         grpId                            // menubar group
         },

         //* Field index 5: ftMB (Menu: fldMENUH) *
         {
         ftMB,                            // field type
         4,                               // field height (in rows)
         14,                              // field width (in columns)
         wpul.row,                        // Y/X offset in window
         short(wpul.col += 22 + 1),
         "^Quick Help\n"                  // field text
         "^Texinfo Help\n"
         "^Help About\n",
         mwAttr,                          // color attributes
         floShift,                        // text formatting option (n/a)
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         false,                           // field outline disabled (n/a)
         true,                            // field is initially invisible
         false,                           // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         wcsALT_H,                        // hotkey character
         grpId                            // menubar group
         },

         //* Field index 6: ftMB (Menu: fldMENUC - context menu) *
         {
         ftMB,                            // field type
         10,                              // field height (in rows)
         18,                              // field width (in columns)
         short(wpul.row += 3),            // Y/X offset in window
         short(wpul.col = winWIDTH - 24),
         "Find       (C-^f)\n"            // field text
         "Find Again (C-^g)\n"
         "To ^Uppercase    \n"
         "To ^Lowercase    \n"
         "────────────────\n"
         "Set ^Bookmark    \n"
         "Go to Bookmar^k  \n"
         "^Clear Bookmarks \n",
         cxAttr,                          // color attributes
         floShift,                        // text formatting option (n/a)
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         false,                           // field outline disabled (n/a)
         true,                            // field is initially invisible
         false,                           // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         wcsALT_G,                        // hotkey character
         noGROUP                          // not a group member
         },

         //* Field index 7: ftSB (Scrollbox: fldS_BOX) *
         {
         ftSB,                            // field type
         4,                               // field height (in rows)
         short(SB_WIDTH + 2),             // field width (in columns)
         short(wpul.row += 4),            // Y/X offset in window
         short(wpul.col = winWIDTH / 2 - SB_WIDTH / 2 - 3),
         "Hotkeys assigned to Menus:       \n"
         "Menubar:wcsF02    File:wcsALT_F  \n"
         "Edit   :wcsALT_E  Help:wcsALT_H  \n"
         "Context:wcsALT_G                 \n"
         "            --------             \n"
         "Menu controls are invisible by   \n"
         "default, and become visible only \n"
         "when activated by the associated \n"
         "hotkey, or when invoked under    \n"
         "application control. Menus may be\n"
         "grouped, meaning that specified  \n"
         "menu objects are logically tied  \n"
         "together into a functional unit. \n"
         "A stand-alone menu is referred to\n"
         "as a \"context\" menu because it \n"
         "becomes active only within the   \n"
         "context (as application defined).\n"
         "A \"Menubar\" is a special-use   \n"
         "menu which manages a menu group, \n"
         "and is identified by the fact    \n"
         "that it has a height of one(1).  \n"
         "Additionally, menubar controls   \n"
         "may, if desired, be set as always\n"
         "visible.\n"
         "Scrollbox controls are a special \n"
         "case of the standard Textbox     \n"
         "control. The user can scroll     \n"
         "(vertically) through the data,   \n"
         "but the data may not be edited.  \n"
         "The cursor is invisible within a \n"
         "scrollbox, and instead, an       \n"
         "indicator character '▶' (U+25B6) \n"
         "is used to show the position.    \n",
         acaInterior,                     // color attributes
         floToken,                        // text formatting option (word-processing)
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         true,                            // field outline enabled
         false,                           // field is visible (not hidden)
         true,                            // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },

         //* Field index 8: ftTB (Textbox: fldTB_RE) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         TB_WIDTH,                        // field width (in columns)
         short(wpul.row += 5),            // Y/X offset in window
         short(wpul.col = winWIDTH / 2 - TB_WIDTH / 2 - 1),
         "       (menu selection)       ",// initial field text
         tbAttr,                          // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         true,                            // read-only field
         false,                           // field outline disabled
         false,                           // field is visible (not hidden)
         true,                            // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },

         //* Field index 9: ftPB (Pushbutton: fldPB_MB) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         24,                              // field width (in columns)
         short(wpul.row += 2),            // Y/X offset in window
         short(wpul.col = 4),
         " Activate the Menu ^Bar  ",     // field text
         pbAttr,                          // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         }
      },
      //* Optional arguments are set to default values. *
      fldPB_MB          // index of field with initial focus
      // Remaining parameters receive default values.
      //acaUSEDFLT,     // default color attributes for control with focus
      //true,           // beep on unhandled keycode received
      //true,           // initial state of Insert/Overstrike flag
      //true,           // user access to Insert/Overstrike mode
      //false,          // enable cursor shape change for Insert/Overstrike
      //csBblock,       // cursor shape when Insert mode
      //csBuline        // cursor shape when Overstrike mode
   ) ;

   #if 0    // development only
   // Test the skField 'labelConfig' for a Menubar control. *
   // Overrides the wcsF02 assigned during instantiation.   *
   skfPtr->fld[fldMENU0].labelConfig( " Menuba^r Label ", acAttr(acaFG_BLUE | acaBGb_GREEN), true ) ;
   #endif   // development only

   //**************************
   //* Define an ACWin object *
   //**************************
   ACWin *win = new ACWin
         ( winROW, winCOL,                      // position (upper-left corner) 
           winHEIGHT, winWIDTH,                 // dimensions (height, width)
           ltSINGLE,                            // border style
           acaBorder,                           // border attributes
           acaInterior,                         // interior attributes
           " ACWin MenuBar and Context Menu ",  // window title
           skfPtr
         ) ;

   //**----------------------------------------------**
   //** Scrollbox and Menu per-row color attributes: **
   //**        (See note in method header.)          **
   //**----------------------------------------------**
   #define MULTICOLOR_PREOPEN (1)
   acAttr rowAttr[35] ;    // receives attribute bitfields(slightly larger than needed)
   short  rowCnt ;         // receives attribute count
   #if MULTICOLOR_PREOPEN != 0
   tmw_Multicolor ( &skfPtr->fld[fldS_BOX],           // initialize Scrollbox array
                    rowAttr, rowCnt ) ;
   win->SetFieldAttr ( fldS_BOX, rowAttr, rowCnt ) ;  // set Scrollbox attributes
   tmw_Multicolor ( &skfPtr->fld[fldMENUE],           // initialize group-member menu array
                    rowAttr, rowCnt ) ;
   win->SetFieldAttr ( fldMENUE, rowAttr, rowCnt ) ;  // set Menu attributes
   tmw_Multicolor ( &skfPtr->fld[fldMENUC],           // initialize context menu array
                    rowAttr, rowCnt ) ;
   win->SetFieldAttr ( fldMENUC, rowAttr, rowCnt ) ;  // set Menu attributes

   //* Note: Opening the window disables input buffering AND *
   //*       sets the echo option to 'softEchoA'.            *
   win->OpenWindow () ;

   #else    // MULTICOLOR_PREOPEN == 0
   win->OpenWindow () ;

   winInfo wInfo ;
   win->WinInfo ( wInfo ) ;
   tmw_Multicolor ( &wInfo.skf->fld[fldS_BOX],        // initialize Scrollbox array
                    rowAttr, rowCnt ) ;
   win->SetFieldAttr ( fldS_BOX, rowAttr, rowCnt ) ;  // set Scrollbox attributes
   tmw_Multicolor ( &skfPtr->fld[fldMENUE],           // initialize group-member menu array
                    rowAttr, rowCnt ) ;
   win->SetFieldAttr ( fldMENUE, rowAttr, rowCnt ) ;  // set Menu attributes
   tmw_Multicolor ( &skfPtr->fld[fldMENUC],           // initialize context menu array
                    rowAttr, rowCnt ) ;
   win->SetFieldAttr ( fldMENUC, rowAttr, rowCnt ) ;  // set Menu attributes
   win->RefreshWin() ;           // ensure that the modifications are visible
   #endif   // MULTICOLOR_PREOPEN

   //* Set to no-echo. Note that if editing a field, then *
   //* echo will automatically be set correctly.          *
   win->acSetEchoOption ( noEcho ) ;

   delete skfPtr ;                  // delete the local skForm object
   cbPtr = win ;                    // give callback method access to ACWin object

   //* Write static text to the window. *
   wpul = { 2, 4 } ;
   gs = "This window contains five 'ftMB' Menubox fields (initially \n"
        "invisible), and a 'ftSB' Scrollbox control in addition to \n"
        "three 'ftPB' Pushbutton controls and a read-only 'ftTB' \n"
        "text control which reports the menu selection. Within menus, \n"
        "borders are automatically added to the interior of the menu \n"
        "fields, and hotkeys are underlined." ;
   win->Write ( wpul, gs ) ;

   //* Establish a callback method to monitor user input.  *
   win->EstablishCallback ( &tmw_Callback ) ;

   //***************************
   //* Interact with the user. *
   //***************************
   win->EditForm ( f ) ;

   //* Note: Closing the window re-enables input buffering *
   //*       AND resets the echo option to 'termEcho'.     *
   //* Note: For this test, window display is not erased.  *
   win->CloseWindow ( false ) ;
   cbPtr = NULL ;                      // reset the external pointer

   delete ( win ) ;                    // release the allocated resourses

   //* Restore buffered input and prepare to exit.*
   this->acBufferedInput ( true, termEcho ) ;
   this->acSetFg ( aesFG_DFLT ) ;
   this->acSetCursor ( exitROW, ZERO ) ;

}  //* End Test_Menu() *

//*************************
//*    tmw_Multicolor     *
//*************************
//********************************************************************************
//* Non-member method:                                                           *
//* ------------------                                                           *
//* Called by Test_Menu() to initialize an array of color attributes to be used  *
//* in Menus and Scrollbox controls which are to have per-row color attributes.  *
//*                                                                              *
//* Input  : fptr   : pointer to an initialized skField object                   *
//*          acArray: pointer to array to receive the acAttr values              *
//*          rowcnt : (by reference) receives number of attributes written to    *
//*                   acArray                                                    *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* -- The acaUSEDFLT value indicates that the target row is to be displayed     *
//*    using the control's interior color ('aca' member).                        *
//********************************************************************************

static void tmw_Multicolor ( const skField* fptr, acAttr* acArray, short& rowcnt )
{
   acaExpand acaBld ;                     // build attribute bitfields
   int nli = ZERO ;                       // newline index
   rowcnt  = ZERO ;                       // initialize caller's counter

   if ( fptr->type == ftSB )              // Scrollbox controls
   {
      const acAttr sbA = acaBld.buildRgb( true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MAX),
                                          true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MED) ) ;
      const acAttr sbB = acaBld.buildRgb( true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MED),
                                          true, WebSafeRGB(wsrgbGREEN + 28) ) ;
      while ( (nli = fptr->txt.after( NEWLINE, nli )) > ZERO ) // count source rows
         ++rowcnt ;
      //* If the last row was not terminated by a newline, add one.*
      if ( fptr->txt.gstr()[fptr->txt.gschars() - 2] != NEWLINE )
         ++rowcnt ;
      for ( short i = ZERO ; i < rowcnt ; ++i ) // populate the array
      {
         if ( i == ZERO || i == 4 )
            acArray[i] = sbA ;
         else if ( (i >= 1 && i <= 3) || (i == (rowcnt - 2)) )
            acArray[i] = sbB ;
         else
            acArray[i] = acaUSEDFLT ;
      } ;
   }

   else                                   // Menu controls
   {
      if ( fptr->grp != noGROUP )         // group-member menu
      {
         //* Group-member Menu *
         rowcnt = fptr->hgt - 1 ;         // exclude lower borders
         acArray[0] = acaUSEDFLT ;        // populate the array
         acArray[1] = acaBld.build8Bit( true, 196, true, 129 ) ;
         acArray[2] = acaBld.build8Bit( true,  82, true, 129 ) ;
         acArray[3] = acaBld.build8Bit( true,  21, true, 129 ) ;
         acArray[4] = acaBld.build8Bit( true, 208, true, 129 ) ;
      }
      else                                // context menu
      {
         rowcnt = fptr->hgt - 2 ;         // exclude upper/lower borders
         for ( short i = ZERO ; i < rowcnt ; ++i ) // populate the array
         {
            if ( i == 4 )
               acArray[i] = acaBld.build8Bit( true, 201, true, 88 ) ;
            else
               acArray[i] = acaUSEDFLT ;
         }
      }
   }

}  //* End tmw_Multicolor() *

//*************************
//*     tmw_Callback      *
//*************************
//********************************************************************************
//* Non-member method:                                                           *
//* ------------------                                                           *
//* Callback method for Test_Menu() above. Handle the callbacks from EditForm()  *
//*                                                                              *
//* As with all callback methods, some intimate assumptions about the member     *
//* method's data are made here. If something changes in Test_Menu(), ALWAYS     *
//* check here to determine whether adjustments are needed.                      *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: value of 'wkey' parameter  *
//********************************************************************************
//* Notes:                                                                       *
//* -- Menubar _could_ stay open all the time, while sub-menus attached to the   *
//*    menubar are always invisible when they don't have focus.                  *
//* -- Context menu is visible _only_ when it has focus.                         *
//* -- The read-only textbox is used to report activity within the menus.        *
//*                                                                              *
//*                                                                              *
//********************************************************************************

static wchar_t tmw_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   enum fldIndex : short
   {
      fldPB_EXIT = ZERO,                  // ftPB (Exit)
      fldPB_CX,                           // ftPB (toggle ConteXt menu)
      fldMENU0,                           // ftMB (Menubar)
      fldMENUF,                           // ftMB (File menu)
      fldMENUE,                           // ftMB (Edit menu)
      fldMENUH,                           // ftMB (Help menu)
      fldMENUC,                           // ftMB (stand-alone, Context menu)
      fldS_BOX,                           // ftSB Scrollbox (view, but no edit)
      fldTB_RE,                           // ftTB Textbox (report menu selection)
      fldPB_MB,                           // ftPB (toggle MenuBar)
      fldCOUNT                            // number of fields
   } ;

   gString gsTxt ;                        // copy of field text
   skField skfld ;                        // copy of field data for analysis
//   int indx ;                             // character index
   wchar_t menu_char = L'?' ;             // menu item hotkey
   short   menu_item ;                    // menu item index

   //* First call *
   if ( firstTime )
   {

   }

   //* Note that the hotkey for a field has been automagically  *
   //* converted to wcsENTER.                                   *
   if ( (wkey == wcsENTER) || (wkey == wcsSPACE) )
   {
      switch ( ctrlIndex )
      {
         case fldPB_MB:                   // Enable menubar field
            cbPtr->SetFieldText ( fldTB_RE, " Menubar Group Activated..." ) ;
            //* Activate the menubar and set input focus. *
            cbPtr->SetFieldHidden ( fldMENU0, false, false, false ) ;
            cbPtr->SetInputFocus ( fldMENU0 ) ;
            // Programmer's Note: If the menubar is already active, then pushbutton 
            // cannot have been pressed; AND when a menu is open, field hotkeys are 
            // disabled.
            wkey = NULLCHAR ;             // operation handled
            break ;
         case fldPB_CX:                   // Enable context menu
            cbPtr->SetFieldText ( fldTB_RE, " Context Menu Activated..." ) ;
            //* Activate the context menu and set input focus. *
            //* This re-draws the window interior.             *
            cbPtr->SetFieldHidden ( fldMENUC, false, false, false ) ;
            cbPtr->SetInputFocus ( fldMENUC ) ;
            wkey = NULLCHAR ;             // operation handled
            break ;

         case fldPB_EXIT:                 // Exit pushbutton
            wkey = wcsCTL_4 ;
            break ;

         case fldMENU0:
            //* Sub-menu attached to Menubar is being activated.*
            //* Determine which sub-menu is being activated.    *
            //* Note that menubar controls have no hotkeys, so  *
            //* menu_char receives the first character of token.*
            if ( (cbPtr->GetFieldItem ( ctrlIndex, menu_item, menu_char )) )
            {
               //* Note that menubar controls have no hotkeys, so *
               //* the first character of the token is used here. *
               menu_char = *gsTxt.gstr() ;
               if ( (menu_item == 0) || (menu_char == L'F') )     // Reference the "File" menu
                  cbPtr->SetFieldText ( fldTB_RE, " File Menu Activated..." ) ;
               else if ( (menu_item == 1) || (menu_char == L'E') )// Reference the "Edit" menu
                  cbPtr->SetFieldText ( fldTB_RE, " Edit Menu Activated..." ) ;
               else if ( (menu_item == 2) || (menu_char == L'H') )// Reference the "Help" menu
                  cbPtr->SetFieldText ( fldTB_RE, " Help Menu Activated..." ) ;
               else
                  cbPtr->SetFieldText ( fldTB_RE, " Menubar IP Error..." ) ;
            }
            break ;

         //* If a menu item was selected from the menu with focus, *
         //* update the textbox text to report the selection.      *
         //* Note that the alphabetic item selections will probably*
         //* not be seen here, so The ACWin class will convert the *
         //* the alphabetic character to Enter/Space so we can     *
         //* request the item number of the selection.             *
         case fldMENUF:
         case fldMENUE:
         case fldMENUH:
         case fldMENUC:
            // Programmer's Note: The 'gsTxt' parameter is not referenced here.
            // It is included in the call only to verify the logic of the called method.
            if ( (cbPtr->GetFieldItem ( ctrlIndex, menu_item, menu_char, &gsTxt )) )
            {
               gsTxt.compose( "Select %s Item:%02hd '%C'",
                              (ctrlIndex == fldMENUF ? "MENU_F" :
                               ctrlIndex == fldMENUE ? "MENU_E" :
                               ctrlIndex == fldMENUH ? "MENU_H" : "CONTEXT"),
                              &menu_item, &menu_char ) ;
               cbPtr->SetFieldText ( fldTB_RE, gsTxt ) ;

               //* If File menu exit item, signal exit. *
               if ( (ctrlIndex == fldMENUF) && (menu_char == L'x') )
                  wkey = wcsCTL_4 ;
            }
            break ;

         //* While item "selection" is possible in Scrollbox controls,  *
         //* this particular Scrollbox is set up to display ordinary    *
         //* text data. However, if Enter/Space key is pressed within   *
         //* the Scrollbox, the "item" is reported as validation of the *
         //* skField-class getItem() method.                            *
         //* See the AcEdit application's Open-File sequence for a      *
         //* more realistic use of selection within a Scrollbox control.*
         case fldS_BOX:
            cbPtr->GetFieldData( ctrlIndex, skfld ) ;    // get skField for Menubar
            if ( (skfld.getItem ( gsTxt, menu_item )) )  // get token index
            {
               gString gs( gsTxt ) ;
               gs.limitChars( 5 ) ;
               gs.append( L"..." ) ;
               gsTxt.compose( "Select SBOX Item:%02hd '%S'", &menu_item, gs.gstr() ) ;
               cbPtr->SetFieldText ( fldTB_RE, gsTxt ) ;
            }
            break ;

         default:
            break ;
      }
   }     // (Space or Enter)

   //* Hotkeys for menu invocation *
   if ( (wkey == wcsF02) || (wkey == wcsALT_G) ||
        (wkey == wcsALT_F) || (wkey == wcsALT_E) || (wkey == wcsALT_H) )
   {
      switch ( wkey )
      {
         case wcsF02:               // Reference the Mewnubar
            cbPtr->SetFieldText ( fldTB_RE, " Menubar Activated..." ) ;
            break ;
         case wcsALT_F:             // Reference the "File" menu
            cbPtr->SetFieldText ( fldTB_RE, " File Menu Activated..." ) ;
            break ;
         case wcsALT_E:             // Reference the "Edit" menu
            cbPtr->SetFieldText ( fldTB_RE, " Edit Menu Activated..." ) ;
            break ;
         case wcsALT_H:             // Reference the "Help" menu
            cbPtr->SetFieldText ( fldTB_RE, " Help Menu Activated..." ) ;
            break ;
         case wcsALT_G:             // Reference the "Context" menu
            cbPtr->SetFieldText ( fldTB_RE, " Context Menu Activated..." ) ;
            break ;
         default:                   // (can't happen)
            break ;
      }
      // Programmer's Note: It would be dangerous to modify the execution flow 
      // in response to a hotkey which will be handled automatically by the 
      // ACWin class. Here, we simply report the operation.
   }

   return wkey ;

}  //* End tmw_Callback() *

//*************************
//*      Test_Hilite      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Open an ACWin window to test:                                                *
//*  a) Textbox text highlighting tests.                                         *
//*  b) Find Next, Find Previous                                                 *
//*  c) Spinner (ftSP) tests.                                                    *
//*     NOTE: Spinner controls are not yet implemented.                          *
//*           When implemented, the spinner will select the highlight modifier.  *
//*                                                                              *
//* Input  : tMod: (optional, undocumented) text modifier attribute              *
//*                Until the Spinner control is implemented tMod can be used to  *
//*                specify the text modifier attribute used for the highlight.   *
//*          fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Hilite ( wchar_t tMod, aeSeq fg )
{  // invoke: W h
   const short winROW    = 4,             // offset into terminal window
               winCOL    = 4,
               winHEIGHT = 25,            // height of window (rows)
               winWIDTH  = 86,            // width of window (columns)
               TB_WIDTH  = 57 ;           // width of Textbox controls

   acaExpand acaBld ;                     // construct color attributes
   const acAttr acaBorder =               // window border: bright yellow on blue
         acaBld.buildRgb( true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX),
                          true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MAX * 0.65) ) ;
   const acAttr acaInterior = acAttr(acaBld.build8Bit( false, 0, true, 230 ) | acaBOLD) ;
   const acAttr acaStatic = acAttr(acaBld.build8Bit( false, 0, true, 227 ) | acaBOLD) ;
   const acAttr pbAttr = acaBld.build8Bit( true, 160, true, 82 ) ;
   const acAttr tb1Attr = acAttr(acaFG_BLUE | acaBG_GREY) ;
   const acAttr tb2Attr = acaBld.build8Bit( true, 16, true, 123 ) ;
   const acAttr tb3Attr =
         acaBld.buildRgb( true, WebSafeRGB(wsrgbBLACK),
                          true, WebSafeRGB(wsrgbGREEN + wsrgbSHADE_MAX) ) ;
   const acAttr spinAttr = acaBld.build8Bit ( true, 75, true, 225 ) ;

   enum fld : short
   {
      fldTB_SEGMENT = ZERO,               // Gather text segment for which to search
      fldPB_FWRD,                         // Scan foreward for text to be hilighted
      fldPB_BKWD,                         // Scan backward for text tobe hilighted
      fldPB_EXIT,                         // ftPB (Exit)
      fldTB_SINGLE,                       // Textbox, single-row (always floSHIFT)
      fldTB_SHIFT,                        // Textbox configured for shifted text (floSHIFT)
      fldTB_FLOW,                         // Textbox configured for flowing text (floTOKEN)
//      fldSP_MODI,                         // Spinner control selects text-modifier type
      fldCOUNT                            // number of fields
   } ;

   const char* tbText =
   "The unanimous Declaration of the thirteen united States of America, \n"
   "When in the Course of human events, it becomes necessary for one \n"
   "people to dissolve the political bands which have connected them with another, \n"
   "and to assume among the powers of the earth, the separate and equal station \n"
   "to which the Laws of Nature and of Nature's God entitle them, a decent respect \n"
   "to the opinions of mankind requires that they should declare the causes which \n"
   "impel them to the separation. \n"
   "We hold these truths to be self-evident, that all men are created equal, \n"
   "that they are endowed by their Creator with certain unalienable Rights, \n"
   "that among these are Life, Liberty and the pursuit of Happiness.--That to \n"
   "secure these rights, Governments are instituted among Men, deriving their \n"
   "just powers from the consent of the governed, --That whenever any Form of \n"
   "Government becomes destructive of these ends, it is the Right of the People \n"
   "to alter or to abolish it, and to institute new Government, laying its \n"
   "foundation on such principles and organizing its powers in such form, as to \n"
   "them shall seem most likely to effect their Safety and Happiness." ;

   const char* pbFwrdTxt = " Scan ^Forward （向前扫描）" ;
   const char* pbBkwdTxt = " Scan ^Backward （向后扫描）" ;
   const char* pbExitTxt = "   E^xit （出口） " ;
   gString gs( pbFwrdTxt ) ;
   const short pbFwrdWid = gs.gscols() ;
   gs = pbBkwdTxt ;
   const short pbBkwdWid = gs.gscols() ;
   gs = pbExitTxt ;
   const short pbExitWid = gs.gscols() ;

   gString gsStatic( "Enter text for which to \n"
                     "search, then press one  \n"
                     "of the \"Scan\" buttons.  \n"
                     "Press again to find the \n"
                     "next/previous instance. \n"
                     "输入要搜索的文本，然后  \n"
                     "按下其中一个“扫描”      \n"
                     "按钮。再次按下可查找下  \n"
                     "一个或上一个实例。      \n"
                   ) ;
   gString gsSpinn ( 
                     "┌──────────────────────┐\n"
                     "│ When Spinner controls│\n"
                     "│ are implemented, the │\n"
                     "│ control will be here.│\n"
                     "│     (try CTRL+S)     │\n"
                     "└──────────────────────┘\n"
                   ) ;
   WinPos wpStatic( 2, 59 ) ;                // static text position
   WinPos wpul( short(winHEIGHT - 6), 1 ) ;  // Textbox field positioning
   WinPos wppb( short(winHEIGHT - 4), 5 ) ;  // Pushbutton field positioning
   short f = ZERO ;                 // control index on return from edit (ignored)

   #define NOSPIN (1) // Temporary replacement for Spinner control
   #if NOSPIN != 0 
   //* Initialize the text modifier attribute to be used for highlights. *
   //* This will be replaced by the Spinner value when implemented.      *
   acAttr hlMod = acaUSEDFLT ;
   switch ( tMod )
   {
      case L'b':  hlMod = acaBOLD ;      break ;         // Bold
      case L'i':  hlMod = acaITALIC ;    break ;         // Italic
      case L'u':  hlMod = acaUNDERLINE ; break ;         // Underline
      case L'o':  hlMod = acaOVERLINE ;  break ;         // Overline
      case L'x':  hlMod = acaXOUT ;      break ;         // X-out
      case L'k':  hlMod = acaBLINK ;     break ;         // blinK
      case L'c':  hlMod = acaCONCEAL ;   break ;         // Conceal
      case L'r':  default: hlMod = acaREVERSE ; break ;  // Reverse
   }
   #endif   // NOSPIN 

   //* Construct an skForm-class object and initialize it.     *
   //* The initialized object is passed as a parameter to the  *
   //* ACWin-class constructor.                                *
   skForm *skfPtr = new skForm
   (
      fldCOUNT,
      new skField[fldCOUNT]
      {
         //* Field index 0: ftTB (Textbox: fldTB_SEGMENT) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         short(winWIDTH - 4),             // field width (in columns)
         wpul.row, wpul.col,              // Y/X offset in window
         "Declaration",                   // initial field text
         tb1Attr,                         // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only field
         false,                           // field outline disabled
         false,                           // field is visible (not hidden)
         true,                            // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },
         //* Field index 1: ftPB (Pushbutton: fldPB_FWRD) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         pbFwrdWid,                       // field width (in columns)
         wppb.row, wppb.col,              // Y/X offset in window
         pbFwrdTxt,                       // field text with Hotkey '^F'
         pbAttr,                          // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },
         //* Field index 2: ftPB (Pushbutton: fldPB_BKWD) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         pbBkwdWid,                       // field width (in columns)
         wppb.row,                        // Y/X offset in window
         short(wppb.col += pbFwrdWid + 1),
         pbBkwdTxt,                       // field text with Hotkey '^F'
         pbAttr,                          // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },
         //* Field index 2: ftPB (Pushbutton: fldPB_EXIT) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         pbExitWid,                       // field width (in columns)
         wppb.row,                        // Y/X offset in window
         short(wppb.col += pbBkwdWid + 1),
         pbExitTxt,                       // field text with Hotkey '^x'
         pbAttr,                          // color attributes
         // Remaining parameters receive default values.
         //floChar,                         // text formatting option (n/a)
         //ZERO,                            // insertion point (cursor offset)
         //false,                           // user accessible (not read-only)
         //false,                           // field outline enabled
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },
         //* Field index 3: ftTB (Textbox: fldTB_SINGLE) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         TB_WIDTH,                        // field width (in columns)
         short(wpul.row = 2), wpul.col,   // Y/X offset in window
         tbText,                          // initial field text
         tb2Attr,                         // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only field
         false,                           // field outline disabled
         false,                           // field is visible (not hidden)
         true,                            // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },
         //* Field index 4: ftTB (Textbox: fldTB_SHIFT) *
         {
         ftTB,                            // field type
         5,                               // field height (in rows)
         TB_WIDTH,                        // field width (in columns)
         short(wpul.row += 3), wpul.col,  // Y/X offset in window
         tbText,                          // initial field text
         tb2Attr,                         // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only field
         false,                           // field outline disabled
         false,                           // field is visible (not hidden)
         true,                            // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },
         //* Field index 5: ftTB (Textbox: fldTB_FLOW) *
         {
         ftTB,                            // field type
         5,                               // field height (in rows)
         TB_WIDTH,                        // field width (in columns)
         short(wpul.row += 7), wpul.col,  // Y/X offset in window
         tbText,                          // initial field text
         tb3Attr,                         // color attributes
         floToken,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only field
         false,                           // field outline disabled
         false,                           // field is visible (not hidden)
         true,                            // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },
#if 0    // SPINNER
         //* Field index 6: ftSP (Spinner: fldSP_MODI) *
         {
         ftSP,                            // field type
         2,                               // field height (in rows, 1 or 2)
         SP_WIDTH,                        // field width (in columns)
         ZERO,ZERO,                       // Y/X offset in window
         "Bold\n"
         "Italic\n"
         "Underline\n"
         "Overline\n"
         "X-Out\n"
         "Blinking\n"
         "Invisible\n"
         "Reversed (default)\n",
         acaUSEDFLT,                      // color attributes
         floChar,                         // text formatting option (n/a)
         ZERO,                            // insertion point (n/a)
         false,                           // read-only field
         false,                           // field outline disabled
         false,                           // field is visible (not hidden)
         true,                            // access lock reset
         false,                           // initial binary state: 'reset' (n/a)
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member (n/a)
         },
#endif   // SPINNER
      },
      //* Optional arguments are set to default values. *
      fldTB_SEGMENT     // index of field with initial focus
      // Remaining parameters receive default values.
      //acaUSEDFLT,     // default color attributes for control with focus
      //true,           // beep on unhandled keycode received
      //true,           // initial state of Insert/Overstrike flag
      //true,           // user access to Insert/Overstrike mode
      //false,          // enable cursor shape change for Insert/Overstrike
      //csBblock,       // cursor shape when Insert mode
      //csBuline        // cursor shape when Overstrike mode
   ) ;

   #if NOSPIN != 0
   //* Initialize the text modifier attribute to be used for highlights. *
   //* This will be replaced by the Spinner value when implemented.      *
   skfPtr->fld[fldTB_SINGLE].setHiliteMod ( hlMod ) ;
   skfPtr->fld[fldTB_SHIFT].setHiliteMod ( hlMod ) ;
   skfPtr->fld[fldTB_FLOW].setHiliteMod ( hlMod ) ;
   #endif   // NOSPIN 

   //**************************
   //* Define an ACWin object *
   //**************************
   ACWin *win = new ACWin
         ( winROW, winCOL,                      // position (upper-left corner) 
           winHEIGHT, winWIDTH,                 // dimensions (height, width)
           ltDUAL,                              // border style
           acaBorder,                           // border attributes
           acaInterior,                         // interior attributes
           " Highlight Field Text ",            // window title
           skfPtr
         ) ;

   //* Set field labels *
   win->SetFieldLabel( fldTB_SEGMENT, 
                       "^Enter Text Segment To Be Highlighted (请输入要高亮显示的文本段)" ) ;
   win->SetFieldLabel( fldTB_SINGLE, 
                       "Textbox #1 ^Single Row (单行文本框)" ) ;
   win->SetFieldLabel( fldTB_SHIFT, 
                       "Textbox #2 ^Text Editor Mode (文本编辑器模式)" ) ;
   win->SetFieldLabel( fldTB_FLOW, 
                       "Textbox #3 ^Word Processor Mode (文本自动格式化模式)" ) ;
   //* Enable Footnotes for error messages.*
   win->EnableFootnotes ( true ) ;

   win->OpenWindow () ;             // open the window
   delete skfPtr ;                  // delete the local skForm object
   cbPtr = win ;                    // give callback method access to ACWin object

   //* Write the static text (instructions) to window *
   // Programmer's Note: If this test is invoked immediately after system startup,
   // it often/always causes the application to go off into the weeds.
   //  1) There is an error which trashes the display,
   //  2) The key input is not recognized.
   // It seems that the window setup executes correctly, but the following write 
   // causes some memory violation and/or setting the cursor stops working correctly.
   // This could be related to the hardware not yet being at operating temperature 
   // or the system's response to terminal config is not yet fully awake..
   // It _APPEARS_ that a small delay here solves the crash issue.
   //win->nsleep ( 0, 50 ) ;
   win->nsleep ( 5 ) ; // Experimental

   wpul = win->Write ( wpStatic, gsStatic, acaStatic ) ;
   win->Write ( wpul, gsSpinn, spinAttr ) ;
   wpul = { short(winHEIGHT - 5), ZERO } ;
   gs.clear() ;
   short msg_wid = winWIDTH - 2 ;
   gs.padCols( msg_wid ) ;
   gs.append( NEWLINE ) ;
   gs.padCols( msg_wid * 2 ) ;
   gs.append( "        (case-insensitive search -- 不区分大小写的搜索)   " ) ;
   gs.padCols( msg_wid * 3 ) ;
   win->Write ( wpul, gs, acaStatic ) ;
   win->RefreshWin () ;

   //* Establish a callback method to monitor user input.  *
   win->EstablishCallback ( &thtCallback ) ;

   //***************************
   //* Interact with the user. *
   //***************************
   win->EditForm ( f ) ;

   //* Note: Closing the window re-enables input buffering *
   //*       AND resets the echo option to 'termEcho'.     *
   win->CloseWindow () ;
   cbPtr = NULL ;                      // reset the external pointer

   delete ( win ) ;                    // release the allocated resourses

}  //* End Test_Hilite() *

//*************************
//*      thtCallback      *
//*************************
//********************************************************************************
//* Non-member method:                                                           *
//* ------------------                                                           *
//* Callback method for Test_Hilite() above. Handle the callbacks from EditForm()*
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: value of 'wkey' parameter  *
//********************************************************************************

static wchar_t thtCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   enum fld : short                       // keep synchronized with parent method
   {
      fldTB_SEGMENT = ZERO,               // Gather text segment for which to search
      fldPB_FWRD,                         // Scan for text to be hilighted
      fldPB_BKWD,                         //Scan backward for text tobe hilighted
      fldPB_EXIT,                         // ftPB (Exit)
      fldTB_SINGLE,                       // Textbox, single-row (always floSHIFT)
      fldTB_SHIFT,                        // Textbox configured for shifted text (floSHIFT)
      fldTB_FLOW,                         // Textbox configured for flowing text (floTOKEN)
//      fldSP_MODI,                         // Spinner control selects text-modifier type
      fldCOUNT                            // number of fields
   } ;
   gString gsSrc, gsTrg ;                 // Source and target text data
   skField skfld ;                        // temp copy of field data
   int hl_beg, hl_len ;                   // highlight IP and run length
   int get_beg, get_len ;                 // existing highlight parameters
   short failCnt ;                        // limit error messages for loop

   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   {
      /* nothing to do at this time */
   }

   if ( (wkey == wcsENTER) || (wkey == SPACE) )
   {
      if ( (ctrlIndex == fldPB_FWRD) || (ctrlIndex == fldPB_BKWD) )
      {
         cbPtr->GetFieldText ( fldTB_SEGMENT, gsSrc ) ;  // retrieve the search segment
         if ( gsSrc.gschars() > 1 )
         {
            failCnt = ZERO ;
            for ( short f = fldTB_SINGLE ; f <= fldTB_FLOW ; ++f )
            {
               cbPtr->GetFieldData ( f, skfld ) ;

               //* Scan forward:                                 *
               //* Search forward from IP. If token found at IP, *
               //* then step forward by one char and scan again. *
               if ( ctrlIndex == fldPB_FWRD )
               {
                  hl_beg = skfld.txt.find( gsSrc, skfld.ip, false ) ;
                  if ( hl_beg == skfld.ip )
                  {
                     //* Compare against existing highlighted data (if any) *
                     if ( (skfld.getHilite ( get_beg, get_len, gsTrg )) )
                     {
                        hl_len = gsSrc.gschars() - 1 ;
                        if ( (get_beg == hl_beg) && (get_len == hl_len) )
                        {
                           if ( skfld.ip < skfld.mrptr->ni )
                              hl_beg = skfld.txt.find( gsSrc, (skfld.ip + 1), false ) ;
                           else           // no joy
                              hl_beg = -1 ;
                        }
                     }
                  }
               }

               //* Scan backward:                                *
               //* Search backward from IP. If token found at IP,*
               //* then step back by one char and scan again.    *
               else  // (ctrlIndex == fldPB_BKWD)
               {
                  hl_beg = skfld.txt.findr( gsSrc, skfld.ip, false ) ;
                  if ( hl_beg == skfld.ip )
                  {
                     //* Compare against existing highlighted data (if any) *
                     if ( (skfld.getHilite ( get_beg, get_len, gsTrg )) )
                     {
                        hl_len = gsSrc.gschars() - 1 ;
                        if ( (get_beg == hl_beg) && (get_len == hl_len) )
                        {
                           if ( skfld.ip > ZERO )
                              hl_beg = skfld.txt.findr( gsSrc, (skfld.ip - 1), false ) ;
                           else           // no joy
                              hl_beg = -1 ;
                        }
                     }
                  }
               }

               if ( hl_beg >= ZERO )
               {
                  hl_len = gsSrc.gschars() - 1 ;
                  cbPtr->HilightFieldText ( f, true, hl_beg, hl_len, acaUSEDFLT, true ) ;
               }
               else if ( ++failCnt >= 3 )
                  cbPtr->Footnote ( L" Search text not found. （未找到搜索文本。） ", 20 ) ;
            }
         }
         else
         {
            cbPtr->Footnote ( L" No search text provided. （未提供搜索文本。） ", 25 ) ;
            cbPtr->SetInputFocus ( fldTB_SEGMENT ) ;
         }
         wkey = NULLCHAR ;       // signal that keycode has been handled
      }

      //* The 'Exit' Pushbutton has been pressed. Prepare to exit. *
      else if ( ctrlIndex == fldPB_EXIT ) { wkey = wcsCTL_4 ; }

   }

   //* Exercise the skField getHilite() method:    *
   //* Get a copy of the hilighted/selected text   *
   //* segment and report the results.             *
   else if ( (wkey == wcsCTL_G) )
   {
      if ( (ctrlIndex >= fldTB_SINGLE) && (ctrlIndex <= fldTB_FLOW) )
      {
         cbPtr->GetFieldData ( ctrlIndex, skfld ) ;
         skfld.getHilite ( get_beg, get_len, gsTrg ) ;
         gsSrc.compose( " Capture Highlight (捕获高亮文本) '%S' beg:%d len:%d ",
                        gsTrg.gstr(), &get_beg, &get_len ) ;
         cbPtr->Footnote ( gsSrc.gstr(), 35 ) ;
         wkey = NULLCHAR ;       // signal that keycode has been handled
      }
      else
         cbPtr->acBeep () ;
   }

   #if NOSPIN != 0
   else if ( wkey == wcsCTL_S )
   {
      acAttr oldMod, newMod ;
      cbPtr->GetFieldData ( fldTB_SINGLE, skfld ) ;
      skfld.getHilite ( get_beg, get_len, gsTrg, &oldMod ) ;
      switch ( oldMod )
      {
         case acaREVERSE:     newMod = acaBOLD ;      break ;
         case acaBOLD:        newMod = acaITALIC ;    break ;
         case acaITALIC:      newMod = acaUNDERLINE ; break ;
         case acaUNDERLINE:   newMod = acaOVERLINE ;  break ;
         case acaOVERLINE:    newMod = acaXOUT ;      break ;
         case acaXOUT:        newMod = acaBLINK ;     break ;
         case acaBLINK:       newMod = acaCONCEAL ;   break ;
         case acaCONCEAL:     newMod = acaREVERSE ;   break ;
         default:             newMod = oldMod ;       break ;
      } ;
      cbPtr->SetHilightMod ( fldTB_SINGLE, newMod ) ;
      cbPtr->RefreshField ( fldTB_SINGLE ) ;
      cbPtr->SetHilightMod ( fldTB_SHIFT, newMod ) ;
      cbPtr->RefreshField ( fldTB_SHIFT ) ;
      cbPtr->SetHilightMod ( fldTB_FLOW, newMod ) ;
      cbPtr->RefreshField ( fldTB_FLOW ) ;
      gsSrc.compose( " Next Highlight '%S' beg:%d len:%d mod:%08X -> %08X",
                     gsTrg.gstr(), &get_beg, &get_len, &oldMod, &newMod ) ;
      cbPtr->Footnote ( gsSrc.gstr(), 20 ) ;
      wkey = NULLCHAR ;
   }
   #endif   // NOSPIN
   return wkey ;

}  //* End thtCallback() *

//*************************
//*      Test_Cookie      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* This method creates a simple window which is used as a documentation model   *
//* for initialization of the skForm and skField objects.                        *
//* See the chapter "skForm Class".                                              *
//*                                                                              *
//* It is also used to test the experimental code which "captures" a window's    *
//* display data for use in the Texinfo documentation.                           *
//*                                                                              *
//* Input  : eOpt: sub-option for testing the documentation options              *
//*                2 == two-row Textbox                                          *
//*                3 == draw border around Radiobutton                           *
//*                4 == draw border around Pushbutton                            *
//*        : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Cookie ( wchar_t eOpt, aeSeq fg )
{  // invoke: W e
   const short winROW    = 4,             // offset into terminal window
               winCOL    = 12,
               winHEIGHT = 10,            // height of window (rows)
               winWIDTH  = 42,            // width of window (columns)
               pbFIELD   = 2 ;            // index of pushbutton field

    //* Set the foreground color *
   this->acSetFg ( fg ) ;

   // Focus Color: for controls which can change color when 
   // they have input focus. (Pushbuttons, Radiobuttons)
   acAttr focusAttr = acAttr(acaFGb_GREY | acaBG_RED) ;
   // Non-Focus Color for Pushbuttons and Radiobuttons.
   acAttr nonFocus  = acAttr(acaFG_BLACK | acaBG_RED) ;
   //* Positioning: vertical and horizontal offsets
   WinPos wpul( 1, 2 ) ;


   //* Create an skForm object with three(3) fields:     *
   //* 1) a Textbox (ftTB)                               *
   //* 2) a Radiobutton (ftRB)                           *
   //* 3) a Pushbutton (ftPB)                            *
   //* - - - - - - - - - - - - - - - - - - - - - - - - - *
   //* Note that many members have default values, but   *
   //* for clarity, they are explicity initialized here. *
   //* See skForm and skField initialization constructors*
   //* for details.                                      *
   const short FLD_COUNT = 3 ;            // number of fields
   skForm *skfPtr = new skForm
   (
      FLD_COUNT,                          // number of skField objects in 'fldArray'
      new skField[FLD_COUNT]              // array of field definitions
      {
         //* Field index 0: ftTB (Textbox) *
         {
         ftTB,                            // field type
         short(eOpt == L'2' ? 2 : 1),     // field height (in rows)
         winWIDTH - 6,                    // field width (in columns)
         wpul.row, wpul.col,              // Y/X offset in window
         (eOpt == L'2' ?                  // initial field text
         "Me love cookies! Cookie!\n"
         "Fuzzy and Blue, I'm fuzzy and blue.\n"
         "No other color will do.\n"
         "I'm fuzzy and blue." :          // (copyright PBS)
         "      Me love cookies! Cookie!"),
         acAttr(acaFG_DFLT | acaBG_CYAN), // color attributes for text
         floShift,                        // text formatting option (text-editor mode)
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         true,                            // field outline enabled
         false,                           // field is visible (not hidden)
         true,                            // access lock enabled
         false,                           // binary state not applicable to ftTB
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },
         //* Field index 1: ftRB (Radiobutton) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         winWIDTH - 6,                    // field width (in columns)
         short(wpul.row += 3), wpul.col,  // Y/X offset in window
         "[♦] ^Authorize daily cookie delivery", // field text with Hotkey '^A'
         acAttr(acaFG_BLUE | acaBG_GREY), // color attributes (non-focus)
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         false,                           // field outline enabled
         false,                           // field is visible (not hidden)
         true,                            // access lock enabled
         true,                            // initial binary state: 'set'
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         },
         //* Field index 2: ftPB (Pushbutton) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         16,                              // field width (in columns)
         short(wpul.row += 2),            // Y/X offset in window
         short(winWIDTH / 2 - 9),
         "  ^Close Window  ",             // field text with Hotkey '^C'
         nonFocus,                        // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // user accessible (not read-only)
         false,                           // field outline enabled
         false,                           // field is visible (not hidden)
         true,                            // access lock enabled
         false,                           // initial binary state: 'reset'
         NULLCHAR,                        // no hotkey specified
         noGROUP                          // not a group member
         }
      },
      //* Optional skFormarguments *
      2,          // 'fldIndex'  index of field with initial focus (the Pushbutton)
      focusAttr,  // 'fldAttr'   color attributes for control with focus
      true,       // 'noisy'     beep on unhandled keycode received
      true,       // 'ioFlag'    initial state of Insert/Overstrike flag
      true,       // 'ioToggle'  user access to Insert/Overstrike mode
      true,       // 'ioCursor'  enable cursor shape change for Insert/Overstrike
      csBblock,   // 'insCursor' cursor shape when Insert mode
      csBuline    // 'ovrCursor' cursor shape when Overstrike mode
   ) ;

   //* The initialized skForm template is then passed as *
   //* a parameter to the ACWin constructor.             *
   ACWin *winPtr = new ACWin
         ( winROW, winCOL, 
           winHEIGHT, winWIDTH,
           ltSINGLE,
           acAttr(acaFGb_BROWN | acaBG_BLUE),
           acAttr(acaFG_RED | acaBG_GREY),
           " ACWin-class Window ",
           skfPtr  // partially-initialized skForm object
         ) ;

   // The setup data may now be safely deleted (prevent memory leaks):
   delete skfPtr ; skfPtr = NULL ;
                                   
   //* Open the window (make it visible) *
   winPtr->OpenWindow () ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_PHOTO != 0
   //* For testing only, add some static text to the window.  *
   #if 0    // Not part of the primary test
   WinPos wpStatic( 5, 0 ) ;
   wpStatic = winPtr->Write ( wpStatic, 
                              "This is a sample of \"static\" text which\n" ) ;
   wpStatic = winPtr->Write ( wpStatic, 
                             "is written...................directly to\n" ) ;
   wpStatic = winPtr->Write ( wpStatic, 
                             "the interior of the window for testing." ) ;
   #endif   // Not part of the primary test

   //* Take a screenshot of the window and save to debug file.*
   winPtr->Screenshot ( false, "Called by Test_Cookie" ) ;
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_PHOTO

   //* Get User Input : Returns when Pushbutton control is selected,*
   //* either by "pressing it" (Enter/Space) or via hotkey (ALT+C). *
   short fieldIndex ;   // on return contains index of exit field
   winPtr->EditForm ( fieldIndex, pbFIELD ) ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_PHOTO != 0
   //* Take a screenshot of the window and save to debug file.*
   winPtr->Screenshot ( true, "Called by Test_Cookie" ) ;
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_PHOTO

   //* Get a pointer to final field configuration, *
   //* and retrive final text and button state.    *
   const skForm* skfp = winPtr->GetForm () ;
   gString finalText = skfp->fld[0].txt ; // contexts of textbox
   bool finalRadio = skfp->fld[1].state ; // state of radiobutton flag
   finalText.strip() ;                    // strip leading/trailing whitespace
   gString gsOut( "Textbox    : '%S'\nRadiobutton: %s\n", 
                  finalText.gstr(), (finalRadio ? "set" : "reset") ) ;
   WinPos wpOut( short(winROW + winHEIGHT), winCOL ) ;
   wpOut = this->acWrite ( wpOut, gsOut ) ;

   //* Close the window and delete the object.*
   winPtr->CloseWindow ( false ) ;
   delete winPtr ;

   this->acSetFg ( aesFG_DFLT ) ;      // Reset to terminal default foreground color
   this->acSetCursor ( wpOut ) ;       // prepare to exit

}  //* End Test_Cookie() *

//*************************
//*  Test_Clipboard_API   *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Exercise the API local and system clipboards. All AnsiCmd-class clipboard    *
//* methods are invoked, including the special case of setting the clipboard     *
//* data to an empty string.                                                     *
//*                                                                              *
//* Note that the skForm/skField-specific clipboard methods are not invoked      *
//* here. See the next test,Test_Clipboard_Win() for those tests.                *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Clipboard_API ( aeSeq fg )
{  // invoke: W x a
   const char* testData = "This is a UTF-8 text string. (这是一个 UTF-8 文本字符串。)" ;
   gString gsOut ;                  // text formatting
   gString gs ;                     // send/receive text data
   int  cbBytes,                    // number of UTF-8 bytes
        cbChars,                    // number of UTF-32 characters
        mcbBytes,                   // directly measured bytes
        mcbChars,                   // directly measured chars
        indx ;                      // text index
   bool en = false,                 // set if communication with system CB enabled
        pass = false ;              // status of connection test

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   this->acBufferedInput ( false, noEcho ) ;    // set unbuffered input

   this->ttyWrite ( "This is a test of the API-level clipboard interface.\n" ) ;

   //* Initial test of clipboard access. *
   en = this->acClipboardTest () ;
   gsOut.compose( "-- Initial test: clipboard enabled during startup?: %s\n",
                  (en ? "yes" : "no") ) ;
   this->ttyWrite ( gsOut ) ;

   //* If system clipboard interface is not already enabled, *
   //* perform local clipboard tests.                        *
   if ( ! en )
   {
      gs = testData ;
      mcbBytes = gs.utfbytes() ;
      this->ttyWrite ( "-- Test communications with _LOCAL_ clipboard\n" ) ;

      cbBytes = this->acClipboardSetText ( gs ) ;
      gsOut.compose( "   Sent    : %d bytes: '%S'\n", &cbBytes, gs.gstr() ) ;
      if ( cbBytes != mcbBytes )
      {
         gs.compose( "vs%02d", &mcbBytes ) ;
         indx = gsOut.find( L" bytes:" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
      }
      this->ttyWrite ( gsOut ) ;

      gs.clear() ;
      cbBytes = this->acClipboardGetText ( gs ) ;
      mcbBytes = gs.utfbytes() ;
      gsOut.compose( "   Received: %d bytes: '%S'\n", &cbBytes,gs.gstr() ) ;
      if ( cbBytes != mcbBytes )
      {
         gs.compose( "vs%02d", &mcbBytes ) ;
         indx = gsOut.find( L" bytes:" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
      }
      this->ttyWrite ( gsOut ) ;
   }

   //* Enable system clipboard communication through AnsiCmd interface.*
   en = this->acClipboardEnable ( &gs ) ;
   gsOut.compose( "-- Enable system clipboard communications: %s\n", 
                  (en ? "enabled" : "no connection...") ) ;
   this->ttyWrite ( gsOut ) ;
   if ( en )
   {
      if ( (indx = gs.after( L'\n' )) > ZERO )
         gs.insert( L"                               wl-clipboard v:", indx ) ;
      gs.insert( "WaylandCB    v:" ) ;
      gsOut.compose( "   Communications Established: %S\n", gs.gstr() ) ;
      this->ttyWrite ( gsOut ) ;

      //* Perform the communications test. *
      pass = this->acClipboardTest () ;
      gsOut.compose( "-- Communications Test: %s\n", (pass ? "pass" : "fail") ) ;
      this->ttyWrite ( gsOut ) ;

      //* Write to system clipboard *
      gs = testData ;
      mcbChars = gs.gschars() ;
      mcbBytes = gs.utfbytes() ;
      cbBytes = this->acClipboardSetText ( gs ) ;
      cbChars = this->acClipboardCharCnt () ;
      gsOut.compose( "-- Send    : %02d bytes (%02d chars):\n"
                     "            '%S'\n", 
                     &cbBytes, &cbChars, gs.gstr() ) ;
      cbChars = this->acClipboardCharCnt () ;
      if ( cbBytes != mcbBytes)
      {
         gs.compose( "vs%02d", &mcbBytes ) ;
         indx = gsOut.find( L" bytes" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
         
      }
      if ( cbChars != mcbChars )
      {
         gs.compose( "vs%02d", &mcbChars ) ;
         indx = gsOut.find( L" chars" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
      }
      this->ttyWrite ( gsOut ) ;

      //* Retrieve data from system clipboard *
      gs.clear() ;
      cbBytes = ZERO ;
      cbBytes = this->acClipboardGetText ( gs ) ;
      cbChars = this->acClipboardCharCnt () ;
      mcbChars = gs.gschars() ;
      mcbBytes = gs.utfbytes() ;
      gsOut.compose( "-- Received: %02d bytes (%02d chars):\n"
                     "            '%S'\n", 
                     &cbBytes, &cbChars, gs.gstr() ) ;
      if ( cbBytes != mcbBytes)
      {
         gs.compose( "vs%02d", &mcbBytes ) ;
         indx = gsOut.find( L" bytes" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
         
      }
      if ( cbChars != mcbChars )
      {
         gs.compose( "vs%02d", &mcbChars ) ;
         indx = gsOut.find( L" chars" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
      }
      this->ttyWrite ( gsOut ) ;

      //* User-provided data *
      gs.clear() ;
      this->acClipboardSetText ( gs ) ;   // clear the clipboard
      this->ttyWrite ( "-- Put some data on the system clipboard, then press a key..." ) ;
      this->acPause () ;

      //* Retrieve user's data *
      cbBytes = ZERO ; gs.clear() ;
      this->acClipboardGetText ( gs ) ;
      cbBytes  = this->acClipboardByteCnt () ;
      mcbBytes = gs.utfbytes() ;
      cbChars  = this->acClipboardCharCnt () ;
      mcbChars = gs.gschars() ;
      if ( cbBytes > ZERO )
         this->ttyWrite ( " thank you." ) ;
      gsOut.compose( "\n-- Retrieved: %02d bytes (%02d chars): '%S'\n", 
                     &cbBytes, &cbChars, gs.gstr() ) ;
      if ( cbBytes != mcbBytes)
      {
         gs.compose( "vs%02d", &mcbBytes ) ;
         indx = gsOut.find( L" bytes" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
         
      }
      if ( cbChars != mcbChars )
      {
         gs.compose( "vs%02d", &mcbChars ) ;
         indx = gsOut.find( L" chars" ) ;
         gsOut.insert( gs.gstr(), indx ) ;
      }
      this->ttyWrite ( gsOut ) ;

      //* Terminate connection with system clipboard.*
      this->acClipboardDisable () ;
      this->ttyWrite ( "\n-- Clipboard connection terminated\n" ) ;
   }
   else
   {
      this->ttyWrite ( "   Please Verify:\n"
                       "    a) Build options in AnsiCmd.hpp enabled:\n"
                       "       #define INCLUDE_API (1)\n"
                       "       #define INCLUDE_WAYLANDCB (1)\n"
                       "    b) Linux utility wl-clipboard is installed and that both\n"
                       "       'wl-copy' and 'wl-paste' are visible on the path.\n" ) ;
   }

   //* Restore buffered input and prepare to exit.*
   this->acBufferedInput ( true, termEcho ) ;
   this->acSetFg ( aesFG_DFLT ) ;
   this->ttyWrite ( "   Bye Bye!\n" ) ;

}  //* End Test_Clipboard_API() *

//*************************
//*  Test_Clipboard_Win   *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Exercise the local and system clipboard interface with ACWin/skForm/skField  *
//* objects. Clipboard access is invoked via the clipboard "special keys".       *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Clipboard_Win ( aeSeq fg )
{  // invoke: W x w
   const acAttr bgAttr = acaBGb_CYAN ;    // window interior background attribute
   const short winROW     = 3 ;           // window position
   const short winCOL     = 2 ;
   const short winHEIGHT  = 27 ;          // window dimensions
   const short winWIDTH   = 78 ;
   const char* winTitle = " Exercise Clipboard Access " ;
   const char* staticText = 
      " Position the cursor in either Textbox control.                \n"
      " Select text using the SHIFT key plus one of the arrow keys.   \n"
      " or use ALT+A to select/deselect all text in the control.\n"
      " Copy text to the clipboard and paste text from the clipboard: \n"
      " CTRL+C==Copy, CTRL+X==Cut, CTRL+V==Paste\n"
      " \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
      "      This control displays a copy of the current clipboard contents." ;

   enum fld : short                       // field indices
   {
      fldMULTI = ZERO,                    // multi-row text field
      fldSING,                            // single-row text field
      fldRB_C,                            // Radiobutton for system clipboard enable
      fldSTAT,                            // 'clipboard-stats' text field
      fldCLIP,                            // 'view-clipboard-data' text field
      fldPB_EXIT,                         // Exit Pushbutton
      fldCNT,                             // number of skField objects in window
   } ;

   acaExpand acaBld ;
   const acAttr pbAttr = acaBld.build8Bit( true, 160, true, 82 ) ;
   const acAttr rgbBorder =               // window border: bright yellow on blue
         acaBld.buildRgb( true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX),
                          true, WebSafeRGB(wsrgbBLUE + wsrgbSHADE_MAX * 0.65) ) ;
   const acAttr rgbInterior =             // window interior: red on bright cyan
         acaBld.buildRgb( true, WebSafeRGB(wsrgbRED),
                          true, WebSafeRGB(wsrgbCYAN + wsrgbSHADE_MAX) ) ;

   gString gsOut ;                  // text formatting
   gString gsClip ;                 // send/receive text data
   WinPos wpos ;                    // cursor offset into window object
   WinPos wpul( 6, 1 ) ;            // field origin
   short termRows, termCols ;       // terminal dimensions

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 32, "  (rows/cols of 32/80)\n"
                          "-----------------------" ) ;
 
   //* Set attributes to specified terminal foreground value *
   this->acSetFgBg ( fg, aesBG_DFLT ) ;
   this->acGetTermDimensions ( termRows, termCols ) ; // get terminal window dimensions

   //* -------------------------------------------------------- *
   //* Define the input fields (skForm object). Create an       *
   //* object with fldCNT fields and initialize each field.     *
   //* -------------------------------------------------------- *
   skForm *skfPtr = new skForm
   (
      fldCNT,
      new skField[fldCNT]
      {
         //* Field index 0: ftTB (Textbox: fldMULTI) *
         {
         ftTB,                            // field type
         3,                               // field height (in rows)
         short(winWIDTH - 4),             // field width (in columns)
         wpul.row, wpul.col,              // Y/X offset in window
         "If we were to approach our work, teaching those \n"
         "who come to us to learn as if it were a privilege, \n"
         "as if we were lucky to be in this place at this time \n"
         "to do our work, our whole approach would change.\n"
         "-- Terry Jones – California State University. East Bay",
//         "人生不能像做菜，把所有的料都准备好了才下 锅。 "
//         "Rénshēng búnéng xiàng \n"
//         "zuòcài, bǎ suǒyǒu de liào dōu zhǔnbèi hǎo le cái xiàguō. \n"
//         "I cannot live my life like how I cook. I cannot wait until all the \n"
//         "ingredients are prepared and then cook.\n"
//         "	- 《饮食男女》 (Eat Drink Man Woman)",
         acAttr(acaFG_BLUE | acaBG_GREY), // color attributes
         floChar,                         // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only flag is 'reset'
         true,                            // field outline enabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 1: ftTB (Textbox: fldSINGLE) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         short(winWIDTH - 4),             // field width (in columns)
         short(wpul.row += 5), wpul.col,  // Y/X offset in window
         "\"It’s safe to talk openly and honestly with people "
         "because they’re not really listening.\""
         " -- Chuck Lorre",
//         "If we were to approach our work, teaching those \n"
//         "who come to us to learn as if it were a privilege, \n"
//         "as if we were lucky to be in this place at this time \n"
//         "to do our work, our whole approach would change.\n"
//         "-- Terry Jones – California State University. East Bay",
         acAttr(acaFG_BLUE | acaBG_GREY), // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         false,                           // read-only flag is 'reset'
         true,                            // field outline enabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 2: ftRB (Radiobutton: fldRB_C) *
         {
         ftRB,                            // field type
         1,                               // field height (in rows)
         short(winWIDTH - 12),            // field width (in columns)
         short(wpul.row += 2), short(wpul.col += 4), // Y/X offset in window
         "[ ] ^Enable System Clipboard",  // field text with Hotkey'^E'
         acAttr(acaFG_BLUE | bgAttr),     // color attributes
         floChar,                         // text formatting option (n/a)
         ZERO,                            // IP is dynamically set
         false,                           // read-only flag is 'reset'
         false,                           // field outline disabled
         false,                           // hidden flag is 'reset'
         true,                            // access lock enabled
         false,                           // binary state: 'reset'
         //NULLCHAR,                        // hotkey is embedded in text data
         //noGROUP                          // radiobutton group
         },

         //* Field index 3: ftTB (Textbox: fldSTAT) *
         {
         ftTB,                            // field type
         1,                               // field height (in rows)
         short(winWIDTH - 30),            // field width (in columns)
         short(wpul.row += 1), wpul.col,  // Y/X offset in window
         "Clipboard UTF-8 bytes: xxx  UTF-32 chars: xxx",
         acAttr(acaFG_BLUE | acaBG_GREY), // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         true,                            // read-only flag is 'set'
         false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 3: ftTB (Textbox: fldSTAT) *
         {
         ftTB,                            // field type
         3,                               // field height (in rows)
         short(winWIDTH - 4),             // field width (in columns)
         short(wpul.row += 3), (wpul.col = 1),  // Y/X offset in window
         "*Clipboard Contents*",
         acAttr(acaFGb_GREEN | acaBG_BROWN), // color attributes
         floShift,                        // text formatting option
         ZERO,                            // insertion point (cursor offset)
         true,                            // read-only flag is 'set'
         //false,                           // field outline disabled
         //false,                           // hidden flag is 'reset'
         //true,                            // access lock enabled
         //false,                           // binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },

         //* Field index 4: ftPB (Pushbutton: fldPB_EXIT) *
         {
         ftPB,                            // field type
         1,                               // field height (in rows)
         22,                              // field width (in columns)
         short(wpul.row += 6),            // Y/X offset in window
         short((winWIDTH / 2) - 11),
         " E^XIT - 出口 - Salida ",         // field text with Hotkey '^X'
         pbAttr,                          // color attributes
         floChar,                         // text formatting option (n/a)
         ZERO,                            // insertion point (cursor offset) (n/a)
         false,                           // user accessible (not read-only)
         true,                            // field outline enabled
         // Remaining parameters receive default values.
         //false,                           // field is visible (not hidden)
         //true,                            // access lock enabled
         //false,                           // initial binary state: 'reset'
         //NULLCHAR,                        // no hotkey specified
         //noGROUP                          // not a group member
         },
      },

      //* Initialize remaining skForm parameters.*
      fldMULTI,         // index of field with initial focus
      acaUSEDFLT,       // default color attributes for control with focus
      true,             // beep on unhandled keycode received
      true,             // initial state of Insert/Overstrike flag
      true              // user access to Insert/Overstrike mode
      // Remaining parameters receive default values.
      //false,          // enable cursor shape change for Insert/Overstrike
      //csBblock,       // cursor shape when Insert mode
      //csBuline        // cursor shape when Overstrike mode
   ) ;

   //**************************
   //* Define an ACWin object *
   //**************************
   ACWin *win = new ACWin
         ( winROW, winCOL,                // position (upper-left corner) 
           winHEIGHT, winWIDTH,           // dimensions (height, width)
           ltSINGLE,                      // border style
           rgbBorder,                     // border attributes
           rgbInterior,                   // interior attributes
           winTitle,                      // title text
           skfPtr                         // partially-initialized skForm object
         ) ;

   //*************************************
   //* Open the window (make it visible) *
   //*************************************
   //* -------------------------------------------------------- *
   //* Important Note: The call to OpenWindow() sets unbuffered *
   //* input with non-blocking read and echo option 'softEchoA'.*
   //*  (actually 'noEcho' which has the same keycode group.)   *
   //* -------------------------------------------------------- *
   wpos = win->OpenWindow ( staticText ) ;
   cbPtr = win ;                    // give callback method access to ACWin object

   //* Set field outline with label *
   win->SetFieldLabel ( fldCLIP, "Clipboard Contents", acAttr(acaFG_BROWN | bgAttr),
                        false, true, ltDUAL ) ;

   delete skfPtr ;                        // delete the local skForm object

   //* Establish a callback method to monitor user input.   *
   //* When Pushbutton control selected, EditForm() returns.*
   win->EstablishCallback ( &tcwCallback ) ;

   //******************
   //* Get User Input *
   //******************
   short fldIndx ;
   win->EditForm ( fldIndx, fldPB_EXIT ) ;

   //* Close the window and delete the object.*
   //* Important Note: The call to CloseWindow() sets buffered *
   //* input with blocking read and echo option 'termEcho'.    *
   win->CloseWindow ( false ) ;
   delete win ;
   cbPtr = NULL ;                         // reset the external pointer



   // ========== EXIT SEQUENCE ===========
   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

   //* Restore original terminal settings *
   this->acRestoreTermInfo () ;

   //* Prepare to Exit *
   this->acSetCursor ( aesCUR_ABSPOS, (winROW + winHEIGHT + 1), 1 ) ;

}  //* End Test_Clipboard_Win() *

//*************************
//*      tcwCallback      *
//*************************
//********************************************************************************
//* Non-member method:                                                           *
//* ------------------                                                           *
//* Callback method for Test_Form() above. Handle the callbacks from EditForm()  *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: value of 'wkey' parameter  *
//********************************************************************************

static wchar_t tcwCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   enum fld : short                       // field indices
   {
      fldMULTI = ZERO,                    // multi-row text field
      fldSING,                            // single-row text field
      fldRB_C,                            // Radiobutton for system clipboard enable
      fldSTAT,                            // 'clipboard-stats' text field
      fldCLIP,                            // 'view-clipboard-data' text field
      fldPB_EXIT,                         // Exit Pushbutton
      fldCNT,                             // number of skField objects in window
   } ;

   static bool versionDisplayed = false ; // set first time system clipboard enabled
   gString gsClip ;                       // receive clipboard data

   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   {
      /* nothing to do at this time */
   }

   //* If the Copy or Cut command has been executed, update the field      *
   //* that displays clipboard contents.                                   *
   //* In addition, the Paste command may cause the data in the clipboard  *
   //* data display field to be out-of-synch with the clipboard contents.  *
   if ( (wkey == wcsCOPY) || (wkey == wcsCUT) || (wkey == wcsPASTE) )
   {
      cbPtr->acClipboardGetText ( gsClip ) ;       // get a copy of clipboard text
      cbPtr->SetFieldText ( fldCLIP, gsClip ) ;    // display clipboard text
      int cbBytes = cbPtr->acClipboardByteCnt () ; // update clipboard stats
      int cbChars = cbPtr->acClipboardCharCnt () ;
      gsClip.compose( "Clipboard UTF-8 bytes: %3d  UTF-32 chars: %3d", &cbBytes, &cbChars ) ;
      cbPtr->SetFieldText ( fldSTAT, gsClip ) ;
      //* Returning the null character for Copy/Cut should not be *
      //* necessary because these clipboard keys have already been*
      //* processed, but be safe.                                 *
      if ( wkey != wcsPASTE )
         wkey = NULLCHAR ;                // keycode has been handled
   }

   else if ( (wkey == wcsENTER) || (wkey == SPACE) )
   {
      // Programmer's Note: If a control is selected via hotkey, the keycode 
      // has been automagically converted to Enter/Space keycode.
      //* Enable or disable the system clipboard interface.*
      if ( ctrlIndex == fldRB_C )
      {
         //* Get current state of enable flag.                     *
         //* Note that the flag state change has not yet occurred, *
         //* so the sense of this test is backward.                *
         if ( (cbPtr->GetFieldState( fldRB_C )) )
            cbPtr->acClipboardDisable () ;
         else
         {
            cbPtr->acClipboardEnable ( &gsClip ) ;
            if ( ! versionDisplayed )
            {
               WinPos wp( 13, 54 ) ;
               int indx ;
               if ( (indx = gsClip.after( L'\n' )) > ZERO )
                  gsClip.insert( L"wl-clipboard v:", indx ) ;
               gsClip.insert( "WaylandCB    v:" ) ;
               cbPtr->Write ( wp, gsClip, acAttr(acaFG_BROWN | acaBGb_CYAN) ) ;
               versionDisplayed = true ;
            }
         }
      }
      //* The 'Exit' Pushbutton has been pressed. Prepare to exit. *
      else if ( ctrlIndex == fldPB_EXIT ) { wkey = wcsCTL_4 ; }
   }
   return wkey ;

}  //* End tcwCallback() *

//*************************
//*      Test_Tfman       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test all members of the TFMan class (Temporary File Manager class) through   *
//* direct access to these members.                                              *
//*                                                                              *
//* Note:                                                                        *
//* -----                                                                        *
//* The TFMan object which is optionally created within the AnsiCmd API          *
//* ('tnf' member) is not directly accessible at the application level.          *
//* Application-level access is through the AnsiCmd group of methods:            *
//*       acCreateTemppath()  acCreateTempfile()                                 *
//*       acDeleteTemppath()  acDeleteTempfile()                                 *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Tfman ( aeSeq fg )
{  // invoke: W x t
   gString gs, gst ;
   char tmpname1[gsALLOCMIN] = { "" } ;
   char tmpname2[gsALLOCMIN] = { "" } ;
   char tmpname3[gsALLOCMIN] = { "" } ;
   char ibuff[128] ;
   bool status ;

   //* Set attributes to specified terminal foreground value *
   this->acSetFgBg ( fg, aesBG_DFLT ) ;

   {  // beginning of object scope
   //* Create an instance of the TFMan class in dynamic memory. *
   TFMan tf( "SAM_" ) ;

   const char* subSpec = tf.getTempPath () ; // get filespec of container subdirectory
   gs.compose( "Container Subdir: '%s'\n", subSpec ) ;
   this->ttyWrite ( gs ) ;

   //* Verify that communications have been established *
   if ( (tf.isActive ()) )
   {
      //* Create a temporary file, open it, write to it, close it and report.*
      if ( (status = tf.createTempFile ( gst )) )
      {
         gs.compose( "Temp file created: '%s'\n", gst.ustr() ) ;
         this->ttyWrite ( gs ) ;
         ofstream ofs ( gst.ustr(), ofstream::out | ofstream::trunc ) ;
         if ( ofs.is_open() )
         {
            ofs << "This text was written to and read from the temp file." << endl ;
            ofs.close() ;
            ifstream ifs ( gst.ustr(), ifstream::in ) ;
            if ( ifs.is_open() )
            {
               ifs.getline( ibuff, 128 ) ;
               ifs.close() ;
               gs.compose( "'%s'", ibuff ) ;
               this->ttyWrite ( gs, true, true ) ;
               //* The file S/B in the temp directory now. Check it out. *
               this->ttyWrite ( "Press a Key..." ) ; this->acPause( true ) ;
            }
            else
               this->ttyWrite ( "Unable to open temp file for read.\n" ) ;
         }
         else
            this->ttyWrite ( "Unable to open temp file for write.\n" ) ;

         if ( (status = tf.deleteTempFile ( gst )) )
            this->ttyWrite ( "Temp file deleted.\n" ) ;
         else
            this->ttyWrite ( "Temp file deletion failed.\n" ) ;
         //* The temp file S/B deleted now. Check it out.*
      }
      else
         this->ttyWrite ( "Temp-file creation failed.\n" ) ;
      this->ttyWrite ( "Press a Key..." ) ; this->acPause( true ) ;

      //* Create three(3) temp files, then delete them en mas.*
      short cCnt = ZERO ;
      if ( (tf.createTempFile ( gst )) ) { gst.copy( tmpname1, gsALLOCMIN ) ; ++cCnt ; }
      if ( (tf.createTempFile ( gst )) ) { gst.copy( tmpname2, gsALLOCMIN ) ; ++cCnt ; }
      if ( (tf.createTempFile ( gst )) ) { gst.copy( tmpname3, gsALLOCMIN ) ; ++cCnt ; }
      gs.compose( "File Group: %hd of 3 files created.\n", &cCnt ) ;
      this->ttyWrite ( gs ) ;
      if ( *tmpname1 != NULLCHAR ) { this->ttyWrite ( tmpname1, true, true ) ; }
      if ( *tmpname2 != NULLCHAR ) { this->ttyWrite ( tmpname2, true, true ) ; }
      if ( *tmpname3 != NULLCHAR ) { this->ttyWrite ( tmpname3, true, true ) ; }
      this->ttyWrite ( "Press a Key..." ) ; this->acPause( true ) ;

      if ( (tf.clearTempFiles ()) )
         this->ttyWrite ( "Group delete successful.\n" ) ;
      else
         this->ttyWrite ( "Group delete failed.\n" ) ;
   }
   else
      this->ttyWrite ( "Connection with temporary-file manager failed.\n" ) ;

   //* Note that the object will be deleted when it goes out-of-scope here.*
   //* If it had been created in dynamic memory:                           *
   //*   TFMan* tf = new TFMan( "SAM_" ) ;                                 *
   //* Then the destructor should be called directly:                      *
   //*   delete tf ;                                                       *
   }  // end of object scope
   this->ttyWrite ( "TFMan object has gone out-of-scope, and \n"
                    "container subdirectory has been deleted.\n"
                    "Press a Key..." ) ;
   this->acPause( true ) ;

   // ========== EXIT SEQUENCE ===========
   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

   this->ttyWrite ( "Bye-bye!\n\n" ) ;

}  //* End Test_Tfman() *

//*************************
//*    Test_LaunchExt     *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Launch an external application, either in the current terminal window, or    *
//* as an application external to the current terminal window.                   *
//*                                                                              *
//* This test opens the documentation for the AnsiCmd library in either the      *
//* Texinfo (info) format in the current terminal window,  or the HTML format    *
//* in the default browser.                                                      *
//*                                                                              *
//* In future, we may add a option to open a terminal app in a new terminal      *
//* window or a new tab in the current terminal window. See FileMangler examples.*
//*                                                                              *
//* Input  : opt : test option                                                   *
//*                's' test the AnsiCmd acShellout() method by launching a       *
//*                    terminal application                                      *
//*                'l' launch an external application from within a window       *
//*                    -- launch a terminal application ('info') in the          *
//*                       current window                                         *
//*                    -- launch the default application for display of an       *
//*                       HTML file                                              *
//*          fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_LaunchExt ( wchar_t opt, aeSeq fg )
{  // invoke: W x [s | l] (fg==b, then bash)

   acaExpand acax ;
   gString gs ;

   if ( opt == L's' )
   {
      //* Set attributes to specified terminal foreground value *
      this->acSetFgBg ( fg, aesBG_DFLT ) ;

      // Programmer's Note: The 'fg' parameter specifies the color attributes, 
      // but for this test 'fg' also indicates which command string will be 
      // used in the call to acShellOut(). aesFG_BLUE indicates that call s/b
      // made with no parameters. Other values indicate that call should 
      // include all parameters.
      if ( fg == aesFG_BLUE )
         this->acShellOut () ;
      else
      {
         bool f = false ;
         this->acShellOut ( "echo 'Test acShellOut:\n================\n';"
                            "ls -l AnsiCmd*.[hc]pp;echo ''",
                            true, 
                            acAttr((acax.aeseq2bits( fg, f )) | acaBG_DFLT) ) ;
      }
   }

   else
   {
      const wchar_t rbGrpId = L'a' ;      // radiobutton group ID
      const short winHGT = 16 ;           // window height (rows)
      const short winWID = 64 ;           // window width (columns)
      const short winROW = 4 ;            // window offset (rows)
      const short winCOL = 3 ;            // window offset (columns)
      acaExpand acax ;                    // format bitfields
      const acAttr bdrAttr = acax.buildRgb ( true, WebSafeRGB(wsrgbBROWN + wsrgbSHADE_MAX), 
                                             true, WebSafeRGB(wsrgbMAGENTA) ) ;
      const acAttr txtAttr = acax.buildRgb ( true, WebSafeRGB(wsrgbBLACK), 
                                             true, WebSafeRGB(wsrgbMAGENTA + wsrgbSHADE_MAX) ) ;
      const acAttr pbAttr = acax.build8Bit( true, 160, true, 82 ) ;
      acAttr trmAttr ;
      WinPos wpul( short(winHGT - 4), short(winWID / 2) ) ;
      short fldIndex ;  // field with focus on exit

   const char* StaticText = 
      " \n"
      " Invoke an external application by selecting the type of \n"
      " application, and then press the \"Execute\" pushbutton.\n"
      " - For console utilities, this window will be put into\n"
      "   hibernation mode and the utility will be executed in\n"
      "   the terminal window.\n"
      " - For GUI applications, this window is not modified.\n" ;

   //* Clear (most of) the terminal window using a background color *
      switch ( fg )
      {
         case aesFG_BLACK:
            trmAttr = acax.buildRgb( true, WebSafeRGB(wsrgbGREY + wsrgbSHADES - 2), 
                                     true, WebSafeRGB(wsrgbBLACK + wsrgbSHADES - 2) ) ;
            break ;
         case aesFG_RED:     trmAttr = acax.build8Bit( true, 207, true, 207 ) ; break ;
         case aesFG_GREEN:   trmAttr = acax.build8Bit( true, 193, true, 193 ) ; break ;
         case aesFG_BROWN:   trmAttr = acax.build8Bit( true, 226, true, 226 ) ; break ;
         case aesFG_BLUE:    trmAttr = acax.build8Bit( true, 123, true, 123 ) ; break ;
         case aesFG_MAGENTA: trmAttr = acax.build8Bit( true, 213, true, 213 ) ; break ;
         case aesFG_CYAN:    trmAttr = acax.build8Bit( true, 195, true, 195 ) ; break ;
         case aesFG_GREY:    trmAttr = acax.build8Bit( true, 231, true, 231 ) ; break ;
         default:            trmAttr = acaATTR_DFLT ;                           break ;
      } ;
      this->acClearArea ( 3, 0, (this->termRows - 2), this->termCols, L' ', trmAttr ) ;

      //* Define skForm object *
      enum fldNames : short
      {
         fldGO_PB = ZERO,
         fldEXIT_PB,
         fldSHELL1_RB,
         fldSHELL2_RB,
         fldLAUNCH_RB,
         fldCOUNT
      } ;

      skForm *skfPtr = new skForm
      (
         fldCOUNT,         // number of skField objects in array
         new skField[fldCOUNT]
         {
            //* Field index fldGO_PB: ftPB (Pushbutton - Execute the call) *
            {
               ftPB,                      // field type
               1,                         // field height (in rows)
               9,                         // field width (in columns)
               wpul.row,                  // Y/X offset in window
               short(wpul.col - 9 - 3),
               " ^EXECUTE ",              // initial text data
               pbAttr,                    // color attribute
               // Remaining parameters receive default values.
               //floChar,                 // text formatting option (default)
               //ZERO,                    // insertion point (cursor offset) (default)
               //false,                   // user accessible (not read-only)
               //false,                   // field outline enabled
               //false,                   // field is visible (not hidden)
               //true,                    // access lock enabled
               //false,                   // initial binary state: 'reset'
               //NULLCHAR,                // no hotkey specified
               //noGROUP                  // not a group member
            },

            //* Field index fldEXIT_PB: ftPB (Pushbutton - Exit the window) *
            {
               ftPB,                      // field type
               1,                         // field height (in rows)
               8,                         // field width (in columns)
               wpul.row,                  // Y/X offset in window
               short(wpul.col + 1),
               "  E^XIT   ",              // initial text data
               pbAttr,                    // color attribute
               // Remaining parameters receive default values.
               //floChar,                 // text formatting option (default)
               //ZERO,                    // insertion point (cursor offset) (default)
               //false,                   // user accessible (not read-only)
               //false,                   // field outline enabled
               //false,                   // field is visible (not hidden)
               //true,                    // access lock enabled
               //false,                   // initial binary state: 'reset'
               //NULLCHAR,                // no hotkey specified
               //noGROUP                  // not a group member
            },

            //* Field index fldSHELL1: ftRB (Radiobutton) *
            {
               ftRB,                      // field type
               1,                         // field height (in rows)
               16,                        // field width (in columns)
               short(wpul.row = 8),       // Y/X offset in window
               short(wpul.col -= 19),
               "[ ] ^Shell Out (default shell program)", // field text
               pbAttr,                    // color attribute
               floChar,                   // text formatting option (n/a)
               ZERO,                      // IP is dynamically set
               false,                     // read-only flag is 'reset'
               false,                     // field outline disabled
               false,                     // hidden flag is 'reset'
               true,                      // access lock enabled
               false,                     // binary state: 'reset'
               NULLCHAR,                  // hotkey is embedded in text data
               rbGrpId,                   // radiobutton group
            },

            //* Field index fldSHELL2: ftRB (Radiobutton) *
            {
               ftRB,                      // field type
               1,                         // field height (in rows)
               16,                        // field width (in columns)
               short(++wpul.row), wpul.col, // Y/X offset in window
               "[ ] ^Console Utility (Info reader)", // field text
               pbAttr,                    // color attribute
               floChar,                   // text formatting option (n/a)
               ZERO,                      // IP is dynamically set
               false,                     // read-only flag is 'reset'
               false,                     // field outline disabled
               false,                     // hidden flag is 'reset'
               true,                      // access lock enabled
               true,                      // binary state: 'set'
               NULLCHAR,                  // hotkey is embedded in text data
               rbGrpId,                   // radiobutton group
            },

            //* Field index fldLAUNCH: ftRB (Radiobutton) *
            {
               ftRB,                      // field type
               1,                         // field height (in rows)
               16,                        // field width (in columns)
               short(++wpul.row),         // Y/X offset in window
               wpul.col,
               "[ ] ^Launch Default App. (HTML doc)", // field text
               pbAttr,                    // color attribute
               floChar,                   // text formatting option (n/a)
               ZERO,                      // IP is dynamically set
               false,                     // read-only flag is 'reset'
               false,                     // field outline disabled
               false,                     // hidden flag is 'reset'
               true,                      // access lock enabled
               false,                     // binary state: 'set'
               NULLCHAR,                  // hotkey is embedded in text data
               rbGrpId,                   // radiobutton group
            },
         }     // (skField array)

         //* Initialize remaining skForm parameters.       *
         //* Optional arguments are set to default values. *
         //ZERO,           // index of field with initial focus
         //acaUSEDFLT,     // default color attributes for control with focus
         //true,           // beep on unhandled keycode received
         //true,           // initial state of Insert/Overstrike flag
         //true,           // user access to Insert/Overstrike mode
         //false,          // enable cursor shape change for Insert/Overstrike
         //csBblock,       // cursor shape when Insert mode
         //csBuline        // cursor shape when Overstrike mode
      ) ;                                    

      //***************************
      //* Define the ACWin object *
      //***************************
      ACWin *win = new ACWin
            ( winROW, winCOL,                     // position (upper-left corner) 
              winHGT, winWID,                     // dimensions (height, width)
              ltDUAL,                             // border style
              bdrAttr,                            // border attributes
              txtAttr,                            // interior attributes
              "  Invoke External Applications  ", // title text
              skfPtr                              // partially-initialized skForm object
            ) ;

      //* Open the window and interact with the user.*
      win->OpenWindow ( StaticText ) ;
      cbPtr = win ;                    // give callback method access to ACWin object

      //* Establish a callback method to monitor user input.   *
      //* When Pushbutton control selected, EditForm() returns.*
      win->EstablishCallback ( &tleCallback ) ;

      //******************
      //* Get User Input *
      //******************
      win->EditForm ( fldIndex ) ;

      //* Close the window and release resources.*
      win->CloseWindow () ;
      delete skfPtr ;
      delete win ;
      cbPtr = NULL ;                         // reset the external pointer

      //* Clear the window *
      this->acClearArea ( 3, 0, (this->termRows - 2), this->termCols, L' ', acaATTR_DFLT ) ;
      this->acSetCursor ( winROW, 0 ) ;

   }

   // ========== EXIT SEQUENCE ===========
   //* Restore foreground to default attribute *
   this->acSetFg ( aesFG_DFLT ) ;

   this->ttyWrite ( "Bye-bye!\n\n" ) ;

}  //* End Test_LaunchExt() *

//*************************
//*      tleCallback      *
//*************************
//********************************************************************************
//* Non-member method:                                                           *
//* ------------------                                                           *
//* Callback method for Test_LaunchExt() above.                                  *
//* Called from inside EditForm() to handle launch of external applications      *
//* based on parent window's Radiobutton configuration.                          *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: value of 'wkey' parameter  *
//********************************************************************************

static wchar_t tleCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   //* Define skForm object *
   enum fldNames : short
   {
      fldGO_PB = ZERO,
      fldEXIT_PB,
      fldSHELL1_RB,
      fldSHELL2_RB,
      fldLAUNCH_RB,
      fldCOUNT
   } ;

   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   {
      cbPtr->EnableFootnotes ( true ) ;   // enable window "footnotes"
   }

   if ( (wkey == wcsENTER) || (wkey == SPACE) )
   {
      // Programmer's Note: If a control is selected via hotkey, the keycode 
      // has been automagically converted to Enter/Space keycode.

      if ( ctrlIndex == fldGO_PB )  // if user has select to execute a test
      {
         gString gsCmd, gsFn ;
         short xorMember ;
         //* Get the selection from Radiobutton group *
         cbPtr->GetFieldState ( fldSHELL1_RB, &xorMember ) ;

         switch ( xorMember )
         {
            case fldSHELL1_RB:
               cbPtr->ShellOut ( gsCmd ) ;
               break ;
            case fldSHELL2_RB:
               gsCmd = "info -f ./Texinfo/ansicmd.info -n 'ShellOut'" ;
               cbPtr->ShellOut ( gsCmd ) ;
               break ;
            case fldLAUNCH_RB:
               gsFn.compose( " Default GUI Application " ) ;
               cbPtr->Footnote ( gsFn, ZERO ) ;
               gsCmd = "./Texinfo/ansicmd.html" ;
               cbPtr->LaunchDefaultApp ( gsCmd.ustr() ) ;
               break ;
            default:    break ;     // (should never happen)
         } ;
      }

      //* The 'Exit' Pushbutton has been pressed. Prepare to exit. *
      else if ( ctrlIndex == fldEXIT_PB ) { wkey = wcsCTL_4 ; }
   }

   return wkey ;

}  //* End tleCallback() *

//*************************
//*       Test_Box        *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Create an AC_Box object with various configuration options.                  *
//*                                                                              *
//* The AC_Box class and associated AnsiCmd support methods are intentionally    *
//* simplistic in order to achieve maximum speed using minimum resources.        *
//* To get additional bells and whistles, see the ACWin class.                   *
//*                                                                              *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* The examples given in this test use simple, ANSI escape sequences            *
//* implemented in the basic AnsiCmd-class methods. Give particular attention    *
//* to the SEQUENCE in which the commands are executed.                          *
//* Anything even _moderately_ fancy here is done simply to fascilitate a        *
//* smooth user interface for the test.                                          *
//*                                                                              *
//* Programmer's Note: It is recommended to reset all text modifiers before      *
//* calling acBoxDraw(). Modifiers can cause a severe case of the Uglies when    *
//* drawing the border. Use caution.                                             *
//********************************************************************************

void AnsiCmd::Test_Box ( aeSeq fg )
{  // invoke: W b
   const char *Hello = "\n\n\n\n                 Hello World! " ;
   const char *boxInstructions = 
         "'a' Add text to the box.\n"
         "'w' Write new text in the box.\n"
         "'c' Clear the text area of the box.\n"
         "'e' Erase the box.\n"
         "'r' Redraw the box.\n"
         "'s' Set cursor position\n"
         "'t' Text color attributes.\n"
         "'b' Border color attributes.\n"
         "'l' Line type for border.\n"
         "'i' Italic text modifier (toggle)\n"
         "'u' Underline text modifier (toggle)\n"
         "'q' Quit." ;
   // This paragraph is an excerpt from a short story written by the author.
   // (c) Copyright 2014 The Software Samurai (all rights reserved)
   const char *Chuck = 
         "Yes, Chuck was a nerd, but he had a burning\n"
         "desire to express himself and an overwhelming\n"
         "need to create something that would be of\n"
         "lasting value to someone, though his peers\n"
         "would probably still steal his lunch, and he\n"
         "was sure that his family would just continue\n"
         "to stare at him in slack-jawed\n"
         "incomprehension." ;


   const short boxHEIGHT = 11 ;              // boxB height
   const short boxWIDTH  = 48 ;              // boxB width
   const aeSeq bdrFG1  = aesFG_GREEN ;       // boxB border attributes
   const aeSeq bdrBG1  = aesBG_BLACK ;
   const aeSeq bdrFG2  = aesFGb_RED ;
   const aeSeq bdrBG2  = aesBG_GREY ;
   const aeSeq txtFG1  = aesFGb_CYAN ;       // boxB text attributes
   const aeSeq txtBG1  = aesBG_BLUE ;
   const aeSeq txtFG2  = aesFG_BLUE ;
   const aeSeq txtBG2  = aesBG_CYAN ;

   gString gsOut ;                  // text formatting
   short termRows, termCols ;       // terminal dimensions
   WinPos boxOrig( 4, 0 ) ;         // position - upper left corner of box
   WinPos txtOrig ;                 // coordinates of text area within the box
   WinPos txtCurs ;                 // cursor position
   WinPos wpur, wplr, wpll ;        // corners of text area
   WinPos wpExit( short(boxOrig.row + boxHEIGHT * 2 + 4), 1 ) ; // exit position
   WinPos wp ;                      // text offset
   wchar_t wkey ;                   // user input
   const char *txtPtr = Hello ;     // pointer to active text string
   bool italic = false ;            // if true, write text with italic modifier
   bool underline = false ;         // if true, write text with underline modifier
   bool invisible = false ;         // set when box is erased

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, "  (requires terminal dimensions of 36 rows X 140 columns)\n"
                          "---------------------------------------------------------" ) ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   this->acBufferedInput ( false, noEcho ) ;    // set unbuffered input

   this->acGetTermDimensions ( termRows, termCols ) ; // get terminal window dimensions
   WinPos bdbOrig( short(boxOrig.row + 2), 4 ) ; // brain-dead-box origin
   wp = { short(bdbOrig.row + 7), bdbOrig.col } ;

   //* Say hello (yes, it's too complex :-) *
   this->acSetMod ( aesUNDERLINE ) ;
   this->acSetMod ( aesBOLD ) ;
   this->acSetMod ( aesREVERSE ) ;
   this->acWrite ( boxOrig.row, bdbOrig.col, 
         "          Welcome To Boxing Day!          " ) ;
   this->acSetMod ( aesREVERSE_OFF ) ;
   this->acSetMod ( aesBOLD_OFF ) ;

   //* Display user instructions *
   wp = this->acWrite ( wp, "             Testing Options             \n" ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;
   this->acWrite ( wp, boxInstructions ) ;

   //************************************************
   //* Draw a primitive box, and write text into it.*
   //************************************************
   this->acDrawBox ( bdbOrig, 6, 42, ltDUAL, 
                     "Hello. This is a simple, rectangle drawn\n"
                     "in the terminal window. It has no color\n"
                     "attributes or data storage capabilities.\n"
                     "Please see REAL boxes to the right.--->" ) ;

   //*******************************************
   //* AC_Box basic-initialization constructor *
   //*******************************************
   boxOrig.col = termCols / 2 - boxWIDTH / 2 ;  // calculate horizontal offset
   AC_Box boxA( boxOrig, boxHEIGHT, boxWIDTH ) ;
   txtOrig = this->acBoxDraw ( boxA ) ;
   wp = this->acWrite ( txtOrig, "This is the basic AC_Box structure.\n\n" ) ;
   wp = this->acWrite ( wp, "Terminal default color attributes are used\n"
                            "to draw both foreground and background.\n\n" ) ;
   wp = this->acWrite ( wp, "Use the acWrite() or ttyWrite() method to\n"
                            "write text to the interior of the box.\n"
                            "Range checking of position and width ARE NOT\n"
                            "performed." ) ;
   boxOrig.row += 12 ;

   //******************************************
   //* AC_Box full-initialization constructor *
   //******************************************
   AC_Box boxB( boxOrig, boxHEIGHT, boxWIDTH, ltDUAL, bdrFG1, bdrBG1, txtFG1, txtBG1,
                " Full-initialization AC_Box Class " ) ;
   txtOrig = this->acBoxDraw ( boxB, Hello ) ;
   txtCurs = this->acGetCursor () ;    // get current cursor position

   //* Calculate position of interior corners (for cursor positioning test) *
   wpur = { txtOrig.row, short(txtOrig.col + boxWIDTH - 3) } ;
   wplr = { short(txtOrig.row + boxHEIGHT - 3), wpur.col } ;
   wpll = { wplr.row, txtOrig.col } ;

   while ( (wkey = this->acRead ()) != L'q' )
   {
      //* If box is currently invisible, restore it for the next test. *
      if ( invisible && (wkey != L'r') )
      { this->acBoxDraw ( boxB, txtPtr ) ; invisible = false ; }

      switch ( wkey )
      {
         case L'a':           // Add text
            if ( txtCurs.row < (boxOrig.row + boxHEIGHT - 2) )
               ++txtCurs.row ;
            else
               txtCurs = txtOrig ;
            txtCurs.col = txtOrig.col + 7 ;
            this->acSetFgBg ( boxB.tFgnd, boxB.tBgnd ) ; // box interior attributes
            if ( underline ) { this->acSetMod ( aesUNDERLINE ) ; }
            if ( italic )    { this->acSetMod ( aesITALIC ) ; }
            txtCurs = this->acWrite ( txtCurs, "(Here is some additional text.)" ) ;
            if ( underline ) { this->acSetMod ( aesUNDERLINE_OFF ) ; }
            if ( italic )    { this->acSetMod ( aesITALIC_OFF ) ; }
            this->acSetFgBg ( fg, aesBG_DFLT ) ;         // restore terminal attributes
            break ;
         case L'w':           // Write alternate text
            txtPtr = txtPtr == Hello ? Chuck : Hello ; // select the text to write

            this->acBoxClear ( boxB ) ;       // clear text area AND set attributes
            if ( underline ) { this->acSetMod ( aesUNDERLINE ) ; }
            if ( italic )    { this->acSetMod ( aesITALIC ) ; }
            txtCurs = this->acWrite ( txtOrig, txtPtr ) ; // write the text
            if ( underline ) { this->acSetMod ( aesUNDERLINE_OFF ) ; }
            if ( italic )    { this->acSetMod ( aesITALIC_OFF ) ; }
            this->acSetFgBg ( fg, aesBG_DFLT ) ;       // restore terminal attributes
            break ;
         case L'c':           // Clear text area
            txtOrig = this->acBoxClear ( boxB ) ;
            txtPtr = Hello ;
            break ;
         case L'e':           // Erase the box
            this->acBoxErase ( boxB ) ;
            invisible = true ;
            txtPtr = Hello ;
            break ;
         case L'r':           // Redraw the box
            this->acBoxDraw ( boxB, txtPtr ) ;
            invisible = false ;
            break ;
         case L's':           // Set (visible) cursor position
            txtCurs = this->acGetCursor () ;
            if ( txtCurs == txtOrig )     txtCurs = wpur ;
            else if ( txtCurs == wpur )   txtCurs = wplr ;
            else if ( txtCurs == wplr )   txtCurs = wpll ;
            else                          txtCurs = txtOrig ;
            this->acSetCursor ( txtCurs ) ;
            break ;
         case L't':           // Text color
            if ( boxB.tFgnd == txtFG1 )   // set the alternate colors
            { boxB.tFgnd = txtFG2 ; boxB.tBgnd = txtBG2 ; }
            else
            { boxB.tFgnd = txtFG1 ; boxB.tBgnd = txtBG1 ; }
            this->acBoxDraw ( boxB, txtPtr ) ;
            break ;
         case L'b':           // Border color
            if ( boxB.bFgnd == bdrFG1 )   // set the alternate colors
            { boxB.bFgnd = bdrFG2 ; boxB.bBgnd = bdrBG2 ; }
            else
            { boxB.bFgnd = bdrFG1 ; boxB.bBgnd = bdrBG1 ; }
            this->acBoxDraw ( boxB, txtPtr ) ;
            break ;
         case L'l':           // Line type for border
            boxB.lnType = boxB.lnType == ltDUAL ? ltSINGLE : ltDUAL ;
            this->acBoxDraw ( boxB, txtPtr ) ;
            break ;
         case L'i':           // Italic modifier
         case L'u':           // Underline modifier
            if ( wkey == L'i' )      { italic = italic ? false : true ; }
            else if ( wkey == L'u' ) { underline = underline ? false : true ; }

            this->acBoxClear ( boxB ) ;       // clear text area AND set attributes
            if ( underline ) { this->acSetMod ( aesUNDERLINE ) ; }
            if ( italic )    { this->acSetMod ( aesITALIC ) ; }
            txtCurs = this->acWrite ( txtOrig, txtPtr ) ;
            if ( underline ) { this->acSetMod ( aesUNDERLINE_OFF ) ; }
            if ( italic )    { this->acSetMod ( aesITALIC_OFF ) ; }
            this->acSetFgBg ( fg, aesBG_DFLT ) ;   // restore terminal attributes
            break ;
         default:
            this->acBeep () ;
            break ;
      }
   }

   //* Restore buffered input and prepare to exit.*
   this->acBufferedInput ( true, termEcho ) ;
   this->acSetFg ( aesFG_DFLT ) ;
   this->acSetCursor ( wpExit ) ;

}  //* End Test_Box() *

//*************************
//*       Test_Line       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Use the acDrawLine() method to draw shapes.                                  *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Line (  aeSeq fg )
{  // invoke: W l
   const short boxHEIGHT = 9 ;      // box height
   const short boxWIDTH  = 41 ;     // box width
   const short boxULr = 7 ;         // box origin
   const short boxULc = 4 ;
   gString gsOut ;                  // text formatting
   WinPos wpMsg( 4, 4 ) ;           // message position
   WinPos wpBox( boxULr, boxULc ) ; // position - upper left corner of box
   WinPos wpExit( 16, 1 ) ;         // exit position
   acaExpand acaex ;                // convert aeSeq to acAttr
   bool setFlag ;                   // (dummy)
   acAttr boxBg = (fg == aesFG_DFLT) ? acAttr(acaBGb_GREY | acaREVERSE) :
                  acAttr((acaex.aeseq2bits ( fg, setFlag )) | acaREVERSE) ;

   //* Say Hello *
   this->acSetMod ( aesUNDERLINE ) ;
   this->acSetMod ( aesBOLD ) ;
   this->acSetMod ( aesREVERSE ) ;
   this->acWrite ( wpMsg, "        OK, Recruits, Get In Line!        " ) ;
   this->acSetMod ( aesREVERSE_OFF ) ;
   this->acSetMod ( aesBOLD_OFF ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;
   wpMsg.row += 2 ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   this->acBufferedInput ( false, noEcho ) ;    // set unbuffered input

   //* Draw a box using individual lines *
   this->acWrite ( wpMsg, L"Draw a box using individual lines. Press a key..." ) ;
   this->acRead () ;
   wpBox = this->acDrawLine ( wpBox, boxWIDTH - 1, false, ltSINGLE, wcsUL ) ;
   this->acDrawLine ( wpBox, boxHEIGHT, true, ltSINGLE, wcsUR, wcsLR ) ;
   wpBox = { short(boxULr + 1), boxULc } ;
   wpBox = this->acDrawLine ( wpBox, boxHEIGHT - 1, true, ltSINGLE, L'\0', wcsLL ) ;
   --wpBox.row ;
   this->acDrawLine ( wpBox, boxWIDTH, false, ltSINGLE, wcsLL, wcsLR ) ;

   //* Draw intersecting lines within the box *
   this->acSetCursor ( wpMsg ) ;
   this->acEraseArea ( aesERASE_LINE ) ;
   this->acWrite ( wpMsg, L"Draw horizontal and vertical intersecting lines. Press a key..." ) ;
   this->acRead () ;
   wpBox = { short(boxULr + boxHEIGHT / 2), boxULc } ;
   this->acDrawLine ( wpBox, boxWIDTH, false, ltSINGLE, wcsLTEE, wcsRTEE ) ;
   wpBox = { boxULr, short(boxULc + boxWIDTH / 2) } ;
   wpBox = this->acDrawLine ( wpBox, (boxHEIGHT / 2 + 1), true, 
                              ltSINGLE, wcsTTEE, wcsINSECT ) ;
   this->acDrawLine ( wpBox, (boxHEIGHT / 2), true, ltSINGLE, L'\0', wcsBTEE ) ;

   //* Draw the same object using dual-line characters *
   this->acSetCursor ( wpMsg ) ;
   this->acEraseArea ( aesERASE_LINE ) ;
   this->acWrite ( wpMsg, L"And now, the same drawing using dual line characters. Press a key..." ) ;
   this->acRead () ;
   wpBox = { boxULr, boxULc } ;
   this->acClearArea ( wpBox, boxHEIGHT, boxWIDTH, L' ', boxBg ) ;
   wpBox = this->acDrawLine ( wpBox, boxWIDTH - 1, false, ltDUAL, wcsULd ) ;
   this->acDrawLine ( wpBox, boxHEIGHT, true, ltDUAL, wcsURd, wcsLRd ) ;
   wpBox = { short(boxULr + 1), boxULc } ;
   wpBox = this->acDrawLine ( wpBox, boxHEIGHT - 1, true, ltDUAL, L'\0', wcsLLd ) ;
   --wpBox.row ;
   this->acDrawLine ( wpBox, boxWIDTH, false, ltDUAL, wcsLLd, wcsLRd ) ;
   wpBox = { short(boxULr + boxHEIGHT / 2), boxULc } ;
   this->acDrawLine ( wpBox, boxWIDTH, false, ltDUAL, wcsLTEEd, wcsRTEEd ) ;
   wpBox = { boxULr, short(boxULc + boxWIDTH / 2) } ;
   wpBox = this->acDrawLine ( wpBox, (boxHEIGHT / 2 + 1), true, 
                              ltDUAL, wcsTTEEd, wcsINSECTd ) ;
   this->acDrawLine ( wpBox, (boxHEIGHT / 2), true, ltDUAL, L'\0', wcsBTEEd ) ;

   //* Reset attributes to terminal defaults and say "Goodnight, Gracie". *
   this->acSetAttributes ( acAttr(acaFG_DFLT | acaBG_DFLT | acaCLEAR_MODS) );
   this->acSetCursor ( wpMsg ) ;
   this->acEraseArea ( aesERASE_LINE ) ;
   this->acWrite ( wpMsg, L" Well done, Recruits. Fast on the draw!" ) ;

   //* Restore buffered input and prepare to exit.*
   this->acBufferedInput ( true, termEcho ) ;
   this->acSetFg ( aesFG_DFLT ) ;
   this->acSetCursor ( wpExit ) ;

}  //* End Test_Line() *

//*************************
//*      Test_Write       *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY ansiTest().                                  *
//* Test cursor positioning and text output of the acWrite()/writeNL() method    *
//* sequence. writeNL() is a private method invoked when acWrite() is called     *
//* with data which contains newline characters (which are handled as a special  *
//* case. Vertical scrolling and output truncation at right edge of terminal     *
//* window are verified.                                                         *
//*                                                                              *
//* Input  : fg  : foreground color attribute to be used for the tests           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Write ( aeSeq fg )
{  // invoke: T f
   gString bsOut ;
   WinPos  wp, wpPrompt = this->acGetCursor () ;
   wchar_t wch ;
   bool    done = false ;

   //* Warn about minimum terminal window dimensions.*
   this->acWrite ( 1, 28, "  (requires terminal dimensions of 31 rows X 116 columns)" ) ;
   this->acSetCursor ( wpPrompt ) ;

   //* Set the foreground color *
   this->acSetFg ( fg ) ;

   //* Set stdin stream for non-blocking read  *
   //* (As a side-effect, input buffering is ) *
   //* (disabled, and echo is set to 'noEcho') *
   this->acBlockingInput ( false ) ;

   this->ttyWrite ( L"Test the major output routines: \n"
                     " 'a'=acWrite, 't'=ttyWrite, 'o'=overloads\n"
                     "                                ") ;
   this->acSetCursor ( aesCUR_ROWSUP, 2 ) ;
   wpPrompt = this->acGetCursor () ;

   while ( ! done )
   {
      wch = this->acRead () ;

      if ( wch == L'a' || wch == L'A' )
      {
         const wchar_t* acwText1 = 
            L"acWrite: Starting position is specified,\n"
             "the text to be displayed is specified, and\n"
             "optionally, whether to flush the output stream.\n"
             "Each row of the output begins at the column offset\n"
             "specified for the starting position.\n"
             "After all rows have been written, the return value\n"
             "will be the final cursor position:" ;
         const wchar_t* acwText2 = 
            L"\nIf text for a row would extend beyond the right edge of\n"
             "the terminal window that row of the output will be truncated.\n"
             "If the bottom of the terminal window is reached,\n"
             "the remaining text will be truncated. Scrolling will not occur." ;
         gString bsPos ;
         wp = { wpPrompt.row, 1 } ;
         this->acSetCursor ( wp ) ;
         this->acEraseArea ( aesERASE_EOL ) ;   // erase current row
         this->acEraseArea ( aesERASE_BOW ) ;   // and to bottom of window

         bsOut = acwText1 ;
         wp = { wpPrompt.row, 1 } ;
         wp = this->acWrite ( wp, bsOut ) ;
         bsPos.compose( "<--%02hd:%02hd", &wp.row, &wp.col ) ;
         this->ttyWrite ( bsPos ) ;
         this->nsleep ( 10 ) ;
         ++wp.row ;
         bsOut.append( acwText2 ) ;

         // Programmer's Note: If wp.row == this->termRows but no text has been 
         // written to that row, the output will SEEM to be truncated incorrectly.
         while ( wp.row < this->termRows )
         {
            wp = this->acWrite ( wp, bsOut ) ;
            this->nsleep ( 10 ) ;
            ++wp.row ; wp.col -= 25 ;
         }

         this->nsleep ( 15 ) ;
         this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 4, 1 ) ;
         done = true ;
      }

      else if ( wch == L'o' || wch == L'O' )
      {
         wp = { wpPrompt.row, 1 } ;
         this->acSetCursor ( wp ) ;
         this->acEraseArea ( aesERASE_EOL ) ;   // erase current row
         this->acEraseArea ( aesERASE_BOW ) ;   // and to bottom of window
         const short acwCol = 48 ;

         //* ttyWrite Tests *
         //* Column heading *
         this->acSetMod ( aesUNDERLINE ) ;
         wp = this->acWrite ( wp, "           ttyWrite Overloads            \n" ) ;
         this->acSetMod ( aesUNDERLINE_OFF ) ;

         //* Single UTF-8 character *
         bsOut = "ttyWrite( char, bool )\n" ;
         this->ttyWrite ( bsOut ) ;
         this->ttyWrite ( 'A' ) ;
         this->ttyWrite ( L"<--here\n" ) ;

         //* uint_t* string *
         bsOut = "ttyWrite( uint8_t*, bool )\n" ;
         this->ttyWrite ( (uint8_t*)(bsOut.ustr()) ) ;

         //* char* string *
         bsOut = "ttyWrite( char*, bool )\n" ;
         this->ttyWrite ( bsOut.ustr() ) ;

         //* Single UTF-32 character *
         bsOut = "ttyWrite( wchar_t, bool )\n" ;
         this->ttyWrite ( bsOut ) ;
         this->ttyWrite ( L'B' ) ;
         this->ttyWrite ( L"<--here\n" ) ;

         //* wchar_t* string *
         bsOut = "ttyWrite( wchar_t*, bool )\n" ;
         this->ttyWrite ( bsOut.gstr() ) ;

         //* gString& object *
         bsOut = "ttyWrite( gString&, bool )\n" ;
         this->ttyWrite ( bsOut ) ;

         //* acWrite Tests  *
         //* Column heading *
         wp = { wpPrompt.row, acwCol } ;
         this->acSetMod ( aesUNDERLINE ) ;
         wp = this->acWrite ( wp, "            acWrite Overloads            \n" ) ;
         this->acSetMod ( aesUNDERLINE_OFF ) ;

         //* Single UTF-8 character *
         bsOut = "acWrite( short, short, char, bool )\n" ;
         wp = this->acWrite ( wp, bsOut ) ;
         wp = this->acWrite ( wp.row, wp.col, 'A' ) ;
         this->acSetCursor ( wp ) ; ttyWrite ( L"<--here" ) ;
         wp = { short(wp.row + 1), acwCol } ;

         bsOut = "acWrite( WinPos, char, bool )\n" ;
         wp = this->acWrite ( wp, bsOut ) ;
         wp = this->acWrite ( wp, 'A' ) ;
         this->acSetCursor ( wp ) ; ttyWrite ( L"<--here" ) ;
         wp = { short(wp.row + 1), acwCol } ;

         //* UTF-8 string *
         bsOut = "acWrite( short, short, char*, bool )\n" ;
         wp = this->acWrite ( wp.row, wp.col, bsOut.ustr() ) ;

         bsOut = "acWrite( WinPos, char*, bool )\n" ;
         wp = this->acWrite ( wp, bsOut.ustr() ) ;

         //* Single UTF-32 character *
         wp.col = acwCol ;
         bsOut = "acWrite( short, short, wchar_t, bool )\n" ;
         wp = this->acWrite ( wp, bsOut ) ;
         wp = this->acWrite ( wp.row, wp.col, L'B' ) ;
         this->acSetCursor ( wp ) ; ttyWrite ( L"<--here" ) ;
         wp = { short(wp.row + 1), acwCol } ;

         bsOut = "acWrite( WinPos, wchar_t, bool )\n" ;
         wp = this->acWrite ( wp, bsOut ) ;
         wp = this->acWrite ( wp, L'B' ) ;
         this->acSetCursor ( wp ) ; ttyWrite ( L"<--here" ) ;
         wp = { short(wp.row + 1), acwCol } ;

         //* UTF-32 string *
         bsOut = "acWrite( short, short, wchar_t*, bool )\n" ;
         wp = this->acWrite ( wp.row, wp.col, bsOut.gstr() ) ;
         wp.col = acwCol ;

         bsOut = "acWrite( WinPos, wchar_t*, bool )\n" ;
         wp = this->acWrite ( wp, bsOut.gstr() ) ;
         wp.col = acwCol ;

         //* gString object *
         bsOut = "acWrite( short, short, gString&, bool )\n" ;
         wp = this->acWrite ( wp.row, wp.col, bsOut ) ;
         wp.col = acwCol ;

         bsOut = "acWrite( WinPos, gString&, bool )\n" ;
         wp = this->acWrite ( wp, bsOut ) ;
         wp.col = acwCol ;

         this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 4, 1 ) ;
         done = true ;
      }

      else if ( wch == L't' || wch == L'T' )
      {
         const wchar_t* ttyText1 = 
            L"ttyWrite: Begins at the current cursor position,\n"
             "and for each newline ('\\n') encountered, moves\n"
             "the cursor to the first column of the next row.\n"
             "\n          " ;
         const wchar_t* ttyText2 = 
            L"ttyWrite: The system will automatically wrap text "
             "at the right edge of the terminal window." ;
         const wchar_t* ttyText3 = 
            L"\nttyWrite: If output continues below the bottom of the terminal window, "
             "data in the window will be scrolled upward." ;
         const wchar_t* alpha = 
            L" abcde fghij klmno prqst uvwxyz" ;
         wp = { wpPrompt.row, 1 } ;
         this->acSetCursor ( wp ) ;
         this->acEraseArea ( aesERASE_EOL ) ;   // erase current row
         this->acEraseArea ( aesERASE_BOW ) ;   // and to bottom of window

         bsOut = ttyText1 ;
         this->ttyWrite ( bsOut ) ;
         this->nsleep ( 12 ) ;
         bsOut.append( L'\n' ) ;
         this->ttyWrite ( bsOut ) ;
         this->nsleep ( 12 ) ;

         bsOut = ttyText2 ;
         while ( bsOut.gscols() < (this->termCols + 3) )
            bsOut.append( alpha ) ;
         this->ttyWrite ( bsOut ) ;
         this->nsleep ( 12 ) ;
         this->ttyWrite ( L"<--and the next write will continue from there." ) ;

         this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 4, 1 ) ;
         bsOut = ttyText3 ;
         for ( short r = 6 ; r > ZERO ; --r )
            this->ttyWrite ( bsOut ) ;
         this->nsleep ( 30 ) ;

         //* Clear last few rows of window *
         this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 4, 1 ) ;
         this->acEraseArea ( aesERASE_BOW ) ;
         done = true ;
      }

      else if ( wch == L'x' || wch == L'X' )
      {
         this->acSetCursor ( wpPrompt.row + 2, 1 ) ;
         this->acEraseArea ( aesERASE_EOL ) ;
         done = true ;
      }

      else
      {
         this->ttyWrite ( L"\n\n Try again (or 'x' to exit)..." ) ;
         this->acSetCursor ( wpPrompt ) ;
      }
   }  // while()

   //* Say Good Night, Gracie *
//   this->acSetCursor ( aesCUR_ABSPOS, this->termRows - 4, 1 ) ;
//   this->acEraseArea ( aesERASE_BOW ) ;
   this->ttyWrite ( L"----- Bye-bye! -----\n" ) ;

   //* Return terminal to buffered input with   *
   //* terminal echo AND blocking read of input.*
   this->acBufferedInput ( true, termEcho ) ;

   this->acSetFg ( aesFG_DFLT ) ;      // Reset to terminal default foreground color
   this->acFlushStreams () ;           // Clear input and output buffers

}  //* End Test_Write() *

//*************************
//*     twColorChart      *
//*************************
//********************************************************************************
//* PRIVATE METHOD - CALLED ONLY BY Test_Window().                               *
//* Display a chart of color attributes available for testing the window         *
//* attribute settings.                                                          *
//*                                                                              *
//* Input  : basePos : base position for chart                                   *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::twColorChart ( const WinPos& basePos )
{
   const wchar_t *spaceBar = L"            \n" ;
   const short loopCNT = 17 ;                // iterations
   gString gsOut( "   4-Bit Color\n" ) ;     // text formatting
   WinPos wp = basePos ;                     // cursor position

   //** 4-Bit Color **
   short startIndx = aesBG_BLACK ;           // loop initial
   short endIndx = startIndx + loopCNT ;     // loop termination
   for ( short regnum = ZERO ; regnum < min8BIT ; ++regnum )
      gsOut.append( "%2hd\n", &regnum ) ;
   this->acWrite ( wp, gsOut ) ;
   wp = { short(wp.row + 1), short(wp.col + 3) } ;
   this->acSetCursor ( wp ) ;
   for ( short regindx = aesBG_BLACK ; regindx < endIndx ; ++regindx )
   {
      this->acSetBg ( aeSeq(regindx), true ) ; // set the background color
      this->ttyWrite ( spaceBar ) ;
      this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column
   }
   this->acSetBg ( aesFG_DFLT ) ;            // return to default background

   //** 8-Bit Color **
   const short ABEX = 19 ;
   const uint8_t AteBits[ABEX] = { 16, 21, 27, 28, 33, 52, 57, 64, 
                                   69, 82, 87, 124, 129, 141, 196, 201, 202, 213,
                                   226//, 231
                                   } ;
   wp = { short(basePos.row - 1), short(basePos.col + 16) } ;
   gsOut.compose( "   8-bit Color\n"
                  "   (examples)\n" ) ;
   WinPos wpLegend = wp = this->acWrite ( wp, gsOut ) ;
   gsOut.clear() ;
   wp.col += 3 ;
   this->acSetCursor ( wp ) ;
   for ( short atebit = ZERO ; atebit < ABEX ; ++atebit )
   {
      gsOut.append( "%3hhu\n", &AteBits[atebit] ) ;
      this->acSet8bitBg ( AteBits[atebit] ) ;   // set the background color
      this->ttyWrite ( spaceBar ) ;
      this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column
   }
   this->acSetBg ( aesBG_DFLT ) ;            // return to default background
   gsOut.append( "   Range:16-231" ) ;
   this->acWrite ( wpLegend, gsOut ) ;

   //** RGB Color **
   const uint8_t  wsrgbREDD_MAX  = wsrgbGREEN - 1 ;
   const uint8_t  wsrgbGREE_MAX  = wsrgbBLUE - 1 ;
   const uint8_t  wsrgbBLUE_MAX  = wsrgbBROWN - 1 ;
   const uint8_t  wsrgbBROW_MAX  = wsrgbMAGENTA - 1 ;
   const uint8_t  wsrgbMAGE_MAX  = wsrgbCYAN - 1 ;
   const uint8_t  wsrgbCYAN_MAX  = wsrgbGREY - 1 ;
   const uint8_t  wsrgbGREY_MAX  = wsrgbMAX ;
   const uint8_t  wsrgbBlack = wsrgbBLACK ;
   const uint8_t  wsrgbMax   = wsrgbMAX ;

   gString gs ;               // selector formatting
   acaExpand acaEx ;          // calculate RGB register values
   WebSafeRGB hue ;           // RGB color group
   uint8_t    shade = ZERO ;  // "shade" within the specified RGB color group (hue)
   wchar_t    userSel ;       // character for user selection of RGB attribute

   wp = { short(basePos.row - 1), short(wp.col + 13) } ;
   gsOut.compose( "   Web-safe RGB\n"
                  "   (examples)\n"
                  "%3hhu\n"
                  "%3hhu\n"
                  "%3hhu\n"
                  "%3hhu\n"
                  "%3hhu\n"
                  "%3hhu\n"
                  "%3hhu\n"
                  "%3hhu\n"
                  "   Range:%hhu-%hhu\n",
                  &wsrgbBlack,    &wsrgbREDD_MAX,  &wsrgbGREE_MAX, &wsrgbBLUE_MAX,
                  &wsrgbBROW_MAX, &wsrgbMAGE_MAX, &wsrgbCYAN_MAX, &wsrgbGREY_MAX,
                  &wsrgbBlack, &wsrgbMax ) ;
   this->acWrite ( wp, gsOut ) ;
   wp = { short(wp.row + 2), short(wp.col + 3) } ;
   this->acSetCursor ( wp ) ;
   this->acSetFg ( aesFGb_GREY ) ;

   for ( short r = ZERO ; r <= 7 ; ++r )
   {
      switch ( r )
      {
         case 0:  formatRgbString ( gsOut, 13, wsrgbBLACK ) ;
                  hue = wsrgbRED ;     userSel = L'a' ;     break ;
         case 1:  formatRgbString ( gsOut, 13, wsrgbREDD_MAX ) ;
                  hue = wsrgbRED ;     userSel = L'b' ;     break ;
         case 2:  formatRgbString ( gsOut, 13, wsrgbGREE_MAX ) ;
                  hue = wsrgbGREEN ;   userSel = L'c' ;     break ;
         case 3:  formatRgbString ( gsOut, 13, wsrgbBLUE_MAX ) ;
                  hue = wsrgbBLUE ;    userSel = L'd' ;     break ;
         case 4:  formatRgbString ( gsOut, 13, wsrgbBROW_MAX ) ;
                  this->acSetFg ( aesFG_DFLT ) ; userSel = L'e' ; hue = wsrgbBROWN ;   break ;
         case 5:  formatRgbString ( gsOut, 13, wsrgbMAGE_MAX ) ;
                  hue = wsrgbMAGENTA ; userSel = L'f' ;     break ;
         case 6:  formatRgbString ( gsOut, 13, wsrgbCYAN_MAX ) ;
                  hue = wsrgbCYAN ;    userSel = L'g' ;     break ;
         case 7:  formatRgbString ( gsOut, 13, wsrgbGREY_MAX ) ;
                  hue = wsrgbGREY ;    userSel = L'h' ;     break ;
      }
      gs.compose( L"%C: ", &userSel ) ;
      gsOut.insert( gs.gstr(), 1 ) ;
      this->acSetWebBg ( hue, shade ) ;         // set background attribute
      this->ttyWrite ( gsOut ) ;
      this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ; // move cursor to target column
      shade = wsrgbSHADE_MAX ;   // maximum shade for target color group (hue)
   }

   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ; // return to default fgnd/bkgnd

}  //* End twColorChart() *

//*************************
//*     Test_Sandbox      *
//*************************
//********************************************************************************
//* Create ad hoc tests for the AnsiCmd class.                                   *
//* For those who were too lazy to take Latin in high school, "ad hoc" means     *
//* "created or done for a particular purpose as necessary."                     *
//*                                                                              *
//* Input  : parmA : caller defined                                              *
//*          parmB : caller defined                                              *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_Sandbox ( wchar_t parmA, wchar_t parmB )
{
   gString gsOut( "Welcome to the Sandbox!\n" ) ;
   this->ttyWrite ( gsOut ) ;

   //* Create Tests Here... *




   //* --- End of Sandbox Area - Do Not Modify This Line --- **
   this->acSetCursor ( aesCUR_ABSPOS, (this->termRows - 1), 1 ) ;

}  //* End Test_Sandbox() *

//*************************
//*   Test_ReportCurPos   *
//*************************
//********************************************************************************
//* FOR DEBUGGING ONLY:                                                          *
//* Called only by the debugging methods.                                        *
//* Get the current cursor position and report the positon.                      *
//* Then return the cursor position to the REPORTED position.                    *
//*                                                                              *
//* Input  : row  : row for position report                                      *
//*          col  : column for position report                                   *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Test_ReportCurPos ( short row, short col, WinPos *wpos )
{
   //* Get the current cursor position and report it *
   WinPos wp = this->acGetCursor () ;
   gString gsOut( "Cursor Pos: %hd,%hd", &wp.row, &wp.col ) ;
   gsOut.padCols( 20 ) ;
   this->acWrite ( row, col, gsOut ) ;

   //* If caller provided a WinPos object, return cursor coordinates *
   if ( wpos != NULL )
   {
      wpos->row = wp.row ;
      wpos->col = wp.col ;
   }

   this->acSetCursor ( wp ) ;             // return cursor to reported position

}  //* End Test_ReportCurPos() *

//*************************
//*    acDumpTermInfo     *
//*************************
//********************************************************************************
//* FOR DEBUGGING ONLY:                                                          *
//* Display debugging information for terminal I/O settings                      *
//*                                                                              *
//* Input  : tios : (by reference) data to be formatted and displayed            *
//*          row  : row to begin display                                         *
//*          col  : column to begin display                                      *
//*          verb : (optional, 0 by default) level of verbosity                  *
//*                 0 == report decoded bit fields only (default)                *
//*                      This includes the NCCS macro (c_cc array elements),     *
//*                      c_cc[VTIME] and c_cc[VMIN]                              *
//*                 1 == report binary flags only                                *
//*                 2 == report decoded bit fields AND the binary flags          *
//*                 3 == report only class members that track term settings      *
//*                 4 == report class members, binary flags and decoded fields   *
//*          gstit: (optional, null pointer by default)                          *
//*                 if specified, this is a pointer to a gString object          *
//*                 containing a title to be displayed for the data dump         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* 1) 'csize' bits: 0x00==5 bits, 0x01==6 bits, 0x02==7 bits, 0x03==8 bits.     *
//*    Obviously, they never heard of wchar_t characters i.e. byte stream only.  *
//*                                                                              *
//********************************************************************************

void AnsiCmd::acDumpTermInfo ( const TermIos& tios, short row, short col, 
                               short verb, const gString *gstit )
{
   gString gsOut, gsBin ;     // text formatting
   WinPos wp( row, col ) ;     // text position
   short clrRows,             // rows in cleared area
         clrCols ;            // columns in cleared area

   this->acSetCursorStyle ( csHide ) ;    // make cursor invisible

   //* Clear the target area *
   switch ( verb )
   {
      case 0:  clrRows = 12 ; clrCols = 48 ; break ;
      case 1:  clrRows =  4 ; clrCols = 48 ; break ;
      case 2:  clrRows = 16 ; clrCols = 48 ; break ;
      case 3:  clrRows =  3 ; clrCols = 41 ; break ;
      case 4:
      default: clrRows = 19 ; clrCols = 48 ; break ;
   } ;
   if ( gstit != NULL )       // add a row for the title
      ++clrRows ;
   this->acClearArea ( row, col, clrRows, clrCols ) ;

   //* If specified, display the title for the dump *
   if ( gstit != NULL )
      this->acWrite ( wp.row++, wp.col, *gstit, false ) ;

   //* If specified, format and display the bit fields.*
   if ( (verb == 1) || (verb == 2) || (verb == 4) )
   {
      gsBin.compose( L"-----------------%hB", &tios.c_iflag ) ;
      gsOut.compose( "c_iflag: %S\n", gsBin.gstr() ) ;
      wp = this->acWrite ( wp, gsOut, false ) ;

      gsBin.compose( L"-----------------%hB", &tios.c_oflag ) ;
      gsOut.compose( "c_oflag: %S\n", gsBin.gstr() ) ;
      wp = this->acWrite ( wp, gsOut, false ) ;

      gsBin.compose( L"-----------------%hB", &tios.c_cflag ) ;
      gsOut.compose( "c_cflag: %S\n", gsBin.gstr() ) ;
      wp = this->acWrite ( wp, gsOut, false ) ;

      gsBin.compose( L"-----------------%hB", &tios.c_lflag ) ;
      gsOut.compose( "c_lflag: %S\n", gsBin.gstr() ) ;
      wp = this->acWrite ( wp, gsOut, false ) ;
   }

   //* Display the interesting flags in human-readable format.*
   if ( (verb == 0) || (verb == 2) || (verb == 4) )
   {
      const wchar_t * en = L"enabled " ;
      const wchar_t * di = L"disabled" ;
      short csize  = short(((tios.c_cflag & CSIZE) >> 4) + 5) ; // (see note above)
      bool istrip  = (tios.c_iflag & ISTRIP),
           opost   = (tios.c_oflag & OPOST),
           onlcr   = (tios.c_oflag & ONLCR),
           xtabs   = (tios.c_oflag & XTABS),
           cread   = (tios.c_cflag & CREAD),
           icanon  = (tios.c_lflag & ICANON),
           echo    = (tios.c_lflag & ECHO),
           echonl  = (tios.c_lflag & ECHONL),
           echoctl = (tios.c_lflag & ECHOCTL),
           iexten  = (tios.c_lflag & IEXTEN) ;

      gsOut.compose( "istrip : 7-bit input                 %S\n"
                     "opost  : output flags                %S\n"
                     "onlcr  : convert newlines to cr/lf   %S\n"
                     "xtabs  : convert tabs to spaces      %S\n"
                     "cread  : read terminal input         %S\n"
                     "csize  : character width             %hd bits\n"
                     "icanon : enable canonical input      %S\n"
                     "echo   : echo character input        %S\n"
                     "echonl : echo newlines               %S\n"
                     "echoctl: echo control chars as (^x)  %S\n"
                     "iexten : posix extensions            %S\n",
                     (istrip ? en : di),
                     (opost ? en : di),
                     (onlcr ? en : di),
                     (xtabs ? en : di),
                     (cread ? en : di),
                     &csize,
                     (icanon ? en : di),
                     (echo ? en : di),
                     ((echonl && icanon) ? en : di),
                     (echoctl ? en : di),
                     (iexten ? en : di) ) ;
      this->acWrite ( wp, gsOut ) ;
      wp.row += (gstit == NULL ? 10 : 11) ;

      //* Array of control characters *
      short nccs  = NCCS,
            vtime = VTIME,
            vmin  = VMIN ;
      gsOut.compose( "c_cc[%hd] c_cc[VTIME==%hd]:%hhXh c_cc[VMIN==%hd]:%hhXh\n",
                     &nccs, &vtime, &tios.c_cc[VTIME], &vmin, &tios.c_cc[VMIN] ) ;
      wp = this->acWrite ( wp, gsOut ) ;
   }

   if ( (verb == 3) || (verb == 4) )
   {
      char seOpt[16] ;
      wchar_t seoptChar = (this->tset->inpEch == softEchoC ? L'c' :
                           this->tset->inpEch == softEchoD ? L'd' :
                           this->tset->inpEch == softEchoE ? L'e' : L'a' ) ;
      gsOut.compose( "s%C", &seoptChar ) ;
      gsOut.copy( seOpt, 16 ) ;
      gsOut.compose( "inpBuf:%hhd suspCap:%hhd flowCap:%hhd    curType:%hd(%s) \n"
                     "inpBlk:%hhd quitCap:%hhd inpEch :%hd(%s) cchType:%hd(%s)\n",
                     &this->tset->inpBuf, &this->tset->suspCap, &this->tset->flowCap,
                     &this->tset->curStyle,
                     (this->tset->curStyle == csBblock ? "bb" : 
                      this->tset->curStyle == csSblock ? "sb" : 
                      this->tset->curStyle == csBuline ? "bu" :
                      this->tset->curStyle == csSuline ? "su" : 
                      this->tset->curStyle == csBvbar  ? "bv" : 
                      this->tset->curStyle == csSvbar  ? "sv" : "d"),
                     &this->tset->inpBlk,
                     &this->tset->quitCap, 
                     &this->tset->inpEch, 
                     (this->tset->inpEch == termEcho ? "t" : 
                      this->tset->inpEch == noEcho ? "n" : seOpt),
                     &this->tset->cchType,
                     (this->tset->cchType == cchtExit   ? "e" : 
                      this->tset->cchType == cchtUser   ? "u" : 
                      this->tset->cchType == cchtIgnore ? "i" :
                      this->tset->cchType == cchtCopy   ? "c" : "t")
                   ) ;
      this->acWrite ( wp, gsOut ) ;

      #if 0    // Report the characters associated with signals
      wp = { short(row + 1), short(col + 47) } ;
      gsOut.compose( "VINTR  (SIGINT) : %02hhX C+C\n"
                     "VSUSP  (SIGTSTP): %02hhX C+Z\n"
                     "VQUIT  (SIGQUIT): %02hhX C+4\n"
                     "VDSUSP (SIGTSTP): --\n"
                     "VSTART          : %02hhX C+q\n"
                     "VSTOP           : %02hhX C+s\n",
                     &tios.c_cc[VINTR], &tios.c_cc[VSUSP], &tios.c_cc[VQUIT],
                     //&tios.c_cc[VDSUSP],
                     &tios.c_cc[VSTART], &tios.c_cc[VSTOP] ) ;
      this->acWrite ( wp, gsOut ) ;
      #endif   // Signal Characters
   }

   this->acSetCursorStyle ( csShow ) ;    // make cursor visible

}  //* End acDumpTermInfo() *

//*************************
//*     acaExpandDump     *
//*************************
//********************************************************************************
//* FOR DEBUGGING ONLY:                                                          *
//* Dump contents of a acaExpand object -- for debugging only.                   *
//*                                                                              *
//* Input  : da   : (by reference) acaExpand object to be displayed              *
//*          row  : row to begin display                                         *
//*          col  : column to begin display                                      *
//*          gstit: (optional, null pointer by default)                          *
//*                 if specified, this is a pointer to a gString object          *
//*                 containing a title to be displayed for the data dump         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::acaExpandDump ( const acaExpand& da, short row, short col, 
                              const gString *gstit )
{
   const short DUMP_ROWS = 12,
               DUMP_COLS = 35 ;
   gString gsOut ;                     // text formatting
   WinPos  wp( row, col ) ;            // text position

   //* Clear the target area *
   this->acClearArea ( row, col, DUMP_ROWS, DUMP_COLS ) ;

   if ( gstit != NULL )                // if title specified
   {
      gsOut = gstit->gstr() ;          // working copy of title
      gsOut.limitCols( DUMP_COLS ) ;   // truncate if necessary
      gsOut.padCols( DUMP_COLS, wcsSPACE, true ) ;
      // Programmer's Note: We write these ANSI escape sequences directly, so 
      // we don't interfere with the tests in progress.
      this->ttyWrite ( ansiSeq[aesUNDERLINE] ) ;
      this->acWrite ( wp.row++, wp.col, gsOut ) ;
      this->ttyWrite ( ansiSeq[aesUNDERLINE_OFF] ) ;
   }

   gsOut.compose(
         "acaVal  :%08X  fgBits :%08X\n"
         "allFlags:%08X      fgIndex:  %02hhX\n"
         "modFlags:%08X  bgBits :%08X\n"
         "fgDflt   :%hhd bgDflt  :%hhd bgIndex:%02hhX\n"
         "boldMod  :%hhd ulineMod:%hhd invisMod:%hhd\n"
         "italicMod:%hhd olineMod:%hhd revMod  :%hhd\n"
         "xoutMod  :%hhd blinkMod:%hhd clrMods :%hhd\n",
         &da.acaVal,    &da.fgBits,
         &da.allFlags,  &da.fgIndex,
         &da.modFlags,  &da.bgBits,
         &da.fgDflt,    &da.bgDflt,   &da.bgIndex,
         &da.boldMod,   &da.ulineMod, &da.invisMod,
         &da.italicMod, &da.olineMod, &da.revMod,
         &da.xoutMod,   &da.blinkMod, &da.clrMods ) ;
   wp = this->acWrite ( wp, gsOut ) ;

   gsOut.compose(
         "fgRgb    :%hhd:%02hhX,%02hhX    bgRgb:%hhd:%02hhX,%02hhX\n"
         "FgType   :%3hd(%02hXh)  BgType:%3hd(%02hXh)\n"
         "aesFgnd  :%3hd(%02hXh) aesBgnd:%3hd(%02hXh)\n"
         "rgbRegs  :%02hhX,%02hhX,%02hhX rgbRegs:%02hhX,%02hhX,%02hhX\n",
         &da.fgRgb, &da.fgHue, &da.fgShade, &da.bgRgb, &da.bgHue, &da.bgShade, 
         &da.aesFgType, &da.aesFgType, &da.aesBgType, &da.aesBgType,
         &da.aesFgnd,   &da.aesFgnd,   &da.aesBgnd,   &da.aesBgnd,
         &da.rgbFgR,    &da.rgbFgG,    &da.rgbFgB,
         &da.rgbBgR,    &da.rgbBgG,    &da.rgbBgB ) ;
   wp = this->acWrite ( wp, gsOut ) ;

}  //* End acaExpandDump() *

//*************************
//*      skFormDump       *
//*************************
//********************************************************************************
//* Private Method: called by Test_Form.                                         *
//*                                                                              *
//*                                                                              *
//* Input  : skf  : (by reference) skForm object to be displayed                 *
//*          row  : row to begin display                                         *
//*          col  : column to begin display                                      *
//*          gstit: (optional, null pointer by default)                          *
//*                 if specified, this is a pointer to a gString object          *
//*                 containing a title to be displayed for the data dump         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::skFormDump ( const skForm& skf, short row, short col, const gString* gstit )
{
   gString gsOut( "%S -- fCnt:%hd fi:%hd ins:%hhd insc:%hhd togg:%hhd beep:%hhd"
                  "                                    \n",
                  (gstit != NULL ? gstit->gstr() : L"skForm Contents"),
                  &skf.fCnt, &skf.fi, &skf.ins, &skf.insc, &skf.togg, &skf.beep 
                ) ;
   gString gs ;
   this->acSetMod ( aesUNDERLINE ) ;
   this->acWrite ( row, col, gsOut ) ;
   this->acSetMod ( aesUNDERLINE_OFF ) ;
   ++row ; --col ;
   gsOut.clear() ;

   int   lodi =  26 ;         // number of characters from 'txt' to display
   short f    = ZERO ;        // field index

   for ( ; f < skf.fCnt ; ++f )
   {
      gsOut.append( "%2hd) orig:%-2hd/%-2hd hgt:%-2hd wid:%-2hd ip:%-2d\n"
                    "   cur :%-2hd/%-2hd aca:%04Xh ro:%hhd\n",
                    &f, 
                    &skf.fld[f].orig.row, 
                    &skf.fld[f].orig.col,
                    &skf.fld[f].hgt,
                    &skf.fld[f].wid,
                    &skf.fld[f].ip,
                    &skf.fld[f].cur.row,
                    &skf.fld[f].cur.col, 
                    &skf.fld[f].aca.acaVal,
                    &skf.fld[f].ro ) ;
      gsOut.append( L"     \"" ) ;
      if ( (skf.fld[f].txt.gschars()) > 1 )  // load a fixed number of source characters
      {
         gs = skf.fld[f].txt ;
         gs.erase( NEWLINE, ZERO, false, true ) ;
         gsOut.loadChars( gs.gstr(), lodi, true ) ;
      }
      else                                   // text field is empty
         gsOut.append( L"(empty)" ) ;
      gsOut.append( L"\"\n" ) ;

      if ( (f == 5) || (f == 11) || (f == (skf.fCnt - 1)) )
      {
         this->acWrite ( row, col, gsOut ) ;
         gsOut.clear() ;      // move to next display row
         col += 35 ;
      }
   }

}  //* End skFormDump() *

//*************************
//*     acDumpMods        *
//*************************
//********************************************************************************
//* Private Method: called by acSetMod() or acResetMods().                       *
//* Display the text-modifier tracking flags.                                    *
//*                                                                              *
//* Input  : row  : row for position of display                                  *
//*          col  : column for position of display                               *
//*          title: (optional, null pointer by default)                          *
//*                 if specified, display title on first line of display         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note:                                                           *
//* We make some kludgy moves in this method by scanning the provided title      *
//* text to determine the intended interpretation of: 'Bold' (bold vs. faint)    *
//* and 'Blink' (slow vs. fast) flags.                                           *
//*                                                                              *
//* Note that the 'Framed/Encircled' options are seldom if ever supported by     *
//* terminal emulators, so the attrBits object makes no attempt to support       *
//* those options. Instead, we identify them indirectly via the text of the      *
//* title.                                                                       *
//*                                                                              *
//********************************************************************************

void AnsiCmd::acDumpMods ( short row, short col, const wchar_t *title )
{
   gString gsTit( "Modifiers:" ) ;        // Set title
   if ( title != NULL )
   { gsTit = title ; gsTit.limitCols( 13 ) ; }
   bool fast = false, frame = false, circle = false ;

   //* Save the current modifiers *
   bool tmpBoldMod    = this->attrBits.boldMod,
        tmpItalicMod  = this->attrBits.italicMod,
        tmpUlineMod   = this->attrBits.ulineMod,
        tmpOlineMod   = this->attrBits.olineMod,
        tmpSlineMod   = this->attrBits.xoutMod,
        tmpBlinkMod   = this->attrBits.blinkMod,
        tmpInvisMod   = this->attrBits.invisMod,
        tmpRevMod     = this->attrBits.revMod,
        tmpFrameMod   = false ; 
        //* Note: The acaExpand class does not support the obsolete "framed" *
        //* or "encircled" mods, so we query the title. (see note above)     *
        if ( (gsTit.find( L"Framed" )) >= ZERO )
           frame = true ;
        else if ( (gsTit.find( L"Encircle" )) >= ZERO )
           circle = true ;
        if ( frame || circle )
           tmpFrameMod = true ;

   //* Reset all text modifiers for output *
   this->acResetMods () ;

   gString gsOut( "%S\n"
                  "-------------\n"
                  "boldMod   : %hhd\n"
                  "italicMod : %hhd\n"
                  "ulineMod  : %hhd\n"
                  "olineMod  : %hhd\n"
                  "xoutMod   : %hhd\n"
                  "blinkMod  : %hhd\n"
                  "concealMod: %hhd\n"
                  "reverseMod: %hhd\n"
                  "frameMod  : %hhd\n",
                  gsTit.gstr(),
                  &tmpBoldMod, &tmpItalicMod, 
                  &tmpUlineMod, &tmpOlineMod, &tmpSlineMod, 
                  &tmpBlinkMod, &tmpInvisMod, &tmpRevMod, 
                  &tmpFrameMod ) ;

   this->acWrite ( row, col, gsOut ) ;

   //*********************************
   //* Restore caller's mod settings *
   //*********************************
   //*The 'boldMod' flag can indicate either Bold or Faint, but the Faint mod *
   //* is not implemented in any of our test platforms. (see note above)      *
   if ( tmpBoldMod )
   {
      if ( (gsTit.find( L"faint" )) < ZERO )
         this->acSetMod ( aesBOLD ) ;
   }
   //* The 'blinkMod' flag can indicate either Fast Blink or Slow Blink, but *
   //* our test platforms have only one blink speed. (see note above)        *
   if ( tmpBlinkMod )
   {
      fast = bool((gsTit.find( L"fast" )) >= ZERO) ;
      this->acSetMod ( fast ? aesBLINK_FAST : aesBLINK_SLOW ) ;
   }
   //* The 'Framed' and 'Encircled' options share the tmpFrameMod flag. *
   //* Determine which was used.                                        *
   if ( tmpFrameMod )
   {
      this->acSetMod ( frame ? aesFRAMED : aesENCIRCLE ) ;
   }

   if ( tmpItalicMod )     this->acSetMod ( aesITALIC ) ;
   if ( tmpUlineMod )      this->acSetMod ( aesUNDERLINE ) ;
   if ( tmpOlineMod )      this->acSetMod ( aesOVERLINE ) ;
   if ( tmpSlineMod )      this->acSetMod ( aesXOUT ) ;
   if ( tmpInvisMod )      this->acSetMod ( aesCONCEAL ) ;
   if ( tmpRevMod )        this->acSetMod ( aesREVERSE ) ;
   if ( tmpFrameMod )      this->acSetMod ( aesFRAMED ) ;

}  //* End acDumpMods() *

//*************************
//*     acCaptureAttr     *
//*************************
//********************************************************************************
//* Private Method: called by Test_4Bit()                                        *
//* Capture the current state of the color-attribute settings.                   *
//*                                                                              *
//* Input  : gs   : (by reference) receives the formatted attribute data         *
//*          data : specify which data to report                                 *
//*                 1 : 4-bit foreground and background only                     *
//*                 2 : 8-bit foreground and background indices only             *
//*                 3 : RGB foreground and background only                       *
//*                 4 : All options: 1 + 2 + 3                                   *
//*          nl   : 'true'  append a newline character to output                 *
//*                 'false' no newline character                                 *
//*          title: (optional, false by default) if specified format the         *
//*                 title text corresponding to 'data' value                     *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::acCaptureAttr ( gString& gs, short data, bool nl, bool title )
{
   const short NAME_ITEMS = 17 ;
   const wchar_t *FgNames[NAME_ITEMS] = 
   {
      L"aesFG_BLACK    ",
      L"aesFG_RED      ",
      L"aesFG_GREEN    ",
      L"aesFG_BROWN    ",
      L"aesFG_BLUE     ",
      L"aesFG_MAGENTA  ",
      L"aesFG_CYAN     ",
      L"aesFG_GREY     ",
      L"aesFGb_BLACK   ",
      L"aesFGb_RED     ",
      L"aesFGb_GREEN   ",
      L"aesFGb_BROWN   ",
      L"aesFGb_BLUE    ",
      L"aesFGb_MAGENTA ",
      L"aesFGb_CYAN    ",
      L"aesFGb_GREY    ",
      L"aesFG_DFLT     ",
   } ;
   const wchar_t *BgNames[NAME_ITEMS] = 
   {
      L"aesBG_BLACK    ",
      L"aesBG_RED      ",
      L"aesBG_GREEN    ",
      L"aesBG_BROWN    ",
      L"aesBG_BLUE     ",
      L"aesBG_MAGENTA  ",
      L"aesBG_CYAN     ",
      L"aesBG_GREY     ",
      L"aesBGb_BLACK   ",
      L"aesBGb_RED     ",
      L"aesBGb_GREEN   ",
      L"aesBGb_BROWN   ",
      L"aesBGb_BLUE    ",
      L"aesBGb_MAGENTA ",
      L"aesBGb_CYAN    ",
      L"aesBGb_GREY    ",
      L"aesBG_DFLT     ",
   } ;
   const wchar_t *Title1 = L"%S4-Bit Foregnd  %S  %S4-Bit Backgnd  %S" ;
   const wchar_t *Title2 = L"%SFg Index%S  %SBg Index%S" ;
   const wchar_t *Title3 = L"%SR-- G-- B--%S  %SR-- G-- B--%S" ;
   const char    *Title4 = "%S  %S  %S" ;
   const char    *data1Template = "%S  %S" ;
   const char    *data2Template = "  %3hd       %3hd   " ;
   const char    *data3Template = "%-3hd %-3hd %-3hd  %-3hd %-3hd %-3hd" ;
   const char    *data4Template = "%s  %s  %s" ;
   gString gsTemplate ;                // concatenation template
   short fgIndex = (NAME_ITEMS - 1),   // reference "Default"
         bgIndex = (NAME_ITEMS - 1) ;  // reference "Default"

   //* Return formatted column titles *
   if ( title != false )
   {
      if ( data == 1 )
         gs.compose( Title1, ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                             ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF] ) ;
      else if ( data == 2 )
         gs.compose( Title2, ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                             ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF] ) ;
      else if ( data == 3 )
         gs.compose( Title3, ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                             ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF] ) ;
      else
      {
         gsTemplate.compose( Title4, Title1, Title2, Title3 ) ;
         gs.compose( gsTemplate.gstr(), 
                     ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                     ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                     ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                     ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                     ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF],
                     ansiSeq[aesUNDERLINE], ansiSeq[aesUNDERLINE_OFF] ) ;
      }
   }

   //* Return formatted report of attribute flags *
   else
   {
      //* Set the index into the description arrays *
      if ( this->attrBits.aesFgnd == aesFG_DFLT )
         fgIndex = NAME_ITEMS - 1 ;

      else if ( (this->attrBits.aesFgnd >= aesFG_BLACK) &&
              (this->attrBits.aesFgnd < aesFG_DFLT) )
      { fgIndex = this->attrBits.aesFgnd - aesFG_BLACK ; }

      if ( this->attrBits.aesBgnd == aesBG_DFLT )
         bgIndex = NAME_ITEMS - 1 ;
      else if ( (this->attrBits.aesBgnd >= aesBG_BLACK) &&
              (this->attrBits.aesBgnd < aesBG_DFLT) )
      { bgIndex = this->attrBits.aesBgnd - aesBG_BLACK ; }

      if ( data == 1 )           // display 4-bit foreground/background data
      {
         gs.compose( data1Template, FgNames[fgIndex], BgNames[bgIndex] ) ;
      }
      else if ( data == 2 )      // display 8-bit table lookup for fg/bg and/or greyscale
      {
         #if 0    // acCaptureAttr(2) - Currently Unused
         gs.compose( data2Template, &this->fgnd.attrIndex, &this->bgnd.attrIndex ) ;
         #endif   // U/C
      }
      else if ( data == 3 )      // display R/G/B register settings
      {
         #if 0    // acCaptureAttr(3) - Currently Unused
         gs.compose( data3Template,
                     &this->fgnd.rgbR, &this->fgnd.rgbG, &this->fgnd.rgbB, 
                     &this->bgnd.rgbR, &this->bgnd.rgbG, &this->bgnd.rgbB ) ;
         #endif   // U/C
      }
      else                       // display all data
      {
         gsTemplate.compose( data4Template, data1Template, 
                             data2Template, data3Template ) ;
         #if 0    // acCaptureAttr(4) - Currently Unused
         gs.compose( gsTemplate.gstr(), 
                     FgNames[fgIndex], BgNames[bgIndex], 
                     &this->fgnd.attrIndex, &this->bgnd.attrIndex, 
                     &this->fgnd.rgbR, &this->fgnd.rgbG, &this->fgnd.rgbB, 
                     &this->bgnd.rgbR, &this->bgnd.rgbG, &this->bgnd.rgbB ) ;
         #endif   // U/C
      }
   }

   if ( nl )
      gs.append( L'\n' ) ;

}  //* End acCaptureAttr() *

//*************************
//*    formatRgbString    *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//*                                                                              *
//* Format display text containing RGB register indices. Called by test          *
//* methods which display RGB indices.                                           *
//*                                                                              *
//* Input  : gsOut   : (by reference)                                            *
//*          colWidth: column width of constructed string                        *
//*          webIndex: index indicating R/G/B register combination used for      *
//*                    call to web2regs().                                       *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void formatRgbString ( gString& gsOut, short colWidth, uint8_t webIndex )
{
   acaExpand acax ;                    // instance of class for bitfield parsing

   acax.web2regs( webIndex, false ) ;  // get RGB register combination
   gsOut.compose( " %hhu;%hhu;%hhu",   // format the data string
                  &acax.rgbBgR, &acax.rgbBgG, &acax.rgbBgB ) ;
   gsOut.padCols( colWidth ) ;         // pad to column width
   gsOut.append( L'\n' ) ;             // cursor ends at column 1 of next row

}  //* End formatRgbString() *

//*************************
//*                       *
//*************************
//********************************************************************************
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************


//** Compilation of the entire module is controlled by this statement. **
#endif   // DEBUG_ANSICMD && DEBUG_ANSICMD_TEST && INCLUDE_API
