//********************************************************************************
//* File       : AcEditMenu.cpp                                                  *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2023-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice in AcEdit.cpp.                      *
//* Date       : 03-Apr-2026                                                     *
//* Version    : (see AppVersion string)                                         *
//*                                                                              *
//* Description: Support file for AcEdit class.                                  *
//*              1) Functionality attached to menu items.                        *
//*              2) Functionality associated with shortcut keycodes.             *
//*              3) Housekeeping for the array of FileMan records.               *
//* ---------------------------------------------------------------------------- *
//* Menu System:                                                                 *
//* ------------                                                                 *
//* The application Callback() method handles menu selections made by the user,  *
//* as well as processing any shortcut keycodes received.                        *
//* -- The menu system layout is similar to that of any GUI text editor.         *
//*    The author finds this to be superior to the functional-but-arcane user    *
//*    user interface for editors like Emacs, Pico, Nano, etc.                   *
//* -- Shortcut keycodes mirror some of the most commonly used menu items, so    *
//*    a user who has learned the shortcuts can bypass the menu system for       *
//*    those operations.                                                         *
//* ---------------------------------------------------------------------------- *
//* Text Editing and Data Tracking:                                              *
//* -------------------------------                                              *
//* 1) Moving among TextEdit and LineNum fields                                  *
//*    Most, but not all coordination among these objects is handled by the      *
//*    updateFocus() method which moves the input focus among the TextEdit fields*
//*    and updates the associated LineNumber and Gutter fields and associated    *
//*    FileMan records.                                                          *
//*    a) Each TextEdit field is associated with a LineNumber field which        *
//*       (duh!) displays line numbers for the text being edited.                *
//*       -- linenumMatch() non-member method returns the index of the LineNum   *
//*          field corresponding to the specified TextEdit field index.          *
//*          These indices are members of enum fldIndex.                         *
//*    b) The Gutter field reports stats on the file being edited in the TextEdit*
//*       field which currently has the input focus. The Gutter field follows    *
//        the input focus, and is not associated with any particular field.      *
//*    c) Each _visible_ TextEdit field is paired with a FileMan record.         *
//*       -- The TextEdit field which currently has the input focus is paired    *
//*          with the "active" FileMan record. (see below)                       *
//*       -- A TextEdit field which is visible but does not have focus is paired *
//*          with an "inactive" FileMan record, (see below)                      *
//* 2) Tracking cursor movement and edits, updating temporary data and updating  *
//*    the source file.                                                          *
//*    A) Tracking is done via an array of FileMan records which contain a       *
//*       variety of information:                                                *
//*        a) each open or recently open file                                    *
//*        b) a new buffer which is not yet associated with any file             *
//*        c) unused records                                                     *
//*       These records are updated in response to one of the following actions: *
//*        a) on startup, saved records are retrieved from the configuration file*
//*           -- files which were open in the previous session are reopened.     *
//*           -- "recently edited" file records are stored for potential recall. *
//*        b) a new file is opened for edit                                      *
//*        c) a recently edited file is reopened                                 *
//*        d) a new empty buffer is created                                      *
//*        e) an auto-save operation is performed                                *
//*        f) user invokes a save to source file                                 *
//*        g) the input focus moves from one TextEdit field to another           *
//*    B) In addition to the FileMan records described by the previous item,     *
//*       there are two dynamically updated records, AREC and IREC.              *
//*       a) AREC is the "active" record, that is the record associated with     *
//*          the TextEdit field which currently has focus. , this->fman[AREC]    *
//*       b) IREC references the dynamic data for visible-but-inactive TextEdit  *
//*          field (if any). , this->fman[IREC]                                  *
//*    C) Data Storage:                                                          *
//*       a) The maximum number of open/recently-opened files is defined as:     *
//*          MAX_FILES.                                                          *
//*       b) The size of the FileMan array is defined as:                        *
//*          MAX_FMANS.                                                          *
//*       c) The first MAX_FILES records in the array are reserved for data      *
//*          describing the list of files which are currently, or have recently  *
//*          been under edit by the application.                                 *
//*       d) The remaining records in the array are dynamically updated in       *
//*          response to user input. These objects are referenced through the    *
//*          constant indices:                                                   *
//*          - fman[AREC] : linked to the TextEdit field which has input focus   *
//*          - fman[IREC] : linked to the TextEdit field (if any) which is       *
//*                         currently visible, but which DOES NOT have focus.    *
//*       e) The 'active' flag indicates whether the data contained in the target*
//*          field is currently associated with a visible TextEdit control.      *
//*       f) The 'sindx' is the index of the "source: FileMan record from which  *
//*          the 'active' data were copied.                                      *
//* ---------------------------------------------------------------------------- *
//* Dialogs:                                                                     *
//* --------                                                                     *
//* The application uses Dialogs (semi-independent sub-windows) within the       *
//* main application window. These dialog windows open in response to user       *
//* request, to ask for a decision, or to alert the user to some underlying      *
//* activity which has occurred. A partial list of application dialogs follows.  *
//*                                                                              *
//* 1) Open a file for editing.                                                  *
//*    Display a list of files in the current directory; allow user to           *
//*    navigate the filesystem and select a file to be edited.                   *
//* 2) Display a list of recently-edited files from which the user can select    *
//*    a file to be reopened.                                                    *
//* 3) Save-As dialog.                                                           *
//*    Prompt the user to enter a new name under which the file will be saved.   *
//*    User can navigate through the filesystem to the desired directory, and    *
//*    provide a name for the file to be saved in the target directory.          *
//* 4) Display a list of currently-open files (buffers) from which the user      *
//*    can choose the buffer to be loaded into the current TextEdit field.       *
//* 5) Display file stats for the file currently being edited.                   *
//* 6) Compare Files:                                                            *
//*    Opens a dialog from which user can select two files to be compared.       *
//*    The system utilities 'diff' and 'less' are then invoked to generate the   *
//*    comparison data and to present it to the user, respectively.              *
//* 7) Clipboard Information.                                                    *
//*    Report the status of the current connection to the system clipboard, and  *
//*    a preview of the data currently on the clipboard.                         *
//* 8) Various dialogs for interactive configuration of the application          *
//*    environment.                                                              *
//*                                                                              *
//********************************************************************************

//*****************
//* 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 *
//**************
static const int MENU_FIELDS = 6 ;        // number of fields in the menu structure
static const fldIndex mFields[MENU_FIELDS] = 
   { fiMENUBAR, fiM_FILE, fiM_EDIT, fiM_VIEW, fiM_UTIL, fiM_HELP } ;
   //    2         3         4         5         6         7

static char wcbVersion[16] = { "?.?.??" } ;  // WaylandCB-class version (if available)
static char wlcVersion[16] ={ "?.?.?" } ;    // 'wl-clipboard' utility version (if available)

//* Dialog skField items. Used for: *
//* -- reportOpenFiles()            *
//* -- reportRecentFiles()          *
//* -- reportCwdFiles()             *
enum repfldName : short
{
   rfFILES = ZERO,
   rfSELECT,
   rfCANCEL,
   rfCOUNT
} ;

//* Formatting options for call to repfmt
enum repfldOption : short
{
   rfoptBuffers,        // display files currently open for edit
   rfoptRecents,        // display files in 'recents' list but not currently under edit
   rfoptCwdList,        // display list of files in CWD
   rfoptSaveAs,         // (select directory and filename for save) [currently unused]
} ;
//* CZONE - Expand 'active' to an enum for greater flexibility.                  *
//* Input  : dlgOpt : specify the dialog configuration (member of enum DialogOpt)*

//* Dialog skField items. Used for: *
//* saveFileAs() and its callback.  *
enum sfaName : short
{
   sfaTEXT = ZERO,            // Textbox for writing filename
   sfaWARN,                   // read-only Textbox for warnings
   sfaSAVE,                   // Pushbutton for "Save"
   sfaCANCEL,                 // Pushbutton for "Cancel"
   sfaVFAT,                   // VFAT filename compatibility Radiobutton
   sfaCWD,                    // Scrollbox containing CWD data
   sfaCOUNT                   // number of dialog fields
} ;

//* Used by both saveFileAs() and its callback.*
static const char* sfaFilterMsg[uilENV][2] = 
{
   {                                   // English
   "All Linux chars are valid (except '/' and '`').",
   "Compatibility with VFAT/NTFS filesystems.",
   },
   {                                   // Zhongwen
   "所有 Linux 字符均有效（'/' 和 '`' 除外）。",
   "与 VFAT/NTFS 文件系统兼容。",
   },
} ;

//* Controls for the CompareFiles() dialog *
enum cmpControls : short
{
   cmpCWD_TB = ZERO,    // read-only
   cmpSCROLL,
   cmpFILEA_TB,
   cmpFILEB_TB,
   cmpBRIEF_RB,         // XOR group
   cmpLEFT_RB,          // XOR group
   cmpFULL_RB,          // XOR group
   cmpDIFF_RB,          // XOR group
   cmpCOMPARE_PB,
   cmpCLOSE_PB,
   cmpSWAP_PB,
   cmpTEMP_RB,
   cmpCTRLS
} ;


//********************
//* Local prototypes *
//********************
//* Match the TextEdit control with the associated LineNum control.*
bool linenumMatch ( fldIndex srcIndx, fldIndex& matchIndx ) ;
void extractCbVersion ( const gString& gsVer ) ;   // extract clipboard version number
//* Extract 'selected' item from dialog Scrollbox control.*
static bool scrollboxItem ( const skField* fptr, gString& iTxt, wchar_t& iBull ) ;
extern const char* decodePermissions ( uint16_t mode, bool compress = false ) ;
extern const char* decodeFiletype ( fmFType ftype ) ;

//* Pointer to AcEdit object i.e. 'this->'    *
//* to handle callbacks from the ACDlg class. *
AcEdit *aceditPtr = NULL ;
//* Non-member callback function which indirectly *
//* calls a member method to handle callbacks.    *
wchar_t nonmember_dlgCallback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;


//*************************
//*       gotoLine        *
//*************************
//********************************************************************************
//* Open a dialog and allow user to select a target line number for moving       *
//* the cursor position (IP).                                                    *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true'  if move to new line number                                  *
//*          'false' if move was cancelled                                       *
//********************************************************************************

bool AcEdit::gotoLine ( void )
{
#if 1    // CZONE - Goto Line
   enum fldName
   {
      fldTEXT = ZERO,
      fldOK,
      fldCAN,
      fldCOUNT
   } ;
   static const char* dlgTitle[uilENV] =
   {
      "  Go To Line  ",                   // English
      "    转到行    ",                      // Zhongwen
   } ;
   static const char* dlgLabels[uilENV][fldCOUNT] =
   {
      {
         "-",              // (placeholder, unused)
         "   ^OK   ",
         " ^CANCEL ",
      },
      {
         "-",              // (placeholder, unused)
         " 好的 ^O ",
         " 取消 ^C "
      },
   } ;

   const char* Template = " %-10s (1-%S):" ; // template for static text
   // Programmer's Note: The text of this array must be no more than 
   // ten (10) columns. This allows a maximum width for display of the 
   // line number. See the formatting template, above.
   static const char* dlgText[uilENV] =
   { //1234567890
      "Go to line",                       // English
      "转到行",                              // Zhongwen
   } ;

   skField skfld ;                        // data for field with focus
   this->win->GetFieldData ( this->focus, skfld ) ;

   acAttr pbuttAttr = acAttr(acaFGb_RED | acaBGb_GREEN) ; // color attributes
   acAttr focusAttr = acAttr(acaFGb_GREEN | acaBGb_RED) ;
   acAttr dlgBdr = acaUSEDFLT ;
   gString gsTxt,                         // static text data
           gsfmt ;                        // number formatting
   WinPos  dlgPos ;
   int  dlgHgt  = 6,                      // dialog height
        tbWid   = 23,                     // width of Textbox for input
        dlgWid  = tbWid + 4,              // dialog width
        lastRow = skfld.mrptr->rlst + 1,  // last row of field text (NOT index)
        trgline,                          // target row specified by user
        trgIP ;                           // target IP
   bool lset = false ;

   gsfmt.formatInt( lastRow, 6, true ) ;
   gsTxt.compose( Template, dlgText[this->lang], gsfmt.gstr() ) ;
   gsTxt.padCols( dlgWid - 4, L'=' ) ;
/* TEMP - Textbox position */ gsTxt.append( "\n=======================" ) ;
   //* Position the dialog within the focus field. *
   dlgPos = skfld.orig ;
   dlgPos.row += (skfld.hgt / 4) ;
   dlgPos.col += (skfld.wid / 2) - (dlgWid / 2) ;


   //* Define the dialog controls *
   skForm* skfp = new skForm
   (
      fldCOUNT,
      new skField[fldCOUNT]
      {
         //* Field index fldTEXT: ftTB (Textbox) *
         {
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(dlgWid - 4),            // field width (in columns)
            1, ZERO,                      // Y/X offset in window
            "",                           // field text (initially none)
            acAttr(acaFG_BLUE | acaBGb_GREY), // color attributes
            floShift,                     // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index fldOK: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            8,                            // field width (in columns)
            short(dlgHgt - 3), -9,        // Y/X offset in window
            dlgLabels[this->lang][fldOK], // field text
            pbuttAttr,                    // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index fldCANCEL: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            10,                           // field width (in columns)
            short(dlgHgt - 3), 2,         // Y/X offset in window
            dlgLabels[this->lang][fldCAN],// field text
            pbuttAttr,                    // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },
      },
      //* Initialize remaining skForm parameters to default values.*
      ZERO,             // index of field with initial focus
      focusAttr         // 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
   ) ;

      //* Launch the dialog and interact with user (filthy beasts!)  *
      //* If dialog was closed with the "Search" pushbutton, save    *
      //* the search parameters.                                     *
      //* If search text was provided, enable the search.            *
      short exitFld = 
         this->win->Dialog ( dlgHgt, dlgWid, skfp, 
                             gsTxt.ustr(), dlgTitle[this->lang],
                             dlgPos.row, dlgPos.col, ltSINGLE, dlgBdr, this->dlgAttr ) ;

      //* If a line number was entered *
      if ( (exitFld == fldOK) && (skfp->fld[fldTEXT].txt.gschars() > 1) )
      {
         //* Get user's value *
         if ( (skfp->fld[fldTEXT].txt.gscanf( "%d", &trgline )) == 1 )
         {
            //* Range check. Note that user enters a line "number", *
            //* but we work with the "index" into the array.        *
            if ( trgline < 1 )            { trgline = 1 ; }
            else if ( trgline > lastRow ) { trgline = lastRow ; }
            --trgline ;
            trgIP = skfld.mrptr->roff[trgline] ;
/* TEMP */ trgIP += 12 ;
            this->win->SetFieldIP ( this->focus, trgIP, true, true ) ;
            this->updateAuxData () ;
         }
//* TEMP */ this->win->dumpMrp2Log ( &skfld, L"focus field" ) ;
/* TEMP */ int actip = this->win->GetFieldIP ( this->focus ) ;
/* TEMP */ gsdbg.compose( "trgLine text: '%S' val:%d trgIP:%d vs. actual:%d", 
/* TEMP */    skfp->fld[fldTEXT].txt.gstr(), &trgline, &trgIP, &actip ) ;
/* TEMP */ ofsdbg << gsdbg << endl ;
      }

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

   return lset ;
#endif   // U/C
}  //* End gotoLine() *

//*************************
//*     findSubstring     *
//*************************
//********************************************************************************
//* Scan the text of the current buffer for a substring matching the current     *
//* contents of 'subStr'.                                                        *
//*                                                                              *
//* This method should be called from a loop. This avoids recursive calls which  *
//* could easily eat significant stack memory. Because this method is called     *
//* only from two places (both from the 'Callback' method) access is controlled. *
//*                                                                              *
//* Input  : wkey : (by reference) keycode which invoked the search (see notes)  *
//*          setup: (optional, default: null pointer) initial configuration      *
//*                 of option flags. This is a bitfield created and transmitted  *
//*                 during configuration.                                        *
//*                    bit 0 : set == parameter contains valid data              *
//*                    bit 1 : set == search forward (currently always set)      *
//*                    bit 2 : set == case-sensitive search                      *
//*                    bit 3 : set == auto-wrap enabled                          *
//*                    bit 4 : set == regular expression search enabled          *
//*                    bits 5:15 currently undefined                             *
//*                                                                              *
//* Returns: 'true'  if caller should immediately invoke this method again       *
//*                  to continue the search in the current direction.            *
//*          'false' if substring found, or if user has signalled the end        *
//*                  of the search sequence                                      *
//********************************************************************************
//* Keycodes:                                                                    *
//* ---------                                                                    *
//* 1) wcsCTL_F: "Find"                                                          *
//*    a) Open a dialog to capture user's search string.                         *
//*    b) Search forward or backward depending on current setting of 'search_dir'*
//*                                                                              *
//* 2) wcsCTL_G: "Find Next"                                                     *
//*    a) If an existing search substring, search forward from the current IP    *
//*       toward the end of file.                                                *
//*    b) If no existing search substring, open the interactive dialog.          *
//*                                                                              *
//* 3) wcsCTL_R: "Find Previous"                                                 *
//*    a) If an existing search substring, search backward from the current IP   *
//*       toward the top of file.                                                *
//*    b) If no existing search substring, open the interactive dialog.          *
//*                                                                              *
//* 4) Continuing search from top/bottom of file.                                *
//*    a) If a search for the substring was conducted BUT the search fai led,    *
//*       then a decision dialog is opened, asking whether user want to continue *
//*       the search from the top of file (forward search) or from the bottom    *
//*       of file (reverse search).                                              *
//*    b) If the user answers in the affirmative, then:                          *
//*        i) The field IP is set to the beginning of file for continuing        *
//*           forward search, or to the end of file for continuing reverse       *
//*           search.                                                            *
//*       ii) the 'wkey' parameter is updated to reflect the direction of        *
//*           the new search:                                                    *
//*           -> (wcsCTL_G == continue forward from top of file)                 *
//*           -> (wcsCTL_R == continue in backward from bottom of file)          *
//*    c) If a matching substring was found, OR if user cancels the search,      *
//*       then 'wkey' is not modified.                                           *
//*                                                                              *
//*       ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---        *
//* Programmer's Note: A search for multiple tokens which may be on different    *
//* lines is an interesting project. Example;                                    *
//*    Find: "born in the USA"                                                   *
//*    Exists in the text as: "born\nin the USA"                                 *
//* A straightforward search would not find it because of the embedded newline.  *
//* Because this type of search would only be useful with flowing text. it is    *
//* not high on the list.                                                        *
//*       ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---        *
//* Programer's Note: Regular-expression searches are not yet implemented.       *
//* This would be an interesting, but time-consuming project.                    *
//* Currently, we simply leverage the search capabilities of the gString class.  *
//*                                                                              *
//* Consider a "find-all" option. this would invoke 'less' with a find option.   *
//* or create a temp file which brackets text with color attributes.             *
//********************************************************************************

bool AcEdit::findSubstring ( wchar_t& wkey, uint16_t* setup )
{
   #define ENABLE_WRAP (0)       // disabled, not yet implemented
   #define ENABLE_REGEXP (0)     // disabled, not yet implemented
   enum fldName
   {
      fldTEXT = ZERO,
      fldDIR,
      fldCASE,
      fldWRAP,
      fldREGEXP,
      fldSEARCH,
      fldCANCEL,
      fldCOUNT
   } ;
   static const char* titleText[uilENV] =
   {
      " Find Text ",             // English
      " 搜索文本 ",                  // Zhongwen
   } ;
   static const char* staticText[uilENV] = 
   {  // (none at this time)
      {                          // English
         " "
      },
      {                          // Zhongwen
         " "
      },
   } ;

   static const char* Labels[uilENV][fldCOUNT] =
   {
      {                          // English
         " Enter Search ^Text ",
         "[ ] ^Forward Search",
         "[ ] C^ase Sensitive Search",
         "[ ] ^Wrap Search Across Lines",
         "[ ] ^Regular Expression Search",
         " ^SEARCH ",
         " ^CANCEL ",
      },
      {                          // Zhongwen
         " 输入搜索文本 ^T ",
         "[ ] 向前搜索 ^F",
         "[ ] 区分大小写搜索 ^A",
         "[ ] 自动跨行继续搜索 ^W",
         "[ ] 启用正则表达式搜索 ^R",
         " 搜索 ^S ",
         " 取消 ^C ",
      },
   } ;

   //* Static text for the query dialog *
   static const char* Query[2][uilENV] =
   {
      //* Forward-search query *
      {
         // English
        "No more data matches found.         \n"
        "Continue search from top of file?   \n",
        // Zhongwen
        "       未找到更多匹配的数据。       \n"
        "       从文件顶部继续搜索吗？       \n",
      },
      //* Reverse-search query *
      {
         // English
        "No more data matches found.         \n"
        "Continue search from bottom of file?\n",
        // Zhongwen
        "       未找到更多匹配的数据。       \n"
        "       是否继续从文件底部搜索？    \n",
      },
   } ;

   //* Static text for the no-matches dialog *
   static const char* NoJoy[uilENV] =
   {
      "\n     No data match found.     \n",     // English
      "\n      未找到匹配的数据。      \n",              // Zhongwen
   } ;

   const int SSCHARS = 128 ;              // max chars in substring
   static wchar_t sub_str[SSCHARS] = { L"" } ; // most recent substring
   static bool search_dir  = false ;      // search direction flag (set==foreward)
   static bool search_case = false ;      // case-sensitive flag (set==sensitive)
   static bool search_wrap = false ;      // auto-wrap search enabled (set==enabled)
   static bool search_regx = false ;      // regexp search enabled (set==enabled)

   acAttr dlgBdr = acaUSEDFLT ;
   acaExpand acexp ;
   acAttr labAttr = acexp.build8Bit( true, 21, true, 45 ) ;
   acAttr focusAttr = acAttr(acaFGb_GREEN | acaBGb_RED) ;
   gString gs( sub_str ) ;                // text analysis
   short q_msg = ZERO ;                   // query dialog static text index
   bool fromTop = false ;                 // set when search begins at top or bottom of text
   bool ssContinue = false ;              // return value

   //* If caller has passed initial flag states *
   //* from the config file, initialize flags.  *
   if ( (setup != NULL) && (*setup & 0x0001) != ZERO ) // if initial call
   {
      if ( (*setup & 0x0002) != ZERO )    // direction
         search_dir = true ;
      if ( (*setup & 0x0004) != ZERO )    // case
         search_case = true ;
      if ( (*setup & 0x0008) != ZERO )    // wrap
         search_wrap = true ;
      #if ENABLE_REGEXP != 0
      if ( (*setup & 0x0010) != ZERO )    // regexp (not currently implemented)
         search_regx = true ;
      #endif   // ENABLE_REGEXP
      return false ;                      // note the early return
   }

   //* If 'find' command, or if no substring saved, *
   //* open the dialog to interact with user.       *
   if ( (wkey == wcsCTL_F) || (*sub_str == L'\0') )
   {
      const short dlgHgt = 12,
                  dlgWid = 48 ;
      gString gsSave( sub_str) ;                   // backup copy of existing substring
      WinPos wpCtrl( 2, (-(dlgWid / 2) + 2) ) ;    // control position
      short exitFld ;                              // index of field with focus on exit


      skForm* skfp = new skForm
      (
         fldCOUNT,
         new skField[fldCOUNT]
         {
            //* Field index fldTEXT: ftTB (Textbox) *
            {
               ftTB,                         // field type
               1,                            // field height (in rows)
               short(dlgWid - 4),            // field width (in columns)
               wpCtrl.row, wpCtrl.col,       // Y/X offset in window
               gs.ustr(),                    // field text (initially none)
               acAttr(acaFG_BLUE | acaBGb_GREY), // color attributes
               floShift,                     // text formatting option
               //ZERO,                       // insertion point (cursor offset)
               //false,                      // user accessible (not read-only)
               //false,                      // field outline disabled
               //true,                       // field is visible (not hidden)
               //true,                       // access lock enabled
               //false,                      // binary state 
               //NULLCHAR,                   // no hotkey specified (embedded in text)
               //noGROUP                     // not a group member
            },

            //* Field index fldDIR: ftRB (Radiobutton) *
            {
               ftRB,                         // field type
               1,                            // field height (in rows)
               3,                            // field width (in columns)
               short(wpCtrl.row += 2),       // Y/X offset in window
               short(wpCtrl.col += 2),
               Labels[this->lang][fldDIR],   // field text
               labAttr,                      // color attributes
               floChar,                      // text formatting option
               ZERO,                         // insertion point (n/a)
               false,                        // user accessible (not read-only)
               false,                        // field outline disabled
               false,                        // field is visible (not hidden)
               true,                         // access lock enabled
               search_dir,                   // binary state 
               //NULLCHAR,                   // no hotkey specified (embedded in text)
               //noGROUP                     // not a group member
            },

            //* Field index fldCASE: ftRB (Radiobutton) *
            {
               ftRB,                         // field type
               1,                            // field height (in rows)
               3,                            // field width (in columns)
               ++wpCtrl.row, wpCtrl.col,     // Y/X offset in window
               Labels[this->lang][fldCASE],  // field text
               labAttr,                      // color attributes
               floChar,                      // text formatting option
               ZERO,                         // insertion point (n/a)
               false,                        // user accessible (not read-only)
               false,                        // field outline disabled
               false,                        // field is visible (not hidden)
               true,                         // access lock enabled
               search_case,                  // binary state 
               //NULLCHAR,                   // no hotkey specified (embedded in text)
               //noGROUP                     // not a group member
            },

            //* Field index fldWRAP: ftRB (Radiobutton) *
            {
               ftRB,                         // field type
               1,                            // field height (in rows)
               3,                            // field width (in columns)
               ++wpCtrl.row, wpCtrl.col,     // Y/X offset in window
               Labels[this->lang][fldWRAP],  // field text
               #if ENABLE_WRAP != 0
               labAttr,                      // color attributes
               floChar,                      // text formatting option
               ZERO,                         // insertion point (n/a)
               false,                        // read-only flag is reset
               #else    // wrap not yet implemented
               acAttr(acaFG_BLUE | acaBG_GREY), // color attributes
               floChar,                      // text formatting option
               ZERO,                         // insertion point (n/a)
               true,                         // read-only flag is set
               #endif   // ENABLE_WRAP
               false,                        // field outline disabled
               false,                        // field is visible (not hidden)
               true,                         // access lock enabled
               search_wrap,                  // binary state 
               //NULLCHAR,                   // no hotkey specified (embedded in text)
               //noGROUP                     // not a group member
            },

            //* Field index fldREGEXP: ftRB (Radiobutton) *
            {
               ftRB,                         // field type
               1,                            // field height (in rows)
               3,                            // field width (in columns)
               ++wpCtrl.row, wpCtrl.col,     // Y/X offset in window
               Labels[this->lang][fldREGEXP],// field text
               #if ENABLE_REGEXP != 0
               labAttr,                      // color attributes
               floChar,                      // text formatting option
               ZERO,                         // insertion point (n/a)
               false,                        // read-only flag is reset
               #else    // regex not yet implemented
               acAttr(acaFG_BLUE | acaBG_GREY), // color attributes
               floChar,                      // text formatting option
               ZERO,                         // insertion point (n/a)
               true,                         // read-only flag is set
               #endif   // ENABLE_REGEXP
               false,                        // field outline disabled
               false,                        // field is visible (not hidden)
               true,                         // access lock enabled
               search_regx,                  // binary state 
               //NULLCHAR,                   // no hotkey specified (embedded in text)
               //noGROUP                     // not a group member
            },

            //* Field index fldSEARCH: ftPB (Pushbutton) *
            {
               ftPB,                         // field type
               1,                            // field height (in rows)
               8,                            // field width (in columns)
               short(dlgHgt - 3), -9,        // Y/X offset in window
               Labels[this->lang][fldSEARCH],// field text
               acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
               //floChar,                    // text formatting option
               //ZERO,                       // insertion point (cursor offset)
               //false,                      // user accessible (not read-only)
               //false,                      // field outline disabled
               //true,                       // field is visible (not hidden)
               //true,                       // access lock enabled
               //false,                      // binary state 
               //NULLCHAR,                   // no hotkey specified (embedded in text)
               //noGROUP                     // not a group member
            },

            //* Field index fldCANCEL: ftPB (Pushbutton) *
            {
               ftPB,                         // field type
               1,                            // field height (in rows)
               10,                           // field width (in columns)
               short(dlgHgt - 3), 2,         // Y/X offset in window
               Labels[this->lang][fldCANCEL],// field text
               acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
               //floChar,                    // text formatting option
               //ZERO,                       // insertion point (cursor offset)
               //false,                      // user accessible (not read-only)
               //false,                      // field outline disabled
               //true,                       // field is visible (not hidden)
               //true,                       // access lock enabled
               //false,                      // binary state 
               //NULLCHAR,                   // no hotkey specified (embedded in text)
               //noGROUP                     // not a group member
            },
         },
         //* Initialize remaining skForm parameters to default values.*
         ZERO,             // index of field with initial focus
         focusAttr         // 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
      ) ;

      //* Set Textbox label *
      skfp->fld[fldTEXT].labelConfig ( Labels[this->lang][fldTEXT], labAttr ) ;

      //* Launch the dialog and interact with user (filthy beasts!)  *
      //* If dialog was closed with the "Search" pushbutton, save    *
      //* the search parameters.                                     *
      //* If search text was provided, enable the search.            *
      exitFld = this->win->Dialog ( dlgHgt, dlgWid, skfp, 
                                    staticText[this->lang], titleText[this->lang],
                                    1, -1, ltSINGLE, dlgBdr, this->dlgAttr, -1, 1, NULL ) ;
      if ( exitFld == fldSEARCH )
      {
         gs = skfp->fld[fldTEXT].txt ;    // save the sub-string
         gs.copy( sub_str, SSCHARS ) ;
         search_dir  = skfp->fld[fldDIR].state ;  // save state of search-direction flag
         search_case = skfp->fld[fldCASE].state ; // save state of case-sensitivity flag
         search_wrap = skfp->fld[fldWRAP].state ; // save state of auto-wrap flag
         search_regx = skfp->fld[fldREGEXP].state;// save state of regexp flag
         if ( *sub_str != NULLCHAR )
            wkey = (search_dir ? wcsCTL_G : wcsCTL_R ) ;
      }
      else                    // (exitFld == fldCANCEL,, search cancelled)
      { gsSave.copy( sub_str, SSCHARS ) ; wkey = NULLCHAR ; }

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

   //* If search substring specified, OR if 'find again' AND existing *
   //* substring saved, search again in the specified direction.      *
   if ( ((wkey == wcsCTL_G) || (wkey == wcsCTL_R)) && (*sub_str != NULLCHAR) )
   {
      int hl_beg, hl_len ;                // highlight beginning and char count
      gString hl_txt ;                    // text of highlighted substring
      int get_beg, get_len ;              // index and char count of substring
      short findx = this->win->GetInputFocus () ;  // field index
      skField skfld ;                     // local copy of field data
      this->win->GetFieldData ( findx, skfld ) ;

      //* If search begins at top or bottom of data *
      fromTop = ((skfld.ip == ZERO) || (skfld.ip == skfld.mrptr->ni)) ? true : false ;

      //* Search from IP, forward *
      if ( wkey == wcsCTL_G )
      {
         if ( skfld.ip < skfld.mrptr->ni )
         {
            //* Search forward from IP. If token found at IP, *
            //* then step forward by one char and scan again. *
            hl_beg = skfld.txt.find( sub_str, skfld.ip, search_case) ;
            if ( hl_beg == skfld.ip )     // if substring found at current IP
            {
               if ( (skfld.getHilite ( get_beg, get_len, hl_txt )) )
               {
                  hl_len = hl_txt.gschars() - 1 ;
                  if ( (get_beg == hl_beg) && (get_len == hl_len) )
                  {
                     if ( skfld.ip < skfld.mrptr->ni )
                        hl_beg = skfld.txt.find( sub_str, (skfld.ip + 1), search_case ) ;
                     else           // no joy
                        hl_beg = -1 ;
                  }
               }
            }
            if ( hl_beg > skfld.ip )      // if another occurance of substring found
            {
               gs = sub_str ;
               hl_len = gs.gschars() - 1 ;  // chars in substring (not incl. null)
               this->win->HilightFieldText ( findx, true, hl_beg, hl_len ) ;
            }
            else { q_msg = ZERO ; }       // quick dialog text index
         }
         else { q_msg = ZERO ; }          // quick dialog text index
      }
      //* Search from IP, backward (wkey == wcsCTL_R) *
      else
      {
         if ( skfld.ip > ZERO )
         {
            //* Search backward from IP-1. *
            hl_beg = skfld.txt.findr( sub_str, (skfld.ip - 1), search_case) ;
            if ( hl_beg >= ZERO )
            {
               gs = sub_str ;
               hl_len = gs.gschars() - 1 ;  // chars in substring (not incl. null)
               this->win->HilightFieldText ( findx, true, hl_beg, hl_len ) ;
            }
            else { q_msg = 1 ; }       // quick dialog text index
         }
         else { q_msg = 1 ; }       // quick dialog text index
      }

      //* If substring was not found, ask user whether scan should *
      //* continue from beginning/end of file; OR if full scan     *
      //* with no matches found, we're done.                       *
      if ( hl_beg < ZERO )
      {
         gString gsMsg ;
         short   ticks = ZERO ;
         bool    query = true ;
         if ( fromTop )
         { gsMsg = NoJoy[this->lang] ; ticks = 2 ; query = false ; }
         else
            gsMsg = Query[q_msg][this->lang] ;

         if ( (this->infoDialog ( gsMsg, query, NULL, -1, -1, ticks )) )
         {
            this->win->SetFieldIP ( findx, ((q_msg > ZERO) ? skfld.mrptr->ni : ZERO) ) ;
            ssContinue = true ;
         }
      }
   }

   return ssContinue ;

}  //* End findSubstring() *

//*************************
//*    findDelimMatch     *
//*************************
//********************************************************************************
//* For C++ / C Perl / etc. source files, scan for the delimeter that            *
//* complements the delimiter under (or immediately to the left of) the cursor.  *
//* Supported delimiter pairs are:                                               *
//*  ( ) parentheses                                                             *
//*  [ ] square brackets                                                         *
//*  { } curly braces                                                            *
//*  < > angle brackets                                                          *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if matching delimeter found, else 'false'                    *
//********************************************************************************

bool AcEdit::findDelimMatch ( void )
{
   const wchar_t lP = L'(', rP = L')' ;
   const wchar_t lS = L'[', rS = L']' ;
   const wchar_t lC = L'{', rC = L'}' ;
   const wchar_t lA = L'<', rA = L'>' ;

   skField* fptr = &this->wInfo.skf->fld[this->focus] ;
   const wchar_t* wptr = fptr->txt.gstr() ;
   wchar_t src, trg ;                     // source delimeter, target delimeter
   int  ip = fptr->ip ;                   // start position for scan
   int  dir = 1 ;                         // scan direction
   int  bump = ZERO ;                     // target offset adjustment
   int  skip = ZERO ;                     // number of mismatch delimeters
   bool foundIt = true ;                  // return value

   src = wptr[ip] ;
   switch ( src )
   {
      case lP: trg = rP ; break ;
      case rP: trg = lP ; dir = -1 ; break ;
      case lS: trg = rS ; break ;
      case rS: trg = lS ; dir = -1 ; break ;
      case lC: trg = rC ; break ;
      case rC: trg = lC ; dir = -1 ; break ;
      case lA: trg = rA ; break ;
      case rA: trg = lA ; dir = -1 ; break ;
      default:
         if ( ip > ZERO )
         {
            ++bump ;
            src = wptr[--ip] ;               // reference the (potential) src char
            switch ( src )
            {
               case lP: trg = rP ; break ;
               case rP: trg = lP ; dir = -1 ; break ;
               case lS: trg = rS ; break ;
               case rS: trg = lS ; dir = -1 ; break ;
               case lC: trg = rC ; break ;
               case rC: trg = lC ; dir = -1 ; break ;
               case lA: trg = rA ; break ;
               case rA: trg = lA ; dir = -1 ; break ;
               default: foundIt = false ;    // delimiter not found
            } ;
         }
         else { foundIt = false ; }          // delimiter not found
         break ;
   } ;

   if ( foundIt )                            // if source delimeter identified
   {
      foundIt = false ;
      int limit = dir > ZERO ? (fptr->txt.gschars()) - 1 : ZERO ;
      while ( ip != limit )
      {
         ip += dir ;                         // move by one step
         if ( wptr[ip] == src )              // src char embedded before trg
         { ++skip ; continue ; }
         if ( wptr[ip] == trg )
         {
            if ( skip > ZERO ) { --skip ; }  // matching embedded char before trg
            else if ( skip == ZERO )
            { foundIt = true ; break ; }
         }
      }
      if ( foundIt )
      {
         //* Set ip on (or just after) target delimeter *
         this->win->SetFieldIP ( this->focus, ip + bump ) ;
      }
   }
   if ( ! foundIt && (this->wInfo.skf->beep) )
      this->win->acBeep () ;
   return foundIt ;

}  // End findDelimMatch() *

//*************************
//*     findTagMatch      *
//*************************
//********************************************************************************
//* For HTML / XML and similar files, scan for opening/closing tag which matches *
//* the tag under the cursor.                                                    *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if matching tag found, else 'false'                          *
//********************************************************************************

bool AcEdit::findTagMatch ( void )
{
   gString gs ;                           // text analysis
   bool foundIt = false ;                 // return value

/* TEMP */ gs = "HTML Tag Matching is \n"
/* TEMP */      "not yet implemented  \n" ;
/* TEMP */ this->flashDialog ( gs, 3 ) ;

   return foundIt ;

}  // End findTagMatch() *

//*************************
//*     clipboardInfo     *
//*************************
//********************************************************************************
//* Open a dialog to report the current clipboard setting and the current        *
//* clipboard contents (if any).                                                 *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Possible Enhancements:                                                       *
//* -- Optionally clear the clipboard.                                           *
//* -- Optionally allow user to enable/disable system clipboard communications   *
//*    channel.                                                                  *
//********************************************************************************

void AcEdit::clipboardInfo ( void )
{
   const char* titleText[uilENV] = 
   {
       " Clipboard Access Info. ",        // English
       " 剪贴板访问信息 ",                       // Zhongwen
   } ;
   const char* delimText[uilENV] = 
   {
      "TEXT",                             // English
      "文本数据",                             // Zhongwen
   } ;
   const char* emptyText[uilENV] = 
   {
      "           (clipboard is empty)",  // English
      "               （剪贴板为空）",           // Zhongwen
   } ;
   const char* activeText[uilENV][3] = 
   {
      {                                   // English
         "active",
         "connected",
         "uninitialized",
      },
      {                                   // Zhongwen
         "活性",
         "已连接",
         "未初始化",
      },
   } ;
   const char* templateText[uilENV] = 
   {
      "Local Clipboard Interface : %s\n"  // English
      "System Clipboard Interface: %s\n"
      "Contents: %S bytes, %S characters\n"
      "%S\n"
      "%S",

      "本地剪贴板界面： %s\n"  // Zhongwen
      "系统剪贴板界面： %s\n"
      "内容： %S 字节数, %S 字符数\n"
      "%S\n"
      "%S",
   } ;
   const short dlgWID = this->wInfo.width / 2 ;
   const int   txtHGT = 4 ;
   const int   txtWID = dlgWID - 4 ;
   gString gs,
           gsText,            // text on clipboard
           gsDelim( "%C%s", &wcsSCAN9, delimText[this->lang] ) ; // delimiter line
   gsDelim.padCols( dlgWID - 4, wcsSCAN9 ) ;

   //* Refrence the 'active'/'inactive' message text *
   short actLocal = ZERO, actSystem = 1 ;
   if ( wcbVersion[0] == '?' ) { actLocal  = 2 ; }
   if ( wlcVersion[0] == '?' ) { actSystem = 2 ; }

   this->win->acClipboardGetText ( gsText ) ;

   //* Format the byte and character counts.*
   int cbBytes = gsText.utfbytes(),       // clipboard bytes
       cbChars = gsText.gschars() ;       // clipboard characters
   wchar_t byteTxt[16], charTxt[16] ;
   gs.formatInt( cbBytes, 6, true ) ;
   gs.copy( byteTxt, 16 ) ;
   gs.formatInt( cbChars, 6, true ) ;
   gs.copy( charTxt, 16 ) ;

   //* If clipboard is empty, so indicate.*
   if ( cbChars == 1 )
      gsText = emptyText[this->lang] ;

   //* Format the clipboard data to fit the display area.*
   int fmtChars,                          // characters in _formatted_ data
       fmtRows = ZERO ;                   // display rows in _formatted_ data
   gsText.formatParagraph ( txtHGT, txtWID, true, true ) ;
   fmtChars = gsText.gschars() ;
   if ( fmtChars < cbChars )              // if text was truncated
   { gsText.limitChars( fmtChars - 5 ) ; gsText.append( "..." ) ; }
   const wchar_t* wptr = gsText.gstr() ;
   for ( int c = ZERO ; wptr[c] != NULLCHAR ; ++c )
   { if ( wptr[c] == NEWLINE ) { ++fmtRows ; } }
   while ( fmtRows <= txtHGT ) // (note: creates an empty row below static text)
   { gsText.append( NEWLINE ) ; ++fmtRows ; }

   gs.compose( templateText[this->lang], 
               activeText[this->lang][actLocal], activeText[this->lang][actSystem], 
               byteTxt, charTxt, gsDelim.gstr(), gsText.gstr() ) ;

   //* Open the dialog to display the information. *
   this->win->QuickDialog( gs, false, titleText[this->lang], 0, 0, 2 ) ;

}  //* End clipboardInfo() *

//*************************
//*    reportOpenFiles    *
//*************************
//********************************************************************************
//* Create a list of files currently open for edit.                              *
//* Allow user to select a file to be displayed and edited in the TextEdit field *
//* which currently has focus.                                                   *
//*                                                                              *
//*                                                                              *
//* Input  : selItem : (by reference) receives the index of the selected         *
//*                    FileMan record (if any)                                   *
//*                                                                              *
//* Returns: 'true'  if user has pressed the "select" pushbutton indicating that *
//*                  an item has been selected from the list of open files.      *
//*          'false' if user has pressed the "cancel" pushbutton indicating that *
//*                  no selection was made. (or if only one open file)           *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* -- Create a dialog with a Scrollbox control and two Pushbuttons.             *
//* -- The Scrollbox contains a list of open files including as much of          *
//*    the filespec as possible.                                                 *
//*    -- For each file, a badge is displayed indicating whether there are       *
//*       unsaved edits in the file.                                             *
//* -- The 'SELECT' pushbutton will cause the selected item to be returned       *
//*    in the 'selRec' parameter.                                                *
//*    -- This is an index into the FileMan array (fman[]).                      *
//*       Programmer's Note: Tracking source index locally in 'oRec' would not   *
//*       be necessary if all open files were guaranteed to be located at the    *
//*       top of the list. See note in To Do list.                               *
//*    -- If the selected file is already displayed in the target field, then a  *
//*       "cancelled" status will be returned, leaving target field's unchanged. *
//* -- The 'CANCEL' pushbutton returns leaving the target field unchanged.       *
//*                                                                              *
//********************************************************************************

bool AcEdit::reportOpenFiles ( short& selRec )
{
   //* Define the dialog window *
   static const char* Title[uilENV] = 
   {
      "  Currently Open Buffers  ",    // English
      "  当前打开的缓冲区  ",                  // Zhongwen
   } ;
   static const char* modLabel[uilENV] = 
   {
      "Unsaved Mods",                  // English
      "未保存的修改",                        // Zhongwen
   } ;
   static const char* noFiles[uilENV] = 
   {
      "No files are currently open.",  // English
      "目前没有打开任何文件。",                   // Zhongwen
   } ;
   static const char* notSaved[uilENV] =
   {
      "filespec not yet specified/unsaved buffer", // English
      "文件规范尚未指定/未保存的缓冲区",              // Zhongwen
   } ;
   const char* specTemplate = " %C %S  (%S)\n" ; // format each filespec record
   const char* modTemplate = "\n %C%C%s" ;   // indicator for mods column

   short itemCnt = ZERO,                  // items to be displayed
         fldHgt,                          // height of Scrollbox control
         dlgHgt,                          // dialog height in rows
         dlgWid = this->wInfo.width - 2,  // dialog width in columns
         fldWid = dlgWid - 2,             // width of fldFILES control
         exitFld,                         // index of field with focus on exit from dialog
         oRec[MAX_FILES],                 // record indices for open files
         ori = ZERO ;                     // index of next free record in oRec
   gString gsList ;                       // formatted list of files
   gString gsName, gsPath ;               // separated filename and file path
   gString gsStatic ;                     // static text displayed in dialog
   gString gsUser( "/home/%s", this->userInfo.userName ) ; // '/home/username' comparison substring
   gString gs ;                           // text analysis
   bool selection = false ;               // return value

   for ( short f = ZERO ; f < this->fmcnt ; ++f )
   {
      if ( this->fman[f].open )
         ++itemCnt ;
   }
   //* Scrollbox field height == number of open files,       *
   //* (minimum of 2),and dialog height is field height + 3. *
   fldHgt = itemCnt >= 2 ? itemCnt : 2 ;
   dlgHgt = fldHgt + 3 ;

/* TEMP */ gsdbg.compose( "reportOpenFiles( itemCnt:%hd )", &itemCnt ) ; ofsdbg << gsdbg << endl ;
   //* Format the list of filespecs *
   int pathAvail, maxPathlen ;            // for truncating a record
   if ( itemCnt > ZERO )
   {
      for( short r = ZERO ; r < this->fmcnt ; ++r )
      {
         if ( this->fman[r].open )
         {
            oRec[ori++] = r ;             // track index of source record
            if ( *this->fman[r].srcSpec != NULLCHAR )
               gs = this->fman[r].srcSpec ;
            else
               gs = notSaved[this->lang] ;
            this->ExtractFilename ( gsName, gs ) ;
            this->ExtractPathname ( gsPath, gs) ;
            if ( (gsPath.find( gsUser )) == ZERO ) // replace "/home/username" with "~"
               gsPath.replace( gsUser.gstr(), L"~" ) ;
            gs.compose( specTemplate, (this->fman[r].mods ? &wcsDIAMOND : &wcsBULLET),
                        gsName.gstr(), gsPath.gstr() ) ;
   
            //* If record length greater than available space, reformat path string.*
            //* Limit path length by shifting left and adding "..."                 *
            // Programmer's Note: This calculation is hard-coded and entirely too cute; 
            // and is therefore rather fragile. Beware!
            if ( gs.gscols() > fldWid )
            {
               pathAvail = fldWid - (gsName.gscols() + 12) ;
               maxPathlen = pathAvail - gsPath.gscols() ;
               gsPath.shiftCols( maxPathlen ) ;
               gsPath.insert( L"..." ) ;
               gs.compose( specTemplate, (this->fman[r].mods ? &wcsDIAMOND : &wcsBULLET),
                           gsName.gstr(), gsPath.gstr() ) ;
            }
            gsList.append( gs.gstr() ) ;
         }
      }
//* TEMP */ gsdbg.clear(); for ( short i = ZERO ; i < itemCnt ; ++i )
//* TEMP */ gsdbg.append( "   item:%02hd -> record:%02hd\n", &i, &oRec[i] ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;

      //* Format the dialog's static text *
      gsStatic.compose( modTemplate, &wcsINSERT, &wcsSCAN7, modLabel[this->lang] ) ;
      for ( short r = 1 ; r < fldHgt ; ++r ) { gsStatic.insert( NEWLINE ) ; }
   }
   else     // no open files
   {
      gsList = noFiles[this->lang] ;
      gsList.padCols( fldWid, L' ', true ) ;
//      initFocus = fldCANCEL ;
      // Note: if no open files, then no static text is needed
   }

   //*********************
   //* Define the dialog *
   //*********************
   skForm* skfp = this->repdlgFmt ( rfoptBuffers, itemCnt, dlgHgt, dlgWid, gsList.ustr() ) ;

   //* If there is only one record displayed, then 'selection' *
   //* does not apply. Set focus on Cancel Pushbutton and set  *
   //* other fields as read-only.                              *
   if ( itemCnt == 1 )
   {
      skfp->fi = rfCANCEL ;
      skfp->fld[rfFILES].ro = skfp->fld[rfSELECT].ro = true ;
   }

   //* Open the dialog *
   exitFld = this->win->Dialog ( dlgHgt, dlgWid, skfp, gsStatic.ustr(), 
                                 Title[this->lang], ZERO, -1, 
                                 ltDUAL, acaUSEDFLT, this->dlgAttr ) ;

   //* If a file was selected, set caller's 'selRec' parameter, *
   //* AND if selected record DOES NOT reference the file       *
   //* currently under edit in the target field, return success.*
   // Programmer's Note: This conversion could be cleaner. See note above.
   if ( exitFld == rfSELECT )
   {
      short srcRec  = this->fman[AREC].sindx ;
      skfp->fld[ZERO].getItem ( gs, selRec, true ) ;
      selRec = oRec[selRec] ;
      //* Compare selected record with record        *
      //* currently loaded into the field with focus.*
/* TEMP */ gsdbg.compose( "reportOpenFiles( rfSELECT: selRec:%hd sindx:%hd\n   '%S'", 
/* TEMP */   &selRec, &srcRec, gs.gstr() ) ;
//* TEMP */   &selRec, &focusRec->sindx, gs.gstr() ) ;
/* TEMP */ ofsdbg << gsdbg << endl ;
//      if ( selRec != focusRec->sindx )
      if ( selRec != srcRec )
         selection = true ;
/* TEMP */ if ( selection ) { ofsdbg << "Good Selection" << endl ; } else { ofsdbg << "Bad Selection" << endl ; }
//* TEMP */ this->dumpFileMan ( 4 ) ;
   }
   else  // (exitFld == fldCANCEL)
   { /* nothing to do */ }

   if ( skfp != NULL )                    // release the dynamic allocations
      delete skfp ;

   return selection ;

}  //* End reportOpenFiles() *

//*************************
//*   setActiveFileMan    *
//*************************
//********************************************************************************
//* Initialize the "active" FileMan object to be associated with the TextEdit    *
//* field which has the input focus. This record will be updated in response     *
//* to keycodes received by the user while the TextEdit field has focus.         *
//*                                                                              *
//* Note: The TextEdit field is not modified by this method.                     *
//*                                                                              *
//* Input  : srcIndx : (optional, default: -1)                                   *
//*                    -- If specified, this is the index of the FileMan         *
//*                       "source" record used for initialization.               *
//*                    -- If not specified, then the current "source" record     *
//*                       will be used for initialization.                       *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::setActiveFileMan ( short srcIndx )
{
   //* Range check. Note: out-of-range value would be a program error.*
   if ( srcIndx >= MAX_FILES )
      srcIndx = -1 ;
   if ( srcIndx < ZERO )                     // if source record not specified
      srcIndx = this->fman[AREC].sindx ;     // use current source record

   //* Save current positioning data, etc. to source record.*
   //* If unsaved data in the TextEdit field with focus,    *
   //* perform auto-save.                                   *
   this->saveFileMan () ;

   this->fman[AREC] = this->fman[srcIndx] ;  // copy source record to "active" record
   this->fman[AREC].sindx = srcIndx ;        // update source record indicator

   //* Set TextEdit and LineNum field indicators.*
   linenumMatch ((this->fman[AREC].tindx = this->focus), this->fman[AREC].lindx ) ;
   this->fman[AREC].active = true ;          // set target record as active

}  //* End setActiveFileMan() *

//*************************
//*   swapActiveFileMan   *
//*************************
//********************************************************************************
//* Swap (exchange) the data in the "active" and "inactive" FileMan records.     *
//* Called when input focus is moving between two _visible_ TextEdit fields.     *
//* a) The 'focus' member currently references the TextEdit field that           *
//*    WILL LOSE focus. That is, focus has not yet moved to alternate field.     *
//* b) The records may, or may not reference the same file.                      *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note: It is assumed that data of inactive record has been       *
//* previously saved (when it lost focus), and does not require a save here.     *
//********************************************************************************

void AcEdit::swapActiveFileMan ( void )
{
   FileMan fmtmp ;                        // temp storage

   //* If unsaved data in active record, perform auto-save. *
   if ( this->fman[AREC].wmods )
   {
      /* not yet implemented */
   }

   fmtmp = this->fman[IREC] ;
   this->fman[IREC] = this->fman[AREC] ;
   this->fman[AREC] = fmtmp ;
   this->fman[AREC].active = true ;       // update the flags
   this->fman[IREC].active = false ;

}  //* End swapActiveFileMan() *

//*************************
//*      saveFileMan      *
//*************************
//********************************************************************************
//* Copy the (possibly modified) dynamic data of the active/inactive" FileMan    *
//* record back to its "source" record.                                          *
//*                                                                              *
//* Note that the 'sindx' member of FileMan is set ONLY for the active (AREC)    *
//* and inactive (IREC) records; therefore, only those records can be "saved".   *
//*                                                                              *
//* 1) If the TextEdit field contains unsaved mods, update the backup file.      *
//*    i.e. perform an auto-save operation.                                      *
//* 2) Copy the positioning members to source record.                            *
//*                                                                              *
//* Input  : activeRec : (optional, default:true)                                *
//*                      if 'true'  save the "active" record                     *
//*                      if 'false' save the "inactive: record                   *
//*                                 Note that if inactive record specified, but  *
//*                                 has not been initialized, then do nothing.   *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::saveFileMan ( bool activeRec )
{
   short si = (activeRec ? AREC : IREC),  // index of copy source
         ti = this->fman[si].sindx ;      // index of copy target

   //* If valid target, save current positioning data, etc.*
   // Programmer's Note: The upper range limit could allow targets beyond the
   // the next "free" record (fmcnt), but only if 'sindx' is wrong (unlikely).
   if ( (ti >= ZERO) && (ti < MAX_FILES) )
   {
      this->fman[ti].vtop = this->fman[si].vtop ;
      this->fman[ti].voff = this->fman[si].voff ;
      this->fman[ti].hoff = this->fman[si].hoff ;

      //* If unsaved data, perform auto-save. *
      if ( this->fman[si].wmods )
      {
         /* not yet implemented */
      }
   }

}  //* End saveFileMan() *

//*************************
//*   reportRecentFiles   *
//*************************
//********************************************************************************
//* Display a list of files which were previously open, (but not currently open).*
//* Allow user to select a file to be opened and edited in the text field        *
//* which currently has focus.                                                   *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//*                                                                              *
//*                                                                              *
//********************************************************************************

void AcEdit::reportRecentFiles ( void )
{
   //* Define the dialog window *
   static const char* Title[uilENV] = 
   {
      "  Recently Edited Files  ",                // English
      "  最近编辑的文件  ",                              // Zhongwen
   } ;
   static const char* noFiles[uilENV] = 
   {
      "List of recently edited files is empty.",  // English
      "最近编辑的文件列表为空。",                             // Zhongwen
   } ;
   const char* specTemplate = " %C %S  (%S)\n" ; // format each filespec record

   short itemCnt = ZERO,                  // items to be displayed
         fldHgt,                          // height of Scrollbox control
         dlgHgt,                          // dialog height in rows
         dlgWid = this->wInfo.width - 2,  // dialog width in columns
         fldWid = dlgWid - 2,             // width of fldFILES control
         exitFld ;                        // index of field with focus on exit from dialog
   gString gsList ;                       // formatted list of files
   gString gsName, gsPath ;               // separated filename and file path
   gString gsStatic ;                     // static text displayed in dialog
   gString gsUser( "/home/%s", this->userInfo.userName ) ; // '/home/username' comparison substring
   gString gs ;                           // text analysis

   for ( short f = ZERO ; f < this->fmcnt ; ++f )
   {
      if ( !this->fman[f].open && (*this->fman[f].srcSpec != NULLCHAR) )
         ++itemCnt ;
   }
   //* Scrollbox field height == number of non-open files in 'recents' *
   //* list, (minimum of 2). Dialog height is 4 rows > Scrollbox.      *
   fldHgt = itemCnt >= 2 ? itemCnt : 2 ;
   dlgHgt = fldHgt + 4 ;

   //* Format the list of filespecs *
   int pathAvail, maxPathlen ;            // for truncating a record
   if ( itemCnt > ZERO )
   {
      for( short f = ZERO ; f < this->fmcnt ; ++f )
      {
         if ( !this->fman[f].open && (*this->fman[f].srcSpec != NULLCHAR) )
         {
            gs = this->fman[f].tnf.fSpec ;
            this->ExtractFilename ( gsName, gs ) ;
            this->ExtractPathname ( gsPath, gs) ;
            if ( (gsPath.find( gsUser )) == ZERO ) // replace "/home/username" with "~"
               gsPath.replace( gsUser.gstr(), L"~" ) ;
            gs.compose( specTemplate, &wcsBULLET, gsName.gstr(), gsPath.gstr() ) ;

            //* If record length greater than available space, reformat path string.*
            //* Limit path length by shifting left and adding "..."                 *
            // Programmer's Note: This calculation is hard-coded and entirely too cute; 
            // and is therefore rather fragile. Beware!
            if ( gs.gscols() > fldWid )
            {
               pathAvail = fldWid - (gsName.gscols() + 12) ;
               maxPathlen = pathAvail - gsPath.gscols() ;
               gsPath.shiftCols( maxPathlen ) ;
               gsPath.insert( L"..." ) ;
               gs.compose( specTemplate, (this->fman[f].mods ? &wcsDIAMOND : &wcsBULLET),
                           gsName.gstr(), gsPath.gstr() ) ;
            }
            gsList.append( gs.gstr() ) ;
         }
      }
   }
   else     // no recently open files
   {
      gsList = noFiles[this->lang] ;
      gsList.padCols( fldWid, L' ', true ) ;
   }

   //* Initialize the dialog static text. This is a        *
   //* pseudo-line which is positioned below the Scrollbox.*
   gsStatic.clear() ;
   gsStatic.padCols( dlgWid - 4, wcsHORIZs ) ;
   //* Advance dialog static-text to appear below Scrollbox. *
   for ( short nl = fldHgt ; nl > ZERO ; --nl )
      gsStatic.insert( NEWLINE ) ;

   //*********************
   //* Define the dialog *
   //*********************
   skForm* skfp = this->repdlgFmt ( rfoptRecents, itemCnt, dlgHgt, dlgWid, gsList.ustr() ) ;
   --skfp->fld[rfFILES].hgt ;    // reduce height of Scrollbox by one

   //* Open the dialog *
   exitFld = this->win->Dialog ( dlgHgt, dlgWid, skfp, gsStatic.ustr(), 
                                 Title[this->lang], ZERO, -1, 
                                 ltDUAL, acaUSEDFLT, this->dlgAttr ) ;

   //* If a file was selected for edit, open the file and load *
   //* the buffer into the text control with input focus.      *
   if ( exitFld == rfSELECT )
   {
// CZONE - reportFileList(SELECT) - Open selected "recent" file.
   }
   else  // (exitFld == fldCANCEL)
   { /* nothing to do */ }

   if ( skfp != NULL )                    // release the dynamic allocations
      delete skfp ;

}  //* End reportRecentFiles() *

#if 0 // DEPRECATED: This is redundant with select4Edit() and is not currently called.
//*************************
//*    reportCwdFiles     *
//*************************
//********************************************************************************
//* Display a list of files in the current working directory (CWD). Allow user   *
//* to select a file to be opened for edit.                                      *
//*                                                                              *
//* Input  : fileSpec : (by reference) if user selects a file for edit, the      *
//*                     filespec is returned here.                               *
//*                     If no selection made, an empty string is returned.       *
//*                                                                              *
//* Returns: 'true' if user selected a non-directory filename, else 'false'      *
//********************************************************************************

   bool reportCwdFiles ( gString& fileSpec ) ;
bool AcEdit::reportCwdFiles ( gString& fileSpec )
{
   //* Define the dialog window *
   static const char* Title[uilENV] = 
   {
      " Files In the Current Directory ", // English
      " 当前目录中的文件 ",                       // Zhongwen
   } ;
   static const char* warnAccess[uilENV] =
   {
      "Access Error! Unable to reach:",   // English
      "     访问错误！无法连接到：     ",            // Zhongwen
   } ;
   const short maxfldHgt = this->wInfo.height - 5 ; // maximum Scrollbox height

   gString gsList ;                       // formatted list of files
   gString gsStatic ;                     // static text displayed in dialog
//   gString gsUser( "/home/%s", this->userInfo.userName ) ; // '/home/username' comparison substring
   gString gs ;                           // text analysis
   MRParse tmpMrp ;                       // parsing data for Scrollbox field
   int indx ;                             // general index
   wchar_t bullChar ;                     // distinguish between dir names and filenames
   acAttr* acArray = NULL ;               // pointer to dynamic allocation
   short itemCnt = ZERO,                  // items to be displayed
         fldHgt,                          // height of Scrollbox control
         dlgHgt,                          // dialog height in rows
         dlgWid = this->wInfo.width - 2,  // dialog width in columns
         fldWid = dlgWid - 2,             // width of fldFILES control
         exitFld ;                        // index of field with focus on exit from dialog
   bool fileSelected = false ;            // return value

   skForm* skfp = NULL ;         // pointer to skForm object
   gString  cwdPath ;            // filespec of CWD
   gString  tspec ;              // target filespec
   bool     done = false ;       // loop control

   fileSpec.clear() ;                     // clear caller's buffer

   do
   {
      //* Filespec of current working directory. *
      this->getCWD ( cwdPath ) ;
//OBSOLETE      if ( !(this->getCWD ( cwdPath )) ) { cwdPath = L"." ; }

      //* Initialize the dialog static text. This is a        *
      //* pseudo-line which is positioned below the Scrollbox.*
      gsStatic.clear() ;
      gsStatic.padCols( dlgWid - 4, wcsHORIZs ) ;

      itemCnt = this->readCwd ( gsList, acArray, fldWid ) ;
      if ( (fldHgt = itemCnt) > maxfldHgt ) // range limit Scrollbox height
         fldHgt = maxfldHgt - 1 ;
// CZONE - Min field height for Scrollbox == 2, but we should always provide at least 4-8.
      dlgHgt = fldHgt + 4 ;   // calculate dialog height relative to Scrollbox height

      //* Advance dialog static-text to appear below Scrollbox. *
      for ( short nl = fldHgt ; nl > ZERO ; --nl )
         gsStatic.insert( NEWLINE ) ;

      //*********************
      //* Define the dialog *
      //*********************
      skfp = this->repdlgFmt ( rfoptCwdList, itemCnt, dlgHgt, dlgWid, gsList.ustr() ) ;
      --skfp->fld[rfFILES].hgt ;    // reduce height of Scrollbox by one

      //* Apply multi-color attributes to Scrollbox data. *
      skfp->fld[rfFILES].colorConfig ( acArray, itemCnt ) ;

      aceditPtr = this ;
      this->adlg = adOpen ;      // indicate the active dialog (used by callback)

      //*******************
      //* Open the dialog *
      //*******************
#if 0    // CZONE - Call with callback pointer.
      exitFld = this->win->Dialog ( dlgHgt, dlgWid, skfp, gsStatic.ustr(), 
                                    Title[this->lang], ZERO, -1, ltDUAL, 
                                    acaUSEDFLT, this->dlgAttr, 0, 1,
                                    nonmember_dlgCallback ) ;
#else    // Stand-alone
      exitFld = this->win->Dialog ( dlgHgt, dlgWid, skfp, gsStatic.ustr(), 
                                    Title[this->lang], ZERO, -1, 
                                    ltDUAL, acaUSEDFLT, this->dlgAttr ) ;
#endif   // Stand-alone

      this->adlg = adNONE ;      // dialog inactive
      aceditPtr = NULL ;

      if ( exitFld == rfSELECT )
      {
// CZONE - This section can be updated to call a non-member method to extract 
//       - filename from item and return the bullChar value.
         //* Parse the record into its designator and filename components.*
         //* The "designator" character is one of PCHAR/DCHAR/FCHAR.      *
         //* Note that the target record is terminated by a newline       *
         //* character, and the record may have been padded with spaces   *
         //* to the full width of the display field.                      *
         skfp->fld[rfFILES].getMrp ( tmpMrp ) ;
         bullChar = skfp->fld[rfFILES].txt.gstr()[tmpMrp.bor + 2] ;
         gs = &skfp->fld[rfFILES].txt.gstr()[tmpMrp.bor + 4] ;
         if ( (indx = gs.find( NEWLINE )) > ZERO )
            gs.limitChars( indx ) ;
         gs.strip() ;

         //* The "selected" item indicates the "parent" directory *
         if ( bullChar == PCHAR )
         {
            if ( (cwdPath.compare( L"/" )) != ZERO )  // if not already at root dir
            {
               this->ExtractPathname ( fileSpec, cwdPath ) ;
               if ( !(this->setCWD ( fileSpec )) )
               { //* Malformed filespec OR user lacks read access (unlikely) *
                  gString gsWarn = warnAccess[this->lang] ;
                  gs.padCols( gsWarn.gscols(), SPACE, true ) ;
                  gsWarn.append( "\n%S\n", gs.gstr() ) ;
                  this->win->QuickDialog ( gsWarn, false, NULL, 0, 0, -1, -1, 8 ) ;
                  done = true ;
               }
            }
            else
               this->win->acBeep () ;
         }
         //* The "selected" item is a subdirectory name *
         else if ( bullChar == DCHAR )
         {
            fileSpec.compose( "%S/%S", cwdPath.gstr(), gs.gstr() ) ;
            if ( !(this->setCWD ( fileSpec )) )
            {  //* Malformed filespec OR user lacks read access *
               gString gsWarn = warnAccess[this->lang] ;
               gs.padCols( gsWarn.gscols(), SPACE, true ) ;
               gsWarn.append( "\n%S\n", gs.gstr() ) ;
               this->win->QuickDialog ( gsWarn, false, NULL, 0, 0, -1, -1, 8 ) ;
               done = true ;
            }
         }
         //* The "selected" item is a non-directory filename *
         else  // (bullChar==FCHAR)
         {
            fileSpec.compose( "%S/%S", cwdPath.gstr(), gs.gstr() ) ;
            done = fileSelected = true ;
         }
      }
      else  // (exitFld == fldCANCEL)
         done = true ;

      //* Release dynamic allocations *
      if ( acArray != NULL )
      { delete[] acArray ; acArray = NULL ; }
      if ( skfp != NULL )
      { delete skfp ; skfp = NULL ; }
   }
   while ( ! done ) ;

   return fileSelected ;

}  //* End reportCwdFiles() *
#endif// DEPRECATED: This file is redundant with select4Edit() and is not currently called.

//*************************
//*       repdlgFmt       *
//*************************
//********************************************************************************
//* Format the caller's skForm object.                                           *
//* Called by various methods which are very similar in function, but display    *
//* different data sets.                                                         *
//*                                                                              *
//*                                                                              *
//* Input  : dlgOpt : specify the dialog configuration                           *
//*                   (member of enum repfldOption)                              *
//*          fCnt   : number of records in the 'fldText' array                   *
//*          dlgHgt : dialog height in rows                                      *
//*          dlgWid : dialog width in columns                                    *
//*          fldText: formatted text to be displayed in the Scrollbox control    *
//*                                                                              *
//* Returns: pointer to dynamically-allocated and fully-initialized skForm object*
//*          IMPORTANT NOTE: Caller is responsible for release of dynamic alloc. *
//********************************************************************************

// CZONE - Add a parameter for min/max Scrollbox height.
skForm* AcEdit::repdlgFmt ( short dlgOpt, short fCnt, short dlgHgt, short dlgWid,
                            const char* fldText )
{
   //* Define the dialog window *
   static const char* selLabels[uilENV][rfCOUNT] = // Pushbutton labels ("Select")
   {
      {                          // English
         "----------",  // (dummy record)
         "  SELECT  ",
         "  CANCEL  ",
      },
      {                          // Zhongwen
         "----------",  // (dummy record)
         "   选择   ",
         "   取消   ",
      },
   } ;
   static const char* opnLabels[uilENV][rfCOUNT] = // Pushbutton labels ("Open")
   {
      {                          // English
         "----------",  // (dummy record)
         "   OPEN   ",
         "  CANCEL  ",
      },
      {                          // Zhongwen
         "----------",  // (dummy record)
         "   打开   ",
         "   取消   ",
      },
   } ;
   acAttr pbAttr = acAttr(acaFGb_RED | acaBGb_GREEN) ;
   acAttr focusAttr = acAttr(acaFGb_GREEN | acaBGb_RED) ;
   short fldHgt = dlgHgt - 3,             // height of Scrollbox control
         fldWid = dlgWid - 2,             // width of fldFILES control
         initFocus = fCnt > ZERO ? rfFILES : rfCANCEL ; // index of field with initial focus


   skForm* skfp = new skForm
   (
      rfCOUNT,
      new skField[rfCOUNT]
      {
         //* Field index rfFILES: ftSB (Scrollbox) *
         {
            ftSB,                         // field type
            fldHgt,                       // field height (in rows)
            fldWid,                       // field width (in columns)
            ZERO, ZERO,                   // Y/X offset in window
            fldText,                      // field text
            this->auxAttr,                // color attributes
            floToken,                     // text formatting option
            ZERO,                         // insertion point (cursor offset)
            bool(fCnt > ZERO ? false : true), // read-only flag
            //false,                      // field outline disabled
            //false,                      // hidden flag is 'reset'
            //true,                       // access lock enabled
            //false,                      // initial binary state
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //* Field index rfSELECT: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            10,                           // field width (in columns)
            short(dlgHgt - 3), -11,       // Y/X offset in window
            ((dlgOpt == rfoptBuffers) ? selLabels[this->lang][rfSELECT] :
                                        opnLabels[this->lang][rfSELECT]),
#if 1    // CZONE - Use dlgOpt
// rfoptBuffers -> "Select"
// rfoptRecents -> "Open"
// rfoptCwdList -> "Open"
#else    // Use binary flag
            (active ? Labels[this->lang][rfSELECT] : altLabel[this->lang]), // field text
#endif   // Use binary flag
            (fCnt > ZERO ? pbAttr : acAttr(acaFGb_GREY | acaBG_GREY)), // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (cursor offset)
            bool(fCnt > ZERO ? false : true), // read-only flag
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index rfCANCEL: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            10,                           // field width (in columns)
            short(dlgHgt - 3), 1,         // Y/X offset in window
            ((dlgOpt == rfoptBuffers) ? selLabels[this->lang][rfCANCEL] :
                                        opnLabels[this->lang][rfCANCEL]),
#if 1    // CZONE - Use dlgOpt
#else    // Use binary flag
            Labels[this->lang][rfCANCEL], // field text
#endif   // Use binary flag
            pbAttr,                       // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },
      },
      //* Initialize remaining skForm parameters to default values.*
      initFocus,        // index of field with initial focus
      focusAttr         // 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
   ) ;

   return ( skfp ) ;

}  //* End repdlgFmt() *

//*************************
//*    reportFileStats    *
//*************************
//********************************************************************************
//* Display file statistics for the file under edit in the text field with focus.*
//*                                                                              *
//*                                                                              *
//* Input  : fmPtr : pointer to FileMan object containing the stat data          *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* System stat structure                                                        *
//*                                                                              *
//* The statx() function uses the statx structure. (Linux 4.11, glibc 2.28)      *
//* #include <fcntl.h> and #include <sys/stat.h>                                 *
//*                                                                              *
//* struct statx                                                                 *
//* {                                                                            *
//*    __u32 stx_mask;        /* Mask of bits indicating                         *
//*                              filled fields */                                *
//*    __u32 stx_blksize;     /* Block size for filesystem I/O */                *
//*    __u64 stx_attributes;  /* Extra file attribute indicators */              *
//*    __u32 stx_nlink;       /* Number of hard links */                         *
//*    __u32 stx_uid;         /* User ID of owner */                             *
//*    __u32 stx_gid;         /* Group ID of owner */                            *
//*    __u16 stx_mode;        /* File type and mode */                           *
//*    __u64 stx_ino;         /* Inode number */                                 *
//*    __u64 stx_size;        /* Total size in bytes */                          *
//*    __u64 stx_blocks;      /* Number of 512B blocks allocated */              *
//*    __u64 stx_attributes_mask;                                                *
//*                           /* Mask to show what's supported                   *
//*                              in stx_attributes */                            *
//*                                                                              *
//*    /* The following fields are file timestamps */                            *
//*    struct statx_timestamp stx_atime;  /* Last access */                      *
//*    struct statx_timestamp stx_btime;  /* Creation */                         *
//*    struct statx_timestamp stx_ctime;  /* Last status change */               *
//*    struct statx_timestamp stx_mtime;  /* Last modification */                *
//*                                                                              *
//*    /* If this file represents a device, then the next two                    *
//*       fields contain the ID of the device */                                 *
//*    __u32 stx_rdev_major;  /* Major ID */                                     *
//*    __u32 stx_rdev_minor;  /* Minor ID */                                     *
//*                                                                              *
//*    /* The next two fields contain the ID of the device                       *
//*       containing the filesystem where the file resides */                    *
//*    __u32 stx_dev_major;   /* Major ID */                                     *
//*    __u32 stx_dev_minor;   /* Minor ID */                                     *
//*                                                                              *
//*    __u64 stx_mnt_id;      /* Mount ID */                                     *
//*                                                                              *
//*    /* Direct I/O alignment restrictions */                                   *
//*    __u32 stx_dio_mem_align;                                                  *
//*    __u32 stx_dio_offset_align;                                               *
//* };                                                                           *
//*                                                                              *
//* The file timestamps are structures of the following type:                    *
//*                                                                              *
//* struct statx_timestamp {                                                     *
//*   __s64 tv_sec;    /* Seconds since the Epoch (UNIX time) */                 *
//*   __u32 tv_nsec;   /* Nanoseconds since tv_sec */                            *
//* };                                                                           *
//*                                                                              *
//* Permission bit macros:                                                       *
//* ----------------------                                                       *
//* S_IRWXU     00700   owner has read, write, and execute permission            *
//* S_IRUSR     00400   owner has read permission                                *
//* S_IWUSR     00200   owner has write permission                               *
//* S_IXUSR     00100   owner has execute permission                             *
//*                                                                              *
//* S_IRWXG     00070   group has read, write, and execute permission            *
//* S_IRGRP     00040   group has read permission                                *
//* S_IWGRP     00020   group has write permission                               *
//* S_IXGRP     00010   group has execute permission                             *
//*                                                                              *
//* S_IRWXO     00007   others (not in group) read, write, and execute permission*
//* S_IROTH     00004   others have read permission                              *
//* S_IWOTH     00002   others have write permission                             *
//* S_IXOTH     00001   others have execute permission                           *
//*                                                                              *
//*  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---  ---   *
//* [Deprecated stat64 structure - not used in this application]                 *
//* struct stat64             // supports file size > 2*31 bytes --------------  *
//* {                                                                            *
//*    dev_t      st_dev ;    // device                            (8 bytes)     *
//*    ino64_t    st_ino ;    // inode  (__u_quat_t type)          (8 bytes)     *
//*    mode_t     st_mode ;   // mode i.e. permissions & file type (4 bytes)     *
//*    nlink_t    st_nlink ;  // number of hard links              (8 bytes)     *
//*    uid_t      st_uid ;    // user ID of file's owner           (4 bytes)     *
//*    gid_t      st_gid ;    // group ID of file's owner          (4 bytes)     *
//*    dev_t      st_rdev ;   // device type (if an inode device)  (8 bytes)     *
//*    off64_t    st_size ;   // total size, in bytes (__quad_t)   (8 bytes)     *
//*    blksize_t  st_blksize; // blocksize for file system i/o     (8 bytes)     *
//*    blkcnt64_t st_blocks ; // blocks allocated (__quat_t type)  (8 bytes)     *
//*    time_t     st_atime ;  // time of last access (file opened) (8 bytes)     *
//*    time_t     st_mtime ;  // time of last modification         (8 bytes)     *
//*    time_t     st_ctime ;  // time of last change (in status)   (8 bytes)     *
//*    - - -                                                                     *
//*    __ino_t    __st_ino    // legacy 32-bit inode                             *
//* } ;                                                                          *
//*                                                                              *
//********************************************************************************

void AcEdit::reportFileStats ( const FileMan* fmPtr )
{
   static const char* statTemplate[uilENV] = 
   {
      {
         "%S\n"
         "        Timestamp:          │ Owner: %s\n"
         "Modify: %s │ Type : %s\n"
         "Access: %s │ Bytes: %s\n"
         "Stats : %s │ INODE: %lld\n"
         "Birth : %s │ Links: %-3d\n"
         "╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌\n"
         "    Owner  Group  Other     │ Characters: %s\n"
         "    %s     │ Text Lines: %s\n"
         "        %12s        │ Free space: %s\n"
         "╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┴╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌\n"
      },
      {
         "%S\n"
         "         时间戳:            │ 物主 : %s\n"
         "修改的: %s │ 类型 : %s\n"
         "使用权: %s │ 字节 : %s\n"
         "统计学: %s │ INODE: %lld\n"
         "出生  : %s │ 链接 : %-3d\n"
         "╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌\n"
         "    物主   团体   别人      │ 字符数    : %s\n"
         "    %S     │ 文本行    : %s\n"
         "        %12s        │ 自由空间  : %s\n"
         "╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┴╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌\n"
      },
   } ;
   const char* nomodsMsg = "............" ;
   const char* modMsg[uilENV] =
   {
      "Unsaved Mods",                     // English
      "未保存的修改",                           // Zhongwen
   } ;

   const short dlgWID  = 51 ; // dialog width
   const short unWID   = 12 ; // user-name width
   const short unLEN   = 32 ; // user-name buffer length
   const short timeLEN = 20 ; // timestamp buffer length

   //* Format the filename *
   gString gsName, gs( fmPtr->srcSpec ) ;
   this->ExtractFilename ( gsName, gs ) ;
   gsName.limitCols( dlgWID - 2 ) ;

   //* Format user ID and user name *
   const char* unk[uilENV] = { "unknown", "不见经传" } ;
   char uname[unLEN] ;
   gs = unk[this->lang] ;
   if ( this->userInfo.goodData )
      gs.compose( "%4d %s", &this->userInfo.userID, this->userInfo.userName ) ;
   gs.limitCols( unWID ) ;          // truncate if necessary
   gs.copy( uname, unLEN ) ;

   //* Format the timestamps *
   localTime lt ;
   char mtime[timeLEN], atime[timeLEN], ctime[timeLEN], btime[timeLEN] ;
   this->epoch2Timestamp ( fmPtr->tnf.rawStats.stx_mtime.tv_sec, gs ) ;
   gs.copy( mtime, timeLEN ) ;
   this->epoch2Timestamp ( fmPtr->tnf.rawStats.stx_atime.tv_sec, gs ) ;
   gs.copy( atime, timeLEN ) ;
   this->epoch2Timestamp ( fmPtr->tnf.rawStats.stx_ctime.tv_sec, gs ) ;
   gs.copy( ctime, timeLEN ) ;
   this->epoch2Timestamp ( fmPtr->tnf.rawStats.stx_btime.tv_sec, gs ) ;
   gs.copy( btime, timeLEN ) ;

   //* Decode file type *
   char ftype[unLEN] ;
   gs = decodeFiletype ( fmPtr->tnf.fType ) ;
   gs.copy( ftype, unLEN) ;

   //* Format the file permissions *
   char perms[unLEN] ;
   gs = decodePermissions ( fmPtr->tnf.rawStats.stx_mode ) ;
   gs.copy( perms, unLEN ) ;

   //* Report data about field with input focus.    *
   //* This field contains the data reported above. *
   //* Format Characters, Text Lines and Free Space *
   char fbytes[unWID], fchars[unWID], 
        tlines[unWID], freechars[unWID], fmods[16] ;
   int32_t i;
   skField  skfld ;
   fldIndex fIndx = fldIndex(this->win->GetInputFocus ()) ;
   this->win->GetFieldData ( fIndx, skfld ) ;
   gs.formatInt( fmPtr->tnf.fBytes, 7, true ) ; // byte count
   gs.copy( fbytes, unWID ) ;

   i = skfld.txt.gschars() ;              // character count
   gs.formatInt( i, 7, true ) ;
   gs.copy( fchars, unWID ) ;
   i = gsALLOCMAX - i ;                   // free space
   gs.formatInt( i, 7, true ) ;
   gs.copy( freechars, unWID ) ;
   i = skfld.mrptr->rCnt ;                // text rows
   gs.formatInt( i, 7, true ) ;
   gs.copy( tlines, unWID ) ;
   gs = nomodsMsg ;                       // file modifications message
   if ( fmPtr->mods || fmPtr->wmods )
      gs = modMsg[this->lang] ;
   gs.copy( fmods, 16 ) ;

   //* Cconcatenate the static text for the dialog. *
   gString gsStatic( statTemplate[this->lang], gsName.gstr(), uname,
//      mtime, fTypes[fmPtr->tnf.fType],
      mtime, ftype,
      atime, fbytes,
      ctime, &fmPtr->tnf.rawStats.stx_ino,
      btime, &fmPtr->tnf.rawStats.stx_nlink,
      fchars, 
      perms, tlines, 
      fmods, freechars
      ) ;

   this->infoDialog ( gsStatic, false ) ;

}  //* End reportFileStats() *

//*************************
//*       autoSave        *
//*************************
//********************************************************************************
//* Save TextEdit data to the working copy of the file.                          *
//* This method is invoked at intervals by the callback method.                  *
//* It may also called as a preliminary to saving working copy to source.        *
//*                                                                              *
//* Note: On entry, the 'wmods' flag is ignored on the premise that caller       *
//*       is not a complete idiot.                                               *
//*                                                                              *
//* Input  : fmIndx : (optional, default:AREC) target FileMan record             *
//*                   [CURRENTLY IGNORED]                                        *
//*                                                                              *
//* Returns: 'true'  if successful                                               *
//*          'false' if file access error (unlikely)                             *
//********************************************************************************
//* Notes:                                                                       *
//* -- By design, only the active record fman[AREC] field can contain unsaved    *
//*    TextEdit data. The inactive record fman[IREC] will have been saved when   *
//*    it lost focus. Buffers which are not currently associated with a visible  *
//*    field were saved when they went invisible.                                *
//*                                                                              *
//* - Get data from TextEdit field.                                              *
//* - Open target file (truncate).                                               *
//* - Write the retrieved TextEdit data.                                         *
//* - Close the file.                                                            *
//* - Reset the 'wmods' flag.                                                    *
//* - Note: Working copy is not yet saved to source file, so 'mods' flag is not  *
//*   reset, and Gutter field will not be updated.                               *
//*                                                                              *
//* -- This method includes an optional Gutter message alerting user that        *
//*    auto-save has occurred; however it seems more annoying than useful.       *
//********************************************************************************

bool AcEdit::autoSave ( short fmIndx )
{
   #define DEBUG_AUTOSAVE (0)             // for debugging only
   #define AS_NOTIFY (0)                  // notify when auto-save occurs
   #if AS_NOTIFY != 0
   //* Note that the message will be centered *
   //* in the Gutter message area.            *
   const char* Notify[uilENV] = 
   {
      "Auto-Save",
      "自动保存",
   } ;
   #endif   // AS_NOTIFY

   fmIndx = AREC ;                        // assume that target is active record

   #if DEBUG_ACE != 0 && DEBUG_AUTOSAVE != 0
   tnFName tnfBefore, tnfAfter ;
   gString gsas( this->fman[fmIndx].wrkSpec ), gsts ;
   char tsb[64], tsa[64] ;
   this->GetFileStats ( tnfBefore, gsas ) ;
   this->epoch2Timestamp ( tnfBefore.modTime.epoch, gsts ) ;
   gsts.copy( tsb, 64 ) ;
   #endif   // DEBUG_ACE && DEBUG_AUTOSAVE

   gString gsTxt ;
   int srcIndx ;
   bool status = false ;                  // return value

   this->win->GetFieldText ( this->fman[fmIndx].tindx, gsTxt ) ;
   ofstream ofs( this->fman[fmIndx].wrkSpec, ofstream::trunc ) ;
   if ( ofs.is_open() )
   {
      ofs << gsTxt ;
      ofs.close() ;
      this->fman[fmIndx].wmods = false ;
      status = true ;

      #if AS_NOTIFY != 0
      gsTxt.compose( "%s", Notify[this->lang] ) ;
      this->updateGutter ( false, gsTxt.gstr(), true, 1 ) ;
      #endif   // AS_NOTIFY
      
      #if DEBUG_ACE != 0 && DEBUG_AUTOSAVE != 0
      this->GetFileStats ( tnfAfter, gsas ) ;
      this->epoch2Timestamp ( tnfAfter.modTime.epoch, gsts ) ;
      gsts.copy( tsa, 64 ) ;
      gsas.compose( "%-36s \n"
                    "Before: %s  %llu bytes \n"
                    "After : %s  %llu bytes \n",
                    this->fman[fmIndx].tnf.fName,
                    tsb, &tnfBefore.fBytes,
                    tsa, &tnfAfter.fBytes ) ;
      this->flashDialog ( gsas, 15 ) ;
      #endif   // DEBUG_ACE && DEBUG_AUTOSAVE

// CZONE - This should update some part of the source record, 'mods' flag if 
// nothing else. What else has lasting value? position?
      //* Update the source record *
      if ( (srcIndx = this->fman[fmIndx].sindx) >= ZERO )
      {
         this->fman[srcIndx].mods = true ;
      }
   }
   return status ;

}  //* End autoSave() *

//*************************
//*       fullSave        *
//*************************
//********************************************************************************
//* Update the source file for a file currently under edit.                      *
//* -- This method may be invoked directly by the user through the File/Save     *
//*    menu item or via the CTRL+S shortcut key. Both of these commands refer    *
//*    exclusively to the fman[AREC] record which is the record for the field    *
//*    which currently has the input focus.                                      *
//* -- This method may also be called during the application shutdown sequence   *
//*    if user has not already saved the file. In this case, 'fmIndx' may refer  *
//*    to any file in the fman[] array.                                          *
//*                                                                              *
//* If target source file exists, it will be updated; however, if the source     *
//* file does not yet exist, call the save-as method which will prompt user      *
//* to specify a name for the new file.                                          *
//*                                                                              *
//* 1) On entry, if the 'wmods' flag is set, indicating unsaved data in the      *
//*    TextEdit field, the autoSave() method is called to update the working     *
//*    copy of the file.                                                         *
//* 2) The (updated) working copy of the file is then copied to the source       *
//*    filespec overwriting the previous source data.                            *
//*    After the update, the source file and the working copy should be          *
//*    identical.                                                                *
//* 3) The 'wmods' and 'mods' flags will be reset, indicating that there are     *
//*    no unsaved data associated with the file.                                 *
//*                                                                              *
//* Note that the user is free to save a file that doesn't need saving.          *
//*                                                                              *
//*                                                                              *
//* Input  : fmIndx : (optional, default:AREC) target FileMan record             *
//*                   -- This will normally be the record associated with the    *
//*                      file data displayed in the TextEdit field with focus.   *
//*                   -- During the shutdown sequence, this could be any file    *
//*                      with unsaved edits.                                     *
//*                      (user forgot to save before invoking shutdown sequence) *
//*                                                                              *
//* Returns: 'true'  if successful                                               *
//*          'false' if file access error (unlikely)                             *
//********************************************************************************
//* Notes:                                                                       *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

bool AcEdit::fullSave ( short fmIndx )
{
   const char* savedAt[uilENV] =          // gutter save message
   {
      "Saved at",                         // English
      "已保存",                              // Zhongwen
   } ;

   gString gsbk ;                         // backup filespec
   bool status = false ;                  // return value

   if ( (fmIndx >= 0) && (fmIndx < MAX_FMANS) )
   {
      FileMan* fmptr = &this->fman[fmIndx] ;
//* TEMP */ this->dumpFileMan ( 5, false, "fullSave 1" ) ;
      //* If a source file for the data has not yet been created, *
      //* call save-as method to establish the source file.       *
      if ( *fmptr->srcSpec == NULLCHAR )
      {
         return ( (this->saveFileAs ()) ) ;  // note the early return
      }

      //* Update working copy, (assumes the AREC record) *
      if ( fmptr->wmods )
         this->autoSave ( fmIndx ) ;

      //* Rename source file as backup file *
      gsbk.compose( "%s~", fmptr->srcSpec ) ;
      this->RenameFile ( fmptr->srcSpec, gsbk.ustr() ) ;

      //* Copy working file to source.           *
      //* If copy successful, delete backup copy *
      if ( (this->CopyFile ( fmptr->wrkSpec, fmptr->srcSpec )) == ZERO )
         this->DeleteFile ( gsbk.ustr() ) ;

      //* Update file stats. Note that source and copy stats s/b  *
      //* nearly identical (except for inode); however, the source*
      //* file stats will take precedence, so do stats for source *
      //* and share.                                              *
      //* Update the FileMan records: both the "active" record    *
      //* (if any) and the "source" record.                       *
      short srcIndx = (fmIndx >= AREC ? fmptr->sindx : fmIndx ) ;
      this->GetFileStats ( this->fman[srcIndx].tnf, fmptr->srcSpec ) ;
      time_t srcTime = this->fman[srcIndx].tnf.rawStats.stx_mtime.tv_sec ;
// CZONE - This section may have logical problems. Needs some thought...
      if ( fmIndx >= AREC )
      {
         int64_t ino = fmptr->tnf.rawStats.stx_ino ;           // save the inode
         fmptr->tnf = this->fman[srcIndx].tnf ;                // copy stats
         fmptr->tnf.rawStats.stx_ino = ino ;                   // restore inode
         this->fman[srcIndx].mods = fmptr->mods = false ;      // reset the 'mods' flag
         this->fman[srcIndx].edtime = fmptr->edtime = srcTime ;// update timestamp
         this->fman[srcIndx].vtop = fmptr->vtop ;              // copy position data
         this->fman[srcIndx].voff = fmptr->voff ;
         this->fman[srcIndx].hoff = fmptr->hoff ;
      }
      else
      {
         this->fman[srcIndx].mods = false ;                    // reset the 'mods' flag
         this->fman[srcIndx].edtime = srcTime ;                // update timestamp
      }
//* TEMP */ this->dumpFileMan ( &this->fman[fmptr->sindx], false, false, "source" ) ;

      //* Update gutter stats to reflect no-mods-pending.*
      this->updateGutter ( true ) ;

      //* Flash a file-saved message in the Gutter field."
      this->formatTimestamp ( this->fman[srcIndx].tnf.modTime, gsbk  ) ;
      int indx = gsbk.findlast( L' ' ) ;
      gString gs( "%s%S", savedAt[this->lang], &gsbk.gstr()[indx] ) ;
      this->updateGutter ( false, gs.gstr(), true, 1 ) ;
   }
   return status ;

}  //* End fullSave() *

//*************************
//*        saveAll        *
//*************************
//********************************************************************************
//* Scan the list of files currently under edit and verify that all              *
//* associated source files have been updated.                                   *
//*                                                                              *
//* For each FileMan record which has the 'open' flag set, verify that:          *
//*  1) If TextEdit field contains unsaved data, perform an auto-save.           *
//*  2) If working copy of file contains data which has not been saved to        *
//*     source file, ask user what to do:                                        *
//*     a) save changes                                                          *
//*     b) discard changes                                                       *
//*     c) return to edit mode                                                   *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true'  if all modified data was either saved or discarded          *
//*                  according to user instructions.                             *
//*          'false' if user has specified a return to edit mode                 *
//********************************************************************************

bool AcEdit::saveAll ( void )
{
   bool status = false ;                  // return value
#if 1    // CZONE - saveAll()
// Is it practical to combine saveAll with the config file update?

   for ( short r = ZERO ; r < MAX_FMANS ; ++r )
   {
      if ( this->fman[r].open )
      {
      }
   }

/* TEMP */ status = true ;
#endif   // U/C
   return status ;

}  //* End saveAll() *

//*************************
//*      saveFileAs       *
//*************************
//********************************************************************************
//* Save file under a new name. There are two general scenarios:                 *
//* 1) A new set of data that is not yet associated with any file on disc.       *
//*    a) The buffer containing the data will be updated to include the name     *
//*       filespec and stat data for the new file.                               *
//*                                                                              *
//* 2) Data associated with an existing file.                                    *
//*    a) The existing file if open, will be closed without modification.        *
//*    b) The buffer containing the data will be copied to a new buffer, and     *
//*       the buffer referencing the closed file will become inactive.           *
//*    c) The data will be saved to a newly-created file.                        *
//*                                                                              *
//* -- The filename provided by the user must be unique within the target        *
//*    directory. The save is not allowed to overwrite an existing file.         *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  :                                                                     *
//*                                                                              *
//*                                                                              *
//* Returns: 'true'  if file written successfully                                *
//*          'false' if:                                                         *
//*               a) user cancels the operation                                  *
//*               b) user does not have write permission for target directory    *
//*               c) other system error                                          *
//*                                                                              *
//********************************************************************************

bool AcEdit::saveFileAs ( void )
{
   static const char* Title[uilENV] = 
   {
      " Save File As ",          // English
      " 另存为 ",                   // Zhongwen
   } ;
   static const char* Labels[uilENV][sfaCOUNT] = // Field labels
   {
      {                          // English
         "----------",  // (dummy record for Textbox)
         "----------",  // (dummy record for warning Textbox)
         "   SAVE   ",
         "  CANCEL  ",
         "[ ] VFAT compatible",
         "----------",  // (dummy record for Scrollbox)
      },
      {                          // Zhongwen
         "----------",  // (dummy record for Textbox)
         "----------",  // (dummy record for warning Textbox)
         "  节省  ",
         "   取消   ",
         "[ ] VFAT 兼容",
         "----------",  // (dummy record for Scrollbox)
      },
   } ;
   //* Note that this text acts as a template for initial static text. (see below) *
   static const char* staticText[uilENV] = 
   {
      "Save buffer data under a new filename.        \n" // English
      "The file will be saved to the current working \n"
      "directory (CWD). The filename specified must  \n"
      "be unique within the target directory.        \n"
      " \n"    // Scrollbox1
      " \n"    // Scrollbox2
      " \n"    // Scrollbox3
      " \n"    // Scrollbox4
      " \n"    // Warn TB and Radiobuttn
      " \n"    // Filename Textbox
      "%s\n"   // Filter message
      " \n",

      "                                              \n" // Zhongwen
      "将缓冲区数据保存到新文件名下。                  \n"
      "文件将保存到当前工作目录 (CWD)。                \n"
      "指定的文件名在目标目录中必须是唯一的。          \n"
      " \n"    // Scrollbox1
      " \n"    // Scrollbox2
      " \n"    // Scrollbox3
      " \n"    // Scrollbox4
      " \n"    // Warn TB and Radiobuttn
      " \n"    // Filename Textbox
      "%s\n"   // Filter message
      " \n",
   } ;

   acAttr pbAttr = acAttr(acaFGb_RED | acaBGb_GREEN) ;
   acAttr warnAttr = acAttr(acaFG_RED | (this->dlgAttr & acaBG_MASK)) ;
   acAttr focusAttr = acAttr(acaFGb_GREEN | acaBGb_RED) ;
   gString gsList ;                       // formatted list of files
   gString gsFNames, gsDNames ;           // accumulator buffers
   gString gsCwd ;                        // filespec of CWD
   //* Construct the dialog's static text _including_ the text filter *
   //* message matching the initial state of the Radiobutton.         *
   gString gsStatic( staticText[this->lang], // static text displayed in dialog
                     sfaFilterMsg[this->lang][ZERO] ) ;
   gString gs ;                     // text analysis
   acAttr* acArray = NULL ;         // pointer to dynamic allocation
   short itemCnt = ZERO,            // items to be displayed
         fldHgt  = 4,               // height of Scrollbox control
         fldWid  = 48,              // width of Scrollbox and Textbox controls
         warnWid = fldWid / 2 + 4,  // width of warnint control
         dlgHgt  = 3,               // final dialog height in rows based on static text
         dlgWid = fldWid + 4,       // dialog width in columns
         fldOffy = 4,               // field offset from top of dialog
         fldOffx = -(fldWid / 2),   // field offset from center of dialog
         vfOffx  = 5,               // X offset for VFAT Radiobutton
         exitFld ;                  // index of field with focus on exit from dialog
   bool vfatFilter = false,         // initial state of VFAT Radiobutton
        status = false ;            // return value

   //* Adjust Radiobutton X offset to right edge of dialog. *
   //*  (This is a positive offset from center of dialog.)  *
   gs = Labels[this->lang][sfaVFAT] ;
   vfOffx = (dlgWid / 2 - 2) - gs.gscols();

   //* Calculate dialog height based on height of static text.*
   short stHgt, stWid ;
   this->win->ParagraphSize ( gsStatic, stHgt, stWid ) ;
   dlgHgt += stHgt ;

   //* Scan the CWD, format the individual records, and create *
   //* a list of color attributes, one for each record.        *
   itemCnt = this->readCwd( gsList, acArray, fldWid ) ;

   //*********************
   //* Define the dialog *
   //*********************
   skForm* skfp = new skForm
   (
      sfaCOUNT,
      new skField[sfaCOUNT]
      {
         {  // sfaTEXT
            ftTB,                         // field type
            1,                            // field height (in rows)
            fldWid,                       // field width (in columns)
            short(fldOffy + fldHgt + 1),  // Y/X offset in window
            fldOffx,
            "",                           // field text (initially none)
            acAttr(acaFG_BLUE | acaBGb_GREY), // color attributes
            floShift,                     // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },
         {  // sfaWARN
            ftTB,                         // field type
            1,                            // field height (in rows)
            warnWid,                      // field width (in columns)
            short(fldOffy + fldHgt),      // Y/X offset in window
            short(fldOffx + 1),
            "",                           // field text (initially none)
            warnAttr,                       // color attributes
            floShift,                     // text formatting option
            ZERO,                         // insertion point (cursor offset)
            true,                         // read-only
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },
         {  // sfaSAVE
            ftPB,                         // field type
            1,                            // field height (in rows)
            10,                           // field width (in columns)
            short(dlgHgt - 3), -11,       // Y/X offset in window
            Labels[this->lang][sfaSAVE],  // field text
            pbAttr,                       // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },
         {  // sfaCANCEL
            ftPB,                         // field type
            1,                            // field height (in rows)
            10,                           // field width (in columns)
            short(dlgHgt - 3), 1,         // Y/X offset in dialog
            Labels[this->lang][sfaCANCEL],// field text
            pbAttr,                       // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },
         {  // sfaVFAT
            ftRB,                         // field type
            1,                            // field height (in rows)
            3,                            // field width (in columns)
            short(fldOffy + fldHgt),      // Y/X offset in window
            vfOffx, 
            Labels[this->lang][sfaVFAT],  // field text
            this->dlgAttr,                // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (n/a)
            false,                        // read-only flag is reset
            false,                        // field outline disabled
            false,                        // field is visible (not hidden)
            true,                         // access lock enabled
            vfatFilter,                   // binary state initially reset
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },
         {  // sfaCWD
            ftSB,                         // field type
            fldHgt,                       // field height (in rows)
            fldWid,                       // field width (in columns)
            fldOffy, fldOffx,             // Y/X offset in window
            gsList.ustr(),                // field text
            this->auxAttr,                // color attributes
            floToken,                     // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // read-only flag
            //false,                      // field outline disabled
            //false,                      // hidden flag is 'reset'
            //true,                       // access lock enabled
            //false,                      // initial binary state
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },
      },
      //* Initialize remaining skForm parameters to default values.*
      ZERO,             // index of field with initial focus
      focusAttr         // 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
   ) ;

   //* Apply multi-color attributes to Scrollbox data. *
   skfp->fld[sfaCWD].colorConfig ( acArray, itemCnt ) ;

   aceditPtr = this ;
   this->adlg = adSaveAs ;    // indicate the active dialog (used by callback)

   //*******************
   //* Open the dialog *
   //*******************
   exitFld = this->win->Dialog ( dlgHgt, dlgWid, skfp, gsStatic.ustr(), 
                                 Title[this->lang], 1, -1, ltSINGLE, 
                                 acaUSEDFLT, this->dlgAttr, 0, 1, 
                                 nonmember_dlgCallback ) ;

   this->adlg = adNONE ;      // dialog inactive
   aceditPtr = NULL ;
   gString gsTrg( skfp->fld[sfaTEXT].txt ) ;
   if ( (exitFld == sfaSAVE) && (gsTrg.gschars() > 1) )
   {
      //* Construct the full filespec *
      this->getCWD ( gsCwd ) ;
      gs.compose( "%S/%S", gsCwd.gstr(), gsTrg.gstr() ) ;
/* TEMP */ gsdbg.compose( "SaveFileAs( '%S' )", gs.gstr() ) ; ofsdbg << gsdbg << endl ;
      //* Write file to target, then verify that write was successful.*
// What are we actually writing to target? temp file? field text?
// write field data to temp file, THEN copy to to new target sourcename.
// The mechanism s/b as error proof as possible.
// 1) Target has been verified not to exist.
// 2) copy temp file as new source filename
// 3) verify that copy was successful
// Which fields need update? srcSpec
   }
// If callback can do a timed message, we don't need to do it here.
   else if ( exitFld == sfaCANCEL )       // user cancelled the rename
   {
   }

   //* Release dynamic allocations *
   if ( acArray != NULL )
   { delete[] acArray ; acArray = NULL ; }
   delete skfp ;

   return status ;

}  //* End saveFileAs() *

//*************************
//*      sfaCallback      *
//*************************
//********************************************************************************
//* Callback method used by the "Save File As" dialog.                           *
//*                                                                              *
//* 1) Track the Enter key within the Scrollbox.                                 *
//*    a) If Enter is pressed on a subdirectory item, display that subdirectory. *
//*    b) If Enter is pressed on the parent-directory item, display parent dir.  *
//*    c) If Enter is pressed on a non-directory name, copy that name to the     *
//*       Textbox.                                                               *
//* 2) Track Pushbutton selection.                                               *
//*    a) If Save Pushbutton selected, retrieve the Textbox data and verify      *
//*       that all characters are filename characters.                           *
//*       - If filename is valid, verify that it is unique within the directory. *
//*         This requires retrieving a copy of the Scrollbox data for comparison.*
//*         - If valid and unique, return the exit-dialog signal.                *
//*       - If invalid character(s), or filename is not unique, write a warning  *
//*         message and return focus to Textbox.                                 *
//*       - If Textbox is empty, then same as Cancel Pushbutton.                 *
//*    c) If Cancel Pushbutton selected, return the exit-dialog signal.          *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: The Dialog() method calls this method once with          *
//*                     firstTime==true, to perform any required initialization. *
//*                     All subsequent calls are made with firstTime==false.     *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          if kecode is not handled by this method: 'wkey' is returned.        *
//*          Special Case: returns wcsCTL_4 to signal that EditDialog() should   *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//********************************************************************************
//* Notes:                                                                       *
//* Aside from its usefulness to the application, this callback explicitly tests *
//* the dialog-specific methods of the ACWin class:                              *
//*    Dialog_GetText()                                                          *
//*    Dialog_SetText()                                                          *
//*    Dialog_GetItem()                                                          *
//*    Dialog_GetState()                                                         *
//*    Dialog_GetField()                                                         *
//*    Dialog_Write()                                                            *
//*                                                                              *
//********************************************************************************

wchar_t AcEdit::sfaCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   static const char* Warn_Dupe[uilENV] =
   { //1234567890123456789012345678 // Warn field width is 28 columns.
      "Error, file already exists.",   // English
      "错误，文件已存在。",                     // Zhongwen
   } ;
   static const char* Warn_Name[uilENV] = 
   { //1234567890123456789012345678 // Warn field width is 28 columns.
      "Invalid filename characters.",  // English
      "错误，文件名中包含无效字符。",                // Zhongwen
   } ;
   static const char* Warn_Access[uilENV] =
   { //1234567890123456789012345678 // Warn field width is 28 columns.
      "Unable to access target dir!",  // English
      "无法访问目标目录！",                     // Zhongwen
   } ;
   static const char* noName[uilENV] = 
   {
      "  Filename not specified.",
      "       未指定文件名。",
   } ;
   static const char* Cancel_Msg[uilENV] = 
   {
      "    Operation Cancelled.",      // English
      "      重命名操作已取消。",               // Zhongwen
   } ;

   const short filterMsgRow = 10 ;        // dialog row where filter message is displayed
   static bool warnMsg = false ;          // set if warning message is visible
   gString gs, gsCwd, gsName, gsList ;
   const skField* skfPtr = NULL ;         // pointer to a control within the dialog
   acAttr* acArray = NULL ;               // pointer to array of Scrollbox attributes
   short itemCnt ;                        // number of items in Scrollbox control
   short item ;                           // Scrollbox item selected
   bool  vfat ;                           // state of text filter Radiobutton
                                          // (set==VFAT compatible)

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

   //* If a warning message is currently displayed, erase it. *
   if ( warnMsg )
   {
      gs.clear() ;
      this->win->Dialog_SetText( sfaWARN, gs ) ;
      warnMsg = false ;
   }

   //* Note that the hotkey for a field has been automagically  *
   //* converted to wcsENTER.                                   *
   if ( (wkey == wcsENTER) || (wkey == wcsSPACE) )
   {
      switch ( ctrlIndex )
      {
         case sfaSAVE:
            this->win->Dialog_GetText ( sfaTEXT, gsName ) ; // retrieve filename data
            if ( (gsName.gschars()) > 1 )
            {
               //* Validate the filename characters.                *
               //* Radiobutton state determines which filter to use.*
               this->win->Dialog_GetState ( sfaVFAT, vfat ) ;
               if ( (this->isFilename ( gsName, false, vfat )) )
               {
                  //* Get Scrollbox text. Compare user-provided filename   *
                  //* with each filename in the array of filenames in CWD. *
                  this->win->Dialog_GetField ( sfaCWD, skfPtr ) ;
                  gsList = skfPtr->txt ;
                  const wchar_t* wsrc = gsList.gstr() ;  // pointer to source data
                  wchar_t wtrg[MAX_FNAME] ;              // filename target buffer
                  int   wchars = gsList.gschars(),       // source characters
                        si = ZERO,
                        ti = ZERO ;                      // target index
                  bool match = false ;                   // set if duplicate name
                  //* Step over the "parent-dir" item at top of list.*
                  while ( (si < wchars) && (wsrc[si] != NEWLINE) )
                     ++si ;
                  ++si ;

                  //* Extract each filename *
                  for( ; si < wchars ; ++si )
                  {
                     while ( (wsrc[si] != NEWLINE) && (wsrc[si] != NULLCHAR) )
                        wtrg[ti++] = wsrc[si++] ;
                     wtrg[ti] = NULLCHAR ;         // terminate the string
                     ti = ZERO ;                   // reset target index
                     gs = wtrg ;                   // load raw data item
                     gs.shiftChars( -3 ) ;         // discard item prefix
                     gs.strip() ;                  // strip leading/trailing whitespace
                     //* Compare with user's filename *
                     if ( (gsName.compare( gs )) == ZERO )
                     { match = true ; break ; }
                  }

                  //* If user's data matches an existing    *
                  //* filename in CWD, warn of duplication. *
                  if ( match )
                  {
                     gs = Warn_Dupe[this->lang] ;
                     this->win->Dialog_SetText( sfaWARN, gs ) ;
                     warnMsg = true ;           // remember that warning was posted
                     wkey = NULLCHAR ;          // keycode handled
                  }

                  //* Else, a valid, non-duplicate filename    *
                  //* specified. Signal that dialog s/b closed.*
                  //* Filename is in Textbox control.          *
                  else
                     wkey = wcsCTL_4 ;
               }
               else
               {
                  //* Warn of invalid filename character(s) *
                  gs = Warn_Name[this->lang] ;
                  this->win->Dialog_SetText( sfaWARN, gs ) ;
                  warnMsg = true ;           // remember that warning was posted
                  wkey = NULLCHAR ;          // keycode handled
               }
            }
            else     // empty filename string
            {
               gs = noName[this->lang] ;
               this->win->Dialog_SetText( sfaWARN, gs ) ;
               this->win->nsleep ( 20 ) ;
               wkey = wcsCTL_4 ;
            }
            break ;

         case sfaCANCEL:                  // Cancel pushbutton
            gs = Cancel_Msg[this->lang] ;
            this->win->Dialog_SetText( sfaWARN, gs ) ;
            this->win->nsleep ( 16 ) ;
            wkey = wcsCTL_4 ;
            break ;

         case sfaCWD:                     // Scrollbox display of CWD files
            //* Get a copy of "selected" item *
            if ( (this->win->Dialog_GetItem ( sfaCWD, gs, item, true )) )
            {
// CZONE - saveFileAs() callback - seems to work.
/* TEMP */ gsdbg.compose( "call Dialog_GetItem ( iNum:%hd iTxt:'%S' )", &item, gs.gstr() ) ;
/* TEMP */ ofsdbg << gsdbg << endl ;
               wchar_t indyChar = *gs.gstr() ;   // isolate indicator character
               gsName = &gs.gstr()[2] ;          // item text without prefix

               //* Move to parent directory or subdirectory *
               if ( (indyChar == PCHAR) || (indyChar == DCHAR) )
               {
                  //* Get filespec of CWD *
                  this->getCWD ( gsCwd ) ;
//OBSOLETE                  if ( !(this->getCWD ( gsCwd )) ) { gsCwd = L"." ; }
                  if ( indyChar == PCHAR )   // moving to parent directory
                  {
                     gs = gsCwd ;
                     this->ExtractPathname ( gsCwd, gs ) ;
                  }
                  else                       // moving to subdirectory
                     gsCwd.append( "/%S", gsName.gstr() ) ;

                  if ( (this->setCWD ( gsCwd )) )
                  {
                     //* Scan new CWD and format the entries.  *
                     // Create an array of color attributes to *
                     //* match the records                     *
                     this->win->Dialog_GetField ( sfaCWD, skfPtr ) ;
                     itemCnt = this->readCwd ( gsList, acArray, skfPtr->wid ) ;
                     //* Update text data, and array of color attributes. *
                     // Programmer's Note: Refresh is not performed after text 
                     // update, but is performed only after attribute update.
                     this->win->Dialog_SetText ( ctrlIndex, gsList, acaUSEDFLT, false ) ;
                     this->win->Dialog_SetAttr ( ctrlIndex, acArray, itemCnt ) ;
                     if ( acArray != NULL )     // release the dynamic allocation
                     { delete[] acArray ; acArray = NULL ; }
                     //* Signal caller that operation is compete.*
                     wkey = NULLCHAR ;
                  }
                  else
                  { //* Malformed filespec OR user lacks read access *
                     gs = Warn_Access[this->lang] ;
                     this->win->Dialog_SetText( sfaWARN, gs ) ;
                     warnMsg = true ;           // remember that warning was posted
                     wkey = NULLCHAR ;          // keycode handled
                  }
               }
               else                          // non-directory filename
                  this->win->Dialog_SetText ( sfaTEXT, gsName ) ;
            }
            // else(call is unlikely to fail)
            break ;

         case sfaVFAT:
            //* State of Radiobutton is being toggled.          *
            //* Match the filter message with Radiobutton state *
            //* NOTE: The state flag has not yet been updated   *
            //* at this point, so the sense is reversed.        *
            this->win->Dialog_GetState ( sfaVFAT, vfat ) ;
            gs = sfaFilterMsg[this->lang][(vfat ? 0 : 1)] ;
// CZONE - Verify X offset for message, (called method changed).
            this->win->Dialog_Write ( gs.gstr(), filterMsgRow, 0, true ) ;
            break ;
      } ;
   }

   return wkey ;

}  //* End sfaCallback() *

//*************************
//*      dlgCallback      *
//*************************
//********************************************************************************
//* This method determines which ACDlg object is active and calls the associated *
//* callback method.                                                             *
//*                                                                              *
//* Note that no more than one dialog may be open at any given moment.           *
//* The open dialog is indicate by the actDlg member variable which              *
//*                                                                              *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: if set, indicates that initialization should occur.      *
//*                                                                              *
//* Returns: keycode returned by called method                                   *
//********************************************************************************

wchar_t AcEdit::dlgCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{

   if ( this->adlg == adOpen )
      wkey = this->s4eCallback ( ctrlIndex, wkey, firstTime ) ;
   else if ( this->adlg == adSaveAs )
      wkey = this->sfaCallback ( ctrlIndex, wkey, firstTime ) ;
   else if ( this->adlg == adCompare )
      wkey = this->cmpCallback ( ctrlIndex, wkey, firstTime ) ;
   else if ( this->adlg == adOrphan )
      wkey = this->rorCallback ( ctrlIndex, wkey, firstTime ) ;

   return wkey ;

}  //* End dlgCallback() *

//*************************
//* nonmember_dlgCallback *
//*************************
//********************************************************************************
//* Non-member function:                                                         *
//* --------------------                                                         *
//* Callback function to monitor user interaction.                               *
//* Handle the callbacks from ACDlg-class objects.                               *
//* The static pointer 'memberPtr' gives this method access to all AcEdit data   *
//* members and public methods; however, it is currently used ONLY for calling   *
//* the member method: dlgCallback().                                            *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: (optional, default: false) calling method calls this     *
//*                     function once with the 'firstTime' flag set, to perform  *
//*                     any required initialization.                             *
//*                     All subsequent calls are made with 'firstTime' reset.    *
//*                                                                              *
//* Returns: keycode returned by called method                                   *
//********************************************************************************

wchar_t nonmember_dlgCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{

   return ( (aceditPtr->dlgCallback ( ctrlIndex, wkey, firstTime )) ) ;

}  //* End nonmember_dlgCallback() *

//*************************
//*     scrollboxItem     *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* Extract 'selected' item from dialog Scrollbox control.                       *
//* Called by various methods that gather user selections via dialog.            *
//*                                                                              *
//* Parse the record into its designator and filename components.                *
//*  a) The "designator" character is one of PCHAR/DCHAR/FCHAR; however, only    *
//*     FCHAR records are valid in this context.                                 *
//*  b) The target record will be terminated by a newline character.             *
//*     The '\n' is removed.                                                     *
//*  c) The record may have been padded with spaces to the full width of the     *
//*     display field. Leading and trailing whitespace characters are removed.   *
//*                                                                              *
//* NOTE: This function makes assumptions about the format of the retrieved      *
//*       item, so it should only be called by methods which have the expected   *
//*       Scrollbox formatting.                                                  *
//*                                                                              *
//* Input  : fptr  : pointer to an skField object describing an ftSB control     *
//*          iTxt  : (by reference) receives item text                           *
//*          iBull : (by reference) receives item designator character           *
//*                      PCHAR || DCHAR || FCHAR                                 *
//*                                                                              *
//* Returns: 'true'  if designator character is FCHAR (non-directory filename)   *
//*                  AND non-empty item text successfully extracted              *
//*          'false' otherwise                                                   *
//********************************************************************************

static bool scrollboxItem ( const skField* fptr, gString& iTxt, wchar_t& iBull )
{
   MRParse tmpMrp ;                       // parsing data for Scrollbox field
   short   item ;                         // Scrollbox row index
   bool    status = false ;               // return value

   fptr->getItem ( iTxt, item ) ;
   iBull = *iTxt.gstr() ;                 // extract the indicator character
   iTxt.shiftChars( -1 ) ;                // remove indicator char from string
   iTxt.strip() ;                         // strip leading and trailing whitespace (if any)
   if ( (iTxt.gschars() > 1) && (iBull == FCHAR) )
      status = true ;
//* TEMP */ gsdbg.compose( "scrollboxItem, getItem( item:%hd iBull:'%C' '%S' )", 
//* TEMP */                &item, &iBull, iTxt.gstr() ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;

   return status ;

}  //* End scrollboxItem() *

//*************************
//*    reportKeycodes     *
//*************************
//********************************************************************************
//* Report the keycodes and key combinations for the list of shortcut keys.      *
//*                                                                              *
//* User-defined keycodes may be any _unused_ member of the following groups:    *
//* ASCII Control Key Combinations: wcsCTL_A - wcsCTL_Z                          *
//* Alt Key Combinations: wcsALT_A - wcsALT_Z, wcsALT_0 - wcsALT_9,              *
//*                       Alt + cursor movement keys                             *
//*                       Alt + punctuation keys                                 *
//* Function Key Combinations: wcsF01 - wcsF12, wcsSF01 - wcsSF12,               *
//*                            wcsAF01 - wcsAF12                                 *
//* See documentation for more information.                                      *
//*                                                                              *
//* Default values are defined in ShortkeyDflt[] array.                          *
//* Keycode names are defined in ShortkeyName[] array.                           *
//* Key combinations are defined in ShortkeyDesc[] array.                        *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Note: It is complex (and slow) to convert a hex keycode into the equivalent  *
//* constant value name. Example: 0x002197 --> "wcsPGUP"                         *
//* The configuration program performs this conversion, but for the current      *
//* release at least, we think it's too much for this simple method.             *
//* Refer to the Test_AsciiCodes() method in AnsiCmdTest.cpp for an of decoding  *
//* these values.                                                                *
//* For this reason, any user-defined (non-default) shortcut keys, are displayed *
//* here using the actual hex value of the keycode.                              *
//********************************************************************************

void AcEdit::reportKeycodes ( void )
{
   enum fldName : short
   {
      fldLIST_SB,
      fldCLOSE,
      fldDOCS,
      fldCOUNT
   } ;
   static const char* Title[uilENV] = 
   {
      " \"Shortcut Key\" Keycodes ",
      "  “快捷键”键码  ",
   } ;
   static const char* Header[uilENV] = 
   {// Programmer's Note: This text must be exactly the width of the Scrollbox.
    // 123456789012345678901234567890123456789012345678901234567890
      "   KEY NAME    | KEY(s)    KEYCODE   |  CUSTOM KEYCODE \n",
      "     键名      | 组合键      键码    |    自定义键码   \n",
   } ;
   static const char* Instructions[uilENV] = 
   {
      "Shortcut keys are a convenient way to access common    \n" // English
      "application functionality. The default values for each \n"
      "are listed here, along with the custom keycode constant\n"
      "(if any) specified in the configuration file.          \n"
      "Please see documentation for details.                  \n",
      "快捷键是访问常见应用程序功能的便捷方式。 \n"                                   // Zhongwen
      "这里列出了每个快捷键的默认值， \n"
      "以及自定义键码常量（如果有） \n"
      "以及配置文件中指定的自定义键码常量（如果有）。 \n"
      "请参阅文档了解详情。 \n",
   } ;
   static const char* Labels[uilENV][fldCOUNT] =
   {
      {                          // English
         " --- ",    // (dummy)
         "  ^CLOSE  ",
         " ^DOCUMENTATION ",
      },
      {                          // Zhongwen
         " --- ",    // (dummy)
         "  ^CLOSE  ",
         " ^DOCUMENTATION ",
      },
   } ;
   //* Define the controls for the dialog *
   acAttr sbbAttr = acAttr(acaFGb_BROWN | acaBG_BLACK) ;
   acAttr sbiAttr = acAttr(acaFGb_GREY | acaBG_BLACK) ;
   gString gs ;
   short sbHgt   = 6,
         sbWid   = 55,
         dlgHgt  = 16,
         dlgWid  = sbWid + 4,
         txtVOff = sbHgt + 2,
         pbVOff  = dlgWid - 2 ;

   //* Format Scrollbox display data *
   gString gsSbText ;
   for ( short k = ZERO ; k < SHORT_KEYS ; ++k )
   {
      gs.compose( "%-15s %-21s ", ShortkeyName[k], ShortkeyDesc[k] ) ;
      if ( this->keys.ukey[k] != this->keys.dkey[k] )
         gs.append( "%06X (hex)", &this->keys.ukey[k] ) ; // (see note above)
      else
         gs.append( L"   ---" ) ;
      gsSbText.append( "%S\n", gs.gstr() ) ;
   }

// CZONE - reportKeycodes()
   skForm* skfp = new skForm
   (
      fldCOUNT,
      new skField[fldCOUNT]
      {
         //* Field index fnLIST_SB: ftSB (Scrollbox) *
         {
            ftSB,                         // field type
            sbHgt,                        // field height (in rows)
            sbWid,                        // field width (in columns)
            1, 0,                         // Y/X offset in window
            gsSbText.ustr(),              // field text
            sbiAttr,                      // color attributes
            floToken,                     // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // read-only flag
            //false,                      // field outline enabled below
            //false,                      // hidden flag is 'reset'
            //true,                       // access lock enabled
            //false,                      // initial binary state
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         //* Field index fnCLOSE: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            10,                           // field width (in columns)
            pbVOff, -11,                  // Y/X offset in window
            Labels[this->lang][fldCLOSE], // field text with Hotkey '^C'
            acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index fnDOCS: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            10,                           // field width (in columns)
            pbVOff, 1,                    // Y/X offset in window
            Labels[this->lang][fldDOCS],  // field text with Hotkey '^D'
            acAttr(acaFGb_RED | acaBGb_GREEN), // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

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

   //* Configure Scrollbox field outline and label *
   skfp->fld[fldLIST_SB].outlineConfig ( ltSINGLE, sbbAttr ) ;
   skfp->fld[fldLIST_SB].labelConfig ( Header[this->lang], acAttr(sbbAttr | acaUNDERLINE) ) ;

   //**********************
   //* Interact with user *
   //**********************
   //* Define a dialog window to display the default  *
   //* shortcut keycodes, currently active keycodes   *
   //* and available options.                         *
   this->win->Dialog ( dlgHgt, dlgWid, skfp, Instructions[this->lang], Title[this->lang], 
                       1, -1, ltDUAL,
                       acaUSEDFLT, this->dlgAttr, txtVOff, 1, NULL, false ) ;

   delete skfp ;                          // release the dynamic allocation

}  //* End reportKeycodes() *

//*************************
//*      select4Edit      *
//*************************
//********************************************************************************
//* Display the files in the current working directory (CWD) and allow user      *
//* navigate through the filesystem to select a file for editing.                *
//*                                                                              *
//*                                                                              *
//* Input  : fi : index of target text edit field                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* 1) Display the files in the current working directory                        *
//*    a) Scan all filenames in CWD into two different groups, subdirectories    *
//*       and other files. The subdirectory names will be displayed above the    *
//*       other filenames.                                                       *
//*    b) Filenames excluded from display list are:                              *
//*       "."   'this' directory                                                 *
//*       ".."  'parent' directory                                               *
//*       ".#filename#" filenames which are (probably) the hidden working-copy   *
//*           files for each file currently open for edit. (The test is not      *
//*           definitive.                                                        *
//*           Hidden files not associated with the application WILL BE displayed.*
//*    b) Sort each group alphabetically (actually a numeric sort), case         *
//*       insensitive.                                                           *
//*    c) Each record takes the form: " - filename\n". The leading sequence of   *
//*       three characters is used to indicates whether the file is a            *
//*       subdirectory name.                                                     *
//*       Technical Note: The ACWin and ACDlg classes automatically reformat     *
//*       Scrollbox records to include an additional leading space to accomodate *
//*       the scroll indicator AND trailing padding to fill the width of the     *
//*       field.                                                                 *
//*       -- The rcfSort() function tests for filename lengths greater than      *
//*          the available space, and truncates the filename if necessary.       *
//*    d) The first item in the list is the "Parent Directory" item.             *
//*       If user selects this item, then CWD will become the parent directory,  *
//*       and dialog will be reopened as described above.                        *
//*       If already at root directory("/"), request will be denied.             *
//* 2) If user selects a subdirectory name from the list, then that directory    *
//*    will become the CWD and dialog will be reopened as described above --     *
//*    unless user does not have read access to that directory.                  *
//* 3) If user selects a file, caller can open it for edit or perform some       *
//*    other task using that file. Note that the file is not necessarily a       *
//*    'regular', plain-text file. Tests for those criteria must be applied      *
//*    by caller.                                                                *
//*                                                                              *
//********************************************************************************

void AcEdit::select4Edit ( fldIndex fi )
{
   static const char* maxedOut[uilENV] = 
   {
      " Sorry, Maximum number of open files (%hd) \n",
      "   抱歉，已达到打开文件的最大数量 (%hd) \n",
   } ;
   static const char* Title[uilENV] = 
   {
      "  Open a File For Editing  ",        // English
      "  打开文件进行编辑  ",                       // Zhongwen
   } ;
   const short maxfldHgt = this->wInfo.height - 5 ; // maximum Scrollbox height
   const short minfldHgt = 8 ;                      // minimum Scrollbox height


   gString gsStatic ;                     // static text displayed in dialog
   gString gsList ;                       // formatted list of files
   gString gsCwd ;                        // filespec of CWD
   gString fileSpec ;                     // full filespec of selected file
   gString gs ;                           // text analysis
   skForm* skfp = NULL ;                  // pointer to skForm object
   tnFName tnf ;                          // file stats
   acAttr* acArray = NULL ;               // pointer to dynamic allocation
   short itemCnt = ZERO,                  // items to be displayed
         fldHgt,                          // height of Scrollbox control
         dlgHgt,                          // dialog height in rows
         dlgWid = this->wInfo.width - 2,  // dialog width in columns
         fldWid = dlgWid - 2,             // width of fldFILES control
         exitFld,                         // index of field with focus on exit from dialog
         openFiles = ZERO ;               // count of files currently open for edit
   bool handled = false ;                 // set if selected file is in 'recents' list


   //* Count the files currently open for edit.*
   //* If max, then we can't open any more.    *
   for ( short f = ZERO ; f < MAX_FILES ; ++f )
   { if ( this->fman[f].open ) { ++openFiles ; } }
   if ( openFiles == MAX_FILES )
   {
      fileSpec.compose( maxedOut[this->lang], &MAX_FILES ) ;
      this->infoDialog ( fileSpec, false, NULL, -1 ) ;
      return ;       // note the early return
   }

   //* Filespec of current working directory. *
   this->getCWD ( gsCwd ) ;
//OBSOLETE   if ( !(this->getCWD ( gsCwd )) ) { gsCwd = L"." ; }

   //* Initialize the dialog static text. This is a        *
   //* pseudo-line which is positioned below the Scrollbox.*
   gsStatic.clear() ;
   gsStatic.padCols( dlgWid - 4, wcsHORIZs ) ;

   //* Scan the CWD, format the individual records, and create *
   //* a list of color attributes, one for each record.        *
   itemCnt = this->readCwd ( gsList, acArray, fldWid ) ;
   if ( (fldHgt = itemCnt) > maxfldHgt ) // range limit Scrollbox height
      fldHgt = maxfldHgt - 1 ;
   else if ( fldHgt < minfldHgt )
      fldHgt = minfldHgt ;
   dlgHgt = fldHgt + 4 ;   // calculate dialog height relative to Scrollbox height

   //* Advance dialog static-text to appear below Scrollbox. *
   for ( short nl = fldHgt ; nl > ZERO ; --nl )
      gsStatic.insert( NEWLINE ) ;

   //*********************
   //* Define the dialog *
   //*********************
   skfp = this->repdlgFmt ( rfoptCwdList, itemCnt, dlgHgt, dlgWid, gsList.ustr() ) ;
   //* Reduce height of Scrollbox by one to accomodate static text.*
   --skfp->fld[rfFILES].hgt ;

   //* Apply multi-color attributes to Scrollbox data. *
   skfp->fld[rfFILES].colorConfig ( acArray, itemCnt ) ;

   aceditPtr = this ;
   this->adlg = adOpen ;      // indicate the active dialog (used by callback)

   //*******************
   //* Open the dialog *
   //*******************
   exitFld = this->win->Dialog ( dlgHgt, dlgWid, skfp, gsStatic.ustr(), 
                                 Title[this->lang], ZERO, -1, ltDUAL, 
                                 acaUSEDFLT, this->dlgAttr, 0, 1,
                                 nonmember_dlgCallback ) ;

   this->adlg = adNONE ;      // dialog inactive
   aceditPtr = NULL ;

#if 0    // MOST OF THIS WILL MOVE TO THE CALLBACK
   if ( exitFld == rfSELECT )
   {
// CZONE - This section can be updated to call a non-member method to extract 
//       - filename from item and return the bullChar value.See Dialog_GetItem().
      //* Parse the record into its designator and filename components.*
      //* The "designator" character is one of PCHAR/DCHAR/FCHAR.      *
      //* Note that the target record is terminated by a newline       *
      //* character, and the record may have been padded with spaces   *
      //* to the full width of the display field.                      *
      skfp->fld[rfFILES].getMrp ( tmpMrp ) ;
      bullChar = skfp->fld[rfFILES].txt.gstr()[tmpMrp.bor + 2] ;
      gs = &skfp->fld[rfFILES].txt.gstr()[tmpMrp.bor + 4] ;
      if ( (indx = gs.find( NEWLINE )) > ZERO )
         gs.limitChars( indx ) ;
      gs.strip() ;

      //* The "selected" item indicates the "parent" directory *
      if ( bullChar == PCHAR )
      {
         if ( (cwdPath.compare( L"/" )) != ZERO )  // if not already at root dir
         {
            this->ExtractPathname ( fileSpec, cwdPath ) ;
            if ( !(this->setCWD ( fileSpec )) )
            { //* Malformed filespec OR user lacks read access (unlikely) *
               gString gsWarn = warnAccess[this->lang] ;
               gs.padCols( gsWarn.gscols(), SPACE, true ) ;
               gsWarn.append( "\n%S\n", gs.gstr() ) ;
               this->win->QuickDialog ( gsWarn, false, NULL, 0, 0, -1, -1, 8 ) ;
            }
         }
         else
            this->win->acBeep () ;
      }
      //* The "selected" item is a subdirectory name *
      else if ( bullChar == DCHAR )
      {
         fileSpec.compose( "%S/%S", cwdPath.gstr(), gs.gstr() ) ;
         if ( !(this->setCWD ( fileSpec )) )
         {  //* Malformed filespec OR user lacks read access *
            gString gsWarn = warnAccess[this->lang] ;
            gs.padCols( gsWarn.gscols(), SPACE, true ) ;
            gsWarn.append( "\n%S\n", gs.gstr() ) ;
            this->win->XuickDialog ( gsWarn, false, NULL, 0, 0, -1, -1, 8 ) ;
         }
      }
      //* The "selected" item is a non-directory filename *
      else  // (bullChar==FCHAR)
      {
         fileSpec.compose( "%S/%S", cwdPath.gstr(), gs.gstr() ) ;
         fileSelected = true ;
      }
   }
#endif   // MOST OF THIS WILL MOVE TO THE CALLBACK

   //* If user has selected a file for edit, *
   //* validate the target and open the file.*
   if ( exitFld == rfSELECT )
   {
#define DEBUG_TEMP (1)
#if DEBUG_TEMP != 0
gString gsid ;
int     ix ;
#endif   // DEBUG_TEMP
      //* Parse the record into its designator and filename components.*
      //* For success, indyChar == FCHAR, and filename != empty string.*
      wchar_t indyChar ;
      if ( (scrollboxItem ( &skfp->fld[rfFILES], gs, indyChar )) )
      {
         this->getCWD ( gsCwd ) ;         // refresh CWD
//OBSOLETE         if ( !(this->getCWD ( gsCwd )) ) { gsCwd = L"." ; } // refresh CWD
         fileSpec.compose( "%S/%S", gsCwd.gstr(), gs.gstr() ) ;
#if DEBUG_TEMP != 0
gsid.compose( "select4Edit('%S' indyChar:'%C'      \n"
              "> '%S'\n\n", gs.gstr(), &indyChar, fileSpec.gstr() ) ;
ix = gsid.find( "AnsiCmd" ) ;
gsid.insert( "  \n   ", ix ) ;
#endif   // DEBUG_TEMP

         //* Get file stats in a temporary tnFName object.             *
         //* Also check to be sure it is a 'regular' 'plain-text' file.*
         if ( ((this->GetFileStats ( tnf, fileSpec )) == ZERO) &&
              (tnf.fType == fmREG_TYPE) && (this->isTextfile ( fileSpec )) )
         {
            //* Scan for an existing 'fman' record. *
            short f = ZERO ;
            for ( ; f < this->fmcnt ; ++f )
            {
               if ( (fileSpec.compare( this->fman[f].tnf.fSpec )) == ZERO )
               {
                  //* If the target file is already open, warn user,    *
                  //* and if the target is not already displayed in the *
                  //* control with focus, display the active buffer in  *
                  //* text control with focus.                          *
                  if ( this->fman[f].open )
                  {
#if DEBUG_TEMP != 0
gsid.append( "Selected file is already open. \n" ) ;
#endif   // DEBUG_TEMP
                     //* Report that file is already open,     *
                     //* then load the file into the current   *
                     //* control (if it isn't already).        *

                  }
                  //* Target file is listed, but is not currently open. *
                  //* Open the file and display the buffer in text      *
                  //* control which currently has focus.                *
                  else
                  {
#if DEBUG_TEMP != 0
gsid.append( "Selected file is in the 'recents' list. \n" ) ;
#endif   // DEBUG_TEMP
                  }
                  handled = true ;     // selected file is in the active-file list
                  break ;
               }
            }

            //* Target is a valid file for edit, but is not in the list.   *
            //* If not max-open-files, add the file to the list, open the  *
            //* file, and display the buffer in text control with focus.   *
            if ( ! handled )
            {
#if DEBUG_TEMP != 0
gsid.append( "Selected file is neither 'open' nor 'recents'. \n" ) ;
#endif   // DEBUG_TEMP
            }
         }
         //* Either file does not exist, OR it   *
         //* does not meet the criteria for edit.*
         else
         {
#if DEBUG_TEMP != 0
gsid.append( "Selected file cannot be opened. \n" ) ;
#endif   // DEBUG_TEMP
         }
      }
      else
      { /* This would be a design error. */ }
#if DEBUG_TEMP != 0
gsid.append( NEWLINE ) ;
this->infoDialog ( gsid, false, "  select4Edit  ", 4, -1 ) ;
#endif   // DEBUG_TEMP
   }

   //* Release dynamic allocations *
   if ( acArray != NULL )
   { delete[] acArray ; acArray = NULL ; }
   if ( skfp != NULL )
   { delete skfp ; skfp = NULL ; }

//* TEMP */ else { this->updateGutter ( false, L"No Selection", false ) ; }

}  //* End select4Edit() *

//*************************
//*      s4eCallback      *
//*************************
//********************************************************************************
//* Callback method used by the "Select For Edit" dialog.                        *
//*                                                                              *
//* 1) Track the Enter key within the Scrollbox.                                 *
//*    a) If Enter is pressed on a subdirectory item, display that subdirectory. *
//*    b) If Enter is pressed on the parent-directory item, display parent dir.  *
//*    c) If Enter is pressed on a non-directory name, discard the keycode.      *
//*                                                                              *
//* 2) Track Pushbutton selection.                                               *
//*    a) If Open Pushbutton selected,                                           *
//*                                                                              *
//*    b) If Cancel Pushbutton selected, return the exit-dialog signal.          *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: The Dialog() method calls this method once with          *
//*                     firstTime==true, to perform any required initialization. *
//*                     All subsequent calls are made with firstTime==false.     *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          if kecode is not handled by this method: 'wkey' is returned.        *
//*          Special Case: returns wcsCTL_4 to signal that EditDialog() should   *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//********************************************************************************

wchar_t AcEdit::s4eCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
// CZONE - s4eCallback()
   static bool warnMsg = false ;          // set if warning message is visible
   gString gs, gsCwd, gsName, gsList ;
   const skField* skfPtr = NULL ;         // pointer to a control within the dialog
   acAttr* acArray = NULL ;               // pointer to array of Scrollbox attributes
   wchar_t indyChar ;                     // indicator character for selected item
   short itemCnt ;                        // number of items in Scrollbox control
   short item ;                           // Scrollbox item selected

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

//* TEMP */ gsdbg.compose( "s4eCallback( ctrlIndex:%hd wkey:%04X firstTime:%hhd", &ctrlIndex, &wkey, &firstTime ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;

   //* If a warning message is currently displayed, erase it. *
   if ( warnMsg )
   {

      warnMsg = false ;
   }

   //* Note that the hotkey for a field has been automagically  *
   //* converted to wcsENTER.                                   *
   if ( (wkey == wcsENTER) || (wkey == wcsSPACE) )
   {
      switch ( ctrlIndex )
      {
         case rfSELECT:                   // Select pushbutton
            if ( (this->win->Dialog_GetItem ( rfFILES, gs, item )) )
            {
               indyChar = *gs.gstr() ;    // isolate indicator character
               if ( indyChar == FCHAR )   // selected item references a non-dir file
                  wkey = wcsCTL_4 ;
               else                       // rfFILES item is a directory name
               {
                  if ( this->wInfo.skf->beep )
                     this->win->acBeep () ; // file not selected
                  //wkey = NULLCHAR ;         // keycode handled
                  // Caller will process the keycode as a move to the Cancel button.
               }
            }
            break ;

         case rfCANCEL:                   // Cancel pushbutton
            wkey = wcsCTL_4 ;
            break ;

         case rfFILES:                    // Scrollbox display of CWD files
            if ( (this->win->Dialog_GetItem ( rfFILES, gs, item )) )
            {
               indyChar = *gs.gstr() ;   // isolate indicator character
               gsName = &gs.gstr()[2] ;          // item text without prefix

               //* Move to parent directory or subdirectory *
               if ( (indyChar == PCHAR) || (indyChar == DCHAR) )
               {
                  //* Get filespec of CWD *
                  this->getCWD ( gsCwd ) ;
//OBSOLETE                  if ( !(this->getCWD ( gsCwd )) ) { gsCwd = L"." ; }
                  if ( indyChar == PCHAR )   // moving to parent directory
                  {
                     gs = gsCwd ;
                     this->ExtractPathname ( gsCwd, gs ) ;
                  }
                  else                       // moving to subdirectory
                     gsCwd.append( "/%S", gsName.gstr() ) ;

                  if ( (this->setCWD ( gsCwd )) )
                  {
                     //* Scan new CWD and format the entries.  *
                     //* Create an array of color attributes   *
                     //* to match the records.                 *
                     //* Note that itemCnt will always be >=1  *
                     //* because of "parent" record.           *
                     this->win->Dialog_GetField ( rfFILES, skfPtr ) ;
                     itemCnt = this->readCwd ( gsList, acArray, skfPtr->wid ) ;

                     //* Update text data, and array of color attributes. *
                     // Programmer's Note: Refresh is not performed after text 
                     // update, but is performed only after attribute update.
                     this->win->Dialog_SetText ( ctrlIndex, gsList, acaUSEDFLT, false ) ;
                     this->win->Dialog_SetAttr ( ctrlIndex, acArray, itemCnt ) ;
                     if ( acArray != NULL )     // release the dynamic allocation
                     { delete[] acArray ; acArray = NULL ; }

                     //* Signal caller that operation is compete.*
                     wkey = NULLCHAR ;
                  }
                  else
                  { //* Malformed filespec OR user lacks read access *

                     warnMsg = true ;           // remember that warning was posted
                     wkey = NULLCHAR ;          // keycode handled
                  }
               }

               //* Else, non-directory filename "selected".*
               else
               { /* Ignore the selection */ }
            }
            // else(call is unlikely to fail)
            break ;
      } ;
   }

   return wkey ;

}  //* End sfaCallback() *

//*************************
//*     CompareFiles      *
//*************************
//********************************************************************************
//* Compare two files for:                                                       *
//*  a) Timestamp (mod)                                                          *
//*  b) Size                                                                     *
//*  c) Contents (compare lines)                                                 *
//*                                                                              *
//* Comparison is handled by the 'diff' system utility. Diff output is redirected*
//* to a temporary file, which is then displayed via the 'less' utility.         *
//* Data format for the comparison may be selected via the Radiobutton group.    *
//*                                                                              *
//* Only "regular", plain text files may be compared.                            *
//* The user may select the files to be compared from the Scrollbox data, OR     *
//* may type the filename directly.                                              *
//*                                                                              *
//* User activity is monitored by a callback method (see below).                 *
//*                                                                              *
//* This method exercises various dialog control types:                          *
//*   Textbox     - both read-only and user accessible                           *
//*   Scrollbox   - display content of current working directory                 *
//*   Radiobutton - an XOR group of controls                                     *
//*   Pushbuttons - both direct actions and closing of the dialog                *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::CompareFiles ( void )
{
   static const char* cfStaticText[uilENV] = 
   {
      "Compare the text and statistics of two files.\n"  // English
      "Traverse the directory tree and select the   \n"
      "files to be compared.\n\n\n\n\n\n\n\n\n"
      "File A:   \n"
      "File B:   \n",
    //|123456789012345678901234567890123456789012345678901|

      "比较两个文件的文本内容和统计数据。\n"                              // Zhongwen
      "遍历目录树并选择要比较的文件。\n\n\n\n\n\n\n\n\n\n"
      "文件 A:   \n"
      "文件 B:   \n",
   } ;
   static const char* cfTitle[uilENV] = 
   {
      "  File Comparison  ",
      "   文件比较   ",
   } ;
   static const char* cfLabels[uilENV][cmpCTRLS] = 
   {
      {  // English
         "Current Directory:",
         "---",            // placeholder (scrollbox)
         "---",            // placeholder (textbox)
         "---",            // placeholder (textbox)
         "[◆] ^Brief Report (identical or different)",
         "[◆] Common lines on ^Left (differences on right)",
         "[◆] ^Full text in both left and right columns",
         "[◆] Report ^Differences only",
         "  ^COMPARE  ",
         "   CL^OSE   ",
         "   ^SWAP    ",
         "[✔] Include ^temp files",
      },

      {  // Zhongwen
         "当前目录：",
         "---",            // placeholder (scrollbox)
         "---",            // placeholder (textbox)
         "---",            // placeholder (textbox)
         "[◆] 简要报告（相同或不同） ^B",
         "[◆] 左侧为共同部分（右侧为差异部分） ^L",
         "[◆] 左右两栏均显示全文。 ^F",
         "[◆] 仅报告差异 ^D",
         "  比较  ^C  ",
         "   关闭 ^O  ",
         "   交换 ^S  ",
         "[✔] 包含临时文件 ^T",
      },
   } ;
   //* Horizontal offset for cmpTEMP_RB *
   const short tempRBcol[uilENV] = { 3, 6 } ;

   acaExpand acexp ;
   acAttr labAttr = acexp.build8Bit( true,  21, true, 45 ) ;
   acAttr tmpAttr = acexp.build8Bit( true,  34, true, 45 ) ;
   acAttr cwdAttr = acexp.build8Bit( true, 226, true, 21 ) ;
   acAttr tbaAttr = acexp.build8Bit( true, 226, true, 27 ) ;
   acAttr tbbAttr = acexp.build8Bit( true, 226, true, 135 ) ;
   acAttr pbAttr = acAttr(acaFGb_RED | acaBGb_GREEN) ;
   acAttr focusAttr = acAttr(acaFGb_GREEN | acaBGb_RED) ;
   short dlgHgt  = 20,                    // height of dialog
         dlgWid  = 53,                    // width of dialog
         scrWid  = dlgWid - 2,            // width of Scrollbox
         scrHgt  = 6,                     // height of Scrollbox
         fldYoff = 4,                     // field offset in Y
         fldXoff = 2,                     // field offset in X
         pbWid   = 11 ;                   // width of Pushbuttons

   //********************************************
   //* Scan the current working directory (CWD) *
   //********************************************
   gString gsCwd, gsList ;
   acAttr* acArray = NULL ;
   this->getCWD ( gsCwd ) ;
//OBSOLETE   if ( !(this->getCWD ( gsCwd )) ) { gsCwd = L"." ; }
   int cwdIp = gsCwd.gschars() - 1 ;      // move IP to end of text
   short itemCnt = this->readCwd ( gsList, acArray, scrWid ) ;

   //********************************
   //* Initialize the skForm object *
   //********************************
   skForm* skfp = new skForm
   (
      cmpCTRLS,
      new skField[cmpCTRLS]
      {
         {  // Field index cmpCWD_TB: ftTB (Textbox - read only)
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(dlgWid - 4),            // field width (in columns)
            fldYoff, ZERO,                // Y/X offset in window
            gsCwd.ustr(),                 // field text (CWD)
            cwdAttr,                      // color attributes
            floShift,                     // text formatting option
            cwdIp,                        // insertion point (cursor offset)
            true,                         // read-only
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index cmpSCROLL: ftSB (Scrollbox) *
         {
            ftSB,                         // field type
            scrHgt,                       // field height (in rows)
            scrWid,                       // field width (in columns)
            short(fldYoff += 1), ZERO,    // Y/X offset in window
            gsList.ustr(),                // field text
            this->auxAttr,                // color attributes
            floToken,                     // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // read-only flag
            //false,                      // field outline disabled
            //false,                      // hidden flag is 'reset'
            //true,                       // access lock enabled
            //false,                      // initial binary state
            //NULLCHAR,                   // no hotkey specified
            //noGROUP                     // not a group member
         },

         {  // Field index cmpFILEA_TB: ftTB (Textbox)
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(dlgWid - 14),           // field width (in columns)
            short(fldYoff += scrHgt),     // Y/X offset in window
            short(fldXoff = -(dlgWid / 2 - 12)),
            "",                           // field text (initially none)
            tbaAttr,                      // color attributes
            floShift,                     // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         {  // Field index cmpFILEB_TB: ftTB (Textbox)
            ftTB,                         // field type
            1,                            // field height (in rows)
            short(dlgWid - 14),           // field width (in columns)
            short(fldYoff += 1), fldXoff, // Y/X offset in window
            "",                           // field text (initially none)
            tbbAttr,                      // color attributes
            floShift,                     // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index cmpBRIEF_RB: ftRB (Radiobutton) *
         {
            ftRB,                         // field type
            1,                            // field height (in rows)
            3,                            // field width (in columns)
            short(fldYoff += 1),          // Y/X offset in window
            short(-(dlgWid / 2 - 3)),
            cfLabels[this->lang][cmpBRIEF_RB], // field text
            labAttr,                      // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (n/a)
            false,                        // user accessible (not read-only)
            false,                        // field outline disabled
            false,                        // field is visible (not hidden)
            true,                         // access lock enabled
            false,                        // binary state 
            NULLCHAR,                     // no hotkey specified (embedded in text)
            L'a'                          // XOR group member
         },

         //* Field index cmpLEFT_RB: ftRB (Radiobutton) *
         {
            ftRB,                         // field type
            1,                            // field height (in rows)
            3,                            // field width (in columns)
            short(fldYoff += 1),          // Y/X offset in window
            short(-(dlgWid / 2 - 3)),
            cfLabels[this->lang][cmpLEFT_RB], // field text
            labAttr,                      // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (n/a)
            false,                        // user accessible (not read-only)
            false,                        // field outline disabled
            false,                        // field is visible (not hidden)
            true,                         // access lock enabled
            true,                         // binary state 
            NULLCHAR,                     // no hotkey specified (embedded in text)
            L'a'                          // XOR group member
         },

         //* Field index cmpFULL_RB: ftRB (Radiobutton) *
         {
            ftRB,                         // field type
            1,                            // field height (in rows)
            3,                            // field width (in columns)
            short(fldYoff += 1),          // Y/X offset in window
            short(-(dlgWid / 2 - 3)),
            cfLabels[this->lang][cmpFULL_RB], // field text
            labAttr,                      // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (n/a)
            false,                        // user accessible (not read-only)
            false,                        // field outline disabled
            false,                        // field is visible (not hidden)
            true,                         // access lock enabled
            false,                        // binary state 
            NULLCHAR,                     // no hotkey specified (embedded in text)
            L'a'                          // XOR group member
         },

         //* Field index cmpDIFF_RB: ftRB (Radiobutton) *
         {
            ftRB,                         // field type
            1,                            // field height (in rows)
            3,                            // field width (in columns)
            short(fldYoff += 1),          // Y/X offset in window
            short(-(dlgWid / 2 - 3)),
            cfLabels[this->lang][cmpDIFF_RB], // field text
            labAttr,                      // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (n/a)
            false,                        // user accessible (not read-only)
            false,                        // field outline disabled
            false,                        // field is visible (not hidden)
            true,                         // access lock enabled
            false,                        // binary state 
            NULLCHAR,                     // no hotkey specified (embedded in text)
            L'a'                          // XOR group member
         },

         //* Field index cmpCOMPARE_PB: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            pbWid,                        // field width (in columns)
            short(fldYoff += 1),          // Y/X offset in window
            short(fldXoff = -(dlgWid / 2 - 9)),
            cfLabels[this->lang][cmpCOMPARE_PB], // field text
            pbAttr,                       // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index cmpCLOSE_PB: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            pbWid,                        // field width (in columns)
            fldYoff,                      // Y/X offset in window
            short( fldXoff += pbWid + 2),
            cfLabels[this->lang][cmpCLOSE_PB], // field text
            pbAttr,                       // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index cmpSWAP_PB: ftPB (Pushbutton) *
         {
            ftPB,                         // field type
            1,                            // field height (in rows)
            pbWid,                        // field width (in columns)
            fldYoff, short(fldXoff += pbWid + 2), // Y/X offset in window
            cfLabels[this->lang][cmpSWAP_PB], // field text
            pbAttr,                       // color attributes
            //floChar,                    // text formatting option
            //ZERO,                       // insertion point (cursor offset)
            //false,                      // user accessible (not read-only)
            //false,                      // field outline disabled
            //true,                       // field is visible (not hidden)
            //true,                       // access lock enabled
            //false,                      // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP                     // not a group member
         },

         //* Field index cmpTEMP_RB: ftRB (Radiobutton) *
         {
            ftRB,                         // field type
            1,                            // field height (in rows)
            3,                            // field width (in columns)
            2, tempRBcol[this->lang],     // Y/X offset in window
            cfLabels[this->lang][cmpTEMP_RB], // field text
            tmpAttr,                      // color attributes
            floChar,                      // text formatting option
            ZERO,                         // insertion point (n/a)
            false,                        // user accessible (not read-only)
            false,                        // field outline disabled
            false,                        // field is visible (not hidden)
            true,                         // access lock enabled
            false,                        // binary state 
            //NULLCHAR,                   // no hotkey specified (embedded in text)
            //noGROUP,                    // XOR group member
         },

      },
      //* Initialize remaining skForm parameters to default values.*
      cmpSCROLL,        // index of field with initial focus
      focusAttr         // 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
   ) ;

   //* Set field labels *
   skfp->fld[cmpCWD_TB].labelConfig ( cfLabels[this->lang][cmpCWD_TB], labAttr, true ) ;
   skfp->fld[cmpBRIEF_RB].labelConfig ( cfLabels[this->lang][cmpBRIEF_RB], labAttr ) ;
   skfp->fld[cmpLEFT_RB].labelConfig ( cfLabels[this->lang][cmpLEFT_RB], labAttr ) ;
   skfp->fld[cmpDIFF_RB].labelConfig ( cfLabels[this->lang][cmpDIFF_RB], labAttr ) ;
   skfp->fld[cmpTEMP_RB].labelConfig ( tmpAttr, true ) ;


   //* Apply multi-color attributes to Scrollbox data. *
   if ( acArray != NULL )
   {
      skfp->fld[cmpSCROLL].colorConfig ( acArray, itemCnt ) ;
      delete[] acArray ;      // release the dynamic allocation
      acArray = NULL ;
   }

   aceditPtr = this ;
   this->adlg = adCompare ;   // indicate the active dialog (used by callback)

   //*******************
   //* Open the dialog *
   //*******************
   this->win->Dialog ( dlgHgt, dlgWid, skfp, cfStaticText[this->lang], 
                       cfTitle[this->lang], ZERO, -1, 
                       ltDUAL, acaUSEDFLT, this->dlgAttr, 0, 1,
                       nonmember_dlgCallback ) ;

   this->adlg = adNONE ;      // dialog inactive
   aceditPtr = NULL ;

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

}  //* End CompareFiles() *

//*************************
//*      cmpCallback      *
//*************************
//********************************************************************************
//* Callback method used by the "Compare Files" dialog.                          *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: The Dialog() method calls this method once with          *
//*                     firstTime==true, to perform any required initialization. *
//*                     All subsequent calls are made with firstTime==false.     *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          if kecode is not handled by this method: 'wkey' is returned.        *
//*          Special Case: returns wcsCTL_4 to signal that EditDialog() should   *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//********************************************************************************
//* Notes:                                                                       *
//* 1) This method contains an undocumented shortcut key option, (wcsALT_W).     *
//*    This option loads FileA and FileB Textbox fields with:                    *
//*       a) Load FileA Textbox with source filespec.                            *
//*       b) Load FileB Textbox with working-copy filespec.                      *
//*    Note that these files may not be in CWD, but we "know" that they exist    *
//*    and are valid targets. This option is useful during save and auto-save    *
//*    operations to ensure that save is successful.                             *
//*                                                                              *
//********************************************************************************

wchar_t AcEdit::cmpCallback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   static const char* notRegular[uilENV] = 
   {
      "**  Access Error or not a text file. **",  // English
      "**   访问错误或文件不是文本文件。    **",                // Zhongwen
   } ;
   static gString gsLog ;                 // filespec of log file
   gString gs, gstmp,                     // text management
           gsFSpec,                       // filespec
           gsCwd,                         // current working directory
           gsList ;                       // list of files in CWD
   const skField* skfPtr = NULL ;         // pointer to a control within the dialog
   acAttr* acArray = NULL ;               // pointer to array of Scrollbox attributes
   tnFName faStats, fbStats ;             // stats for source files
   wchar_t indyChar ;                     // indicator character for selected item
   int     dsValue = ZERO ;               // parameter for Dialog_Scroll()
   cmpControls trgFld ;                   // index of target field
   short itemCnt ;                        // number of items in Scrollbox control
   short item ;                           // Scrollbox item selected
   bool inclTmpFiles ;                    // state of cmpTEMP_RB 'set' flag

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

   //* Note that the hotkey for a field has been automagically  *
   //* converted to wcsENTER.                                   *
   if ( (wkey == wcsENTER) || (wkey == wcsSPACE) )
   {
      switch ( ctrlIndex )
      {
         case cmpCOMPARE_PB:              // Compare pushbutton
            //* Verify that both FileA and FileB have been specified *
            this->win->Dialog_GetText ( cmpFILEA_TB, gs ) ;
            this->win->Dialog_GetText ( cmpFILEB_TB, gstmp ) ;
            if ( ((gs.gschars()) > 1) && ((gstmp.gschars()) > 1) )
            {
               short realFiles = ZERO,
                     rbIndx ;
               //* Construct filespecs for the two files    *
               //* to be compared, and get file stats.      *
               //* Verify that both targets actually exist, *
               //* that they are regular, plain text files, *
               //* and that they are different files.       *
               //*     (user's often play tricks on us)     *
               gsFSpec = gs ;
               if ( ((this->GetFileStats ( faStats, gsFSpec )) == ZERO) &&
                    faStats.readAcc && (faStats.fType == fmREG_TYPE) &&
                    (this->isTextfile ( gsFSpec )) )
               {
                  ++realFiles ;
                  gsFSpec = gstmp ;
                  if ( ((this->GetFileStats ( fbStats, gsFSpec )) == ZERO) &&
                       fbStats.readAcc && (fbStats.fType == fmREG_TYPE) &&
                       (this->isTextfile ( gsFSpec )) )
                  { ++realFiles ; }
               }
               if ( realFiles == 2 )
               {
                  //* Retrieve and encode the comparison parameters. *
                  this->win->Dialog_GetStateXOR ( cmpBRIEF_RB, rbIndx ) ;
                  switch ( rbIndx )
                  {
                     case cmpLEFT_RB:     indyChar = L'L' ;          break ;
                     case cmpFULL_RB:     indyChar = L'F' ;          break ;
                     case cmpDIFF_RB:     indyChar = L'D' ;          break ;
                     case cmpBRIEF_RB:  default: indyChar = L'B' ;   break ;
                  } ;

                  //* If logfile has not yet been created, do it now. *
                  if ( (gsLog.gschars()) == 1 )
                     this->win->acCreateTempfile ( gsLog ) ;
                  if ( (gsLog.gschars()) > 1 )
                  {
                     //* Compare the files *
                     this->compareFiles ( faStats, fbStats, gsLog, indyChar ) ;
                     gs.compose( "less -c '%S'", gsLog.gstr() ) ;
                     this->win->ShellOut ( gs ) ;

                     //* The parent window has been refreshed by the *
                     //* ShellOut() call. Refresh the dialog window. *
                     this->win->Dialog_Refresh ( true ) ;
                  }
                  else { /* error creating log file (unlikely) */ }
               }
               else     // one of the files failed to meet the criteria
               {  //* Replace the text of the offending filespec with a warning *
                  //* message. (gsFSpec contains the naughty filespec.)         *
                  this->win->Dialog_GetText ( cmpFILEB_TB, gs ) ;
                  if ( gs == gsFSpec ) { trgFld = cmpFILEB_TB ; }
                  else                 { trgFld = cmpFILEA_TB ; }
                  gs = notRegular[this->lang] ;
                  this->win->Dialog_SetText ( trgFld, gs ) ;
               }
            }
            else
            {  //* Two targets must be specified.*
               trgFld = (gs.gschars() == 1) ? cmpFILEA_TB : cmpFILEB_TB ;
               gs = notRegular[this->lang] ;
               this->win->Dialog_SetText ( trgFld, gs ) ;
            }
            break ;
         case cmpCLOSE_PB:                // Close pushbutton
            if ( (gsLog.gschars()) > 1 )  // delete the log file
               this->win->acDeleteTempfile ( gsLog ) ;
            wkey = wcsCTL_4 ;             // signal dialog exit
            break ;

         case cmpSWAP_PB:                 // Close pushbutton
            //* Exchange contents of the two filename Textbox controls *
            this->win->Dialog_GetText ( cmpFILEA_TB, gs ) ;
            this->win->Dialog_GetText ( cmpFILEB_TB, gstmp ) ;
            this->win->Dialog_SetText ( cmpFILEA_TB, gstmp ) ;
            this->win->Dialog_Scroll ( cmpFILEA_TB, dsValue, false, true ) ;
            this->win->Dialog_SetText ( cmpFILEB_TB, gs ) ;
            this->win->Dialog_Scroll ( cmpFILEB_TB, dsValue, false, true ) ;
            break ;

         case cmpTEMP_RB:
            //* Reread the list to include/exclude application *
            //* temporary (working copy) files,                *
            //* Note that RB flag has not yet been updated.    *
            this->win->Dialog_GetField ( cmpSCROLL, skfPtr ) ;
            this->win->Dialog_GetState ( cmpTEMP_RB, inclTmpFiles ) ;
            inclTmpFiles = inclTmpFiles ? false : true ;
            itemCnt = this->readCwd ( gsList, acArray, skfPtr->wid, 
                        acaUSEDFLT, acaUSEDFLT, inclTmpFiles ) ;
            this->win->Dialog_SetText ( cmpSCROLL, gsList, acaUSEDFLT, false ) ;
            this->win->Dialog_SetAttr ( cmpSCROLL, acArray, itemCnt ) ;
            if ( acArray != NULL )     // release the dynamic allocation
            { delete[] acArray ; acArray = NULL ; }
            break ;

         case cmpBRIEF_RB:                // Configuration options
         case cmpLEFT_RB:
         case cmpFULL_RB:
         case cmpDIFF_RB:
            break ;

         case cmpSCROLL:
            if ( (this->win->Dialog_GetItem ( cmpSCROLL, gs, item, false, true )) )
            {
               indyChar = *gs.gstr() ;    // isolate indicator character
               gs.shiftChars( -2 ) ;      // remove the prefix sequence
               gs.strip() ;               // strip leading/trailing whitespace
               if ( indyChar == FCHAR )   // selected item references a non-dir file
               {
                  //* Copy the filespec to target Textbox control. *
                  //* a) Determine which textbox to target.        *
                  //* b) Retrieve filespec of CWD.                 *
                  //* c) Create the target filespec.               *
                  //* d) If field A is empty, copy to field A.     *
                  //* e) Else if field B is empty, copy to field B.*
                  //* f) Else, overwrite field A.                  *
                  //* g) adjust horiz shift to make tail visible.  *
                  trgFld = cmpFILEA_TB ;
                  this->win->Dialog_GetText ( cmpFILEA_TB, gstmp ) ;
                  int faChars = gstmp.gschars(),
                      fbChars = 1 ;
                  if ( faChars > 1 )
                  {
                     this->win->Dialog_GetText ( cmpFILEB_TB, gstmp ) ;
                     fbChars = gstmp.gschars() ;
                  }
                  if ( (faChars > 1) && (fbChars == 1) )
                     trgFld = cmpFILEB_TB ;
                  this->win->Dialog_GetText ( cmpCWD_TB, gsCwd ) ;
                  gsCwd.append( "/%S", gs.gstr() ) ;
                  this->win->Dialog_SetText ( trgFld, gsCwd ) ;
                  this->win->Dialog_Scroll ( trgFld, dsValue, false, true ) ;
                  dsValue = 1 ;
                  this->win->Dialog_Scroll ( cmpSCROLL, dsValue ) ;

                  //* The item 'selection' has already been handled. *
                  //* Nothing for caller to do.                      *
                  wkey = NULLCHAR ; 
               }
               //* Move to parent directory or subdirectory *
               else if ( (indyChar == PCHAR) || (indyChar == DCHAR) )
               {
                  //* Get filespec of CWD *
                  this->getCWD ( gsCwd ) ;
                  if ( indyChar == PCHAR )   // moving to parent directory
                  {
                     gstmp = gsCwd ;
                     this->ExtractPathname ( gsCwd, gstmp ) ;
                  }
                  else                       // moving to subdirectory
                     gsCwd.append( "/%S", gs.gstr() ) ;

                  if ( (this->setCWD ( gsCwd )) )
                  {
                     //* Update the CWD in directory textbox.
                     this->win->Dialog_SetText ( cmpCWD_TB, gsCwd ) ;
                     this->win->Dialog_Scroll ( cmpCWD_TB, dsValue, false, true ) ;

                     //* Scan new CWD and format the entries.  *
                     //* Create an array of color attributes   *
                     //* to match the records.                 *
                     //* Note that itemCnt will always be >=1  *
                     //* because of "parent" record.           *
                     this->win->Dialog_GetField ( cmpSCROLL, skfPtr ) ;
                     this->win->Dialog_GetState ( cmpTEMP_RB, inclTmpFiles ) ;
                     itemCnt = this->readCwd ( gsList, acArray, skfPtr->wid, 
                                 acaUSEDFLT, acaUSEDFLT, inclTmpFiles ) ;

                     //* Update text data, and array of color attributes. *
                     // Programmer's Note: Refresh is not performed after text 
                     // update, but is performed only after attribute update.
                     this->win->Dialog_SetText ( ctrlIndex, gsList, acaUSEDFLT, false ) ;
                     this->win->Dialog_SetAttr ( ctrlIndex, acArray, itemCnt ) ;

                     if ( acArray != NULL )     // release the dynamic allocation
                     { delete[] acArray ; acArray = NULL ; }

                     //* Signal caller that operation is compete.*
                     wkey = NULLCHAR ;
                  }
                  else
                  { //* Malformed filespec OR user lacks read access *
                     wkey = NULLCHAR ;          // keycode handled
                  }
               }
            }
            break ;
      } ;
   }

   //* Set up comparison of focus field source     *
   //* file and its working copy. (see note above) *
   else if ( wkey == wcsALT_W )
   {
      dsValue = ZERO ;
      gs = this->fman[AREC].srcSpec ;
      this->win->Dialog_SetText ( cmpFILEA_TB, gs ) ;
      this->win->Dialog_Scroll ( cmpFILEA_TB, dsValue, false, true ) ;
      gs = this->fman[AREC].wrkSpec ;
      this->win->Dialog_SetText ( cmpFILEB_TB, gs ) ;
      this->win->Dialog_Scroll ( cmpFILEB_TB, dsValue, false, true ) ;
      wkey = NULLCHAR ;          // keycode handled
   }

   return wkey ;

}  //* End cmpCallback() *

//*************************
//*      infoDialog       *
//*************************
//********************************************************************************
//* Display caller's information and return user's response.                     *
//* This isolates the calls to QuickDialog() to ensure consistency.              *
//*                                                                              *
//* Caller should have verified that text dimensions fit within the parent       *
//* window; however, dimensions are verified here, and if out-of-range, text     *
//* will be reformatted and truncated if necessary.                              *
//*                                                                              *
//* Text formatting determines dialog dimensions, that is, dialog height and     *
//* width parameters always receive default values. This may be revisited...     *
//*                                                                              *
//*                                                                              *
//* Input  : gsInfo : (by reference) text to be displayed                        *
//*          uDecide: if reset, dialog will have ONE pushbutton control (info)   *
//*                   if set, dialog will have TWO pushbutton controls (decision)*
//*          title  : (optional, default: null pointer) dialog title             *
//*          vOff   : (optional, default: zero) vertical offset                  *
//*                   positive value == row offset from top                      *
//*                   -1 == centered vertically                                  *
//*          hOff   : (optional, default: -1) horizontal offset                  *
//*                   -1 == centered, positive value == column offset from left  *
//*          timeout: (optional, default: zero) timeout in seconds before        *
//*                   automatic closure. (zero == wait for pushbutton press)     *
//*                                                                              *
//* Returns: 'false' if info dialog or if user specified 'No'                    *
//*          'true'  if user specified 'Yes'                                     *
//********************************************************************************

bool AcEdit::infoDialog ( const gString& gsInfo, bool uDecide, const char* title, 
                          short vOff, short hOff, short timeout )
{
   gString gsOut( gsInfo ) ;
   //* Verify dialog dimensions fit within the parent window *
   short maxHgt = this->wInfo.height - 4,
         maxWid = this->wInfo.width - 6,
         txtHgt, txtWid ;
   this->win->ParagraphSize ( gsOut, txtHgt, txtWid ) ;
   if ( (txtHgt > maxHgt) || (txtWid > maxWid) )
      this->win->FormatParagraph ( gsOut, maxHgt, maxWid ) ;

   bool status = this->win->QuickDialog ( gsOut, uDecide, title,
                                          ZERO, ZERO, vOff, hOff, timeout ) ;
   return status ;

}  //* End infoDialog() *

//*************************
//*      flashDialog      *
//*************************
//********************************************************************************
//* Display a timed dialog in the center of the TextEdit field which has focus.  *
//* Note that this is not an ACDlg object. Instead it is simply a write          *
//* directly to the window interior. This write temporarily obscures a portion   *
//* of the TextEdit control which currently has the input focus.                 *
//*                                                                              *
//* The data are formatted into a borderless (or bordered, 'border' option)      *
//* rectangle and is displayed in the default window interior color, or the      *
//* specified fgnd/bgnd attributes.                                              *
//*                                                                              *
//* The source paragraph will be truncated on the right edge and bottom edge     *
//* as necessary to keep the text within the area of the focus field.            *
//*                                                                              *
//* This method is intended primarily for debugging messages; however, it may    *
//* have additional uses.                                                        *
//*                                                                              *
//* Input  : gsInfo : (by reference) text to be displayed                        *
//*          timeout: display time in seconds: 60 seconds maximum                *
//*                   The delay will be terminated by any keypress.              *
//*          color  : (optional, default: acaUSEDFLT) color attributes for dialog*
//*                   If not specified, default window background attributes     *
//*                   will be used.                                              *
//*          border : (optional, default: false)                                 *
//*                   if set, draw a border around the paragraph                 *
//*                   if reset, display paragraph without a border               *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::flashDialog ( const gString& gsInfo, short timeout, 
                           acAttr color, bool border )
{
   const int MAX = 512 ;
   gString gsOut, gs ;
   const wchar_t* wptr = gsInfo.gstr() ;
   wchar_t wbuff[MAX + 1] ;
   const skField* skfp = &this->wInfo.skf->fld[this->focus] ;
   WinPos wp( skfp->orig ) ;
   int si = ZERO, ti = ZERO ;
   short fldHgt = skfp->hgt,
         fldWid = skfp->wid,
         maxHgt = fldHgt - 2,
         maxWid = fldWid - 2,
         trgHgt, trgWid, msgHgt, msgWid ;

   this->win->acSetCursorStyle ( csHide ) ;  // set cursor invisible
   if ( (timeout < 1) || (timeout > 60) )    // range check timeout value
      timeout = 60 ;

   //* Get dimensions of source data *
   this->win->ParagraphSize ( gsInfo, trgHgt, trgWid ) ;
   trgWid += 2 ;                             // include leading/trailing space
   if ( trgWid > maxWid )                    // limit width
      trgWid = maxWid ;

   if ( border )                             // if border option specified
   {
      gs.padCols( trgWid, wcsHORIZ ) ;
      gs.copy( wbuff, MAX ) ;
      gsOut.compose( "%C%S%C\n", &wcsULs, wbuff, &wcsURs ) ;
   }

   for ( short r = ZERO ; (wptr[si] != NULLCHAR) && (r < maxHgt) ; ++r )
   {
      wbuff[ti++] = SPACE ;
      do
      {
         wbuff[ti++] = wptr[si++] ;
         if ( ti >= MAX )                    // line wider than buffer (unlikely)
         {
            wbuff[ti] = NULLCHAR ;           // terminate the string
            while ( (wptr[si] != NEWLINE) && (wptr[si] != NULLCHAR) ) { ++si ; }
         }
      }
      while ( (wptr[si] != NEWLINE) && (wptr[si] != NULLCHAR) ) ;
      wbuff[ti] = NULLCHAR ;                 // terminate the string
      if ( wptr[si] == NEWLINE ) { ++si ; }  // step over source newline

      gs = wbuff ;
      gs.limitCols( trgWid ) ;               // if necessary, truncate line with
      gs.padCols( trgWid ) ;                 // pad to target width
      if ( border )                          // if border option specified
      { gs.insert( wcsVERT ) ; gs.append( wcsVERT ) ; }
      if ( (wptr[si] != NULLCHAR) && (r < maxHgt) ) // for rows except last, add newline
         gs.append( NEWLINE ) ;
      gsOut.append( gs.gstr() ) ;            // concatenate row
      ti = ZERO ;                            // reset target index
   }

   if ( border )                             // if border option specified
   {
      gs.clear() ;
      gs.padCols( trgWid, wcsHORIZ ) ;
      gs.copy( wbuff, MAX ) ;
      gsOut.append( "\n%C%S%C", &wcsLLs, wbuff, &wcsLRs ) ;
   }

   //* Position the data in the center of the field 
   this->win->ParagraphSize ( gsOut, msgHgt, msgWid ) ;
   short rowOff = (fldHgt / 2) - (msgHgt / 2 + 1), 
         colOff = (fldWid / 2) - (msgWid / 2 + 1) ;
   if ( (rowOff + msgHgt) <= fldHgt )
      wp.row += rowOff ;
   if ( (colOff + msgWid) <= fldWid )
      wp.col += colOff ;

   this->win->Write ( wp, gsOut, color ) ;   // display the message

   //* Wait for specified period OR until keypress *
   this->win->acWait ( timeout ) ;
   this->win->acSetCursorStyle ( csShow ) ;  // set cursor visible
   this->win->RefreshField () ;              // redraw field that was obscured

}  //* End flashDialog() *

//*************************
//*     linenumMatch      *
//*************************
//********************************************************************************
//* Non-nenber Method:                                                           *
//* ------------------                                                           *
//* Match the TextEdit control with the associated LineNum control.              *
//* The match can go either way: a) find LineNum control matching specified      *
//* TextEdit, or b) find TextEdit control matching specified LineNum.            *
//*                                                                              *
//* Technical Note: fiLINEvl matches both fiTEXTfw and fiTEXTvl.                 *
//*   Try to avoid srcIndx==fiLINEvl, but if specified, then                     *
//*   the "most likely" match is returned.                                       *
//*                                                                              *
//* Input  : srcIndx : member of enum fldIndex to match                          *
//*                    fiTEXTfw <= srcIndx <= fiTEXThl ||                        *
//*                    fiLINEvl <= srcIndx <. fiLINEhl                           *
//*          matchIndx: (by reference) receives index of matching control        *
//*                                                                              *
//* Returns: 'true' if match found                                               *
//*          'false' if srcIndex is out-of-range (lineNum unchanged)             *
//********************************************************************************

bool linenumMatch ( fldIndex srcIndx, fldIndex& matchIndx )
{
   bool status = true ;

      switch ( srcIndx )
      {
         case fiTEXTfw:
         case fiTEXTvl: matchIndx = fiLINEvl ;  break ;
         case fiTEXTvr: matchIndx = fiLINEvr ;  break ;
         case fiTEXThu: matchIndx = fiLINEhu ;  break ;
         case fiTEXThl: matchIndx = fiLINEhl ;  break ;

         case fiLINEvl: matchIndx = fiTEXTvl ;  break ; // (see note)
         case fiLINEvr: matchIndx = fiTEXTvr ;  break ;
         case fiLINEhu: matchIndx = fiTEXThu ;  break ;
         case fiLINEhl: matchIndx = fiTEXThl ;  break ;

         default: status = false ;              break ; // caller is an idiot
      }

   return status ;

}  //* End linenumMatch() *

//*************************
//*      ContextHelp      *
//*************************
//********************************************************************************
//* Invoke the 'Info' documentation system to display contextual help information*
//* for the application.                                                         *
//*                                                                              *
//* Variations of this method are used by the demo apps built on the AnsiCmd     *
//* library to access the specified chapter or section of the overall            *
//* documentation for the distribution package.                                  *
//*                                                                              *
//* -- The 'info' documentation reader is invoked via the 'ShellOut' method.     *
//*    This method releases control of the terminal window's I/O streams before  *
//*    invoking the external utility, and restores application control on return.*
//* -- The invocation of the 'info' reader uses the entire terminal window,      *
//*    so saving any displayed data before invoking the shell program and        *
//*    restoring the display data after return must be done here.                *
//*                                                                              *
//* Input  : ccode  : member of contextCode indicating the context of call       *
//*                                                                              *
//* Returns: 'true' if successful, 'false' if file not found or system error     *
//********************************************************************************
//* Programmer's Notes:                                                          *
//* a) The Texinfo documentation for this application is located within the      *
//*    larger AnsiCmd Library documentation.                                     *
//* b) The relative path to the documentation is hard-coded for each application *
//*    in this package.                                                          *
//* c) The 'ccode' parameter is designed to specify which chapter/section of     *
//*    the documentation to reference when opening the file. This could be as    *
//*    simple as the chapter describing the application or as detailed as any    *
//*    addressable node in the document.                                         *
//*                                                                              *
//*                                                                              *
//********************************************************************************

bool AcEdit::ContextHelp ( contextCode ccode )
{
   const char* chTemplate = "info -f %S -n '%s'" ;
   gString  helpPath( "../Texinfo/ansicmd.info" ),
            shellCmd ;           // construct the command
   tnFName tnFile ;              // receives file stats
   bool status = false ;

   //* Be sure the help file exists *
   if ( (this->GetFileStats ( tnFile, helpPath )) == ZERO )
   {
      //* If a "regular" file and user has read access *
      if ( tnFile.readAcc && (tnFile.fType == fmREG_TYPE) )
      {
         status = true ;                  // declare success
         switch ( ccode)
         {
            case cxtACEDIT:
               shellCmd.compose( chTemplate, helpPath.gstr(),
                                 "AcEdit Demo App" ) ;
               break ;
            case cxtMAIN:        // open the document at the top level menu
            default:
               shellCmd.compose( "info -f %S", helpPath.gstr() ) ;
               break ;
         } ;

         //* Call the shell to run the external utility. *
         // Programmer's Note: The return value is unreliable, so it is ignored.
         this->win->ShellOut ( shellCmd ) ;
      }
   }

   //* Documentation is not in the default position *
   if ( ! status )
      this->docsNotFound () ;

   return status ;

}  //* End ContextHelp() *

//*************************
//*       HtmlDocs        *
//*************************
//********************************************************************************
//* Open the HTML documentation file in the default browser/HTML reader app.     *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if successful, 'false' if file not found or system error     *
//********************************************************************************

bool AcEdit::HtmlDocs ( void )
{
   gString  docsPath( "../Texinfo/ansicmd.html" ) ; // filespec
   tnFName tnFile ;                       // receives file stats
   bool status = false ;                  // return value

   //* Be sure the documentation file exists *
   if ( (this->GetFileStats ( tnFile, docsPath )) == ZERO )
   {
      //* If a "regular" file and user has read access *
      if ( tnFile.readAcc && (tnFile.fType == fmREG_TYPE) )
      {
         if ( (this->win->LaunchDefaultApp ( docsPath.ustr() )) >= ZERO )
            status = true ;
      }
   }

   //* Documentation is not in the default position *
   //* or error opening the external application.   *
   if ( ! status )
      this->docsNotFound () ;

   return status ;

}  //* HtmlDocs() *

//*************************
//*     docsNotFound      *
//*************************
//********************************************************************************
//* Report that app documentation was not found.                                 *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::docsNotFound ( void )
{
   const char* noHelp[uilENV] = 
   {
      " Documentation file was not found. ", // uilEN
      "   未找到文档文件。  ",                       // uilZH
   } ;
   gString gs( noHelp[this->lang] ) ;
   this->infoDialog ( gs, false, NULL, 2, -1) ;
   this->updateWindow () ;       // refresh parent window

}  //* docsNotFound() *

//*************************
//*   extractCbVersion    *
//*************************
//********************************************************************************
//* Non-member function:                                                         *
//* --------------------                                                         *
//* Scan and store the version numbers of the WaylandCB-class object within      *
//* the AnsiCmd library, and if available, the version of the external           *
//* 'wl-clipboard' utilities.                                                    *
//*                                                                              *
//* Input  : gsVer : (by reference) contains raw version strings reported        *
//*                  during system clipboard initialization                      *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void extractCbVersion ( const gString& gsVer )
{
   int indx = gsVer.after( L'\n' ) ;
   if ( indx > ZERO )                     // if version data was captured
   {
      gString gs( &gsVer.gstr()[indx] ) ;
      gs.copy( wlcVersion, 16 ) ;
      gs = gsVer ;
      indx = gs.find( L'\n' ) ;
      gs.limitChars( indx ) ;
      gs.copy( wcbVersion, 16 ) ;
   }

}  //* End extractCbVersion() *

//*************************
//*       HelpAbout       *
//*************************
//********************************************************************************
//* Open a dialog to display information about the application.                  *
//*  - name                                                                      *
//*  - version number                                                            *
//*  - copyright message                                                         *
//*  - Contact info, website.                                                    *
//*  - AnsiCmd library version, gString version and WaylandCB version.           *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::HelpAbout ( void )
{
   const char* const enTemplate = 
   "AcEdit (ace): Copyright (c) %S v:%S\n"
   "              The Software Samurai\n"
   "              http://www.SoftwareSam.us/\n"
   " \n"
   "This application is based upon the author's AnsiCmd library   \n"
   "which implements the ANSI escape sequences available within a \n"
   "Linux terminal window. It is not intended as a full-featured  \n"
   "text editor; but rather is a testing platform for the AnsiCmd \n"
   "library and the ACWin and ACDlg classes within the library.   \n"
   "             AnsiCmd v:%s  WaylandCB    v:%s \n"
   "             gString v:%s  wl-clipboard v:%s \n";
//   "x---------- AnsiCmd v:%s    gString v:%s ------------x\n"
//   "x---------- WaylandCB v:%s  wl-clipboard v:%s --------x\n";
   const char* const zhTemplate = 
   "AcEdit (ace): 版权 (c) %S 版:%S\n"
   "              The Software Samurai (软件武士)\n"
   "              http://www.SoftwareSam.us/\n"
   " \n"
   "  该应用程序基于同一作者的 AnsiCmd 库。该库实现了\n"
   "  Linux 终端窗口内可用的 ANSI 转义序列。它并非一个\n"
   "  全功能的文本编辑器；而是一个 AnsiCmd 功能的测试平台。\n" // this row determines dialog width
   "       AnsiCmd 版:%s  WaylandCB    版:%s \n"
   "       gString 版:%s  wl-clipboard 版:%s \n" ;

   gString gs ;
   const wchar_t *crYears,
                 *appVersion = this->GetAppVersion ( crYears ) ;
   const char *acVersion = this->win->acVersion (),
              *gsVersion = gs.Get_gString_Version() ;
   gs.compose( this->lang == uilZH ? zhTemplate : enTemplate,
               crYears, appVersion, acVersion, wcbVersion, gsVersion, wlcVersion ) ;

   this->infoDialog ( gs, false ) ;
   this->updateWindow () ;

}  //* End HelpAbout() *

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

