//********************************************************************************
//* File       : AcEditFile.cpp                                                  *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2023-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice in AcEdit.cpp.                      *
//* Date       : 02-Apr-2026                                                     *
//* Version    : (see AppVersion string)                                         *
//*                                                                              *
//* Description: Support file for AcEdit class.                                  *
//*              1) Parse command-line arguments.                                *
//*              2) Scan the configuration file.                                 *
//*                                                                              *
//********************************************************************************

//*****************
//* Include Files *
//*****************
#include "AcEdit.hpp"         //* Application-class definition

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

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

//* Message indices for call to gclaFmtErrmsg. *
enum FEMVAL : short
{
   femSTAT_ERR = ZERO,     // file stat error (not found, etc.)
   femACC_ERR,             // read/write access error
   femNOT_REG,             // target is not a 'regular' file
   femNOT_TXT,             // target is not a 'plain-text' file
   femFILE_MAX,            // maximum number of data files exceeded
   femLOC_ERR,             // unsupported language or locale
   femNOT_IMPL,            // option not yet implemented
   femOPT_UNK,             // unknown command-line option
   femOPT_VAL,             // command-line option value out-of-range
   femMSG_CNT              // number of message options
} ;

//* Message indices for call to gclaFmtErrmsg. *
enum CFGVAL : short
{
   cfgNOT_FND = ZERO,      // config file not found
   cfgFMT_ERR,             // invalid config file format
   cfgACC_ERR,             // file read/write access error (or not a plain text file)
   cfgTYP_ERR,             // file not 'regular' and/or plain text
   cfgOPT_UNK,             // unrecognized config option
   cfgOPT_VAL,             // value out of range

   cfgMSG_CNT              // number of message options
} ;

static const char* const attrTemplate = " %C%hd:%C%hd" ; // color attribute decoding
static UILang tuiLang = uilENV ; // temporary language used during startup

//************************
//** Shortcut Key Group **
//************************
const char* const ShortkeyName[SHORT_KEYS] = // shortcut key labels
{
   "Key_Find",       // Open Find-text dialog
   "Key_FindNext",   // Find next occurance of search substring
   "Key_FindPrev",   // Find previous occurance of search substring
   "Key_GotoLine",   // Open dialog to get target line number
   "Key_ShiftUp",    // Block shift text upward
   "Key_ShiftDown",  // Block shift text downward
   "Key_PairMatch",  // Go to matching element of pair: '()', '[]', '{}', '<>'
   "Key_TagMatch",   // Go to matching open/close tag (HTML/XML)
   "Key_MenuBar",    // Invoke the application menu (menu bar)
   "Key_NextField",  // Move input focus to next _visible_ text field (if any)
   "Key_PrevField",  // Move input focus to previous _visible_ text field (if any)
   "Key_NextBuffer", // Load current text field with data from next buffer (if any)
   "Key_PrevBuffer", // Load current text field with data from previous buffer (if any)
   "Key_BufferList", // Display a list of open buffers (filenames)
   "Key_OpenFile",   // Select a file to be opened for edit
   "Key_NewFile",    // Create a  new file to be opened for edit
   "Key_Macro",      // Invoke a defined macro or list all macros
                     // Create additional macros here...
   "Key_FieldSingle",// Go to Field (single field: fiTEXTfw)
   "Key_FieldDualH", // Go to Field (dual horizontal fields: fiTEXThu)
   "Key_FieldDualV", // Go to Field (dual vertical fields: fiTEXTvl)
} ;

const char* const ShortkeyDesc[SHORT_KEYS] = // shortcut key descriptions
{
   "CTRL+F    wcsCTL_F",
   "CTRL+G    wcsCTL_G",
   "CTRL+R    wcsCTL_R",
   "CTRL+L    wcsCTL_L",
   "ALT+/     wcsALT_FS",
   "ALT+'     wcsALT_SQ",
   "ALT+]     wcsALT_RS",
   "ALT+[     wcsALT_LS",
   "F01       wcsF01",
   "ALT+PgDn  wcsAPGDN",
   "ALT+PgUp  wcsAPGUP",
   "C+A+PgDn  wcsCAPGDN",
   "C+A+PgUP  wcsCAPGUP",
   "ALT+B     wcsALT_B",
   "CTRL+O    wcsCTL_O",
   "CTRL+N    wcsCTL_N",
   "ALT+M     wcsALT_M",
   // Create additional macros here...
   "ALT+1     wcsALT_1",
   "ALT+2     wcsALT_2",
   "ALT+3     wcsALT_3",
} ;

const wchar_t ShortkeyDflt[SHORT_KEYS] = // shortcut key default keycode values
{
   wcsCTL_F,   // Key_Find
   wcsCTL_G,   // Key_FindNext
   wcsCTL_R,   // Key_FindPrev
   wcsCTL_L,   // Key_GotoLine
   wcsALT_FS,  // Key_ShiftUp
   wcsALT_SQ,  // Key_ShiftDown
   wcsALT_RS,  // Key_PairMatch
   wcsALT_LS,  // Key_TagMatch
   wcsF01,     // Key_MenuBar
   wcsAPGDN,   // Key_NextField
   wcsAPGUP,   // Key_PrevField
   wcsCAPGDN,  // Key_NextBuffer (wcsADOWN is also accepted)
   wcsCAPGUP,  // Key_PrevBuffer (wcsAUP is also accepted)
   wcsALT_B,   // Key_BufferList
   wcsCTL_O,   // Key_OpenFile
   wcsCTL_N,   // Key_NewFile
   wcsALT_M,   // Key_Macro
               // Create additional macros here...
   wcsALT_1,   // Key_FieldSingle (or wcsALT_0)
   wcsALT_2,   // Key_FieldDualH
   wcsALT_3,   // Key_FieldDualV
} ;

//********************
//* Local prototypes *
//********************
static void gclaFmtErrmsg ( FEMVAL fval, gString& gsErr, 
                            const char* fname = NULL, short errNum = ZERO ) ;
static void configFmtErrmsg ( CFGVAL cval, gString& gsErr, const char* item = NULL ) ;
static bool cfgGetValue ( const gString& gsIn, short& value ) ;
static bool cfgGetColor ( const gString& gsIn, acAttr& newAttr ) ;
static short cfgGetBool ( const gString& gsIn ) ;
extern bool linenumMatch ( fldIndex srcIndx, fldIndex& matchIndx ) ;


//*************************
//*     DefineWindow      *
//*************************
//********************************************************************************
//* Define the position, dimensions, and other attributes of the main window.    *
//*                                                                              *
//* 1) The position of the main window (upper-left corner) may be anywhere       *
//*    within the terminal window that allows for the specified dimensions.      *
//*    This is a 1-based offset from the upper left corner of the terminal       *
//*    window.                                                                   *
//*    a) Default position is row 1, column 1. This is the upper left corner     *
//*       of the terminal window.                                                *
//*    b) Special value: A position value of zero (0) indicates that the window  *
//*       is to be centered in the terminal window for that direction.           *
//*       Example: 1,0  indicates that the window is to be centered horizontally,*
//*                0,1  indicates that the window is to be certered vertically.  *
//*    c) Special value: A position value of minus one (-1) indicates that the   *
//*       window is to be positioned at the maximum value for that direction.    *
//*       Example: 1,-1 indicates that the window is to be positioned with the   *
//*                     right edge against the right side of the terminal window.*
//*                -1,1 indicates that the window is to be positioned with the   *
//*                     bottom edge against the bottom of the terminal window.   *
//* 2) The main window may be any size between the defined minimum (HGT_MIN and  *
//*    WID_MIN) and the dimensions of the terminal window.                       *
//*    a) The default dimensions (HGT_DFLT and WID_DFLT) will be used if no      *
//*       dimensions are specified.                                              *
//*    b) A specified dimension larger than the dimension of the terminal window *
//*       will automatically be adjusted to the terminal window dimension.       *
//*       Example: For a terminal window of 36 rows by 150 columns, a specified  *
//*       height of 99 rows will be adjusted to 36 rows, and a specified width   *
//*       of 199 columns will be adjusted to 150 columns.                        *
//*                                                                              *
//* Input  : ca     : (by reference) copy of command-line arguments              *
//*                                                                              *
//* Returns: 'true'  if successful, else 'false'                                 *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* 1) Menu height is dynamically calculated from the number of text rows for    *
//*    that menu. (In the biz, this is what is known as "data driven" design.)   *
//* 2) Menu width is dynamically calculated from the number of columns in the    *
//*    first row of text in that menu. (ditto!)                                  *
//* 3) Color attributes are read from the configuration file and applied to the  *
//*    window definition.                                                        *
//*    a) One attribute: ca.hilAttr is not used in the window definition.        *
//*       but is used by the application for other purposes.                     *
//*       This is stored in the 'hilAttr' member variable.                       *
//*    b) The app window interior attribute is not associated with a             *
//*       configuration option. This value is reserved for sub-dialog            *
//*       interiors and misc items.                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

bool AcEdit::DefineWindow ( commArgs& ca )
{
   #define DEBUG_DEFWIN (0)   // debug DefineWindow() method using sample text

   static const char* MenuBar_Text[uilENV] =
   {
      "   FILE   EDIT   VIEW   UTIL   HELP",    // English
      "   FILE   EDIT   VIEW   UTIL   HELP",    // Zhongwen
   } ;

   static const char* FileMenu_Text[uilENV] = 
   {
      {                                         // English
         "^New                (C-n)\n"
         "^Open               (C-o)\n"
         "^Recent Files...        >\n"
         "^Close              (C-w)\n"
         "^Save               (C-s)\n"
         "Save ^As...             >\n"
         "^Print              (C-p)\n"
         "E^xit               (C-q)\n"
      },
      {                                         // Zhongwen
         "新文件          ^N  (C-n)\n"
         "打开文件        ^O  (C-o)\n"
         "列出最近的文件  ^R      >\n"
         "关闭文件        ^C  (C-w)\n"
         "保存文件        ^S  (C-s)\n"
         "重命名并保存    ^A      >\n"
         "打印            ^P  (C-p)\n"
         "出口            ^X  (C-q)\n"
      },
   } ;

   static const char* EditMenu_Text[uilENV] = 
   {
      {                                         // English
      "Copy Selection   (C-^c)\n"
      "Cut Selection    (C-^x)\n"
      "Paste Clipboard  (C-^v)\n"
      "Find Text        (C-^f)\n"
      "Find Again (fwd) (C-^g)\n"
      "Find Again (rev) (C-^r)\n"
      "Go To Line       (C-^l)\n"
      },
      {                                         // Zhongwen
      "复制选择         (C-^c)\n"
      "剪切选择         (C-^x)\n"
      "粘贴剪贴板数据   (C-^v)\n"
      "查找文本         (C-^f)\n"
      "再次查找（向前） (C-^g)\n"
      "再次查找（反向） (C-^r)\n"
      "跳转到指定行     (C-^l)\n"
      },
   } ;

   static const char* ViewMenu_Text[uilENV] = 
   {
      {                                         // English
         "Single Window   (A-1)\n"
         "Dual Horizontal (A-2)\n"
         "Dual Vertical   (A-3)\n"
         "Next Window  (A-PgDn)\n"
         "Prev Window  (A-PgUp)\n"
         "Next Buffer (CA-PgDn)\n"
         "Prev Buffer (CA-PgUp)\n"
         "View Open Files (A-b)\n"
         "View File Stats      \n"
      },
      {                                         // Zhongwen
         "Single Window   (A-1)\n"
         "Dual Horizontal (A-2)\n"
         "Dual Vertical   (A-3)\n"
         "Next Window  (A-PgDn)\n"
         "Prev Window  (A-PgUp)\n"
         "Next Buffer (CA-PgDn)\n"
         "Prev Buffer (CA-PgUp)\n"
         "View Open Files (A-b)\n"
         "View File Stats      \n"
      },
   } ;

   static const char* UtilMenu_Text[uilENV] =
   {
      {                                         // English
         "^Macros                  \n"
         "Compare ^Files           \n"
         "^Clipboard Info.         \n"
         "^Terminal Status         \n"
         "C^onfiguration           \n"
      },
      {                                         // Zhongwen
         "宏定义                ^M \n"
         "比较文件              ^F \n"
         "剪贴板信息            ^C \n"
         "终端状态              ^T \n"
         "配置                  ^O \n"
      },
   } ;

   static const char* HelpMenu_Text[uilENV] =
   {
      {                                         // English
         "Info Docs  (Sh+F01)\n"
         "HTML Docs          \n"
         "^Key Bindings       \n"
         "Help ^About         \n"
      },
      {                                         // Zhongwen
         "Info Docs  (Sh+F01)\n"
         "HTML Docs          \n"
         "^Key Bindings       \n"
         "Help ^About         \n"
      },
   } ;

   //* Menu dimensions. Includes left, right and bottom   *
   //* borders. Width _does not_ include hotkey indicator *
   //* characters ('^').                                  *
   const short fiM_FILE_rows = 9,  fiM_FILE_cols = 26,
               fiM_EDIT_rows = 8,  fiM_EDIT_cols = 24,
               fiM_VIEW_rows = 10, fiM_VIEW_cols = 23,
               fiM_UTIL_rows = 6,  fiM_UTIL_cols = 26,
               fiM_HELP_rows = 5,  fiM_HELP_cols = 21 ;

   const short LN_WID = 4 ;         // line-number field width (four digits <= 9999)
   const wchar_t menuGrp = L'a' ;   // menu group ID

   //* Application Default Color Attributes *
   acaExpand acax ;
   const acAttr brdrDflt = acAttr(acaFGb_GREY | acaBG_BLUE) ;   // app border attributes
   const acAttr intrDflt = acAttr(acaFG_INDEX | acaBOLD | acaBG_BLACK) ; // app window interior attributes
   const acAttr dilgDflt = acax.build8Bit( false, min8BIT, true, 45  ) ; // dialog window attributes
   const acAttr textDflt = acax.build8Bit( true, min8BIT, true, max8BIT ) ; // edit field attributes
   const acAttr hiltDflt = acax.build8Bit( true, min8BIT, true, 193 ) ; // edit window cursor line
   const acAttr menuDflt = acAttr(acaFGb_BROWN | acaBGb_BLUE) ; // menu color attributes
   const acAttr gutrDflt = acAttr(acaFG_BLACK | acaBG_GREY) ;   // aux fields attributes
   const acAttr termDflt = acAttr(acaFG_DFLT | acaBG_DFLT) ;    // terminal window attributes

   //* If attribute not specified in config file or *
   //* on command line, use default.                *
   if ( ca.bdrAttr == acaUSEDFLT ) { ca.bdrAttr = brdrDflt ; }
   if ( ca.intAttr == acaUSEDFLT ) { ca.intAttr = intrDflt ; }
   if ( ca.txtAttr == acaUSEDFLT ) { ca.txtAttr = textDflt ; }
   if ( ca.hilAttr == acaUSEDFLT ) { ca.hilAttr = hiltDflt ; }
   if ( ca.dlgAttr == acaUSEDFLT ) { ca.dlgAttr = dilgDflt ; }
   if ( ca.mnuAttr == acaUSEDFLT ) { ca.mnuAttr = menuDflt ; }
   if ( ca.auxAttr == acaUSEDFLT ) { ca.auxAttr = gutrDflt ; }
   if ( ca.trmAttr == acaUSEDFLT ) { ca.trmAttr = termDflt ; }

   //* Initialize general-purpose attributes.                      *
   //* These attributes are not associated with a particular field.*
   this->hilAttr = ca.hilAttr ;
   this->dlgAttr = ca.dlgAttr ;
   this->auxAttr = ca.auxAttr ;

   //* Create a TermConfig object with default settings.    *
   //* Member        Default     Member         Default     *
   //* ------------  --------    -------------  --------    *
   //* 'termStream'  stdIn       'foreground'   aesFG_DFLT  *
   //* 'background'  aesBG_DFLT  'nonStandard'  false       *
   //* 'cursorStyle' csDflt      'localeName'   ""          *
   //* ---------------------------------------------------- *
   //* If a non-default locale was specified, copy the      *
   //* 'localeName' member to the terminal configuration    *
   //* object. If alternate locale name is not specified,   *
   //* the empty string indicates that locale is to be      *
   //* taken from the terminal environment.                 *
   TermConfig tConfig ;
   if ( *ca.locale != NULLCHAR )
   { gString gs( ca.locale ) ; gs.copy( tConfig.localeName, LOCALE_LEN ) ; }

   WinPos wpul( ZERO, ZERO ) ;            // field positioning
   short termRows, termCols ;             // terminal window dimensions
   gString gsHLine ;                      // text of horizontal divider
   bool status = false ;                  // return value

   //* Get the terminal window dimensions *
   AnsiCmd *ansi = new AnsiCmd ;
   ansi->acGetTermDimensions ( termRows, termCols ) ;
   delete ansi ;        // delete the AnsiCmd object

   //* Validate command-line parameters *
   if ( ca.winHgt < HGT_MIN )       { ca.winHgt = HGT_MIN ;  }
   if ( ca.winHgt > termRows )      { ca.winHgt = termRows ; }
   if ( ca.winWid < WID_MIN )       { ca.winWid = WID_MIN ;  }
   if ( ca.winWid > termCols )      { ca.winWid = termCols ; }
   if ( ca.winRow > ZERO )
   {
      if ( (ca.winRow + ca.winHgt - 1) > termRows )
         ca.winRow = termRows - ca.winHgt + 1 ;
   }
   else if ( ca.winRow == ZERO )
      ca.winRow = 1 + ca.winHgt / 2 ;
   else     // (ca.winRow < ZERO)
      ca.winRow = termRows - ca.winHgt ;
   if ( ca.winCol > ZERO )
   {
      if ( (ca.winCol + ca.winWid - 1) > termCols )
         ca.winCol = termCols - ca.winWid + 1 ;
   }
   else if ( ca.winCol == ZERO )
      ca.winCol = 1 + ca.winWid / 2 ;
   else     // (ca.winCol < ZERO)
      ca.winCol = termCols - ca.winWid ;

   //* Calculate edit field dimensions and positions.    *
   //* The 'hdif' and 'wdif' flags, if set, indicate     *
   //* whether horizontal field pair and vertical field  *
   //* pair, respectively, will be different sizes.      *
   const bool  hdif = bool(((ca.winHgt - 3) % 2) == ZERO) ;
   const bool  wdif = bool(((ca.winWid) % 2) != ZERO) ;
   const short fiTXThgt  = ca.winHgt - 3 ;            // fiTEXTfw (full-window text field
   const short fiTXTwid  = ca.winWid - LN_WID - 2 ;
   const WinPos fiTXTpos( ZERO, LN_WID ) ;

   const short fiTHUhgt  = fiTXThgt / 2 ;             // Horizontal, upper field
   const short fiTHUwid  = fiTXTwid ;
   const WinPos fiTHUpos( fiTXTpos ) ;

   const short fiTHLhgt  = fiTHUhgt - (hdif ? 1 : 0) ;// Horizontal, lower field
   const short fiTHLwid  = fiTXTwid ;
   const WinPos fiTHLpos( short(fiTHUpos.row + fiTHUhgt + 1), fiTHUpos.col) ;

   //* Construct text of horizontal divider field. *
   gsHLine.clear();
   gsHLine.padCols( fiTXTwid, wcsHORIZs ) ;

   const short fiTVLhgt  = fiTXThgt ;                 // Vertical, left field
   const short fiTVLwid  = fiTXTwid / 2 - LN_WID / 2 + (wdif ? 1 : 0) ;
   const WinPos fiTVLpos( fiTXTpos ) ;

   const short fiTVRhgt  = fiTVLhgt ;                 // Vertical, right field
   const short fiTVRwid  = fiTVLwid - (wdif ? 1 : 0) ;
   const WinPos fiTVRpos( fiTVLpos.row, short(fiTVLpos.col + fiTVLwid + LN_WID) ) ;

   //* Construct the window title.*
   gString gsTitle( titleTemplate, appVersion, crYears ) ;
   gsTitle.insert( L' ' ) ; gsTitle.append( L' ' ) ;

   //* Save the Tab Emulation width *
   this->tabWid = ca.tabWid ;

   //******************************************************
   //* Define the fields: see enum fldIndex.              *
   //* Allocate a TEMPORARY skForm object which serves as *
   //* a parameter for instantiation of the window, below.*
   //******************************************************
   skForm *skf = new skForm
   (
      fiFIELDS,
      new skField[fiFIELDS]
      {
         //*****************************************
         //* Line number field: index 0: fiLINEvl  *
         //* Textbox (ftTB)                        *
         //*****************************************
         {
            ftTB,                         // field type
            short(ca.winHgt - 3),         // field height (in rows)
            LN_WID,                       // field width (in columns)
            ZERO, ZERO,                   // Y/X offset in window
            "",                           // field text
            ca.auxAttr,                   // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (cursor offset)
            true,                         // read-only flag is 'set'
            false,                        // field outline disabled
            false,                        // hidden flag is 'reset'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************
         //* Line number field: index 1: fiLINEvr  *
         //* Textbox (ftTB)                        *
         //*****************************************
         {
            ftTB,                         // field type
            short(ca.winHgt - 3),         // field height (in rows)
            LN_WID,                       // field width (in columns)
            ZERO, short(LN_WID + fiTVLwid),  // Y/X offset in window
            "",                           // field text
            ca.auxAttr,                   // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (cursor offset)
            true,                         // read-only flag is 'set'
            false,                        // field outline disabled
            true,                         // hidden flag is 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************
         //* Line number field: index 2: fiLINEhu  *
         //* Textbox (ftTB)                        *
         //*****************************************
         {
            ftTB,                         // field type
            fiTHUhgt,                     // field height (in rows)
            LN_WID,                       // field width (in columns)
            ZERO, ZERO,                   // Y/X offset in window
            "",                           // field text
            ca.auxAttr,                   // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (cursor offset)
            true,                         // read-only flag is 'set'
            false,                        // field outline disabled
            true,                         // hidden flag is 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************
         //* Line number field: index 3: fiLINEhl  *
         //* Textbox (ftTB)                        *
         //*****************************************
         {
            ftTB,                         // field type
            short(fiTHLhgt + 1),          // field height (in rows)
            LN_WID,                       // field width (in columns)
            short(fiTHLpos.row - 1), ZERO,// Y/X offset in window
            "",                           // field text
            ca.auxAttr,                   // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (cursor offset)
            true,                         // read-only flag is 'set'
            false,                        // field outline disabled
            true,                         // hidden flag is 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                         // binary state: 'reset'
            //NULLCHAR,                      // no hotkey specified
            //noGROUP                        // not a group member
         },

         //******************************************
         //* Primary Text field : index 4: fiTEXTfw *
         //* Textbox (ftTB)                         *
         //******************************************
         {
            ftTB,                         // field type
            fiTXThgt,                     // field height (in rows)
            fiTXTwid,                     // field width (in columns)
            fiTXTpos.row, fiTXTpos.col,   // Y/X offset in window
            "",                           // field text
            ca.txtAttr,                   // 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'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************************
         //* Vertical Left Text field : index 5: fiTEXTvl      *
         //* Textbox (ftTB)                                    *
         //*****************************************************
         {
            ftTB,                         // field type
            fiTVLhgt,                     // field height (in rows)
            fiTVLwid,                     // field width (in columns)
            fiTVLpos.row, fiTVLpos.col,   // Y/X offset in window
            "",                           // field text
            ca.txtAttr,                   // color attributes
            floShift,                     // text formatting option
            ZERO,                         // insertion point (cursor offset)
            false,                        // read-only flag is 'reset'
            false,                        // field outline disabled
            true,                         // hidden flag is initially 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************************
         //* Vertical Right Text field : index 6: fiTEXTvr     *
         //* Textbox (ftTB)                                    *
         //*****************************************************
         {
            ftTB,                         // field type
            fiTVRhgt,                     // field height (in rows)
            fiTVRwid,                     // field width (in columns)
            fiTVRpos.row, fiTVRpos.col,   // Y/X offset in window
            "",                           // field text
            ca.txtAttr,                   // color attributes
            floShift,                     // text formatting option
            ZERO,                         // insertion point (cursor offset)
            false,                        // read-only flag is 'reset'
            false,                        // field outline disabled
            true,                         // hidden flag is initially 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************************
         //* Horizontal Upper Text field : index 7: fiTEXThu   *
         //* Textbox (ftTB)                                    *
         //*****************************************************
         {
            ftTB,                         // field type
            fiTHUhgt,                     // field height (in rows)
            fiTHUwid,                     // field width (in columns)
            fiTHUpos.row, fiTHUpos.col,   // Y/X offset in window
            "",                           // field text
            ca.txtAttr,                   // color attributes
            floShift,                     // text formatting option
            ZERO,                         // insertion point (cursor offset)
            false,                        // read-only flag is 'reset'
            false,                        // field outline disabled
            true,                         // hidden flag is initially 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************************
         //* Horizontal Lower Text field : index 8: fiTEXThl   *
         //* Textbox (ftTB)                                    *
         //*****************************************************
         {
            ftTB,                         // field type
            fiTHLhgt,                     // field height (in rows)
            fiTHLwid,                     // field width (in columns)
            fiTHLpos.row, fiTHLpos.col,   // Y/X offset in window
            "",                           // field text
            ca.txtAttr,                   // color attributes
            floShift,                     // text formatting option
            ZERO,                         // insertion point (cursor offset)
            false,                        // read-only flag is 'reset'
            false,                        // field outline disabled
            true,                         // hidden flag is initially 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************
         //* Horizontal divider: index 9: fiHDIV   *
         //* Textbox (ftTB)                        *
         //*****************************************
         {
            ftTB,                         // field type
            1,                            // field height (in rows)
            fiTXTwid,                     // field width (in columns)
            short(fiTHLpos.row - 1),      // Y/X offset in window
            fiTXTpos.col,
            gsHLine.ustr(),               // field text
            ca.auxAttr,                   // color attributes
            floShift,                     // text formatting option
            ZERO,                         // insertion point (cursor offset)
            true,                         // read-only flag is 'set'
            false,                        // field outline disabled
            true,                         // hidden flag is 'set'
            false,                        // visibility lock disabled
            // Remaining members receive default values.
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************
         //* Gutter field: index 10: fiGUTTER      *
         //* Textbox (ftTB)                        *
         //*****************************************
         {
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(ca.winWid - 2),         // field width (in columns)
            short(ca.winHgt - 3), ZERO,   // Y/X offset in window
            "",                           // field text
            // Remaining members receive default values.
            ca.auxAttr,                   // 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,                       // locked in visible state
            //false,                      // binary state: 'reset'
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //*****************************************
         //* Menubar field: index 11: fiMENUBAR    *
         //* Menubox (ftMB)                        *
         //*****************************************
         {
            ftMB,                         // field type
            1,                            // field height (in rows)
            short(ca.winWid - LN_WID - 2),// field width (in columns)
            short(wpul.row = ZERO),       // Y/X offset in window
            short(wpul.col = LN_WID),
            MenuBar_Text[this->lang],     // field text
            acAttr(ca.mnuAttr | acaUNDERLINE),// color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (dynamically calculated)
            false,                        // read-only flag is 'reset'
            false,                        // field outline disabled
            true,                         // hidden flag is 'set'
            false,                        // not locked in visible state
            false,                        // binary state: 'reset' (n/a)
            wcsF01,                       // menubar hotkey
            menuGrp,                      // menu group
         },

         //*****************************************
         //* File Menu field: index 12: fiM_FILE   *
         //* Menubox (ftMB)                        *
         //*****************************************
         {
            ftMB,                         // field type
            fiM_FILE_rows,                // field height (in rows)
            fiM_FILE_cols,                // field width (in columns)
            short(wpul.row += 1),         // Y/X offset in window
            short(wpul.col += 2),
            FileMenu_Text[this->lang],    // field text
            ca.mnuAttr,                   // 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'
            false,                        // not locked in visible state
            false,                        // binary state: 'reset'
            wcsALT_F,                     // hotkey activates menu
            menuGrp,                      // menu group
         },

         //*****************************************
         //* Edit Menu field: index 13: fiM_EDIT   *
         //* Menubox (ftMB)                        *
         //*****************************************
         {
            ftMB,                         // field type
            fiM_EDIT_rows,                // field height (in rows)
            fiM_EDIT_cols,                // field width (in columns)
            wpul.row,                     // Y/X offset in window
            short(wpul.col += 7),
            EditMenu_Text[this->lang],    // field text
            ca.mnuAttr,                   // 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'
            false,                        // not locked in visible state
            false,                        // binary state: 'reset'
            wcsALT_E,                     // hotkey activates menu
            menuGrp,                      // menu group
         },

         //*****************************************
         //* View Menu field: index 14: fiM_VIEW   *
         //* Menubox (ftMB)                        *
         //*****************************************
         {
            ftMB,                         // field type
            fiM_VIEW_rows,                // field height (in rows)
            fiM_VIEW_cols,                // field width (in columns)
            wpul.row,                     // Y/X offset in window
            short(wpul.col += 7),
            ViewMenu_Text[this->lang],    // field text
            ca.mnuAttr,                   // 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'
            false,                        // not locked in visible state
            false,                        // binary state: 'reset'
            wcsALT_V,                     // hotkey activates menu
            menuGrp,                      // menu group
         },

         //*****************************************
         //* Util Menu field: index 15: fiM_UTIL   *
         //* Menubox (ftMB)                        *
         //*****************************************
         {
            ftMB,                         // field type
            fiM_UTIL_rows,                // field height (in rows)
            fiM_UTIL_cols,                // field width (in columns)
            wpul.row,                     // Y/X offset in window
            short(wpul.col += 7),
            UtilMenu_Text[this->lang],    // field text
            ca.mnuAttr,                   // 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'
            false,                        // not locked in visible state
            false,                        // binary state: 'reset'
            wcsALT_U,                     // hotkey activates menu
            menuGrp,                      // menu group
         },

         //*****************************************
         //* Help Menu field: index 16: fiM_HELP   *
         //* Menubox (ftMB)                        *
         //*****************************************
         {
            ftMB,                         // field type
            fiM_HELP_rows,                // field height (in rows)
            fiM_HELP_cols,                // field width (in columns)
            wpul.row,                     // Y/X offset in window
            short(wpul.col += 7),
            HelpMenu_Text[this->lang],    // field text
            ca.mnuAttr,                   // 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'
            false,                        // not locked in visible state
            false,                        // binary state: 'reset'
            wcsALT_H,                     // hotkey activates menu
            menuGrp,                      // menu group
         },
      },
      //* Initialize remaining skForm parameters to default values.*
      fiTEXTfw        // 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 window object *
   this->win = new ACWin ( ca.winRow,        // vertical offset
                           ca.winCol,        // horizontal offset
                           ca.winHgt,        // height (rows)
                           ca.winWid,        // width (columns)
                           ca.winBdr,        // border style
                           ca.bdrAttr,       // border attributes
                           ca.intAttr,       // interior attributes
                           gsTitle.ustr(),   // window title
                           skf,              // skForm initialization
                           &tConfig          // custom terminal configuration
                                             // (defaults except locale name)
                         ) ;

   delete skf ;                        // release the dynamic allocation

   if ( this->win != NULL )            // if window opened successfully
   {
      status = true ;                  // return the good news

      #if DEBUG_ACE != 0 && DEBUG_DEFWIN != 0
      if ( !(ca.helpFlag || ca.verFlag) )
      {
         this->win->WinInfo ( this->wInfo ) ;
         this->dumpWinInfo ( 2, 99, &ofsdbg ) ;
      }
      #endif   // DEBUG_ACE && DEBUG_DEFWIN
   }

   return status ;

   #undef DEBUG_DEFWIN     // debug DefineWindow() method
}  //* End DefineWindow() *

//*************************
//*  GetCommandLineArgs   *
//*************************
//********************************************************************************
//* Capture user's command-line arguments.                                       *
//* Valid Arguments: see DisplayHelp() method.                                   *
//*                                                                              *
//*                                                                              *
//* Input  : ca    : (by reference) contains command-line arguments and the      *
//*                  application's copy of the terminal environment.             *
//*                     argCount : number of elements in 'argList' array         *
//*                     argList  : array of command-line arguments               *
//*                     argEnv   : array of environment variables                *
//*                  On return, it contains the decoded command-line parameters. *
//*                                                                              *
//* Returns: member of enum clArgs indicating the action to be performed         *
//********************************************************************************
//* Programmer's Note: Even though it is not documented, we accept both          *
//* lowercase and uppercase option and sub-option characters where possible.     *
//* This allows us to add new, case-sensitive options at a later date.           *
//*                                                                              *
//* Options:                                                                     *
//* --------                                                                     *
//* File specifications:                                                         *
//*   One or more filespecs may be specified as a filename only, relative path,  *
//*   or full filespec.                                                          *
//*   a) Only plain text, "regular" files are accepted. Binary files and complex *
//*      files such as LibreOffice documents will be rejected for edit.          *
//*   a) Symbolic links are followed to the actual target file using the         *
//*      'realpath' function.                                                    *
//*   b) Filename special characters such as '~' (tilde) '.' (here) and          *
//*      '..' (parent) are recognized and converted.                             *
//*                                                                              *
//* --version[A]                                                                 *
//*   Display the application version number, and optionally the AnsiCmd class   *
//*   version number, and then exit.                                             *
//* --help     or -h                                                             *
//* --helpless or -hl                                                            *
//*   Display application help text, either as TTY output or via the 'less'      *
//*   utility.                                                                   *
//*                                                                              *
//* --dimensions                                                                 *
//*   Report the terminal window dimensions and then exit.                       *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//*                                                                              *
//********************************************************************************

clArgs AcEdit::GetCommandLineArgs ( commArgs& ca )
{
   #if DEBUG_ACE != 0
   #define DEBUG_GCLA (0)
   #endif   // DEBUG_ACE

   //* Temporarily set the application locale. *
   //* See note in called method.              *
   this->startupLocale ( true ) ;

   clArgs userOption = clOpen ;           // return value (hope for the best...)

   //* If user provided command-line arguments, parse them. *
   //* Note that first (0th) argument is application name.  *
   if ( ca.argCount > 1 )
   {
      #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
      system ( "clear" ) ;
      if ( this->tfm != NULL )
      {
         gsdbg.compose( "   temp file subdir:%s", (this->tfm->getTempPath()) ) ;
         wcout << gsdbg.gstr() << endl ;
      }
      #endif   // DEBUG_ACE && DEBUG_GCLA

      gString gs ;               // text formatting
      short j = ZERO ;           // for multiple arguments in same token
      char  argLetter ;          // short-form arguments
      bool  multiarg = false,    // 'true' if concatenated arguments
            syntax = false ;     // 'true' if syntax error(s) on command line

      for ( short i = 1 ; (i < ca.argCount) || (multiarg != false) ; ++i )
      {
         #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
         gsdbg.compose( "%hd) '%s'", &i, ca.argList[i] ) ;
         wcout << gsdbg.gstr() << endl ;
         #endif   // DEBUG_ACE && DEBUG_GCLA

         if ( multiarg != false )   // if additional switches in same argument
            --i ;
         else
            j = ZERO ;

         //* If a command-line switch OR continuing previous switch argument *
         if ( ca.argList[i][j] == DASH || multiarg != false )
         {
            multiarg = false ;
            ++j ;

            if ( ca.argList[i][j] == DASH ) // (double dash)
            {  //* Long-form command switches *
               gs = ca.argList[i] ;

               //* Display application version number and copyright  *
               //* information, then exit.                           *
               //* Note: Full option: "--version", but only six(6)   *
               //*       characters are required.                    *
               if ( (gs.compare( L"--vers", true, 6 )) == ZERO )
               {  // Programmer's Note: Request for version number overrides
                  // all other options and error conditions. Among other things,
                  // this is because the documentation recommends that this 
                  // command be used to test the application build.
                  if ( (gs.find( L'v', 3, false )) > ZERO )
                     userOption = clVersiona ;  // include AnsiCmd version
                  else
                     userOption = clVersion ;   // application version only
                  ca.verFlag = true ;
                  break ;     // ignore any remaining arguments
               }

               //* Invoke command-line help *
               else if ( (gs.compare( L"--help", true, 6 )) == ZERO )
               {  // Programmer's Note: Request for help overrides all options 
                  // except '--version'. Note however that parsing of remaining 
                  // arguments continues after receipt of a help request.
                  if ( (gs.find( L"less" )) > ZERO )
                     userOption = clHelpless ;
                  else
                     userOption = clHelp ;
                  ca.helpFlag = true ;
                  continue ;   // finished with this option
               }

               //* Display terminal dimensions, then exit. *
               else if ( (gs.compare( L"--dime", true, 6 )) == ZERO )
               {
                  userOption = clTermSize ;
                  continue ;   // finished with this option
               }

               //* Option that requires arguments *
               else
               {
                  //* Capture the option switch and all of its arguments.      *
                  //* If option and arguments are not separated by a '=',      *
                  //* append the next token which SHOULD contain all arguments *
                  //* because user SHOULD HAVE enclosed all elements of the    *
                  //* argument within single or double quotes.                 *
                  //* Note, however, that users are simple creatures, so if the*
                  //*       next token is malformed, chaos may ensue.          *
                  //*       i.e. parsing of the argument may fail.             *
                  if ( ((gs.find( L'=' )) < ZERO) && ((i + 1) < ca.argCount) )
                        gs.append( "=%s", ca.argList[++i] ) ;

                  //* Alternate language/locale name *
                  else if ( (gs.compare( L"--lang", true, 6 )) == ZERO )
                  {
                     this->gclaLanguage ( gs, ca ) ;
                  }

                  //* Application Color Attributes *
                  else if ( (gs.compare( L"--colo", true, 6 )) == ZERO )
                  {
                     this->gclaColorEncode ( gs, ca ) ;
                  }

                  #if DEBUG_ACE != 0         // THIS MAY GO AWAY
                  else if ( (gs.compare( L"--test", true, 6 )) == ZERO )
                  {
                     // lie: pretend that we are going to open a file for edit
                     userOption = clOpen ;
                     //* If unable to capture test number, set test *
                     //* number to display test menu (index==99).   *
                     if ( (gs.gscanf( L"--test=%hd", &ca.testnum )) != 1 )
                        ca.testnum = 99 ; // index of test menu
                     else if ( (ca.testnum < -1) || (ca.testnum > 99) )
                        ca.testnum = 99 ; // index of test menu
                  }
                  #endif   // DEBUG_ACE

                  //* Invalid long-form option.      *
                  //* Ignore all remaining arguments.*
                  else
                  {
                     for ( int i = ZERO ; i < (gs.gschars()) ; ++i )
                     { if ( (gs.gstr()[i] == L'=') ) { gs.limitChars( i ) ; break ; } }
                     gclaFmtErrmsg ( femOPT_UNK, ca.gsErr, gs.ustr() ) ;
                     this->exitCode = xcSYN ;
                     userOption = clQuickHelp ;
                     syntax = true ;
                  }
               }
            }     // long-form arguments

            //**************************
            //** Short-form Arguments **
            //**************************
            else
            {
               argLetter = ca.argList[i][j] ;

               //** Short-form Help **
               if ( (argLetter == 'h') || (argLetter == 'H') )
               {
                  if ( ca.argList[i][j+1] == 'l' )
                     userOption = clHelpless ;
                  else
                     userOption = clHelp ;
               }

               //* Invalid short-form option.     *
               //* Ignore all remaining arguments.*
               else
               {
                  userOption = clQuickHelp ;
                  this->exitCode = xcSYN ;
                  syntax = true ;
               }
            }     // short-form arguments
         }        // command-line switch

         //* Token does not begin with a DASH, so assume a filespec *
         else
         {
            gs = ca.argList[i] ;       // load the (presumed) filename

            if ( this->fmcnt < MAX_FILES )
            {
               #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
               gsdbg.compose( "filespec %2hd) '%S'", &this->fmcnt, gs.gstr() ) ;
               wcout << gsdbg.gstr() << endl ;
               #endif   // DEBUG_ACE && DEBUG_GCLA

               //* Verify that the specified file exists, and user has access.*
               //* (Duplicates will be filtered out after config file read.   *
               //* If error, the warning will be added to ca.gsErr.           *
               if ( ! (this->gclaFilespec ( gs, ca.gsErr )) )
               { /* nothing to be done at this time */ }
            }
            else
            {
               // Programmer's Note: This is unlikely to happen unless
               // user is seriously over-caffinated.
               gclaFmtErrmsg ( femFILE_MAX, ca.gsErr, gs.ustr() ) ;
            }
         }
      }     // for(;;)

      //* If syntax error(s) and no request for Help or Version *
      //* return signal for Quick Help.                         *
      if ( syntax && ((userOption != clHelpless) && (userOption != clHelp) &&
                      (userOption != clVersiona) && (userOption != clVersion)) )
      { this->exitCode = xcSYN ; userOption = clQuickHelp ; }

      #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
      wcout << L"Press Enter..." ;
      wcin.get() ;
      wcout.flush() ;
      #endif   // DEBUG_ACE && DEBUG_GCLA
   }

   return userOption ;

}  //* End GetCommandLineArgs() *

//*************************
//*     gclaFilespec      *
//*************************
//********************************************************************************
//* Validate source filespec specified as a command-line argument.               *
//* Filespec could be:                                                           *
//* 1) a filename (assumes current working directory)                            *
//* 2) a relative path to source file                                            *
//* 3) an absolute path to source file                                           *
//* Filespec may, or may not include "special" characters                        *
//* such as '~' ".." '.' '/' etc.                                                *
//*                                                                              *
//* Input  : gsSrc  : (by reference) raw filespec provided by user               *
//*          gsErr  : (by reference) receives any error messages                 *
//*                                                                              *
//* Returns: 'true' if file exists, 'regular' and user has read/write permission *
//*          'false' if a) file does not exist                                   *
//*                     b) user access error (file type || read || write)        *
//********************************************************************************

bool AcEdit::gclaFilespec ( const gString& gsSrc, gString& gsErr )
{
   short statCode ;                       // status of stat
   bool  isText,                          // set of target is plain-text file
         status = false ;                 // return value

   //* Construct full filespec and get file stats *
   if ( (statCode = this->GetFileStats ( this->fman[this->fmcnt].tnf, gsSrc )) == ZERO )
   {
      //* File exists. Verify that it is a "regular", plain-text file, *
      //* and that user has both read and write access.                *
      FEMVAL femCode = femMSG_CNT ;
      if ( this->fman[this->fmcnt].tnf.fType == fmREG_TYPE )
      {
         if ( (isText = this->isTextfile ( this->fman[this->fmcnt].tnf.fSpec )) )
         {
            if ( (this->fman[this->fmcnt].tnf.readAcc &&
                  this->fman[this->fmcnt].tnf.writeAcc) )
            {
               this->fman[this->fmcnt].open = true ;  // file will be opened immediately
               ++this->fmcnt ;
               status = true ;
            }
            else
               femCode = femACC_ERR ;
         }
         else
            femCode = femNOT_TXT ;
      }
      else
         femCode = femNOT_REG ;

      if ( femCode != femMSG_CNT )  // if file verification error
         gclaFmtErrmsg ( femCode, gsErr, this->fman[this->fmcnt].tnf.fName ) ;
   }

   //* File not found or other error. *
   else
   {
      int nindx ;
      if ( (nindx = gsSrc.findlast( L'/' )) > ZERO ) { ++nindx ; }
      else { nindx = ZERO ; }
      gclaFmtErrmsg ( femSTAT_ERR, gsErr, &gsSrc.ustr()[nindx] ) ;
   }

   #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
   // If success, then report record: this->fman[this->fmcnt - 1]
   // Else stat error:
   // a) If 'fType' == fmUNKNOWN_TYPE, then file not found.
   // b) If 'fType' == fmTYPES, then broken link, etc.
   // c) Else valid stats at [this->fmcnt], BUT access error.
   short fi = status ? this->fmcnt - 1 : this->fmcnt ;
   if ( (this->fman[fi].tnf.fType == fmUNKNOWN_TYPE) || 
          (this->fman[fi].tnf.fType == fmTYPES) )
   { gsdbg = "    File Not Found!" ; }
   else
   {
      gString gsts ;
      this->formatTimestamp ( this->fman[fi].tnf.modTime, gsts, true ) ;
      gsdbg.compose( "    name: %s\n"
                     "    spec: %s\n"
                     "    r/w : %hhd/%hhd  type:%s tstamp:%S",
                      this->fman[fi].tnf.fName,
                      this->fman[fi].tnf.fSpec,
                     &this->fman[fi].tnf.readAcc,
                     &this->fman[fi].tnf.writeAcc,
                     (this->fman[fi].tnf.fType == fmREG_TYPE  ? "REG" :
                      this->fman[fi].tnf.fType == fmDIR_TYPE  ? "DIR" :
                      this->fman[fi].tnf.fType == fmLINK_TYPE ? "LINK" :
                      "SPECIAL"),
                     gsts.gstr()
                   ) ;
   }
   wcout << gsdbg.gstr() << endl ;
   #endif   // DEBUG_ACE && DEBUG_GCLA

   return status ;

}  //* End gclaFilespec() *

//*************************
//* gclaCDuplicatedFiles  *
//*************************
//********************************************************************************
//* Discard duplicate file records specified for edit.                           *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* a) Duplicated records will only occur if a file specified on the command     *
//*    line matches a record saved in the config file (or if the same file is    *
//*    specified multiple times on the command line, i.e. stupidity).            *
//* b) The 'open' flag for the record specified on the command line will always  *
//*    be set, while the saved duplicate may or may not be set. Therefore, the   *
//*    saved record is overwritten by shifting trailing records toward the top   *
//*    of the list.                                                              *
//*                                                                              *
//********************************************************************************

void AcEdit::gclaDuplicatedFiles ( void )
{
   if ( this->fmcnt > 1 )
   {
      gString gs ;

      for ( short fa = ZERO ; fa < this->fmcnt ; ++fa )
      {
         gs = this->fman[fa].tnf.fSpec ;
         for ( short fb = fa + 1 ; fb < this->fmcnt ; ++fb )
         {
            //* If record 'fb' == record 'fa', shift trailing *
            //* records overwriting record 'fb'.              *
            if ( (gs.compare( this->fman[fb].tnf.fSpec )) == ZERO )
            {
               for ( short si = fb + 1 ; si < this->fmcnt ; )
                  this->fman[fb++] = this->fman[si++] ;
               --this->fmcnt ;
            }
         }
      }
   }
}  //* End gclaDuplicatedFiles() *

//*************************
//*     gclaLanguage      *
//*************************
//********************************************************************************
//* Reconcile locale with user-interface language.                               *
//*  1) Get current locale from environment.                                     *
//*  2) Compare environment locale with requested language.                      *
//*     a) If a match, set 'lang' member to requested language.                  *
//*     b) If a mismatch, set ca.locale and 'lang' members to agree.             *
//*        Caller will set the specified locale.                                 *
//* If a specific user interface language is specified, it overrides automatic   *
//* language selection.                                                          *
//*                                                                              *
//* Input  : cmd  : (by reference) command-line argument                         *
//*                 format: "--language=[en | zh | te]" ==LOCALE_NAME            *
//*          ca   : commArgs class object (by reference)                         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Get locale from environment and compare with specified language:             *
//* If language from the environment (ENV) specified:                            *
//*    a) env == EN, && (lang == EN || lang == ENV), then lang = uilEN           *
//*    b) env == ZH, && (lang == ZH || lang == ENV), then lang = uilZH           *
//*    c) if (env != EN && env != ZH) || (env not available), then               *
//*          set both locale and 'lang' to English                               *
//* If EN or ZH specified, set the locale to correspond to the selected language.*
//*                                                                              *
//* Comparison of specified locale is performed against the first two characters *
//* of the supported locale names (case insensitive).                            *
//*                                                                              *
//* The two-character language codes are defined by ISO 639-1.                   *
//* See the list of defined language codes at:                                   *
//*       https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes           *
//* NOTE: The command-line code for taking the language from the terminal        *
//*       environment is "te". This is technically the code for Telugu, a        *
//*       Dravidian language of south-eastern India. If an enterprising          *
//*       programmer from Telangana builds an application that supports Telugo,  *
//*       then we will be happy to assist in making modifications to our code.   *
//********************************************************************************

void AcEdit::gclaLanguage ( const gString& cmd, commArgs& ca )
{
   #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
   UILang origLang = uilENV ;
   const char* langName[uilENV + 1] =
   { { "uilEN" }, { "uilZH" }, { "uilENV" } } ;
   #endif   // DEBUG_CSVV && DEBUG_GCLA

   const char* dfltEnglish  = "en_US.utf8" ; // locale for English
   const char* dfltZhongwen = "zh_CN.utf8" ; // locale for Chinese (simplified)

   gString gsLocale ;                        // locale filename
   int indx ;                                // text analysis

   //* If a user-specified language code.          *
   //* If unsupported language code (or bad arg),  *
   //* value is initially taken from environment   *
   //* (if available).                             *
   if ( (indx = cmd.after( L'=' )) >= ZERO )
   {
      //* If Chinese language specified *
      if ( (cmd.find( dfltZhongwen, indx, false, 2)) == indx )
         this->lang = uilZH ;
      //* If English language specified *
      else if ( (cmd.find( dfltEnglish, indx, false, 2)) == indx )
         this->lang = uilEN ;
      // => Add additional language tests here <= *

      else
      {
         //* If the 'terminal-environment' code ('te') is not *
         //* specified, add a record to the warning list.     *
         if ( (cmd.gstr()[indx] != NULLCHAR) && 
              (cmd.find( "te", indx, false, 2 )) != indx )
            gclaFmtErrmsg ( femLOC_ERR, ca.gsErr, &cmd.ustr()[indx] ) ;
         this->lang = uilENV ;
         // Note: There may be a mismatch between the locale specified 
         //       in the environment and the selected user interface language.
         //       If so, the warning message is in language from environment.
      }
   }

   //*********************************************
   //* Scan the environment for the locale name. *
   //*********************************************
   //* If locale is not available in environment,*
   //* assume default. Note that env language may*
   //* not be supported by the application, so in*
   //* that case, default language is set below. *
   for ( short i = ZERO ; ca.envList[i] != NULL ; ++i )
   {
      gsLocale = ca.envList[i] ;
      if ( (gsLocale.after( L"LANG=", ZERO, true )) == 5 )
      { gsLocale.shiftChars( -5 ) ; break ; }
   }
   if ( *ca.locale == NULLCHAR )
      gsLocale = dfltEnglish ;

   //* Compare specified language with system locale and reconcile *
   //* the two to support the specified user interface language.   *
   if ( (gsLocale.find( L"zh" )) == ZERO )
   {
      if ( (this->lang == uilENV) || (this->lang == uilZH) )
      { this->lang = uilZH ; }
      else  // ( this->lang == uilEN )
      {
         gsLocale = dfltEnglish ;
         gsLocale.copy( ca.locale, LOCALE_LEN ) ;
      }
   }
   else if ( (gsLocale.find( L"en" )) == ZERO )
   {
      if ( (this->lang == uilENV) || (this->lang == uilEN) )
      { this->lang = uilEN ; }
      else  // ( this->lang == uilZH )
      {
         gsLocale = dfltZhongwen ;
         gsLocale.copy( ca.locale, LOCALE_LEN ) ;
      }
   }
   else     // unsupported locale - set default language and locale from environment
   {
      if ( this->lang == uilENV ) { this->lang = uilEN ; }
      gsLocale.copy( ca.locale, LOCALE_LEN ) ;
   }

   #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
   gsLocale.compose( "gclaLanguage( lang:%s -> %s localeName:%s )",
                     langName[origLang], langName[this->lang],
                     ((*ca.locale != NULLCHAR) ? 
                       ca.locale : "(default)") ) ;
   wcout << gsLocale << endl ;
   #endif   // DEBUG_ACE && DEBUG_GCLA

}  //* End gclaLanguage() *

//*************************
//*    gclaColorEncode    *
//*************************
//********************************************************************************
//* Convert color-attribute arguments to acAttr values.                          *
//*                                                                              *
//* Note that selecting a color scheme should normally be done in the            *
//* configuration file. Command line specification of color attributes mirrors   *
//* the syntax of the config file; and while this makes for efficient coding,    *
//* it is certainly not user friendly.                                           *
//* ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ---- *
//* --color=Border_FG:Border_BG, Text_FG:Text_BG, Hilite_FG:Hilite_BG            *
//*    |     |         |          |       |        |         |                   *
//*    |     |         |          |       |        |         |                   *
//*    |     |         |          |       |        |         + cursor row bGnd   *
//*    |     |         |          |       |        + cursor row foreground       *
//*    |     |         |          |       + text field background                *
//*    |     |         |          + text field foreground                        *
//*    |     |         + border background                                       *
//*    |     + border foreground                                                 *
//*    + option name                                                             *
//*                                                                              *
//* a) Border attributes must be specified                                       *
//* b) Text-field and cursor-row attributes are optional                         *
//* c) Application's default value is indicated by zeros for both values:        *
//*    Ex: --color=f00:f00,F10:f04                                               *
//* d) Undocumented: dialog/menu/aux/termwin attributes may be appended.         *
//*    Used during development only.                                             *
//*                                                                              *
//* Input  : gaCmd   : (by reference) command string containing color attributes.*
//*          ca      : (by reference) receives encoded color attribute data      *
//*                      ca.bdrAttr == application border                        *
//*                      ca.txtAttr == text editor fields                        *
//*                      ca.hilAttr == text editor fields cursor line            *
//                       Other colors are available in the config file only.     *
//*                                                                              *
//* Returns: 'true'  if valid option/argument syntax and value range             *
//*          'false' syntax error or argument out-of-range                       *
//********************************************************************************

bool AcEdit::gclaColorEncode ( const gString& gsCmd, commArgs& ca )
{
   const char* const recTemplate  = "=%C%hd:%C%hd" ; // Format a fg/bg record
   const short MAX_RECS = 7 ;                        // Max source records
   gString gs ;                           // record formatting
   const wchar_t* wptr = gsCmd.gstr() ;   // pointer to source data
   wchar_t fgGrp, bgGrp ;                 // color group designator
   acaExpand acax ;                       // attribute format conversion
   acAttr newAttr ;                       // encoded attribute value
   int   cmdChars = gsCmd.gschars() ;     // source characters
   short recNum = ZERO,                   // source record index
         fgVal,                           // foreground value
         bgVal,                           // background value
         si = ZERO ;                      // source buffer index
   bool  status = false ;                 // return value

   //* Scan past the '=' character *
   if ( (si = gsCmd.after( L'=' )) > ZERO )
   {
      status = true ;                     // provisional success

      #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
      gsdbg.compose( "--->'%S'", &wptr[si] ) ;
      wcout << gsdbg << endl ;
      const wchar_t* const recName[MAX_RECS] =
      { L"B", L"T", L"H", L"D", L"M", L"A", L"W" } ;
      #endif   // DEBUG_ACE && DEBUG_GCLA

      //* Seperate and adjust each pair of values,so that the configuration *
      //* function can be used to encode the attribute pair.                *
      while ( /*(si < cmdChars) &&*/ (recNum < MAX_RECS) )
      {
         if ( (gsCmd.gscanf( si, attrTemplate, &fgGrp, &fgVal, &bgGrp, &bgVal )) == 4 )
         {
            //* If foreground != background, then format the record. *
            //* If foreground == background, then discard the record.*
            if ( fgVal != bgVal )
            {
               //* Format the record data for called function. *
               gs.compose( recTemplate, &fgGrp, &fgVal, &bgGrp, &bgVal ) ;
               if ( (cfgGetColor ( gs, newAttr )) )
               {
                  switch ( recNum )
                  {
                     case 0: ca.bdrAttr = newAttr ; break ; // Border attribute
                     case 1: ca.txtAttr = newAttr ; break ; // Text field attribute
                     case 2: ca.hilAttr = newAttr ; break ; // Text field highlight attribute
                     case 3: ca.dlgAttr = newAttr ; break ; // Dialog attribute
                     case 4: ca.mnuAttr = newAttr ; break ; // Menu controls attribute
                     case 5: ca.auxAttr = newAttr ; break ; // Auxilliary fields attribute
                     case 6: ca.trmAttr = newAttr ; break ; // Terminal window attribute
                     default: si = cmdChars ;       break ; // (too many records specified)
                  }
               }
               else { si = cmdChars ; } // value out-of-range, terminate the loop
            }
            #if DEBUG_ACE != 0 && DEBUG_GCLA != 0
            else
               gs.compose( recTemplate, &fgGrp, &fgVal, &bgGrp, &bgVal ) ;
            gsdbg.compose( "%S) '%S'", recName[recNum], gs.gstr() ) ;
            wcout << gsdbg << endl ;
            #endif   // DEBUG_ACE && DEBUG_GCLA
            
            ++recNum ;     // advance the record index

            //* Scan to the next record.                         *
            //*  (Note that this scan compensates for users who) *
            //*  (can't read instructions about delimeters.    ) *
            while ( (wptr[si] != L',') && (wptr[si] != L' ') && (wptr[si] != L'\0') )
               ++si ;
            while ( (wptr[si] == L' ') || (wptr[si] == L',') )
               ++si ;
         }
         else
         { status = false ; break ; }
      }  // while()
   }
   return status ;

}  //* End gclaColorEncode()

#if 0    // REFERENCE ONLY
//*************************
//*    gclaColorScheme    *
//*************************
//********************************************************************************
//* Set text fgnd/bgnd color attributes for for the application.                 *
//* Optionally, set ACWin border attributes also.                                *
//*                                                                              *
//* Input  : gsCmd  : (by reference) "--color=COLOR" command                     *
//*          tCfg   : (by reference) a TermConfig object which receives          *
//*                   the specified foreground:background:                       *
//*                                 tCfg.foreground and tCfg.background          *
//*                                                                              *
//* Returns: 'true'  if valid syntax                                             *
//*          'false; if syntax error, (default attributes are used)              *
//********************************************************************************

bool EarthPoints::gclaColorScheme ( const gString& gsCmd, TermConfig& tCfg )
{
   gString gsAttr ;                 // color attribute text
   short   offset ;                 // offset to beginning of color name
   short   cnum ;                   // value returned from gclaAttrScan()
   bool    status  = true ;         // return value

   tCfg.foreground = aesFG_DFLT ;   // initialize caller's attribute fields
   tCfg.background = aesBG_DFLT ;

   if ( (offset = (gsCmd.after( L'=' ))) > ZERO )
   {
      gsAttr = &gsCmd.gstr()[offset] ;    // isolate color attribute text

      //* Decode the text foreground color argument. *
      switch ( (cnum = gclaAttrScan ( gsAttr )) )
      {
         case 0:  tCfg.foreground = aesFG_DFLT ;      break ;
         case 1:  tCfg.foreground = aesFG_BLACK ;     break ;
         case 2:  tCfg.foreground = aesFG_RED ;       break ;
         case 3:  tCfg.foreground = aesFG_GREEN ;     break ;
         case 4:  tCfg.foreground = aesFG_BROWN ;     break ;
         case 5:  tCfg.foreground = aesFG_BLUE ;      break ;
         case 6:  tCfg.foreground = aesFG_MAGENTA ;   break ;
         case 7:  tCfg.foreground = aesFG_CYAN ;      break ;
         case 8:  tCfg.foreground = aesFG_GREY ;      break ;
         default: status = false ;                    break ;
      }
      //* Decode the text background color argument. *
      if ( status && (gsAttr.gschars() > 1) )
      {
         switch ( (cnum = gclaAttrScan ( gsAttr )) )
         {
            case 0:  tCfg.background = aesBG_DFLT ;      break ;
            case 1:  tCfg.background = aesBG_BLACK ;     break ;
            case 2:  tCfg.background = aesBG_RED ;       break ;
            case 3:  tCfg.background = aesBG_GREEN ;     break ;
            case 4:  tCfg.background = aesBG_BROWN ;     break ;
            case 5:  tCfg.background = aesBG_BLUE ;      break ;
            case 6:  tCfg.background = aesFG_MAGENTA ;   break ;
            case 7:  tCfg.background = aesBG_CYAN ;      break ;
            case 8:  tCfg.background = aesBG_GREY ;      break ;
            default: status = false ;                    break ;
         }
      }
      //* Decode the border foreground color argument. *
      if ( status && (gsAttr.gschars() > 1) )
      {
         switch ( (cnum = gclaAttrScan ( gsAttr )) )
         {
            case 0:  this->bdrfAttr = acaFG_DFLT ;    break ;
            case 1:  this->bdrfAttr = acaFG_BLACK ;   break ;
            case 2:  this->bdrfAttr = acaFG_RED ;     break ;
            case 3:  this->bdrfAttr = acaFG_GREEN ;   break ;
            case 4:  this->bdrfAttr = acaFG_BROWN ;   break ;
            case 5:  this->bdrfAttr = acaFG_BLUE ;    break ;
            case 6:  this->bdrfAttr = acaFG_MAGENTA ; break ;
            case 7:  this->bdrfAttr = acaFG_CYAN ;    break ;
            case 8:  this->bdrfAttr = acaFG_GREY ;    break ;
            default: status = false ;                 break ;
         }
      }
      //* Decode the border background color argument. *
      if ( status && (gsAttr.gschars() > 1) )
      {
         switch ( (cnum = gclaAttrScan ( gsAttr )) )
         {
            case 0:  this->bdrbAttr = acaBG_DFLT ;    break ;
            case 1:  this->bdrbAttr = acaBG_BLACK ;   break ;
            case 2:  this->bdrbAttr = acaBG_RED ;     break ;
            case 3:  this->bdrbAttr = acaBG_GREEN ;   break ;
            case 4:  this->bdrbAttr = acaBG_BROWN ;   break ;
            case 5:  this->bdrbAttr = acaBG_BLUE ;    break ;
            case 6:  this->bdrbAttr = acaFG_MAGENTA ; break ;
            case 7:  this->bdrbAttr = acaBG_CYAN ;    break ;
            case 8:  this->bdrbAttr = acaBG_GREY ;    break ;
            default: status = false ;                 break ;
         }
      }
   }
   return status ;

}  //* End gclaColorScheme() *

//*************************
//*     gclaAttrScan      *
//*************************
//********************************************************************************
//* Non-member method called by gclaColorScheme():                               *
//* ----------------------------------------------                               *
//* Convert a text attribute to a numeric value so caller can easiy convert to   *
//* a color attribute.                                                           *
//*                                                                              *
//* On return, captured attribute token will have been shifted out, and next     *
//* token (if any) will be at offset zero.                                       *
//*                                                                              *
//* Programmer's Note: Yes, it's sloppy to return a numeric value that the       *
//* caller needs to interpret, but hey, "It's Only a Northern Song"              *
//*                                                                              *
//* Input  : gsAttr : (by reference) contains text data representing color.      *
//*                                                                              *
//* Returns: numeric value:                                                      *
//*          0 == default    5 == blue                                           *
//*          1 == black      6 == magenta                                        *
//*          2 == red        7 == cyan                                           *
//*          3 == green      8 == grey                                           *
//*          4 == brown      9 == syntax error                                   *
//*                                                                              *
//********************************************************************************

static short gclaAttrScan ( gString& gsAttr )
{
   short offset, 
         colorNumber = ZERO ;    // return value (zero by default)

   gsAttr.strip( true, false ) ; // strip leading whitespace

   //* This is a case-insensitive scan *
   // Programmer's Note: We need only the first three(3) characters
   // to identify the color. (note alternate spelling: grey/gray)
   if      ( (gsAttr.find( "dflt" )) == ZERO )   colorNumber = 0 ;
   else if ( (gsAttr.find( "bla" )) == ZERO )    colorNumber = 1 ;
   else if ( (gsAttr.find( "red" )) == ZERO )    colorNumber = 2 ;
   else if ( (gsAttr.find( "gree" )) == ZERO )   colorNumber = 3 ;
   else if ( (gsAttr.find( "bro" )) == ZERO )    colorNumber = 4 ;
   else if ( (gsAttr.find( "blu" )) == ZERO )    colorNumber = 5 ;
   else if ( (gsAttr.find( "mag" )) == ZERO )    colorNumber = 6 ;
   else if ( (gsAttr.find( "cya" )) == ZERO )    colorNumber = 7 ;
   else if ( ((gsAttr.find( "gray" )) == ZERO) ||
             ((gsAttr.find( "grey" )) == ZERO) ) colorNumber = 8 ;
   else  colorNumber = 9 ;    // syntax error

   //* Discard the processed token *
   if ( (offset = gsAttr.after( L':' )) > ZERO )
   {
      gsAttr.shiftChars( -offset ) ;
      gsAttr.strip() ;
   }
   //* If last token processed, clear the data.*
   else
      gsAttr.clear() ;
   return colorNumber ;

}  //* End gclaAttrScan() *
#endif   // REFERENCE ONLY
#undef DEBUG_GCLA

//*************************
//*     StartupLocale     *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Temporarily enable locale from the terminal environment.                     *
//* This is done to enable multi-language support for error messages generated   *
//* during the startup sequence.                                                 *
//* The static, non-member variable, 'tuiLang' at the top of this module is      *
//* used to store the language value used during startup.                        *
//* Member variables are not modified by this method.                            *
//*                                                                              *
//* Technical Note:                                                              *
//* The application's locale is set during the call to DefineWindow().           *
//* This occurs AFTER parsing of command line options and reading of the         *
//* configuration file. To enable multi-language reporting of errors encountered *
//* during these operations, this method is used to TEMPORARILY set application's*
//* locale and user-interface language corresponding to the terminal environment.*
//* -- In the unlikely event that the environment does not specify a locale,     *
//*    error messages will be reported in the default language (English).        *
//*                                                                              *
//* Input  : enable: if set, enable locale                                       *
//*                          (called on entry to GetCommandLineArgs())           *
//*                  if reset, delete locale object                              *
//*                          (called before return from Configure())             *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::startupLocale ( bool enable )
{
   static locale *ioLoc = NULL ;

   if ( ioLoc != NULL )                   // delete the object
   { delete ( ioLoc ) ; ioLoc = NULL ; }

   if ( enable )
   {
      ioLoc = new locale("") ;  // get default locale from environment
      ioLoc->global( *ioLoc ) ; // give it global scope
      //* Temporarily set 'lang' to match the locale.*
      gString gs( ioLoc->name().c_str() ) ;
      int indx ;
      if ( (indx = gs.after( L'=' )) < ZERO )
         indx = ZERO ;
      if ( (gs.find( "zh", indx, false, 2 )) == indx )
         tuiLang = uilZH ;
      // add tests for additional languages here
      else
         tuiLang = uilEN ;
   }

}  //* End startupLocale() *

//*************************
//*     gclaFmtErrmsg     *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* For the specified message type, format an error message.                     *
//* This message is appended to any existing messages unless there is            *
//* insufficient space (unlikely).                                               *
//* -- The static non-member variable 'tuiLang' located at the top of this       *
//*    module specifies the message language.                                    *
//*                                                                              *
//* Input  : fval  : member of enum FEMVAL indicating type of message            *
//*          gsErr : formatted error message is appended to existing data        *
//*          fName : (optional, default: null pointer) filename text or          *
//*                  invalid option, or out-of-range parameter                   *
//*          errNum: (optional, default: zero) file stat error code              *
//*                  [currently unused]                                          *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void gclaFmtErrmsg ( FEMVAL fval, gString& gsErr, const char* fname, short errNum )
{
   //* Messages for each supported language *
   static const char* const Msg[uilENV][femMSG_CNT] =
   {
      {     // English
         "'%s': file not found\n",                 // femSTAT_ERR - file stat error (not found, etc.)
         "'%s': read/write access error\n",        // femACC_ERR  - read/write access error
         "'%s': is not a \"regular\" file.\n",     // femNOT_REG  - target is not a 'regular' file
         "'%s': is not a plain text file.\n",      // femNOT_TXT  - target is not a 'plain-text' file
         "'%s': not loaded (maximum files)\n",     // femFILE_MAX - maximum number of data files exceeded
         "'%s': unsupported language or locale\n", // femLOC_ERR, - unsupported language or locale
         "'%s': option not yet implemented\n",     // femNOT_IMPL - option not yet implemented
         "'%s': unknown option\n",                 // femOPT_UNK  - unknown command-line option
         "'%s': parameter out of range\n",         // femOPT_VAL  - command-line option value out-of-range
      },

      {     // ZhongWen
         "'%s'：文件未找到\n",            // femSTAT_ERR - file stat error (not found, etc.)
         "'%s'：读/写访问错误\n",          // femACC_ERR  - read/write access error
         "'%s'：是一个“常规”文件。\n",       // femNOT_REG  - target is not a 'regular' file
         "'%s'：不是纯文本文件。\n",         // femNOT_TXT  - target is not a 'plain-text' file
         "'%s'：未加载（最大文件数）\n",       // femFILE_MAX - maximum number of data files exceeded
         "'%s'：不支持的语言或语言环境\n",      // femLOC_ERR, - unsupported language or locale
         "'%s': 选项尚未实现\n",          // femNOT_IMPL - option not yet implemented
         "'%s'：未知选项：\n",            // femUNK_OPT  - unknown command-line option
         "'%s'：参数超出范围\n",           // femOPT_VAL  - command-line option value out-of-range
      },
   } ;

   gString gs ;

   switch ( fval )
   {
      case femSTAT_ERR:
      case femACC_ERR:
      case femNOT_REG:
      case femNOT_TXT:
      case femFILE_MAX:
      case femLOC_ERR:
      case femNOT_IMPL:
      case femOPT_UNK:
      case femOPT_VAL:
         gs.compose( Msg[tuiLang][fval], fname ) ;
         break ;

      case femMSG_CNT:  // (should never happen)
      default: 
         break ;
   } ;

   if ( (gs.gschars()) > 1 )
      gsErr.append( gs.gstr() ) ;

}  //* End gclaFmtErrmsg() *

//*************************
//*    configFmtErrmsg    *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* For the specified message type, format an error message.                     *
//* This message is appended to any existing messages unless there is            *
//* insufficient space (unlikely).                                               *
//* -- The static non-member variable 'tuiLang' located at the top of this       *
//*    module specifies the message language.                                    *
//*                                                                              *
//* Input  : cval  : member of enum CFGVAL indicating type of message            *
//*          gsErr : formatted error message is appended to existing data        *
//*          item  : (optional, default: null pointer) item name (or file name)  *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void configFmtErrmsg ( CFGVAL cval, gString& gsErr, const char* item )
{
   //* Messages for each supported language *
   static const char* const Msg[uilENV][femMSG_CNT] =
   {
      {     // English
         "'%s': configuration file not found\n",   // cfgNOT_FND - config file not found
         "'%s': invalid config file format\n",     // cfgFMT_ERR - invalid config file format
         "'%s': file read/write access error\n",   // cfgACC_ERR - file read/write access error
         "'%s': file format is not plain text\n",  // cfgTYP_ERR - file not 'regular' and/or plain text
         "'%s': unrecognized config option\n",     // cfgOPT_UNK - unrecognized config option
         "'%s': value out of range\n",             // cfgOPT_VAL - value out of range
      },

      {     // ZhongWen
         "'%s': 未找到配置文件\n",             // cfgNOT_FND - config file not found
         "'%s': 配置文件格式无效\n",            // cfgFMT_ERR - invalid config file format
         "'%s': 文件读/写访问错误\n",           // cfgACC_ERR - file read/write access error
         "'%s': 文件格式不是纯文本\n",           // cfgTYP_ERR - file not 'regular' and/or plain text
         "'%s': 无法识别的配置选项\n",           // cfgOPT_UNK - unrecognized config option
         "'%s': 值超出范围\n",               // cfgOPT_VAL - value out of range
      },
   } ;

   gString gs ;

   switch ( cval )
   {
      case cfgNOT_FND:
      case cfgFMT_ERR:
      case cfgACC_ERR:
      case cfgTYP_ERR:
      case cfgOPT_UNK:
      case cfgOPT_VAL:
         gs.compose( Msg[tuiLang][cval], item ) ;
         break ;

      case cfgMSG_CNT:  // (should never happen)
      default: 
         break ;
   } ;

   if ( (gs.gschars()) > 1 )
      gsErr.append( gs.gstr() ) ;

}  //* End configFmtErrmsg() *

//*************************
//*       Configure       *
//*************************
//********************************************************************************
//* Read the configuration file, extracting application setup information and    *
//* the list of files which were active when the application was previously      *
//* closed.                                                                      *
//*                                                                              *
//* NOTE: The AcWin class has not yet been instantiated, so range checking on    *
//*       window dimensions and position cannot yet be performed.                *
//*                                                                              *
//* Input  : ca     : (by reference) copy of command-line arguments              *
//*                                                                              *
//* Returns: 'true' if config file read successfully, else 'false'               *
//*           Note: If it is determined that the preivous shutdown was not       *
//*                 clean, the 'dirty' flag will be set to indicate that         *
//*                 caller must do cleanup on asile 5.                           *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* Programatically-saved file records:                                          *
//* A file record consists of two(2) sequential lines:                           *
//*   1) full filespec using the tilda substitution indicating the user's        *
//*      home directory: "/home/sam" is represented as "~"                       *
//*   2) numeric data for the file:                                              *
//*      Example: ~/Documents/chaos_generator.pl                                 *
//*               --& 24, 29, 64EF254F, 1                                        *
//*      Contents: a) recordID tag                                               *
//*                b) index of topmost visible line                              *
//*                c) index of line where cursor is placed                       *
//*                d) timestamp (hex) indicating time most recently modified     *
//*                e) flag indicating: 1==file was open when app was closed      *
//*                                    0==file closed by user before app exit    *
//* a) File which had input focus when application was previously closed will    *
//*    be the first file in the list, followed by other open files in forward    *
//*    sequence.                                                                 *
//*    -- A file specified on the command line will take focus precedence over   *
//*       the file which had focus when the application was previously closed.   *
//*       These files will have been added to the array before files in "saved"  *
//*       list. (duplicates not allowed)                                         *
//* b) "Recent" files (not marked "open" when application was previously closed) *
//*    will follow open files to the limit of recent-file storage.               *
//* c) When reading the list of saved/previous file records, if the saved        *
//*    timestamp does not match the current timestamp, reset the vTop and vOff   *
//*    indices. (This sets cursor position to top-of-file).                      *
//*    - Programmer's Note: If source file epoch timestamp is unknown, then      *
//*      enable the debugging code for this method, and the timestamp(s) will    *
//*      be reported.                                                            *
//*                                                                              *
//* Color Attributes:                                                            *
//* a) A color attribute specified on the command line overrides the             *
//*    corresponding value in the configuration file. This is determined by the  *
//*    value of the commArgs-class member for the target attribute. These        *
//*    members are initialized to 'acaUSEDFLT', so if the value seen here is     *
//*    not equal to 'acaUSEDFLT', it is not modified.                            *
//*                                                                              *
//* User-defined Shortcut Keys:                                                  *
//* a) Shortcut keys are stored in the configuration file by keyname tokens      *
//*    beginning with "Key_". Example: "Key_Find"                                *
//* b) The keycode itself is stored as an ASCII hex value which is scanned       *
//*    into a wchar_t (wide) character type. Example: "Key_Find = 002240"        *
//*    This keycode corresponds to the defined constant wcsCTL_F (Ctrl+F).       *
//* c) The keyname tokens and the default values are defined at the top of this  *
//*    module.                                                                   *
//* d) Because the records are generated programatically, it is assumed that     *
//*    they reference valid character codes.                                     *
//*                                                                              *
//*                                                                              *
//********************************************************************************

bool AcEdit::Configure ( commArgs& ca )
{
   #define DEBUG_CFG (0)            // for debugging only

   static const wchar_t* const idTag    = L"&AceCfg&" ;   // indicates a config format
   static const wchar_t* const progTag  = L"&AceProg&" ;  // indicates start of programatic data
   //static const wchar_t* const progEXIT = L"&AceExit&" ;  // indicates end-of-data
   static const wchar_t* const progNUM  = L"--&" ;        // programatic record tag
   static const wchar_t* const progFMT  = L"--& %hd, %hd, %LX, %hhd" ; // numeric data for file
   static const wchar_t* const keyCODE  = L"Key_" ;       // shortcut key assignment

   gString gsPath,                  // default config filespec
           gsAltPath,               // alternate config filespec
           gsIn ;                   // receives data from target file
   tnFName tnfA, tnfB ;             // target file stats
   acAttr  newAttr ;                // scanned attribute value
   int64_t eTime ;                  // receives scanned timestamp (epoch time)
   short statCode,                  // error code return from file stat
         inchars,                   // bytes in data-line read
         indx,                      // source index
         vTop, vOff, //bOff,          // receives encoded data
         val,                       // scanned integer
         boolParm ;                 // return value from cfgGetBool()
   uint16_t findsub ;               // Find-substring dialog: bitfield value
   bool findCase = false ;          // Find-substring dialog: Case sensitive search
   bool findWrap = false ;          // Find-substring dialog: Auto-wrap to next line
   bool findRegx = false ;          // Find-substring dialog: Regular-expression search
   bool dfltExists = false,         // 'true' if default config file located
        altExists  = false,         // 'true' if alternate config file located
        idFound    = false,         // 'true' if config file ID verified
        progFound  = false,         // 'true' if config prog ID verified
        to_open    = false,         // 'true' if stored record indicates file s/b opened
        status = false ;            // return value

   //* Full filespec of default configuration file. *
   // Programmer's Note: We "know" that the argList[0] filespec will be resolved
   // because the file is currently being executed, (duh!)
   this->GetRealpath ( ca.argList[0], gsPath ) ;
   indx = gsPath.findlast( L'/' ) ; // truncate executable name
   gsPath.limitChars( indx + 1 ) ;
   gsPath.append( configName ) ;    // append default config filename

   //* Determine whether default config file exists *
   if ( ((this->GetFileStats ( tnfA, gsPath )) == ZERO)
        && (this->isTextfile ( gsPath )) )
   {
      gsPath = tnfA.fSpec ;
      gsPath.copy(this->cfgPath, MAX_PATH ) ;
      dfltExists = true ;
   }

   //* If an alternate configuration file was specified *
   if ( ca.cfgFile[0] != NULLCHAR )
   {
      bool isReal ;
      //* Decode the full filespec and get file info. *
      if ( (isReal = this->GetRealpath ( ca.cfgFile, gsAltPath )) &&
           ((this->GetFileStats ( tnfB, gsAltPath )) == ZERO)
           && (this->isTextfile ( gsAltPath )) )
      {
         gsAltPath.copy( this->cfgPath, MAX_PATH ) ;
         altExists = true ;
      }
      else
      {
         //* Errors: 1) not found, 2) not a text file, 3) R/W access error.*
         CFGVAL cfgVal = cfgNOT_FND ;
         if ( isReal )
         { cfgVal = ((tnfB.fType != fmREG_TYPE) ? cfgTYP_ERR : cfgACC_ERR) ; }
         gsAltPath = ca.cfgFile ;
         indx = gsAltPath.findlast( L'/' ) ;
         gsAltPath.shiftChars( -(indx + 1) ) ;
         configFmtErrmsg ( cfgVal, ca.gsErr, gsAltPath.ustr() ) ;
      }
   }

   #if DEBUG_ACE != 0 && DEBUG_CFG != 0
   gsdbg.compose( "Configure: dfltExists:%hhd altExists:%hhd\n", &dfltExists, &altExists ) ;
   system ( "clear" ) ;
   wcout << gsdbg.gstr() ;
   wcout << L"gsPath : '" << gsPath << L"'" ;
   if ( (gsAltPath.gschars()) > 1 )
      wcout << L"\naltPath: '" << gsAltPath << L"'" ;
   wcout << endl ;
   #endif   // DEBUG_ACE && DEBUG_CFG

   //* If either alternate target file or default config file exists *
   //* configuration can proceed.                                    *
   if ( dfltExists || altExists )
   {
      ifstream ifsCfg( this->cfgPath, ifstream::in ) ;
      if ( ifsCfg.is_open() )
      {
         //* Get the line containing file identifier tag. *
         // Note: This tag is not necessary for scanning the data, but
         //       may be important for a later release.
         this->GetLine ( ifsCfg, gsIn ) ;
         if ( (gsIn.find( idTag, ZERO, true )) == 2 )
            idFound = true ;

         #if DEBUG_ACE != 0 && DEBUG_CFG != 0
         wcout << L"ID     : '" << &gsIn.gstr()[2] << L"'" << endl ;
         #endif   // DEBUG_ACE && DEBUG_CFG

         //* Gather application configuration data. *
         //* Loop terminates when the 'progTag' is  *
         //* found, or at the end-of-file.          *
         while ( idFound && (inchars = this->GetLine ( ifsCfg, gsIn )) > ZERO )
         {
            //* Discard comments and blank/empty lines *
            if ( (gsIn.gstr()[0] == L'#') || (gsIn.gschars() == 1) ||
                 ((gsIn.scan()) == (gsIn.gschars()  - 1)) )
               continue ;

            if ( (gsIn.find( progTag, ZERO, true )) == ZERO )
            {
               #if DEBUG_ACE != 0 && DEBUG_CFG != 0
               wcout << L"PROGID : '" << gsIn.gstr() << L"'" << endl ;
               #endif   // DEBUG_ACE && DEBUG_CFG

               progFound = true ;
               break ;
            }

            //** application window height **
            if ( (gsIn.find( L"Height" )) == ZERO )
            {
               if ( (cfgGetValue ( gsIn, val )) )
                  ca.winHgt = val ;
            }

            //** application window width **
            else if ( (gsIn.find( "Width" )) == ZERO )
            {
               if ( (cfgGetValue ( gsIn, val )) )
                  ca.winWid = val ;
            }

            //** application window vertical offset **
            else if ( (gsIn.find( "VOffset" )) == ZERO )
            {
               if ( (cfgGetValue ( gsIn, val )) )
                  ca.winRow = val ;
            }

            //** application window horizontal offset **
            else if ( (gsIn.find( "HOffset" )) == ZERO )
            {
               if ( (cfgGetValue ( gsIn, val )) )
                  ca.winCol = val ;
            }

            //** application window border style **
            else if ( (gsIn.find( "BStyle" )) == ZERO )
            {
               if ( ((indx = gsIn.after( L'=' )) > ZERO) &&
                    ((gsIn.find( "Dual", indx )) > ZERO) )
               { ca.winBdr = ltDUAL ; }
            }

            //** application window border color **
            else if ( (gsIn.find( "BColor" )) == ZERO )
            {
               if ( (ca.bdrAttr == acaUSEDFLT) && (cfgGetColor ( gsIn, newAttr )) )
                  ca.bdrAttr = newAttr ;
            }

            //** application window text-field color **
            else if ( (gsIn.find( "TColor" )) == ZERO )
            {
               if ( (ca.txtAttr == acaUSEDFLT) && (cfgGetColor ( gsIn, newAttr )) )
                  ca.txtAttr = newAttr ;
            }

            //** application window background color and dialog interior color **
            else if ( (gsIn.find( "DColor" )) == ZERO )
            {
               if ( (ca.dlgAttr == acaUSEDFLT) && (cfgGetColor ( gsIn, newAttr )) )
                  ca.dlgAttr = newAttr ;
            }

            //** application window highlight color **
            else if ( (gsIn.find( "HColor" )) == ZERO )
            {
               if ( (ca.hilAttr == acaUSEDFLT) && (cfgGetColor ( gsIn, newAttr )) )
                  ca.hilAttr = newAttr ;
            }

            //** application window menu color **
            else if ( (gsIn.find( "MColor" )) == ZERO )
            {
               if ( (ca.mnuAttr == acaUSEDFLT) && (cfgGetColor ( gsIn, newAttr )) )
                  ca.mnuAttr = newAttr ;
            }

            //** application window auxialliary color (gutter-line and line-#) **
            else if ( (gsIn.find( "AColor" )) == ZERO )
            {
               if ( (ca.auxAttr == acaUSEDFLT) && (cfgGetColor ( gsIn, newAttr )) )
                  ca.auxAttr = newAttr ;
            }

            //** terminal window color (outside the application area) **
            else if ( (gsIn.find( "WColor" )) == ZERO )
            {
               if ( (ca.trmAttr == acaUSEDFLT) && (cfgGetColor ( gsIn, newAttr )) )
                  ca.trmAttr = newAttr ;
            }

            //** tab width **
            else if ( (gsIn.find( "TabCols" )) == ZERO )
            {
               if ( (cfgGetValue ( gsIn, val )) )
               {
                  if ( val < ZERO )          { val = ZERO ; }
                  else if ( val > TAB_MAX )  { val = TAB_MAX ; }
                  ca.tabWid = val ;
               }
            }

            //** system clipboard **
            else if ( (gsIn.find( "ClipBrd" )) == ZERO )
            {
               if ( (boolParm = cfgGetBool ( gsIn )) > ZERO ) { ca.sysclip = true ; }
               else if ( boolParm == ZERO ) { ca.sysclip = false ; }
            }

            //* Find-substring dialog: Case-sensitive *
            else if ( (gsIn.find( "FindCase" )) == ZERO )
            {
               if ( (boolParm = cfgGetBool ( gsIn )) > ZERO ) { findCase = true ; }
               else if ( boolParm == ZERO ) { findCase = false ; }
               //if ( (cfgGetBool ( gsIn )) ) { findCase = true ; }
            }

            //* Find-substring dialog: Auto-wrap to next line *
            else if ( (gsIn.find( "FindAWrap" )) == ZERO )
            {
               if ( (boolParm = cfgGetBool ( gsIn )) > ZERO ) { findWrap = true ; }
               else if ( boolParm == ZERO ) { findWrap = false ; }
               //if ( (cfgGetBool ( gsIn )) ) { findWrap = true ; }
            }

            //* Find-substring dialog: Regular-expression search *
            else if ( (gsIn.find( "FindRegexp" )) == ZERO )
            {
               if ( (boolParm = cfgGetBool ( gsIn )) > ZERO ) { findRegx = true ; }
               else if ( boolParm == ZERO ) { findRegx = false ; }
               //if ( (cfgGetBool ( gsIn )) ) { findRegx = true ; }
            }

            //*** Insert tests for additional options here ***

            //** Unrecognized config option **
            else
            {
               if ( (indx = gsIn.find( L' ' )) < ZERO )
                  indx = gsIn.find( L'=' ) ;
               if ( indx < ZERO ) { indx = 16 ; }
               gsIn.limitChars( indx ) ;
               configFmtErrmsg ( cfgOPT_UNK, ca.gsErr, gsIn.ustr() ) ;
            }

         }     // while()

         //* Initialize the static data of the findSubstring method.*
         //* CAUTION! See bit definitions in called method.         *
         findsub = 0x0003 ;   // set bits for active flag and forward search
         if ( findCase ) { findsub |= 0x0004 ; }
         if ( findWrap ) { findsub |= 0x0008 ; }
         if ( findRegx ) { findsub |= 0x0010 ; }
         wchar_t w = wcsCTL_F ;
         this->findSubstring ( w, &findsub ) ;


         //********************************************
         //* Get programatically-generated data:      *
         //* 1) Scan shortcut key definitions.        *
         //* 2) Scan the paths of previously-open     *
         //*    files and related data.               *
         //********************************************
         if ( progFound )
         {
            #if DEBUG_ACE != 0 && DEBUG_CFG != 0
            gsdbg.compose( "Format : idFound:%hhd progFound:%hhd", &idFound, &progFound ) ;
            wcout << gsdbg.gstr() << endl ;
            #endif   // DEBUG_ACE && DEBUG_CFG
            int64_t tsDiff ;        // timestamp difference
            bool isReg, isTxt ;     // flags for 'regular' and 'plain-text'

            while ( (inchars = this->GetLine ( ifsCfg, gsIn )) > ZERO )
            {
               //* Discard comments and blank/empty lines *
               if ( (gsIn.gstr()[0] == L'#') || (gsIn.gschars() == 1) ||
                    ((gsIn.scan()) == (gsIn.gschars()  - 1)) )
                  continue ;

               //* If an orphaned numeric data line *
               if ( (gsIn.find( progNUM )) == ZERO )
                  continue ;

               //* Shortcut key assignments *
               if ( (gsIn.find( keyCODE )) == ZERO )
               {
                  this->cfgDecodeKeycode ( gsIn, ca.gsErr ) ;
                  continue ;
               }

               //* Construct full filespec and get file stats *
               if ( (statCode = this->GetFileStats ( this->fman[this->fmcnt].tnf, gsIn )) == ZERO )
               {
                  #if DEBUG_ACE != 0 && DEBUG_CFG != 0
                  gsdbg.compose( "File   : '%s'", this->fman[this->fmcnt].tnf.fSpec ) ;
                  wcout << gsdbg << endl ;
                  #endif   // DEBUG_ACE && DEBUG_CFG

                  //* Target file exists.           *
                  //* Get stored data for the file. *
                  this->GetLine ( ifsCfg, gsIn ) ;
                  if ( (gsIn.gscanf( progFMT, &vTop, &vOff, &eTime, &to_open)) == 4 )
                  {
                     this->fman[this->fmcnt].vtop = vTop ;    // index of visible data
                     this->fman[this->fmcnt].voff = vOff ;    // index of focus line
                     this->fman[this->fmcnt].open = to_open ; // set if file will be opened
                     this->fman[this->fmcnt].edtime = eTime ; // used only during development

                     #if DEBUG_ACE != 0 && DEBUG_CFG != 0
                     gsdbg.compose( "         vtop:%02d voff:%02d epoch: 0x%LX open:%hhd",
                        &this->fman[this->fmcnt].vtop, &this->fman[this->fmcnt].voff, 
                        &eTime, &this->fman[this->fmcnt].open ) ;
                     wcout << gsdbg << endl ;
                     #endif   // DEBUG_ACE && DEBUG_CFG

                     //* Compare file timestamp with saved timestamp, and if *
                     //* mismatch, reset the saved indices. See note above.  *
                     if ( eTime >= this->fman[this->fmcnt].tnf.modTime.epoch )
                        tsDiff = eTime - this->fman[this->fmcnt].tnf.modTime.epoch ;
                     else
                        tsDiff = this->fman[this->fmcnt].tnf.modTime.epoch - eTime ;
                     if ( tsDiff > 2 )
                     {
                        this->fman[this->fmcnt].vtop = 
                        this->fman[this->fmcnt].voff = ZERO ;

                        #if DEBUG_ACE != 0 && DEBUG_CFG != 0
                        gsdbg.compose( "     vs. vtop:%02d voff:%02d epoch: 0x%LX open:%hhd",
                           &this->fman[this->fmcnt].vtop, &this->fman[this->fmcnt].voff, 
                           &this->fman[this->fmcnt].tnf.modTime.epoch, 
                           &this->fman[this->fmcnt].open ) ;
                        wcout << gsdbg << endl ;
                        #endif   // DEBUG_ACE && DEBUG_CFG
                     }
                  }
                  else  // (corrupted data format)
                  {
                     this->fman[this->fmcnt].vtop = 
                     this->fman[this->fmcnt].voff = ZERO ;
                     this->fman[this->fmcnt].open = false ;
                     configFmtErrmsg ( cfgFMT_ERR, ca.gsErr, "---" ) ;
                  }

                  //* User must have read access; if not discard the record.*
                  //* Write access is optional, but caller must handle a    *
                  //* read-only source file as a special case.              *
                  if ( this->fman[this->fmcnt].tnf.readAcc )
                  {
                     isTxt = false ;
                     if ( (isReg = this->fman[this->fmcnt].tnf.fType == fmREG_TYPE) &&
                          (isTxt = this->isTextfile ( this->fman[this->fmcnt].tnf.fSpec )) )
                     {
                        gsIn = this->fman[this->fmcnt].tnf.fSpec ;
                        gsIn.copy( this->fman[this->fmcnt].srcSpec, MAX_PATH ) ;
                        ++this->fmcnt ;   // record complete
                     }

                     else  // ( !(isReg && isTxt) )
                     {
                        configFmtErrmsg ( cfgTYP_ERR, ca.gsErr, 
                                          this->fman[this->fmcnt].tnf.fName ) ;
                        this->fman[this->fmcnt].reset () ; // reset the record
 
                        #if DEBUG_ACE != 0 && DEBUG_CFG != 0
                        gsdbg = "         Not a regular, plain-text file." ;
                        wcout << gsdbg << endl ;
                        #endif   // DEBUG_ACE && DEBUG_CFG
                     }
                  }
                  else     // user does not have read access to target
                  {
                     configFmtErrmsg ( cfgACC_ERR, ca.gsErr, 
                                       this->fman[this->fmcnt].tnf.fName ) ;
                  }
               }
               // Note: If file not found, it is not an error. The orphaned 
               // record will be removed from config file during shutdown.
            }
            status = true ;         // config file format verified, success!

            #if DEBUG_ACE != 0 && DEBUG_CFG != 0
            gsdbg.compose( "------>Valid file records: %02hd", &this->fmcnt ) ;
            wcout << gsdbg << endl ;
            #endif   // DEBUG_ACE && DEBUG_CFG
         }
         else                       // unable to locate programatic-data tag
         {
            configFmtErrmsg ( cfgFMT_ERR, ca.gsErr, "---" ) ;
            this->exitCode = xcCFG ;
         }

         ifsCfg.close() ;           // close the file
      }

      //* Check for duplicate files *
      if ( this->fmcnt > 1 )
         this->gclaDuplicatedFiles () ;
   }

   //* Neither alternate config file nor default config file exists *
   else
   {
      indx = gsPath.findlast( L'/' ) ;
      gsPath.shiftChars( -(indx + 1) ) ;
      configFmtErrmsg ( cfgNOT_FND, ca.gsErr, gsPath.ustr() ) ;
      this->exitCode = xcFNF ;
   }

   if ( this->fmcnt == ZERO )
   {
   //* If no files specified for edit, then the active record  *
   //* will be the record attached to the fiTEXTfw             *
   //* (full-window) TextEdit field: this->fman[AREC]          *
   //* The active record will reference the uninitialized      *
   //* record at this->fman[ZERO]. Caller will initialize this *
   //* record by a call to newFile2Edit().                     *
   }

   //* Disable temporary locale setting *
   //* See note in called method.       *
   this->startupLocale ( false ) ;

   #if DEBUG_ACE != 0 && DEBUG_CFG != 0
   wcout << L"\nPress Enter..." << endl ;
   getwchar () ;
   #endif   // DEBUG_ACE && DEBUG_CFG

   return status ;

}  //* End Configure() *

//*************************
//*   cfgDecodeKeycode    *
//*************************
//********************************************************************************
//* Capture the keycode associated with one of the defined shortcut keys.        *
//* and store it in the shortcut key array ('shortKeys' non-member object).      *
//*                                                                              *
//* a) Caller has verified that a keyname is present, but not whether it is a    *
//*    valid keyname or whether it has a valid argument. Validate the keyname    *
//*    and capture the argument.                                                 *
//* b) If no keycode value is specified, then storage array is not modified and  *
//*    the initialized default is used.                                          *
//* c) If a keycode value is specified, use the value to initialize the shortcut *
//*    key. Note that it is assumed here that the value is one of the available  *
//*    shortcut keycodes, _and_ that there are no duplications.                  *
//*    The keycode has been validated when it was set. See cfgSetKeycode().      *
//*                                                                              *
//* Input  : gsIn   : (by reference) source line from config file                *
//*          gsErr  : (by reference) potentially receives error message          *
//*                                                                              *
//* Returns: 'true' if value captured, else 'false'                              *
//********************************************************************************
//* Default value vs. user-specified value.                                      *
//* User specified values are converted to default values when received, so that *
//* the mainline code always knows ONLY the default value for that shortcut.     *
//* a) If focus is on a text editor field, the shortcut keys are recognized.     *
//*    Note that except when in a menu or a sub-dialog, we are likely in an      *
//*    edit field.                                                               *
//********************************************************************************

void AcEdit::cfgDecodeKeycode ( const gString& gsIn, gString& gsErr )
{
   gString gsName ;
   char keyName[32] ;
   wchar_t wchar = ZERO ;
   int indx ;
   bool keyErr = false ;

   //* Isolate the keycode name *
   gsIn.gscanf( "%s", keyName ) ;

   //* If an argument (hex value) is specified *
   if ( ((indx = (gsIn.after( L'=' ))) > ZERO) &&
        ((gsIn.gscanf( indx, " %X", &wchar )) == 1) )
   {
      //* Compare record name with members of ShortkeyNames.*
      //* If a match AND if specified keycode != default,   *
      //* store the keycode.                                *
      gsName = keyName ;
      for ( short i = ZERO ; i < SHORT_KEYS ; ++i )
      {
         if ( ((gsName.compare( ShortkeyName[i])) == ZERO) &&
              (wchar != ShortkeyDflt[i]) )
         { this->keys.ukey[i] = wchar ; break ; }
      }
   }

   if ( keyErr )
      configFmtErrmsg ( cfgFMT_ERR, gsErr, keyName ) ;

   #if DEBUG_ACE != 0 && DEBUG_CFG != 0
   gsdbg.compose( "Keycode: %s:%04X", keyName, &wchar ) ;
   wcout << gsdbg.gstr() << endl ;
   #endif   // DEBUG_ACE && DEBUG_CFG

}  //* End cfgDecodeKeycode() *

//*************************
//*      cfgGetValue      *
//*************************
//********************************************************************************
//* Non-member Method                                                            *
//* -----------------                                                            *
//* Scan the source text and extract the numeric value.                          *
//*                                                                              *
//* Input  : gsIn   : (by reference) source line from config file                *
//*          value  : (by reference) receives scanned value                      *
//*                                                                              *
//* Returns: 'true' if value captured, else 'false'                              *
//********************************************************************************

static bool cfgGetValue ( const gString& gsIn, short& value )
{
   short val = ZERO,                      // scanned value
         indx ;                           // source offset
   bool  status = false ;                 // return value

   value = ZERO ;                         // initialize caller's variable

   if ( (indx = gsIn.after( L'=' )) > ZERO )
   {
      if ( (gsIn.gscanf( indx, " %hd", &val )) == 1 )
      { value = val ; status = true ; }
   }

   #if DEBUG_ACE != 0 && DEBUG_CFG != 0
   gString gsdbg( "'%S'\t(%hd) (%hhd)", gsIn.gstr(), &value, &status ) ;
   wcout << gsdbg.gstr() << endl ;
   #endif   // DEBUG_ACE && DEBUG_CFG

   return status ;

}  //* End cfgGetValue() *

//*************************
//*      cfgGetColor      *
//*************************
//********************************************************************************
//* Non-member Method                                                            *
//* -----------------                                                            *
//* Scan the source text, extract the color-attribute token and convert to       *
//* acAttr bitfield value.                                                       *
//*                                                                              *
//* Input  : gsIn   : (by reference) source line from config file                *
//*          newAttr: (by reference) receives scanned and decoded attribute      *
//*                                                                              *
//* Returns: 'true' if valid data captured, else 'false'                         *
//*          Note: An error is returned only if invalid foreground token(s)      *
//*                identified.                                                   *
//********************************************************************************

static bool cfgGetColor ( const gString& gsIn, acAttr& newAttr )
{
   gString gs ;                           // text formatting
   wchar_t fgGroup, bgGroup ;             // color group designator
   acaExpand acax ;                       // attribute format conversion
   acAttr  fgnd = acaFG_DFLT,             // foreground/background attributes
           bgnd = acaBG_DFLT ;
   short fgVal,                           // foreground value
         bgVal,                           // background value
         tokens,                          // scanned tokens
         indx ;                           // source offset
   bool  status = false ;                 // return value

   newAttr = acaUSEDFLT ;                 // initialize caller's variable

   #if DEBUG_ACE != 0 && DEBUG_CFG != 0
   gString gsdbg ;
   fgGroup = bgGroup = L'?' ;
   fgVal = bgVal = tokens = indx = ZERO ;
   #endif   // DEBUG_ACE && DEBUG_CFG

   if ( (indx = gsIn.after( L'=' )) > ZERO )
   {
      tokens = gsIn.gscanf( indx, attrTemplate, &fgGroup, &fgVal, &bgGroup, &bgVal ) ;

      //* If two foreground and two background tokens captured *
      if ( tokens == 4 )
      {
         if ( (fgGroup == L'f') || (fgGroup == L'F') )
         {
            status = true ;               // hope for the bese

            switch ( fgVal )
            {
               case  0:    fgnd = acaFG_BLACK ;       break ;
               case  1:    fgnd = acaFG_RED ;         break ;
               case  2:    fgnd = acaFG_GREEN ;       break ;
               case  3:    fgnd = acaFG_BLUE ;        break ;
               case  4:    fgnd = acaFG_BROWN ;       break ;
               case  5:    fgnd = acaFG_MAGENTA ;     break ;
               case  6:    fgnd = acaFG_CYAN ;        break ;
               case  7:    fgnd = acaFG_GREY ;        break ;
               case  8:    fgnd = acaFGb_BLACK ;      break ;
               case  9:    fgnd = acaFGb_RED ;        break ;
               case 10:    fgnd = acaFGb_GREEN ;      break ;
               case 11:    fgnd = acaFGb_BLUE ;       break ;
               case 12:    fgnd = acaFGb_BROWN ;      break ;
               case 13:    fgnd = acaFGb_MAGENTA ;    break ;
               case 14:    fgnd = acaFGb_CYAN ;       break ;
               case 15:    fgnd = acaFGb_GREY ;       break ;
               case 16:    fgnd = acaFG_DFLT ;        break ;
               default:    status = false ;           break ;
            } ;
         }
         else if ( (fgGroup == L'e') || (fgGroup == L'E') )
         {
            if ( (fgVal >= min8BIT) && (fgVal <= max8BIT) )
            {
               acax.update( aesFG_INDEX, fgVal ) ;
               fgnd = acax.acaVal ;
               status = true ;
            }
         }
         else if ( (fgGroup == L'r') || (fgGroup == L'R') )
         {
            if ( (fgVal >= wsrgbMIN) && (fgVal <= wsrgbMAX) )
            {
               acax.update( aesFG_RGB, fgVal ) ;
               fgnd = acax.acaVal ;
               status = true ;
            }
         }
         else
            status = false ;              // invalid fgnd group token

         if ( status )                    // if valid foreground data
         {
            acax.reset() ;                // clear the conversion engine

            if ( (bgGroup == L'f') || (bgGroup == L'F') )
            {
               switch ( bgVal )
               {
                  case  0:    bgnd = acaBG_BLACK ;       break ;
                  case  1:    bgnd = acaBG_RED ;         break ;
                  case  2:    bgnd = acaBG_GREEN ;       break ;
                  case  3:    bgnd = acaBG_BLUE ;        break ;
                  case  4:    bgnd = acaBG_BROWN ;       break ;
                  case  5:    bgnd = acaBG_MAGENTA ;     break ;
                  case  6:    bgnd = acaBG_CYAN ;        break ;
                  case  7:    bgnd = acaBG_GREY ;        break ;
                  case  8:    bgnd = acaBGb_BLACK ;      break ;
                  case  9:    bgnd = acaBGb_RED ;        break ;
                  case 10:    bgnd = acaBGb_GREEN ;      break ;
                  case 11:    bgnd = acaBGb_BLUE ;       break ;
                  case 12:    bgnd = acaBGb_BROWN ;      break ;
                  case 13:    bgnd = acaBGb_MAGENTA ;    break ;
                  case 14:    bgnd = acaBGb_CYAN ;       break ;
                  case 15:    bgnd = acaBGb_GREY ;       break ;
                  case 16:    bgnd = acaBG_DFLT ;        break ;
                  default:    status = false ;           break ;
               } ;
            }
            else if ( (bgGroup == L'e') || (bgGroup == L'E') )
            {
               if ( (bgVal >= min8BIT) && (bgVal <= max8BIT) )
               {
                  acax.update( aesBG_INDEX, bgVal ) ;
                  bgnd = acax.acaVal ;
               }
               else
                  status = false ;
            }
            else if ( (bgGroup == L'r') || (bgGroup == L'R') )
            {
               if ( (bgVal >= wsrgbMIN) && (bgVal <= wsrgbMAX) )
               {
                  acax.update( aesBG_RGB, bgVal ) ;
                  bgnd = acax.acaVal ;
               }
               else
                  status = false ;
            }
            else
               status = false ;
         }

         //* If valid fgnd/bgnd, merge them to create the bitfield *
         if ( status )
            newAttr = acAttr(fgnd | bgnd) ;
      }
   }

   #if DEBUG_ACE != 0 && DEBUG_CFG != 0
   gsdbg.compose( "'%S'  %C %3hd %08X %C %3hd %08X == %08X (%hhd)",
                  gsIn.gstr(), &fgGroup, &fgVal, &fgnd, 
                  &bgGroup, &bgVal, &bgnd, &newAttr, &status ) ;
   wcout << gsdbg.gstr() << endl ;
   #endif   // DEBUG_ACE && DEBUG_CFG

   return status ;

}  //* End cfgGetColor() *

//*************************
//*      cfgGetBool       *
//*************************
//********************************************************************************
//* Non-member Method                                                            *
//* -----------------                                                            *
//* Scan the source text for a 'true' or 'false' parameter value.                *
//*                                                                              *
//* Input  : gsIn  : (by reference) source line from config file                 *
//*                                                                              *
//* Returns:  1   if parameter == 'true'                                         *
//*           0   if parameter == 'false'                                        *
//*          -1   if parameter not initialized                                   *
//********************************************************************************

static short cfgGetBool ( const gString& gsIn )
{
   int indx = gsIn.find( L'=' ) ;         // '=' index
   short status = -1 ;                    // return value
   if ( indx > ZERO )
   {
      if ( (gsIn.find( L"true", indx )) > indx )
         status = 1 ;
      else if ( (gsIn.find( L"false", indx )) > indx )
         status = ZERO ;
   }

   #if DEBUG_ACE != 0 && DEBUG_CFG != 0
   gString gsdbg( "'%S'\t(%hd)", gsIn.gstr(), &status ) ;
   wcout << gsdbg.gstr() << endl ;
   #endif   // DEBUG_ACE && DEBUG_CFG

   return status ;

}  //* End cfgGetBool() *
#undef DEBUG_CFG

//*************************
//*     configUpdate      *
//*************************
//********************************************************************************
//* Update the configuration file's programatically-generated data.              *
//* This includes both the shortcut key map and data for the edited files.       *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* 1) The keymap written using data from:                                       *
//*    a) the ShortKeys ('keys') data member, and                                *
//*    b) the static arrays at the top of this module:                           *
//*       ShortkeyName[], ShortkeyDesc[] and ShortkeyDflt[]                      *
//* 2) The data for files edited during the session are taken from the           *
//*    FileMan array ('fman' data member).                                       *
//*    a) Saving the filespec:                                                   *
//*       When saving a filespec to the config file, if path is in user's home   *
//*       directory, create a more generic path by replacing user's home         *
//*       directory with the "home" sequence.                                    *
//*          Example: "/home/sam/" becomes "~/".                                 *
//*       This allows the config file to be moved among user's systems or to     *
//*       another user's partition.                                              *
//*    b) In addition to the filespec, the following additional data are         *
//*       encoded and saved to the file, seperated by commas (','):              *
//*       -- token identifying the stat record                                   *
//*       -- 'vtop' : line number of top visible data line in TextEdit field     *
//*                   (this is a 1-based value)                                  *
//*       -- 'voff' : offset from 'vtop' to line containing IP (0-based)         *
//*       -- 'etime': timestamp when file was last saved (hexadecimal epoch)     *
//*       -- 'open' : set if file was "open" when the session ended              *
//*                   reset if file was "recently open"                          *
//* 3) All comments in the programatically-generated section are generated       *
//*    from the source data. Any user comments in this section (test records,    *
//*    etc.) will be appended at the bottom of the section.                      *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

void AcEdit::configUpdate ( void )
{

}  //* End configUpdate() *

//*************************
//*    configGenerate     *
//*************************
//********************************************************************************
//* Generate a new, default configuration file.                                  *
//*                                                                              *
//* If a file with the specified name already exists, it will be renamed as      *
//* a backup file. Example: "ace.cfg" would become "ace.cfg~"                    *
//* If the backup file already exists, it will be overwritten.                   *
//*                                                                              *
//* Input  : cfgName : (optional, default:null pointer) name of file to be       *
//*                    created. If not specified, then the default filename      *
//*                    "ace.cfg" will be used.                                   *
//*                    File will be created in current working directory (CWD)   *
//*                                                                              *
//* Returns: 'true'  if file successfully generated                              *
//*          'false' if access or system error                                   *
//********************************************************************************

bool AcEdit::configGenerate ( const char* cfgName )
{
   static const char* sectionA = 
   "# &AceCfg&\n"
   "# Configuration File for AcEdit (ace)\n"
   "# ------------------------------------------------------------\n"
   "# Height of window in rows:\n"
   "# Minimum: 27 rows\n"
   "# Maximum: \"full\" indicating full height of terminal window\n"
   "# Default: full height of terminal window\n"
   "Height = \n"
   "\n"
   "# Width of window in columns:\n"
   "# Minimum: 86 columns\n"
   "# Maximum: \"full\" indicating full width of terminal window\n"
   "# Default: 96 columns (or full width of termial, whichever is less)\n"
   "Width = \n"
   "\n"
   "# Vertical offset in rows:\n"
   "# Minimum: 0 rows\n"
   "# Maximum: height of terminal window _minus_ height of editor window\n"
   "# Special: -1 indicates editor window to be placed at bottom edge of terminal window\n"
   "# Default: 0 (zero, top edge of terminal window)\n"
   "VOffset = \n"
   "\n"
   "# Horizontal offset in columns:\n"
   "# Minimum: 0 columns (left edge)\n"
   "# Maximum: width of terminal window _minus_ width of editor window\n"
   "# Special: -1 indicates editor window to be placed at right edge of terminal window\n"
   "# Special: -2 indicates editor window to be centered horizontally in terminal window\n"
   "# Default: 0 (zero, left edge of terminal window)\n"
   "HOffset = \n"
   "\n"
   "# Border style (line type):\n"
   "# Options: \"Single\" or \"Dual\"\n"
   "# Default: Single\n"
   "BStyle = \n" ;
//   "\n" ;

   static const char* sectionB = 
   "# -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -\n"
   "# Specifying Color Attributes:\n"
   "# For examples, please refer to the AnsiCmd library tests embedded\n" 
   "# within the EarthPoints demonstration application:\n"
   "# For color test menu, invoke: ./epts --test=C\n"
   "# \n"
   "#            4-bit palette:    8-bit palette:  24-bit RGB palette:\n"
   "#  Color:   Basic:   Bright:\n"
   "#  -------- ------   -------   --------------  --------------------------\n"
   "#  black      f00     f08                       r00\n"
   "#  red        f01     f09                       r01  - r36  red group\n"
   "#  green      f02     f10       e16             r37  - r72  green group\n"
   "#  blue       f03     f11         through       r73  - r108 blue group\n"
   "#  brown      f04     f12               e231    r109 - r144 brown group\n"
   "#  magenta    f05     f13                       r145 - r180 magenta group\n"
   "#  cyan       f06     f14                       r181 - r216 cyan group\n"
   "#  grey       f07     f15                       r217 - r252 grey group\n"
   "# \n"
   "#  terminal default foreground or background: f16\n"
   "#\n"
   "# Examples: f12:f03    e18:e201    r00:r188    f16:e35     e46:r180\n"
   "# -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -\n"
   "\n"
   "# Border color: foreground:background\n"
   "# Default : f15:f03\n"
   "#BColor = f02:f14\n"
   "\n"
   "# Text color: foreground:background\n"
   "# Default : f00:f15\n"
   "TColor = f00:f06\n"
   "\n"
   "# Highlight (current line) foreground:background\n"
   "#HColor = r04:f12\n"
   "\n"
   "# Dialog (sub-window) color: foreground:background\n"
   "# Default: f00:e21\n"
   "#DColor = f04:f02\n"
   "\n"
   "# Menu color: foreground:background\n"
   "# Default : f12:f11\n"
   "#MColor = f05:e17\n"
   "\n"
   "# Auxilliary color: foreground:background\n"
   "# This includes both the gutter line and the line number field.\n"
   "# Default : f00:f15\n"
   "#AColor = f06:r10\n"
   "\n"
   "# Window (terminal window): foreground:background\n"
   "#WColor = f16:f16\n"
   "\n"
   "# Tab Width:\n"
   "# Tab emulation: Number of columns (space characters) assigned to the TAB key.\n"
   "# The ASCII TAB control code '\t' (09h) is not supported.\n"
   "# Range: 0 <= TabCols <= 8  Default:3\n"
   "TabCols = \n"
   "\n"
   "# System Clipboard Access\n"
   "# Default : true\n"
   "ClipBrd = \n"
   "\n"
   "# Find-substring dialog: Case sensitive search:\n"
   "# true: case sensitive, false: case insensitive (default)\n"
   "FindCase = \n"
   "\n"
   "# Find-substring dialog: Enable Auto-wrap to next line:\n"
   "# true: enabled, false: disabled (default)\n"
   "FindAWrap = \n"
   "\n"
   "# Find-substring dialog: Enable 'regular-expression' search:\n"
   "# true: enabled, false: disabled (default)\n"
   "FindRegexp = \n" ;
//   "\n" ;

   static const char* sectionC =
   "# ============================================================\n"
   "&AceProg&\n"
   "# Caution: Data below this point is programatically generated.\n"
   "# Do not modify!\n"
   "# ============================================================\n"
   "# Key Map: Keycode bindings for shortcut keys.\n"
   "# Note: Each record contains two data lines:\n"
   "# 1) A comment describing the key function and default value.\n"
   "# 2) A token identifying the key and its current hex value.\n"
   "# ------------------------------------------------------------\n"
   "# Find default: Ctrl+F (wcsCTL_F == 0x002240)\n"
   "Key_Find = 002240\n"
   "# FindNext default: Ctrl+G (wcsCTL_G == 0x002241)\n"
   "Key_FindNext = 002241\n"
   "# FindPrev default: Ctrl+R (wcsCTL_R == 0x00224C)\n"
   "Key_FindPrev = 00224C\n"
   "# Go-to-line default: Ctrl+L (wcsCTL_L == 0x002246)\n"
   "Key_GotoLine = 002246\n"
   "# Key_ShiftUp default: Alt+/ (wcsALT_FS == 0x002283)\n"
   "Key_ShiftUp = 002283\n"
   "# Key_ShiftDown default: Alt+' (wcsALT_SQ == 0x00227E)\n"
   "Key_ShiftDown = 00227E\n"
   "# PairMatch default: Alt+} (wcsALT_RS == 0x00228C) (Right Square Bracket)\n"
   "Key_PairMatch = 00228C\n"
   "# TagMatch default: Alt+{ (wcsALT_LS == 0x00228A) (Left Square Bracket)\n"
   "Key_TagMatch = 00228A\n"
   "# MenuBar default: F01 (wcsF01 == 0x002000)\n"
   "Key_MenuBar = 002200\n"
   "# NextField default: Alt+PgDown (wcsAPGDN == 0x219F)\n"
   "Key_NextField = 00219F\n"
   "# PrevField default: Alt+PgUp (wcsAPGUP == 0x219E)\n"
   "Key_PrevField = 00219E\n"
   "# NextBuffer default: Ctrl+Alt+PgDown (wcsCAPGDN == 0x2234)\n"
   "Key_NextBuffer = 002234\n"
   "# PrevBuffer default: Ctrl+Alt+PgUp (wcsCAPGUP == 0x2233)\n"
   "Key_PrevBuffer = 002233\n"
   "# BufferList default: Alt+B (wcsALT_B == 0x225A) (case insensitive)\n"
   "Key_BufferList = 00225A\n"
   "# OpenFile default: Ctrl+O (wcsCTL_O == 0x2249)\n"
   "Key_OpenFile = 002249\n"
   "# NewFile default: Ctrl+N (wcsCTL_N == 0x2248)\n"
   "Key_NewFile = 002248\n"
   "# Macro default: Alt+M (wcsALT_M == 0x2265)\n"
   "Key_Macro = 002265\n"
   "# Go to Field (single field) default: Alt+1 (wcsALT_1)\n"
   "Key_FieldSingle = 002274\n"
   "# Go to Field (dual horizontal fields) default: Alt+2 (wcsALT_2)\n"
   "Key_FieldDualH = 002275\n"
   "# Go to Field (dual vertical fields) default: Alt+3 (wcsALT_3)\n"
   "Key_FieldDualV = 002276\n" ;

   static const char* sectionD =
   "# Previously Open Files\n"
   "# Note: Each record contains two data lines:\n"
   "# 1) Full filespec (home directory replaced by '~').\n"
   "#    (Note that as distributed, the stored paths for the test files are )\n"
   "#    (relative to the source directory because the full path is unknown.)\n"
   "# 2) Stat data:\n"
   "#    a) token identifying the stat record\n"
   "#    b) 'vtop' : line number of top visible data line in edit window\n"
   "#    c) 'voff' : offset from 'vtop' to line containing IP\n"
   "#    d) 'etime': timestamp when file was last saved\n"
   "#    e) 'open' : set if file was open when previous session ended\n"
   "# ------------------------------------------------------------------\n"
   "~/Documents/SoftwareDesign/AnsiCmd/AcEdit/1_TestData/Welcome_To_AcEdit.txt\n"
   "--& 2, 0, 68F402C1, 1\n" ;

   gString gsFName, gsFSpec, gs ;
   tnFName tnf ;                          // file stats
   bool trgExists = false ;               // set if target file already exists
   bool status = false ;                  // return value

   this->getCWD ( gs ) ;                  // current working directory
//* TEMP */ gsdbg.compose( "configGenerate( %s )\n'%S'", cfgName, gs.gstr() ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;

   //* If caller provided a filename, determine whether it already exists.*
   if ( cfgName != NULL )
   {
      gsFName = cfgName ;
      gsFName.limitChars( 64 ) ;          // (let's be reasonable)

      //* If caller's string includes invalid *
      //* filename characters, use default.   *
      if ( !(this->isFilename ( gsFName )) )
         gsFName = configName ;
   }
   else     // use default filename
      gsFName = configName ;
//* TEMP */ gsdbg.compose( "              ( %s )", gsFName.ustr() ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;

   //* Determine whether target exists, in CWD *
   //* and if it does, rename as backup.       *
   gsFSpec.compose( "%S/%S", gs.gstr(), gsFName.gstr() ) ;
//   this->GetRealpath ( gs.ustr(), gsFSpec ) ;
   if ( (this->GetFileStats ( tnf, gsFSpec )) == ZERO )
   {
      trgExists = true ;
      gs.compose( "%S~", gsFSpec.gstr() ) ;
//* TEMP */ gsdbg.compose( "        rename( %S )", gs.gstr() ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;
      //* If backup already exists, delete it.*
      if ( (this->GetFileStats ( tnf, gs )) == ZERO )
      {
//* TEMP */ ofsdbg << "   delete existing backup" << endl ;
         this->DeleteFile ( gs.ustr() ) ;
      }
      //* Rename as backup *
      if ( (this->RenameFile ( gsFSpec.ustr(), gs.ustr() )) == ZERO )
         trgExists = false ;              // successful rename

   }
//* TEMP */ gsdbg.compose( "         fspec( %S ) trgExists:%hhd", gsFSpec.gstr(), &trgExists ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;

   if ( ! trgExists )
   {
      ofstream ofs( gsFSpec.ustr(), ofstream::trunc ) ;
      if ( ofs.is_open() )
      {
         ofs << sectionA << endl ;
         ofs << sectionB << endl ;
         ofs << sectionC << endl ;
         ofs << sectionD << endl ;
         ofs.close() ;
      }
      if ( (this->GetFileStats ( tnf, gsFSpec )) == ZERO )
         status = true ;
   }

   return status ;

}  //* End configGenerate() *

//*************************
//*                       *
//*************************
//********************************************************************************
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  :                                                                     *
//*                                                                              *
//* Returns:                                                                     *
//********************************************************************************

