//********************************************************************************
//* File       : AcEdit.cpp                                                      *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2023-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice below                               *
//* Date       : 01-Apr-2026                                                     *
//* Version    : (see AppVersion string)                                         *
//*                                                                              *
//* Description: A simple text editor based on the AnsiCmd library.              *
//*              This application exercises the ACWin-class functionality and    *
//*              serves as a development platform for the ACWin class.           *
//*                                                                              *
//* Note that this application is several iterations away from being a functional*
//* text editor; however, it provides a template for development of a complex    *
//* console application.                                                         *
//*                                                                              *
//*                                                                              *
//* Development Tools:                  Current:                                 *
//* ----------------------------------  ---------------------------------------- *
//*  GNU G++ v:4.8.0 or higher (C++11)  G++ v:15.2.1 20251211 (C++17)            *
//*  Fedora Linux v:32 or higher        Fedora v:42                              *
//*  GNOME Terminal v:3.46.8 or higher  GNOME Term v:3.56.3 (GNOME 48)           *
//*                                                                              *
//********************************************************************************
//* Copyright Notice:                                                            *
//* This program is free software: you can redistribute it and/or modify it      *
//* under the terms of the GNU General Public License as published by the Free   *
//* Software Foundation, either version 3 of the License, or (at your option)    *
//* any later version.                                                           *
//*                                                                              *
//* This program is distributed in the hope that it will be useful, but          *
//* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY   *
//* or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License     *
//* for more details.                                                            *
//*                                                                              *
//* You should have received a copy of the GNU General Public License along      *
//* with this program.  If not, see <http://www.gnu.org/licenses/>.              *
//*                                                                              *
//********************************************************************************
//* Version History:                                                             *
//*                                                                              *
//* v: 0.00.05 23-Mar-2026                                                       *
//*   --                                                                         *
//*                                                                              *
//*   --                                                                         *
//*                                                                              *
//*   -- Clean up the testing functions in Sandbox().                            *
//*   -- Update documentation.                                                   *
//*                                                                              *
//*                                                                              *
//* v: 0.00.04 19-Apr-2025                                                       *
//*   -- Update skForm/skField initialization to use the initialization          *
//*      constructors.                                                           *
//*   -- Add callback function to monitor user interaction.                      *
//*   -- Convert manual manipulation of menubar group to the enhanced AnsiCmd    *
//*      menu interface.                                                         *
//*   -- QuickDialog() uses the border attribute of the parent window, and uses  *
//*      the parent window interior attribute by default. Although the border    *
//*      attribute is under user control, currently, the app window interior     *
//*      attribute (ca.intAttr) is not. We may revisit this decision in a future *
//*      release.                                                                *
//*   -- Enable system clipboard access. This uses the WaylandCB library which   *
//*      requires the external wl-cliboard utilities.                            *
//*   -- Implement flashDialog() method. This is an ad-hoc pseud-dialog which    *
//*      uses a timer for the display interval and can optionally be terminated  *
//*      by pressing any key.                                                    *
//*   -- Now that the focus-shift code is stable, the updateText() method is     *
//*      obsolete and has been removed.                                          *
//*   -- Limited release.                                                        *
//*                                                                              *
//* v: 0.00.03 19-Nov-2024                                                       *
//*   -- Make minor modifications to support the new definition of the gString   *
//*      class.                                                                  *
//*   -- Import updated algorithm for menu item selection from the AnsiCmd       *
//*      test suite.                                                             *
//*   -- No public distribution.                                                 *
//*                                                                              *
//* v: 0.00.02 21-Oct-2023                                                       *
//*    -- Dialog and text formatting integrated into the ACWin class and         *
//*       related development code removed from AcEdit application.              *
//*    -- Line number field and "gutter-line" field implemented.                 *
//*    -- Menu field stubs created. Menus not yet implemented.                   *
//*    -- Import the user interface language support from CsvView application.   *
//*    -- Import the gString class via the gString.lib.                          *
//*       This application serves as real-world proving ground for the           *
//*       beta release of the gString class.                                     *
//*                                                                              *
//* v: 0.00.01 21-Aug-2023                                                       *
//*    -- First effort. Adapted from the EarthPoints sample application.         *
//*    -- File management methods adapted from the author's FileMangler          *
//*       application.                                                           *
//*    -- Implementation of robust auto-formatting of text data.                 *
//*    -- Implementation of a simple "dialog" window with pushbutton controls.   *
//*    -- Note that the dialog and text formatting will be moved to the ACWin    *
//*       class when the algorithm is stable.                                    *
//*                                                                              *
//********************************************************************************
//* Programmer's Notes:                                                          *
//*                                                                              *
//*                                                                              *
//********************************************************************************
//* To Do List:                                                                  *
//* -----------                                                                  *
//* -- Auto-save timer.                                                          *
//*    If live data have been modified since last save, write live data to       *
//*    work buffer 'wbuff'.                                                      *
//* -- Main Menu                                                                 *
//* -- Sub-menus                                                                 *
//* -- Add an option to edit a file in floToken mode.                            *
//*    If data exists When switching to floToken,either offer to reformat or     *
//*    automagically reformat. Save in .xml or .rtf...                           *
//* --                                                                           *
//*                                                                              *
//* -- All open files s/b guaranteed to be located at the top of this->fman[]    *
//*    array. While this is true initially, we would need to have a              *
//*    record-sorting method to be called when:                                  *
//*      a) an open file is closed                                               *
//*      b) a new file is opened                                                 *
//*      c) a file in the "recents" list is opened                               *
//*    The sorting method would also need to update the 'sindx' value in         *
//*    this->fman[AREC] and/or this->fman[IREC]                                  *
//*    -- When this is implemented, the local tracking in reportOpenFiles()      *
//*       can be eliminated.                                                     *
//* --                                                                           *
//*                                                                              *
//* -- Split gutter field into stats and message segments?                       *
//*                                                                              *
//* -- 'Find' dialog.                                                            *
//* -- 'Find Next', 'Find Prev'                                                  *
//* -- Find in other buffers.                                                    *
//* -- scan-for-match: '{' '[' '(' '<' and HTML tokens, e.g. <span> ... </span>  *
//* -- Select text block                                                         *
//* -- Copy/Cut/Paste                                                            *
//* -- Undo                                                                      *
//*    Where when and how to save data for a potential 'undo' request:           *
//*    a) if paste, save the paste IP and number of characters.                  *
//*    b) if paste overwrites some existing text, then save both the position    *
//*       and char count of pasted data as well as the overwritten data.         *
//* --                                                                           *
//*                                                                              *
//* --                                                                           *
//* --                                                                           *
//* --                                                                           *
//*                                                                              *
//*                                                                              *
//* -- Redo                                                                      *
//* -- 'Save As'                                                                 *
//* -- Compare files (see FileMangler)                                           *
//* -- Next buffer, Prev buffer (CTL+ALT+PgUp and CTL+ALT+PgDn)                  *
//* --                                                                           *
//* -- Make a backup copy of the existing text data for each text field.         *
//*    This can be used for the 'undo' operation.                                *
//* -- Implement 'find-formatted' which scans for a sub-string that is the same  *
//*    as the search string EXCEPT that it may contain a line break.             *
//*    -- This would be integrated into the larger 'find' algorithm.             *
//* -- Implement member method callbacks i.e. static method at application level.*
//* -- Support both plain text and '.html'/'.xml' format.                        *
//* -- Intermittent memory-related crash or disruption during window open.       *
//*    -- Interesting side note: The attribute upgrade in DefineWindow() causes  *
//*       a semi-reliable memory problem on startup. Part of the window is not   *
//*       drawn, or is corrupted. If a 'clear' command before invocation, the    *
//*       problem seldom if ever occurs. What's up with that?                    *
//*       Try flushing the I/O buffers on startup.                               *
//*       Can we implement a count-down to determine whether initialization      *
//*       was successful.                                                        *
//*    -- It happens much less frequently if the lorem ipsum data are not loaded *
//*       on startup.                                                            *
//*    -- look at whether the horizontal edit fields are properly initialized    *
//*       before window is opened. Has not happened at all for at least 2 weeks. *
//* -- Add a field to Scrollbox controls to allow for a separate color attribute *
//*    for each line of text.                                                    *
//*                                                                              *
//* -- Set a flag or other indicator to alert to previous disorderly shutdown.   *
//*    -- If backup files are found which match files in the config file list,   *
//*       or if a filename which appears in the backup format is found, then     *
//*       ask user how to handle it.                                             *
//*       This would mean comparing the timestamps, size and contents of the     *
//*       source and backup files and reporting the information to the user.     *
//*    -- On startup, write a flag to the config file indicating that app is     *
//*       active. If an orderly shutdown, the flag would be reset or removed.    *
//*       If the flag is set on startup, we know that it was not an orderly      *
//*       shutdown. This would not necessarily mean that any source files were   *
//*       unsaved or incomplete.                                                 *
//*                                                                              *
//* -- When to update the programatic file data to the config file.              *
//*    1) On app exit.                                                           *
//*    2) If a "new" file is opened, then update on first save.                  *
//*    3) If a target file is specified on the command line but a config file    *
//*       entry for it does not exist, then update config file: a) on first save,*
//*       b) when first opened, c) when unsaved file is closed.                  *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//*                                                                              *
//* -- Alternate keys for default in cycling a buffer into a field.              *
//*    current defaults: wcsCAPGUP/wcsCAPGDN                                     *
//*    alternative     : wcsALT_ST/wcsALT_GT and wcsALT_CO/wcsALT_LT  _or_       *
//*                      wcsARIGHT/wcsALEFT and wcsAUP/wcsADOWN                  *
//*                                                                              *
//* -- Documentation:                                                            *
//*    --                                                                        *
//*    --                                                                        *
//*    -- Invoke documentation from within application.                          *
//*                                                                              *
//********************************************************************************

//*****************
//* 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 *
//**************

//* Filespec template for working copy of file(s) under edit.*
static const wchar_t* const efileTemplate = L"%S/.#%S#" ;
static const uint32_t BUFF_ALLOC = 1000000 ;  // default work buffer size (bytes)

#if DEBUG_ACE != 0
extern const char* fldId[fiFIELDS+1] ;
//* For debugging: initialized by "--testnum=n" command-line option.*
static short testnum = -1 ;
#endif   // DEBUG_ACE

//********************
//* Local prototypes *
//********************
static wchar_t nonmember_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime ) ;
static AcEdit *nmcPtr ;
static fldIndex udfAltField ( fldIndex srcIndx, fldIndex trgIndx, bool trgvis ) ;
extern void extractCbVersion ( const gString& gsVer ) ;
extern bool linenumMatch ( fldIndex srcIndx, fldIndex& matchIndx ) ;


//*************************
//*        main           *
//*************************
//********************************************************************************
//* Program entry point.                                                         *
//*                                                                              *
//********************************************************************************

int main ( int argc, char* argv[], char* argenv[] )
{
   //* For capture and parsing of command-line arguments *
   commArgs ca( argc, argv, argenv ) ;

   //* Instantiate the application class *
   AcEdit *aced = new AcEdit ( ca ) ;

   //* Save a local copy of the exit code, then delete the EarthPoints object.*
   xCode xcode = aced->GetExitCode () ;
   delete aced ;

   //* Exit, returning control to the shell.*
   exit ( xcode ) ;

}  //* End main() *

//*************************
//*        AcEdit         *
//*************************
//********************************************************************************
//* AcEdit class constructor.                                                    *
//*                                                                              *
//* Input  : ca     : (by reference) copy of command-line arguments              *
//*                                                                              *
//* Returns: implicitly returns pointer to class                                 *
//********************************************************************************
//* Notes on terminal configuration:                                             *
//* Instantiation of The AnsiCmd class performs important configuration          *
//* operations which should be completed before the application writes to        *
//* stdout/stderr and before the application reads from stdin.                   *
//*                                                                              *
//* This is because the application's first access to stdout/stdin/stderr        *
//* will cause certain configuration options to be set for the duration of the   *
//* session. This automatic configuration may cause the application to act in    *
//* unexpected ways. For this reason, it is important to EXPLICITLY set the      *
//* terminal configuration options as early in the application as possible.      *
//* This is done during instantiation of the AnsiCmd or ACWin class.             *
//* The two most critical configuration options are described briefly here.      *
//* See the constructors for additional details.                                 *
//*                                                                              *
//* 1) The width of the I/O streams are set the first time they are used.        *
//*    The wide input/output streams are accessed through the 'wcin' and         *
//*    'wcout' classs, respectively.                                             *
//*    The narrow input/output streams are accessed through the 'cin' and        *
//*    'cout' classes, respectively.                                             *
//*    (Please try not to use the C-language I/O functions any more than         *
//*     absolutely necessary.)                                                   *
//*                                                                              *
//* 2) The terminal's "locale" determines the character encoding used, and the   *
//*    primary language expected. This determines the way the terminal will      *
//*    handle certain text formatting -- for instance, currency symbols          *
//*    and number formats.                                                       *
//*    The terminal default locale may be overridden by the "--language" command *
//*    line option.                                                              *
//*    It is important to understand that if the application does not            *
//*    explicitly set the desired local, the so-called "C-locale" is used.       *
//*    The C-locale restricts the character set to an ASCII superset (7-8-bit    *
//*    characters), and is unacceptable for any serious application.             *
//*    -- See the startupLocale() method for additional information.                                            *
//********************************************************************************

AcEdit::AcEdit ( commArgs& ca )
{
   gString gsOut ;                        // text formatting

   //* Initialize all class data members.*
   this->reset () ;

   //* Create a temporary-file manager for use *
   //* before the ACWin class is created.      *
   this->tfm = new TFMan ;

   //* Parse command-line arguments *
   clArgs userOption = this->GetCommandLineArgs ( ca ) ;

   //* Read the configuration file.          *
   //* If no command-line errors AND if file *
   //* count > zero, then update userOption. *
   this->Configure ( ca ) ;

   //* Copy the test number (if any) to the static variable. (see above) *
   testnum = ca.testnum ;

   //****************************
   //* Define the target window *
   //****************************
   //* (This call also provides access to all AnsiCmd public methods.) *
   if ( (this->DefineWindow ( ca )) )
   {  //* Delete the local temp-file manager *
      if ( this->tfm != NULL )
      { delete this->tfm ; this->tfm = NULL ; }
      //* Enable the ACWin-class temp-file manager.*
      //* This creates a subdirectory on the       *
      //* temp-file path which will contain all    *
      //* temp files created by the application.   *
      this->win->acCreateTemppath ( "ACE_" ) ;
   }

   //**********************************************
   //* If command-line arguments valid, and not a *
   //* cry for help, Open the application window. *
   //**********************************************
   if ( userOption == clOpen )
   {
      //* Get user info to verify that user has access to target files *
      if ( !(this->IdentifyUser ()) )
      { /* Unable to identify user. (unlikely) */ }

      #if DEBUG_ACE != 0 && DEBUG_PSRC != 0
      this->psrc ( true ) ;
      #endif   // DEBUG_ACE && DEBUG_PSRC

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

      //* Open the application window and reference *
      //* the underlying configuration data.        *
      this->win->OpenWindow () ;
      this->win->WinInfo ( this->wInfo ) ;

      //* If specified, enable communication with system clipboard. *
      if ( ca.sysclip )
      {
         //* Enable system clipboard and store   *
         //* wl-clipboard and WaylandCB versions.*
         this->win->acClipboardEnable ( &gsOut ) ;
         extractCbVersion ( gsOut ) ;
      }

      #if DEBUG_ACE != 0 && DEBUG_DUMP_WINFO != 0
      //* Display the window configuration data.*
      this->dumpWinInfo ( DD2WIN_ROW, DD2WIN_COL ) ;
      #endif   // DEBUG_ACE && DEBUG_DUMP_WINFO

      //* Define a callback function to monitor *
      //* user interactions.                    *
      nmcPtr = this ;
      win->EstablishCallback ( &nonmember_Callback ) ;

      //* If error messages were generated during parsing *
      //* of command-line arguments, display them.        *
      if ( (ca.gsErr.gschars()) > 1 )
      {
         //* Limit number of error messages to available window height. *
         int indx = ZERO ;
         short maxMsgRows = this->wInfo.txtRows - 5, 
               msgRows = ZERO ;
         while ( (indx = ca.gsErr.after( L'\n', indx )) >= ZERO )
         { if ( ++msgRows >= maxMsgRows ) { ca.gsErr.limitChars( indx ) ; break ; } }

         this->infoDialog ( ca.gsErr, false ) ;
      }

      //***********************************************************
      //* Edit the target file(s).                                *
      //* Returns when all edits complete and target files closed.*
      //***********************************************************
      this->Editor () ;

      //* Close the application window *
      #if DEBUG_EVIS == 0  // Production
      this->win->CloseWindow () ;         // close the application window
      this->win->acClipboardDisable () ;  // terminate the system clipboard connection
      this->win->ttyWrite ( NEWLINE ) ;   // set cursor at left edge of terminal
      #else    // Development Only - window stays visible
      this->win->CloseWindow ( false ) ;  // close (but don't erase) the application window
      this->win->acClipboardDisable () ;  // terminate the system clipboard connection
      this->win->acSetCursor ( (HGT_MIN + 2), 1 ) ; // set cursor below app window
      #endif   // Development Only

      this->win->acFlushStreams () ;     // flush the I/O streams
   }

   //**********************************
   //* Misc. non-window functionality *
   //**********************************
   else
   {
      //* Set default foreground/background *
      this->win->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;

      //* Display copyright and version information, then exit *
      if ( (userOption == clVersion) || (userOption == clVersiona) )
         this->DisplayVersion ( userOption == clVersiona ? true : false ) ;

      //* Display command-line help *
      else if ( (userOption == clHelp) || (userOption == clHelpless) )
         this->DisplayHelp ( userOption ) ;

      else if ( userOption == clTermSize )
         this->DisplayTermSize () ;

      //* userOption == clQuickHelp command-line syntax errors. *
      else
      {
         this->ComposeTitle ( gsOut, true ) ;
         this->win->ttyWrite ( gsOut, false ) ;
         if ( ca.gsErr.gschars() > 1 )
         {
            ca.gsErr.limitChars( (ca.gsErr.gschars() - 2) ) ; // strip trailing newline
            ca.gsErr.insert( NEWLINE ) ;                      // add leading newline
            this->win->ttyWrite ( ca.gsErr, false ) ;         // report error list
         }
         this->win->acSetMod ( aesBOLD ) ;
         gsOut = L"\nPlease type: \"ace --help\" for available options.\n\n" ;
         this->win->ttyWrite ( gsOut, false ) ;
      }
   }

   //** Housekeeping **
// REDUNDANT   delete this->win ;               // delete the window object
// REDUNDANT   this->win = NULL ;               // and be safe

}  //* End AcEdit() *

//*************************
//*        ~AcEdit        *
//*************************
//********************************************************************************
//* AcEdit class destructor.                                                     *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

AcEdit::~AcEdit ( void )
{
   //* The local temp-file manager should have been *
   //* deleted earlier, but if not, do it now.      *
   if ( this->tfm != NULL )
   { delete this->tfm ; this->tfm = NULL ; }

   //* Restore original terminal settings *
   if ( this->win != NULL )
   { delete ( this->win ) ; this->win = NULL ; }

}  //* End ~AcEdit() *

//*************************
//*        Editor         *
//*************************
//********************************************************************************
//* Validate that user has read/write access to the target file(s) and           *
//* edit the file(s).                                                            *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Initialization                                                               *
//* --------------                                                               *
//* For each file listed in the FileMan array:                                   *
//  1) Make a working copy of the source file.                                   *
//*   It has been verified that target files are:                                *
//*   a) "regular" files,                                                        **
//*   b) plain-text files, and that                                              *
//*   c) user has read/write access.                                             *
//*      (read-only access is possible, but unlikely)                            *
//* 2)Note that previously-open files and files specified on the command line    *
//*   are grouped at the top of the list, with the highest priority file         *
//*   (if any) at this->fman[ZERO].                                              *
//* 3) If the FileMan array contains no entries marked as 'to-be-opened', then   *
//*    an empty FileMan buffer will be assigned to the default TextEdit control. *
//*                                                                              *
//* Programmer's Note: Files are not loaded if any debugging test > 3 specified. *
//* This prevents the working copies from bein orphaned on exit.                 *
//********************************************************************************

void AcEdit::Editor ( void )
{
   gString gsOut ;                        // text formatting
   bool loaded = false ;                  // set if file data loade into TextEdit field

   for ( short f = ZERO ; (f < this->fmcnt) && (testnum <= 3) ; ++f )
   {
      //* Make working copies of all files marked as 'to-be-opened'.*
      if ( this->fman[f].open )
      {
         // The first file opened will be loaded into *
         //* the default (fiTEXTfw) TextEdit control. *
         if ( (this->openFile4Edit ( f )) )
         {
            if ( ! loaded )
            {
               this->loadField ( f, this->focus ) ;
               loaded = true ;
            }
         }
         else
            ; // post a warning if unable to open file (unlikely, but possible)
      }
   }

   //* If no files were loaded above, then open *
   //* a new file (a clean, unsaved buffer).    *
   if ( ! loaded && (testnum <= 3) )
   {
      this->newFile2Edit () ;
   }

   //* Update line-number and gutter fields *
   this->updateGutter ( false, L"Press Alt+H for Help" ) ;
   this->updateWindow () ;

   #if DEBUG_ACE != ZERO
   //* If a development test specified, execute it now. *
   // Programmer's Note: 'testnum' is a static variable at top of module.
   if ( testnum >= ZERO )
   {
      testnum = this->Sandbox ( testnum ) ;
      if ( testnum > 3 )
      {
         #if DEBUG_ACE != 0 && DEBUG_EXT_TESTS != 0
         for ( short r = ZERO ; r < this->fmcnt ; ++r )
         { if ( this->fman[r].open ) { this->closeRecord ( r ) ; } }
         #endif   // DEBUG_ACE && DEBUG_EXT_TESTS
         return ;
      }
      testnum = -1 ;
   }
   #endif   // DEBUG_ACE

   //***************************
   //** Talk With the Animals **
   //***************************
   short exitIndex ;
   this->win->EditForm ( exitIndex ) ;

   #if DEBUG_ACE != 0 && DEBUG_PSRC != 0 // delete the surrogate files
   this->psrc ( false ) ;
   #endif   // DEBUG_ACE && DEBUG_PSRC

   //* Perform housekeeping on each file under edit:     *
   //* 1) Data for all files under edit have been saved  *
   //*    before we arrive here.                         *
   //* 2) Delete the working copy of each "open" file.   *
   //* 3) Update the FileMan records to indicate status. *
   //* 4) Update the programatically-generated data of   *
   //*    the configuration file file.                   *
   /* not yet implemented */
   #if 1    // TEMP - DELETE WORKING COPIES
   for ( short r = ZERO ; r < this->fmcnt ; ++r )
   {
      if ( this->fman[r].open )
         this->closeRecord ( r ) ;
   }
   #endif   // TEMP - DELETE WORKING COPIES


}  //* End Editor() *

//*************************
//*       Callback        *
//*************************
//********************************************************************************
//* Callback function to monitor user interaction.                               *
//* Handle the callbacks from ACWin method: EditForm().                          *
//*                                                                              *
//*                                                                              *
//* Input  : ctrlIndex: index of field/control which currently has input focus   *
//*          wkey     : user's key input                                         *
//*          firstTime: the EstablishCallback() method calls this method once    *
//*                     with firstTime==true, to perform any required            *
//*                     initialization. All subsequent calls are made with       *
//*                     firstTime==false.                                        *
//*                                                                              *
//* Returns: if keycode handled, returns the null character: L'\0'               *
//*          Special Case: returns wcsCTL_4 to signal that EditForm() should     *
//*                        terminate the loop and return to caller.              *
//*                        Note that wcsCTL_4 is the ASCII code for the SIGQUIT  *
//*                        signal, and will never be sent as the 'wkey' value.   *
//*          if kecode is not handled by this method: 'wkey' is returned.        *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* A) Exit Code:                                                                *
//*    The ACWin-class method, EditForm() returns to caller when the wcsCTL_4    *
//*    keycode is received. There are two ways for the user to signal program    *
//*    exit:                                                                     *
//*       1) From the 'File' menu, select the Exit item.                         *
//*       2) Press the Ctrl+Q key combination.                                   *
//*    Note that Ctrl+Q is specified as the exit keycode; however, the ACWin     *
//*    class may have converted it to Alt+Q to conform to the GUI-key definition.*
//*    This method tests for both formats.                                       *
//* B) Moving among edit fields:                                                 *
//*    The application defines five(5) edit fields:                              *
//*       fiTEXTfw : Full height and width of application interior, excluding    *
//*                  the line-number field and the gutter field.                 *
//*       fiTEXThu : Horizontal field filling the upper half of edit area.       *
//*       fiTEXThl : Horizontal field filling the lower half of edit area.       *
//*       fiTEXTvl : Vertical field filling the left half of edit area.          *
//*       fiTEXTvr : Vertical field filling the right half of edit area.         *
//*    When the application opens, the fiTEXTfw field has the input focus.       *
//*     1) The horizontal pair of fields is invoked by pressing wcsALT_2.        *
//*     2) The vertical pair of fields is invoked by pressing wcsALT_3.          *
//*     3) The stand-alone field is invoked by pressing wcsALT_1 (or wcsALT_0).  *
//*    When either the horizontal pair or vertical pair of edit fields is        *
//*    visible, moving between them is done by pressing either:                  *
//*     1) wcsALT_PGUP or 2) wcsALT_PGDN. (This is handled at the library level.)*
//*    When the stand-alone field is visible, these two keycodes are ignored.    *
//* C) The menuing system:                                                       *
//*     1) The "Menubar" is invoked by wcsF01.                                   *
//*        The Menubar is a single row menu which anchors the other members      *
//*        of the menu group.                                                    *
//*        a) The wcsTAB/wcsSTAB keys move among the items of the Menubar.       *
//*           This is handled within the EditForm() loop.                        *
//*        b) The wcsENTER or wcsSPACE (or wcsDARROW) key selects the            *
//*           currently-highlighted item of the Menubar which open the associated*
//*           sub-menu. This is also handled within the EditForm() loop.         *
//*     2) Sub-menus of the menu group:                                          *
//*        fiM_FILE : File open/close/save/save-as, etc.                         *
//*                   The "Exit" item is also included in this menu.             *
//*        fiM_EDIT : copy/cut/paste/paste-special, etc.                         *
//*        fiM_VIEW : move among edit fields, file state, etc.                   *
//*        fiM_UTIL : manage clipboard, terminal configuration, application      *
//*                   configuration, etc.                                        *
//*        fiM_HELP : Invoke one of the types of help:                           *
//*           a) Info help                                                       *
//*           b) HTML help                                                       *
//*           d) Shortcut key bindings                                           *
//*           e) Help About                                                      *
//*                                                                              *
//* D) Updating auxilliary data in response to cursor movement in the Textbox    *
//*    edit controls.                                                            *
//*    1) When cursor movement is detected, the gutter's line/character display  *
//*       needs to be updated, and if the data have been (or will soon be)       *
//*       scrolled, the line-number field may also need to be updated.           *
//*    2) There is a timing issue involved because the cursor movement keycode   *
//*       is seen here _before_ the keycode is actually executed.                *
//*    4) When this happens, a "secret code" keycode is pushed into the input    *
//*       stream so that the callback will be invoked again immediately after    *
//*       the cursor movement and display update have occurred. The LineNumber   *
//*       and Gutter fields can then be updated to report the current text       *
//*       offset and cursor positioning.                                         *
//*    5) Capture of the Insert/Overstrike keycode presents the same problem.    *
//*       It is seen here before the indicator flag this->wInfo.skf->ins         *
//*       is updated.                                                            *
//*                                                                              *
//* Auto-Save:                                                                   *
//* ----------                                                                   *
//* -- Auto-save interval is specified in config file.                           *
//* -- An auto-save consists of copying the field 'txt' data to the backup copy  *
//*    of the file under edit.                                                   *
//*    -- I is the interval between auto-saves.                                  *
//*    -- N is the number of auto-saves to the backup file that will trigger     *
//*       an update of the source file.                                          *
//*    -- An auto-save is performed only if the field 'txt' data have changed.   *
//*                                                                              *
//* -- The trigger for an auto-save is done by comparing the current system      *
//*    local time with the timestamp of the most recent save.                    *
//*    -- To avoid constant stats of the file, the timestamp of the save is      *
//*       recorded in the scratch buffer.                                        *
//*    -- To avoid constant calls to retrieve the system local time, the         *
//*       callback contains a countdown value. When the countdown reaches zero,  *
//*       system time is retrieved and the countdown value is re-initialized.    *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* -- Timestamps of most recent save for source and wcopy are saved in the      *
//*    scratch buffer.                                                           *
//* -- Special events triggering an auto-save:                                   *
//*    -- An explicit save of source _implies_ a save of wcopy and reset of      *
//*       countdown.                                                             *
//*    -- Do an auto-save when switching buffers.                                *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Programmer's Note: The std;;invoke() function (C++17) allows direct use of   *
//* member methods as callback functions. This needs research.                   *
//*                                                                              *
//********************************************************************************

wchar_t AcEdit::Callback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{
   const wchar_t secretCode = wcsBULLSEYE ; // placeholder character for pushback
// TEMP: test value. Actual value needs to be calculated. 30 <= tsCNT <= 350
   const short tsCNT =  50 ;              // number of iterations between auto-save tests
   static short tsCnt ;                   // auto-save countdown
   const  time_t tsINTERVAL = 600 ;       // nominal auto-save interval (in seconds)
//* TEMP */ const  time_t tsINTERVAL = 10 ;
   static time_t tStamp ;                 // time of most recent auto-save

   localTime ltime ;                      // system timestamp
   gString gs ;                           // text formatting
//   const skField* skfp ;
   wchar_t wkey_dflt ;                    // hotkey converted to default value
   wchar_t menu_char ;                    // 'hotkey' character for menu item
   short   menu_item ;                    // selected menu item (1-based)


   //*------------*
   //* First call *
   //*------------*
   if ( firstTime )
   {
      this->GetLocalTime ( ltime ) ;      // initialize auto-save timestamp
      tStamp = ltime.epoch ;
      tsCnt = tsCNT ;                     // initialize auto-save countdown
//* TEMP */ gsdbg.compose( "initial Callback timestamp: %lXh", &tStamp ) ; ofsdbg << gsdbg << endl ;
   }

   //* If focus is on a TextEdit control *
   if ( ((ctrlIndex >= fiTEXTfw) && (ctrlIndex <= fiTEXThl)) )
   {
      //* If scrolling keycodes within a Texbox control, update gutter info.*
      switch ( wkey )
      {
         case wcsUARROW:         // cursor movement keycodes
         case wcsDARROW:
         case wcsLARROW:
         case wcsRARROW:
         case wcsHOME:
         case wcsEND:
         case wcsCHOME:
         case wcsCEND:
         case wcsPGUP:
         case wcsPGDN:
         case wcsBKSP:
         case wcsAENTER:
            this->win->KeycodePush ( secretCode ) ; // queue a display-update code
            break ;

         case wcsSRIGHT:         // text selection keycodes
         case wcsSLEFT:
         case wcsSUP:
         case wcsSDOWN:
         case wcsSELECT:
         case wcsPASTE:
            this->win->KeycodePush ( secretCode ) ; // queue a display-update code
            break ;

         case wcsINSERT:         // toggle Insert/Overstrike mode
            this->win->KeycodePush ( secretCode ) ; // queue a display-update code
            // Programmer's Note: Only the Gutter needs to be updated in response to the
            // Ins/Ovr key; however, the code is cleaner if we update all aux data.
            break ;

         case secretCode:
            //* Update field monitor stats and    *
            //* synchronize line number alignment.*
            this->updateAuxData () ;
            wkey = NULLCHAR ;
            break ;

         case wcsTAB:
         case wcsSTAB:
            //* Emulate a Tab character.              *
            //* Note: This call will push one or more *
            //* characters into the input stream.     *
            if ( wkey == wcsTAB )
            {
               this->tabEmulator () ;
               this->fman[AREC].mods = this->fman[AREC].wmods = true ;
            }
            wkey = NULLCHAR ;             // signal that keycode has been handled
            break ;

         case wcsAPGUP:
         case wcsAPGDN:
            //* If dual visible TextEdit controls,    *
            //* shift focus to the alternate control. *
            switch ( this->focus )
            {
               case fiTEXTvl:
               case fiTEXTvr:
               case fiTEXThu:
               case fiTEXThl:
                  this->updateFocus ( fiFIELDS, wkey ) ;
                  break ;
               default:                   // keycode not valid in current context
                  break ;
            } ;
            wkey = NULLCHAR ;             // signal that keycode has been handled
            break ;

         case wcsCTL_S:                   // Save edits to source file
            this->fullSave () ;
            wkey = NULLCHAR ;             // signal that keycode has been handled
            break ;

         case wcsCTL_L:
            this->gotoLine () ;
            wkey = NULLCHAR ;             // signal that keycode has been handled
            break ;

         case wcsALT_FS:                  // ALT+ForwardSlash block shift up
         case wcsALT_SQ:                  // ALT+SingleQuote  block shift down
            this->win->ShiftFieldText ( this->focus, (wkey == wcsALT_FS ? 1 : -1) ) ;
            this->updateAuxData () ;
            wkey = NULLCHAR ;             // signal that keycode has been handled
            break ;

#if 0    // TEMP TEMP TEMP
         //* If 'find-matching-delimiter' keycode *
         case wcsALT_RS:
            this->findDelimMatch () ;
            wkey = NULLCHAR ; 
            break ;

         //* If 'find-matching-tag keycode *
         case wcsALT_LS:
//            this->findTagMatch () ;
            wkey = NULLCHAR ;
            break ;
#endif   // TEMP TEMP TEMP

         //* Exit the user-interface loop and prepare to exit the application.*
         case wcsCTL_Q:
         case wcsALT_Q:
            //* Before exit, verify that all files    *
            //* currently under edit have been saved. *
            if ( (this->saveAll ()) )
               wkey = wcsCTL_4 ;    // exit the application
            else
               wkey = NULLCHAR ;    // discard the keycode
            break ;

         default:
            if ( (this->keys.is_skey( wkey, wkey_dflt )) )
            {
               switch ( wkey_dflt )
               {
                  //* If 'find-substring' keycode group *
                  case wcsCTL_F: case wcsCTL_G: case wcsCTL_R:
                     while ( (this->findSubstring ( wkey )) ) ;
                     wkey = NULLCHAR ;
                     break ;
         
                  //* If 'update-focus' keycode group *
                  case wcsALT_2:                   // dual-window, horizontal
                  case wcsALT_3:                   // dual-window, vertical
                  case wcsALT_0: case wcsALT_1:    // single-window
                  case wcsAPGUP: case wcsAPGDN:    // move between dual windows
                     this->updateFocus ( fiFIELDS, wkey_dflt ) ;
                     wkey = NULLCHAR ;
                     break ;
         
                  //* If 'load-buffer-into-current-field' keycode group *
                  // Programmer's Note: Because the wcsCAPGDN/wcsCAPGUP keycodes are 
                  // inconvenient keycodes, the is_skey() method automatically converts 
                  // the arrow keycodes ALT+UpArrow(wcsAUP) and ALT+DownArrow(wcsADOWN) 
                  // to the defaults for this operation.
                  case wcsCAPGDN: case wcsCAPGUP: case wcsALT_B:
                     this->selectBuffer ( wkey_dflt ) ;
                     wkey = NULLCHAR ;
                     break ;

                  //* Create a new file *
                  case wcsCTL_N:
                     this->newFile2Edit () ;
                     wkey = NULLCHAR ;
                     break ;

                  //* Open a file for edit *
                  case wcsCTL_O:
                     this->select4Edit ( fldIndex(ctrlIndex) ) ;
                     wkey = NULLCHAR ;
                     break ;

                  //* If 'find-matching-delimiter' keycode *
                  case wcsALT_RS:
                     this->findDelimMatch () ;
                     wkey = NULLCHAR ; 
                     break ;

                  //* If 'find-matching-tag keycode *
                  case wcsALT_LS:
                     this->findTagMatch () ;
                     wkey = NULLCHAR ;
                     break ;

                  default:          // unrecognized shortcut keycode (programmer error)
                     break ;
               } ;
            }

            //* Graphing characters (see notes above) and   *
            //* Enter key: set the 'wmods' flag to indicate *
            //* that field contains unsaved mods.           *
// Test 'mods' and/or 'wmods' ?
            else if ( !this->fman[AREC].wmods && (this->win->isPrint ( wkey )) )
            {
               this->fman[AREC].mods = this->fman[AREC].wmods = true ;
               this->updateAuxData () ;
               //* Get the current time. Used to determine the auto-save interval.*
               this->GetLocalTime ( ltime ) ;
               tStamp = ltime.epoch ;
            }

            //* Unhandled keycode *
            else
            {
               //* If stand-alone Escape key: There is currently *
               //* nothing to be done in response to the Escape  *
               //* key, so discard the keycode.                  *
               if ( wkey == wcsESCAPE )
               {
                  if ( this->wInfo.skf->beep )
                     this->win->acBeep () ; 
                  wkey = NULLCHAR ;
               }
               
            }
            break ;
      } ;

      //* If modification have been made since last save,      *
      //* decrement the auto-save counter, and if terminal     *
      //* count, compare current time to last auto-save time.  *
//* TEMP */ gsdbg.compose( "tsCnt:%3hd", &tsCnt ) ; ofsdbg << gsdbg << endl ;
// Should decrement ONLY if 'wmods' && isPrint()
      if ( this->fman[AREC].wmods )
      {
#if 0    // TEMP TEMP TEMP
if ( (wkey != NULLCHAR) && (wkey != secretCode) )
{
   gsdbg.compose( "wmods:%hhd wkey:%04X tsCnt:%hd", 
                  &this->fman[AREC].wmods, &wkey, &tsCnt ) ;
   ofsdbg << gsdbg << endl ;
}
#endif   // TEMP TEMP TEMP
         // Programmer's Note: This coundown is done for speed.
         // It reduces the number of calls for system-time.
// It would be better to remember isPrint() and test for that; however,
// that test is expensive and is only performed if !wmods.
         if ( (wkey != NULLCHAR) && (wkey != secretCode) && (--tsCnt <= ZERO) )
         {
            this->GetLocalTime ( ltime ) ;
//* TEMP */ time_t tdiff = ltime.epoch - tStamp ;
//* TEMP */ gsdbg.compose( "tStamp:%ld ltime.epoch:%ld tdiff:%ld tsINTERVAL:%ld wmods:%hhd", 
//* TEMP */                 &tStamp, &ltime.epoch, &tdiff, &tsINTERVAL, &this->fman[AREC].wmods ) ;
//* TEMP */ ofsdbg << gsdbg << endl ;
            //* If auto-save interval has expired, *
            //* perform auto-save and reset timer. *
            if ( (ltime.epoch - tStamp) >= tsINTERVAL )
            {
               this->autoSave ( AREC ) ;
               tStamp = ltime.epoch ;
            }
            tsCnt = tsCNT ;               // reset the count
         }
      }
   }

   //* Menu (or Scrollbox) Item Selection:            *
   //* Note that the hotkey for a pushbutton has been *
   //* automagically converted to wcsENTER.           *
   else if ( ((wkey == wcsENTER) || (wkey == wcsSPACE)) &&
             (this->win->GetFieldItem ( ctrlIndex, menu_item, menu_char )) )
   {
/* TEMP */ gsdbg.compose( "Callback( wkey:%04X ctrlIndex:%hd menu_item:%hd menu_char:%C )", 
/* TEMP */    &wkey, &ctrlIndex, &menu_item, &menu_char ) ;
/* TEMP */ ofsdbg << gsdbg << endl ;
      //* If a menu item was selected from the menu with focus, *
      switch ( ctrlIndex )
      {
         case fiM_FILE:                   // selection from the File menu
            switch ( menu_item )
            {
               case 0:                    // create new file
                  this->newFile2Edit () ;
                  break ;
               case 1:                    // open existing file
                  this->select4Edit ( fldIndex(ctrlIndex) ) ;
                  break ;
               case 2:                    // list previously-open files
                  this->reportRecentFiles () ;
                  break ;
               case 3:                    // close file
                  //* If field newer than temp file, update temp file.
                  //* If temp file newer that source file, prompt whether to save or discard mods.*
                  //* If save: temp file updates source file.
                  //*  -- If target does not yet exist, invoke save-as dialog.
                  //* If discard: close out without save and load data for next buffer if any.
                  //* If cancel: return to edit.
/* TEMP */ ofsdbg << "File Menu = 'Close'" << endl ;
#if 0    // CZONE - Close File
#else    // TEMP
gs = "Close a file under edit.  \n\n" ;
this->infoDialog ( gs, false, "  Under Construction  ", 4, -1, 2 ) ;
#endif   // TEMP
                  break ;
               case 4:                    // save file
#if 1    // CZONE - Save File
                  this->fullSave () ;
#else    // TEMP
gs = "Save a file under edit.  " ;
this->infoDialog ( gs, false, "  Under Construction  ", 5, -1, 2 ) ;
#endif   // TEMP
                  break ;
               case 5:                    // save file as...
                  this->saveFileAs () ;
                  break ;
               case 6:                    // print file
#if 0    // CZONE - Print File
#else    // TEMP
gs = "Print a file under edit.  " ;
this->infoDialog ( gs, false, "  Under Construction  ", 5, -1, 2 ) ;
#endif   // TEMP
                  break ;
               case 7:
                  //* Before exit, verify that all files    *
                  //* currently under edit have been saved. *
                  if ( (this->saveAll ()) )
                     wkey = wcsCTL_4 ;    // exit the application
                  break ;
               default:    break ;        // (should never happen)
            }
            break ;

         case fiM_EDIT:                   // selection from the Edit menu
            switch ( menu_item )
            {
               case 0:     break ;        // Copy to clipboard
               case 1:     break ;        // Cut to clipboard
               case 2:     break ;        // Paste from clipboard
               case 3:                    // Find text
                  wkey = wcsCTL_F ;          // (fall through)
               case 4:                    // Find again (forward)
                  if ( menu_item == 5) { wkey = wcsCTL_G ; } // (fall through)
               case 5:                    // Find again (reverse)
                  if ( menu_item == 6) { wkey = wcsCTL_R ; }
                  while ( (this->findSubstring ( wkey )) ) ;
                  wkey = NULLCHAR ;       // keycode handled
                  break ;
               case 6:
                  this->gotoLine () ;
                  wkey = NULLCHAR ;       // keycode handled
                  break ;
               default:    break ;        // (should never happen)
            }
            break ;

         case fiM_VIEW:                   // selection from the View menu
            switch ( menu_item )
            {
               case 0:                    // Single window
                  if ( ctrlIndex != fiTEXTfw )
                     this->updateFocus ( fiTEXTfw ) ;
                  else
                     this->updateWindow ( fiTEXTfw ) ;
                  break ;
               case 1:                    // Dual Horizontal windows
                  if ( (ctrlIndex != fiTEXThu) && (ctrlIndex != fiTEXThl) )
                     this->updateFocus ( fiTEXThu ) ;
                  else
                     this->updateWindow ( fldIndex(ctrlIndex) ) ;
                  break ;
               case 2:                    // Dual vertical windows
                  if ( (ctrlIndex != fiTEXTvl) && (ctrlIndex != fiTEXTvr) )
                     this->updateFocus ( fiTEXTvl ) ;
                  else
                     this->updateWindow ( fldIndex(ctrlIndex) ) ;
                  break ;
               //* Move focus to next/prev Textbox control. (dual-window modes) *
               case 3:                    // Next Window
               case 4:                    // Previous Window
                  {
                  fldIndex trgFld = this->focus ;
                  switch ( this->focus )
                  {
                     case fiTEXThu: trgFld = fiTEXThl ;  break ;
                     case fiTEXThl: trgFld = fiTEXThu ;  break ;
                     case fiTEXTvl: trgFld = fiTEXTvr ;  break ;
                     case fiTEXTvr: trgFld = fiTEXTvl ;  break ;
                     default:                            break ;
                  } ;
                  if ( trgFld != this->focus )        // move focus to target
                     this->updateFocus ( trgFld ) ;
                  else if ( this->wInfo.skf->beep )   // invalid target
                     this->win->acBeep () ;
                  wkey = NULLCHAR ;                   // keycode handled
                  }
                  break ;
               //* Load next/prev open buffer (if any) into current field.
               case 5:                    // Next Buffer
/* TEMP */ ofsdbg << "NextBuffer Menu Item" << endl ;
                  this->selectBuffer ( wcsCAPGDN ) ;
                  break ;
               case 6:                    // Previous Buffer
/* TEMP */ ofsdbg << "PrevBuffer Menu Item" << endl ;
                  this->selectBuffer ( wcsCAPGUP ) ;
                  break ;
               case 7:                    // View list of open files
                  //* Call with shortcut key for view-open-files dialog *
                  this->selectBuffer ( wcsALT_B ) ;
                  wkey = NULLCHAR ;
                  break ;
               case 8:                    // View stats on current file
                  this->reportFileStats ( &this->fman[AREC] ) ;
                  break ;
               default:    break ;        // (should never happen)
            }
            break ;

         case fiM_UTIL:                   // selection from the Util menu
            switch ( menu_item )
            {
               case 0:                    // Macro definitions
// Also: Need a shortcut key to execute macros: Ex: wcsALT_M+(macro_num || macro_char)
// 1) Session macro
// 2) Defined/Saved macros
/* TEMP */ this->updateGutter ( false, L"* Macro Definitions *", true, 2 ) ;
                  break ;
               case 1:                    // Compare files
                  this->CompareFiles () ;
                  break ;
               case 2:                    // Clipboard information
                  this->clipboardInfo() ;
                  break ;
               case 3:                    // Report terminal configuration, etc.
                  this->reportTermWinUser () ;
                  break ;
               case 4:                    // Call configuration utility
/* TEMP */ this->updateGutter ( false, L"* Configure *", true, 2 ) ;
                  break ;
               default:    break ;        // (should never happen)
            }
            break ;

         case fiM_HELP:                   // selection from the Help menu
            switch ( menu_item )
            {
               case 0:                    // Documentation (Info)
                  this->ContextHelp ( cxtACEDIT ) ;
                  break ;
               case 1:                    // Documentation (HTML)
                  this->HtmlDocs () ;
                  break ;
               case 2:                    // Key Bindings
                  this->reportKeycodes () ;
                  break ;
               case 3:                    // HelpAbout
                  this->HelpAbout () ;
                  break ;
               default:    break ;        // (should never happen)
            }
            break ;

         default:
            break ;
         //* a) For an open menu (or menu group) receipt of the*
         //*    Escape key will have caused caller to          *
         //*    automagically close the menu(s).               *
      }
   }     // (Menu item - Space or Enter)

   //* Various special-case keycodes *
   else
   {
      //* If Ins/Ovr is seen outside a TextEdit field, discard it.*
      if ( wkey == wcsINSERT )
         wkey = NULLCHAR ;
   }

   return wkey ;

}  //* End Callback() *

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

static wchar_t nonmember_Callback ( short ctrlIndex, wchar_t wkey, bool firstTime )
{

   return ( (nmcPtr->Callback ( ctrlIndex, wkey, firstTime )) ) ;

}  //* End nonmember_Callback() *

//*************************
//*     openFile4Edit     *
//*************************
//********************************************************************************
//* For the indexed FileMan object:                                              *
//*  1) initialize tracking data                                                 *
//*  2) copy source file to working copy                                         *
//*  3) allocate the scratch buffer                                              *
//*  4) Note that this method does not assign the FileMan object to a specific   *
//*     TextEdit control. That is done through a call to loadField().            *
//*                                                                              *
//* Input  : fmIndx : index into the 'fman' array indicating the target file     *
//*                                                                              *
//* Returns: 'true'  if successful                                               *
//*          'false' if error: error message displayed in dialog                 *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* -- The source file is not directly modified by the editing sequence.         *
//*    It is modified ONLY when the user initiates a "save" operation.           *
//* -- Intermediate edits are performed on a working copy of the original        *
//*    source file.                                                              *
//* -- The working copy is a "hidden" file; that is, it is normally invisible    *
//*    A "hidden" file begins with the period (full stop) character.             *
//*    (see "ls -la").                                                           *
//* -- When creating the working copy, if the target already exists, it is       *
//*    because the application did not shut down smoothly in a previous session. *
//*    It is unlikely that any data were lost at that time, although some edits  *
//*    may not have been saved. See the recoverOrphan() method for details.      *
//* -- Dynamic memory allocation is performed:                                   *
//*       RE-ALLOCATION IS NOT YET IMPLEMENTED.                                  *
//*                                                                              *
//********************************************************************************

bool AcEdit::openFile4Edit ( short fmIndx )
{
// CZONE - openFile4Edit()
   gString gs, gsp, gsf ;
   tnFName tnf ;
   bool    status = false ;

   //* Initialize filespecs for source and backup files.*
   gs = this->fman[fmIndx].tnf.fSpec ;
   gs.copy( this->fman[fmIndx].srcSpec, MAX_PATH ) ;
   this->ExtractPathname ( gsp, gs ) ;
   this->ExtractFilename ( gsf, gs ) ;
   gs.compose( efileTemplate, gsp.gstr(), gsf.gstr() ) ;
   gs.copy( this->fman[fmIndx].wrkSpec, MAX_PATH ) ;

   //* Allocate the scratch buffer to hold the *
   //* transient data created during edit.     *
   /* not yet implemented */

   // The remaining members of the FileMan object will be initialized
   // when the text data are loaded into the target Textbox control.

   //* If target temp file already exists, ask user what to do.*
   //* a) If orphan discarded, open the source file.           *
   //* b) If orphan has replaced source, open the source file. *
   //* c) If orphan renamed, we won't know it here.            *
   if ( (this->GetFileStats ( tnf, gs )) == ZERO )
   {
      this->recoverOrphan ( this->fman[fmIndx].tnf, tnf ) ;
   }

   //* Make a working copy of the source file. *
   //* Note that this is a "hidden" file in    *
   //* the same directory as the original file.*
   if ( (this->CopyFile ( this->fman[fmIndx].srcSpec, 
                          this->fman[fmIndx].wrkSpec )) == ZERO )
   { status = true ; }
   else // copy error
   {
      // (access error, disc full, etc.)
      // Report errno return value.
   }

   return status ;

}  //* End openFile4Edit() *

//*************************
//*      closeRecord      *
//*************************
//********************************************************************************
//* Reconfigure an "open" record to a "recently-edited" record.                  *
//*                                                                              *
//* The the source file associated with this record should already have been     *
//* saved. This method performs housekeeping only on the associated FileMan      *
//* record.                                                                      *
//*                                                                              *
//*  1) The 'srcSpec' member retains the filespec of the source file.            *
//*     a) The timestamp 'edtime' is retained for configuration file update.     *
//*  3) All other data members are reset:                                        *
//*     a) Reset the 'open' flag.                                                *
//*     b) Release the dynamic allocation associated with the record and         *
//*        reset the 'scrCnt' member.                                            *
//*     c) Delete the working copy of the file and set the 'wrkSpec' member      *
//*        to an empty string ("").                                              *
//*     d) Reinitialize the 'tnf' (stats) member.                                *
//*     e) Reset the field indices: 'tindx', 'lindx' and 'sindx'.                *
//*     f) Reset the cursor tracking members: 'vtop', 'voff' and 'hoff'.         *
//*     g) Reset the flag members: 'mods', 'wmods' (Should already have been     *
//*        reset by caller. If not, it's a program error.)                       *
//*                                                                              *
//* Input  : fmIndx : index of target FileMan record                             *
//*                   This IS NOT fman[AREC] or fman[IRED]; it is a record in    *
//*                   the range: zero <= fmIndx < this->fmcnt                    *
//*                                                                              *
//* Returns: 'true'  if successful                                               *
//*          'false' if unable to delete the temp file (unlikely)                *
//********************************************************************************

bool AcEdit::closeRecord ( short fmIndx )
{
   bool status = false ;

// This should never happen.
//   //* If pending changes, ask user whether to save *
//   //* changes or exit without saving.              *
//   if ( this->fman[fmIndx].mods || this->fman[fmIndx].wmods )
//   {
// CZONE - closeRecord()
//   }

   //* Delete the working copy of the file *
   if ( (this->DeleteFile ( this->fman[fmIndx].wrkSpec )) == ZERO )
      status = true ;

   //* Release the dynamic allocation for the file and reset tracking data. *
   this->fman[fmIndx].reset() ;

   return status ;

}  //* End closeRecord() *

//*************************
//*     newFile2Edit      *
//*************************
//********************************************************************************
//* Create and initialize a new buffer to be edited.                             *
//* This buffer is not yet associated with any filename. It exists only in RAM   *
//* until and unless user saves the buffer to a file.                            *
//*                                                                              *
//* -- It is assumed that 'this->focus' is correct.                              *
//* -- The newly initialized FileMan object is then attached to the TextEdit     *
//*    control which has the input focus.                                        *
//* -- If a FileMan object is already associated with the control which has      *
//     focus, that object is detached.                                           *
//*                                                                              *
//*                                                                              *
//* Input  : setActive : (optional, default:true) [CURRENTLY IGNORED]            *
//*                      if 'true',  initialize the "active" record using the    *
//*                                  newly created record                        *
//*                      if 'false', create the new record, but do not copy it   *
//*                                  to the active record (called during         *
//*                                  configuration                               *
//*                                                                              *
//* Returns: 'true'  if buffer created and displayed in target field             *
//*          'false' if file limit reached or other error                        *
//********************************************************************************

bool AcEdit::newFile2Edit ( bool setActive )
{
   #define DEBUG_F2E (0)                  // for debugging only

   //* Placeholder filename for unsaved buffers *
   const char* unsavedName = "    ( ----- )" ;
   static const char* newBuff[uilENV] = 
   {
      "New, buffer created",           // English
      "已创建新的缓冲区",                      // Zhongwen
   } ;

   gString gs ;                           // text management
   short newbi ;                          // index of buffer created
   bool status = false ;                  // return value

   //* If the FileMan array if full, shift the oldest "recent" record out *
   //* to make room for the new record to contain the new buffer.         *
   //* If all records are currently attached to open files, then alert    *
   //* user to close some file(s).                                        *
   /* not yet implemented */

   //* Verify that max buffers have not yet been allocated.*
   if ( this->fmcnt < MAX_FILES )
   {
      newbi = this->fmcnt ;               // index of record to be initialied

      #if DEBUG_ACE != 0 && DEBUG_F2E != 0
      this->dumpFileMan ( &this->fman[newbi], false, false, 
                          "nF2E(A next free record)" ) ;
      #endif   // DEBUG_ACE && DEBUG_F2E

      FileMan* fmptr = &this->fman[newbi] ; // point to next available record
      fmptr->reset() ;                    // reset all members (and release allocations)
      //fmptr->wbAlloc ( BUFF_ALLOC ) ;     // allocate a work buffer

      //* Buffer is not yet associated with a file on disc, *
      //* so use a placeholder name for display in gutter.  *
      gs = unsavedName ;
      gs.copy( fmptr->tnf.fName, MAX_FNAME ) ;
      fmptr->open = true ;
      ++this->fmcnt ;                     // record created

      this->setActiveFileMan ( newbi ) ;  // initialize the "active" record
      this->loadField ( newbi, this->focus ) ; // load the TextEdit field with focus

      //* Update LineNumber and gutter controls *
      this->updateAuxData () ;

      #if DEBUG_ACE != 0 && DEBUG_F2E != 0
      this->dumpFileMan ( 5, false, "nF2E(B active record)" ) ;
      #endif   // DEBUG_ACE && DEBUG_F2E

      //* Visual confirmation to user that new buffer has been created 
      gs = newBuff[this->lang] ;
      this->updateGutter ( false, gs.gstr(), true, 1 ) ;
   }
   return status ;

}  //* End newFile2Edit() *

//*************************
//*       loadField       *
//*************************
//********************************************************************************
//* Using the file data in the 'fmSrc' FileMan record, initialize the TextEdit   *
//* field specified by fldTrg.                                                   *
//*                                                                              *
//* If the "active" FileMan record contains live data, it will be written        *
//* back to its source FileMan record specified by the 'sindx' member.           *
//* If there are unsaved data in the target TextEdit field, it will be saved     *
//* to its backup file.                                                          *
//*                                                                              *
//* Then, using the data of the specified FileMan record, this method will:      *
//* 1) copy the source FileMan record to the "active" record                     *
//* 2) initialize the reference indices to the TextEdit field (tindx),           *
//*    LineNum field (lindx) and the source record (sindx).                      *
//* 3) set the 'active' flag to indicate that the file is being actively edited. *
//* 4) open the working copy of the file and copy the contents to the TextEdit   *
//*    field.                                                                    *
//* 5) When the file is loaded into a TextEdit field, the 'vtop', 'voff' and     *
//*    'hoff' values are used to position the text data within the window.       *
//*    Ideally, this will place the cursor at (or near) its position when the    *
//*    was last being actively edited.                                           *
//*                                                                              *
//* Special Case: If the source record describes a new buffer (the filename has  *
//* not yet been created), then instead of receiving text data,the TextEdit      *
//* field is cleared to reflect the empty buffer.                                *
//*                                                                              *
//*                                                                              *
//* Input  : fmSrc   : index of the 'fman' object containing the source          *
//*                    information for the file to be loaded                     *
//*          fldTrg  : member of enum fldIndex indicating the target text field  *
//*                    to be initialized                                         *
//*          setFocus: (optional, default: 'false') [CURRENTLY UNUSED]           *
//*                    if 'true'  If target does not already have focus shift    *
//*                               the input focus to target field                *
//*                    if 'false' input focus will not be adjusted               *
//*                                                                              *
//* Returns: 'true'  if field reloaded successfully                              *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* -- A file that has previously been open for edit and subsequently added to   *
//*    the list of files in the application's config file will have certain      *
//*    data associated with that record.                                         *
//*    a) the full source filespec                                               *
//*    b) the timestamp (epoch value) when the file was closed                   *
//*    c) the index of the text line which was displayed at the top of the       *
//*       edit window when the file was closed                                   *
//*    d) the row offset from the top displayed row to the row containing the    *
//*       cursor                                                                 *
//*    e) a flag indicating whether the file was open for edit when the          *
//*       previous session ended.                                                *
//* -- For a source file specified on the command line, the filespec will be     *
//*    provided, but the remaining variables will have default values.           *
//* -- The design of Textbox controls does not permit vertical scrolling that    *
//*    would leave empty rows at the bottom of the field. Empty rows are allowed *
//*    only if the data are smaller than the display area. Therefore, in the     *
//*    unlikely case where the 'vtop' and 'voff' values would cause scrolling    *
//*    beyond the limit, the values will be adjusted.                            *
//*    -- Note that the library's text formatting and positioning methods handle *
//*       this problem automagically; however, it is generally a bad idea to     *
//*       rely on magic, so it is handled directly.                              *
//* -- There is a bit of fancy footwork to adjust the vertical offset of the     *
//*    text within the target field.                                             *
//*      a) When the text is initialized, the IP is set to reference the null    *
//*         terminator (i.e. _below_ the target IP).                             *
//*      b) The IP is then set to the head of the text row to be displayed at    *
//*         the top of the field.                                                *
//*      c) The cursor position is then scrolled downward to the target IP.      *
//*    This maneuvering may cause multiple screen updates which may appear as    *
//*    flickering, depending on the speed of the system's display refresh;       *
//*    but getting the text where we want it, priceless.                         *
//*                                                                              *
//*                                                                              *
//* ---------------------------------------------------------------------------- *
//* FileMan Class Data Members                                                   *
//* --------------------------                                                   *
//* Layout and function of the FileMan array is described in the header of       *
//* AcEditMenu.cpp. Please refer to the documentation for a fuller description   *
//* of the FileMan class.                                                        *
//*                                                                              *
//********************************************************************************

bool AcEdit::loadField ( short fmSrc, fldIndex fldTrg, bool setFocus )
{
   #define DEBUG_LOADFIELD (0)            // for debugging only

   bool status = false ;                  // return value

   //* Gross range check of parameters *
   if ( ((fmSrc >= ZERO) && (fmSrc < this->fmcnt)) &&
        ((fldTrg >= fiTEXTfw) && (fldTrg <= fiTEXThl)) )
   {
      gString gsIn,                       // text input
              gsOut ;                     // text output to field
      int sindx = ZERO,                   // source data scan index
          lnCnt = 1,                      // line counter
          newIp = ZERO,                   // character counter
          pHgt = ZERO, pWid = ZERO,       // source data height and width
          max_vtop = ZERO,                // range limits
          max_voff = ZERO ;

      //* Copy the active data record back to its source   *
      //* record. If unsaved edits, perform an auto-update.*
      this->saveFileMan () ;

      //* Copy source data to target *
      this->fman[AREC] = this->fman[fmSrc] ;
      this->fman[AREC].sindx = fmSrc ;
      linenumMatch ( (this->fman[AREC].tindx = fldTrg),
                      this->fman[AREC].lindx ) ;
      this->fman[AREC].active = true ;

      #if DEBUG_ACE != 0 && DEBUG_LOADFIELD != 0
      this->dumpFileMan ( &this->fman[AREC], false, false, "loadField(init)" ) ;
      #endif   // DEBUG_ACE && DEBUG_LOADFIELD

      //* Open the working copy of the file, and read its contents. *
      //* If source record has no associated file(s) on disc, then  *
      //* the target window will initially be empty.                *
      if ( *this->fman[fmSrc].wrkSpec != NULLCHAR )
      {
         ifstream ifs( this->fman[AREC].wrkSpec, ifstream::in ) ;
         if ( ifs.is_open() )
         {
            while ( (this->GetLine ( ifs, gsIn, false )) > ZERO )
            {
               gsOut.append( gsIn.gstr() ) ;
            }
            ifs.close() ;                    // close the file
         }

         //* Get data dimensions, and calculate position limits.*
         //* When the file was closed, it may have been in a    *
         //* window of different size than target window. If so,*
         //* then 'vtop' and 'voff' may need to be adjusted to  *
         //* place the cursor row in visible space.             *
         this->win->ParagraphSize ( gsOut, pHgt, pWid ) ;
         if ( (max_vtop = pHgt - this->wInfo.skf->fld[fldTrg].hgt + 1) < ZERO )
            max_vtop = ZERO ;
         if ( (max_voff = this->wInfo.skf->fld[fldTrg].hgt - 1) >= pHgt )
            max_voff = pHgt - 1 ;
         if ( this->fman[AREC].vtop > max_vtop )
            this->fman[AREC].vtop = max_vtop ;     // bring last row into visible space
         if ( this->fman[AREC].voff > max_voff )
            this->fman[AREC].voff = max_voff ;     // last row is focus row

         //* If first line of file was not displayed when   *
         //* the file was previously closed, calculate the  *
         //* IP value necessary to place the specified line *
         //* at the top of the field. (see note above)      *
         while ( lnCnt < this->fman[AREC].vtop )
         {
            if ( (sindx = gsOut.after ( L'\n', sindx )) >= ZERO )
            { ++lnCnt ; newIp = sindx ; }
            else
               break ;
         }
      }

      #if DEBUG_ACE != 0 && DEBUG_LOADFIELD != 0
      gsdbg.compose( 
         "\nloadField( pWid:%d pHgt:%d max_vtop:%d vtop:%d max_voff:%d voff:%d )",
         &pWid, &pHgt, &max_vtop, &this->fman[AREC].vtop, 
         &max_voff, &this->fman[AREC].voff ) ;
      ofsdbg << gsdbg << endl ;
      #endif   // DEBUG_ACE && DEBUG_LOADFIELD

      //* Initialize target field contents.              *
      //* a) If top of data is visible, just set text.   *
      //* b) Else, set IP to end-of-text and then scroll *
      //*    upward to target position. (see note above) *
      if ( this->fman[AREC].voff == ZERO )
         this->win->SetFieldText ( fldTrg, gsOut ) ;
      else
      {
         this->win->SetFieldData ( fldTrg, gsOut, acaUSEDFLT, gsOut.gschars(), false ) ;
         this->win->SetFieldIP ( fldTrg, newIp, false, false ) ;
         this->win->ScrollFieldText ( fldTrg, this->fman[AREC].voff ) ;
      }

      #if DEBUG_LOADFIELD != 0
      this->dumpFileMan ( &this->fman[AREC], false, true, "loadField(full)" ) ;
      //ofsdbg << "- - - - -" << endl ; this->dumpFileMan () ;
      #endif   // DEBUG_LOADFIELD

      status = true ;                     // parameters are in range
   }
   return status ;

   #undef DEBUG_LOADFIELD
}  //* End loadField() *

//*************************
//*      reloadField      *
//*************************
//********************************************************************************
//* Update the existing data in the target text edit field with the data saved   *
//* in the backup file.                                                          *
//*                                                                              *
//* If the target field is currently visible, or if 'setFocus' flag is set,      *
//* then display will be updated before return.                                  *
//*                                                                              *
//* Input  : fmSrc   : index of the 'fman' record containing the source          *
//*                    information for the file to be loaded                     *
//*          fldIndx : (optional, default: AREC) index of target text field      *
//*                    If specified, member of enum fldIndex indicating the      *
//*                    target text field.                                        *
//*                    If not specified, then target is the Textbox field which  *
//*                    currently has focus.                                      *
//*          setFocus: (optional, default: 'false')                              *
//*                    if 'true'  If target does not already have focus shift    *
//*                               the input focus to target field                *
//*                    if 'false' input focus will not be adjusted               *
//*                                                                              *
//* Returns: 'true'  if field reloaded successfully                              *
//********************************************************************************
//* Notes:                                                                       *
//* ======                                                                       *
//* Differences between loadField() and reloadField():                           *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************

#if 0    // REFERENCE - CURRENTLY UNUSED
bool AcEdit::reloadField ( short fmanIndx, fldIndex fldIndx, bool setFocus )
{
   fldIndex trgFld = fldIndex(this->win->GetInputFocus()) ; // index of target field
   bool status = false ;                  // return value

   //* Gross range check of parameters *
   if ( ((fmanIndx >= ZERO) && (fmanIndx < this->fmcnt)) &&
        ((fldIndx >= fiTEXTfw) && (fldIndx <= fiTEXThl)) )
   {
      status = true ;                     // parameters are in range

      //* If target field is not the field with focus, *
      //* test whether the target is visible.          *
      if ( fldIndx != trgFld )
      {
         if ( ((trgFld == fiTEXTvl) && (fldIndx == fiTEXTvr)) ||
              ((trgFld == fiTEXTvr) && (fldIndx == fiTEXTvl)) ||
              ((trgFld == fiTEXThu) && (fldIndx == fiTEXThl)) ||
              ((trgFld == fiTEXThl) && (fldIndx == fiTEXThu)) )
         { trgFld = fldIndx ; }

         //* Else target field is not currently visible *
         else
         {
            // Should we set focus automagically, or declare failure?
            status = false ;
         }
      }

      //* If source and target validated, initialize/reinitialize *
      //* the target field using the data from the working copy.  *
      if ( status != false )
      {
         gString gsIn,                    // text input
                 gsOut ;                  // text output to field

         //* Open the working copy of the file *
         ifstream ifs( this->fman[fmanIndx].wrkSpec, ifstream::in ) ;
         if ( ifs.is_open() )
         {
            //* Copy contents of the file to the buffer *
            while ( (this->GetLine ( ifs, gsIn )) > ZERO )
            {
               gsIn.append( NEWLINE ) ;
               gsOut.append( gsIn.gstr() ) ;
            }
            ifs.close() ;                    // close the file

            //* Initialize target field contents *
            this->win->SetFieldText ( fldIndx, gsOut ) ;

            //* Initialize FieldMan data *
            skField skf ;
            this->win->GetFieldData ( fldIndx, skf ) ;
            this->win->SetFieldIP ( fldIndx, skf.mrptr->roff[this->fman[fmanIndx].vtop] ) ;
         }
         else              // unable to open target file (unlikely)
         { status = false ; }
      }
   }

   return status ;

}  //* End reloadField() *
#endif   // REFERENCE - CURRENTLY UNUSED

//*************************
//*     selectBuffer      *
//*************************
//********************************************************************************
//* Select an open file to be loaded into the TextEdit field with focus.         *
//* There are two(2) modes of selection:                                         *
//* 1) Automatically select the next/previous open buffer.                       *
//*    Note that next/previous scan will wrap around to head/tail of array.      *
//* 2) Select buffer from the list of open files.                                *
//*                                                                              *
//* Input  : selOpt : keycode specifying the selection option                    *
//*                   The keycode is one of the shortcut keys defined in the     *
//*                   ShortkeyDefault[] array.                                   *
//*                   wcsCAPGDN : next open buffer                               *
//*                   wcsCAPGUP : previous open buffer                           *
//*                   wcsALT_B  : display a list of open buffers and allow       *
//*                               user to select one                             *
//*                                                                              *
//* Returns: 'true'  if file selected and loaded into TextEdif field with focus  *
//*          'false' if user cancelled                                           *
//********************************************************************************

bool AcEdit::selectBuffer ( wchar_t selOpt )
{
//   const short LB_MSGS = 2 ;
   const short dlgWid = 41 ;
   static const char* lbTitle[uilENV] =
   {
      "  Select An Open Buffer  ",        // English
      "  选择一个打开的缓冲区  ",                   // Zhongwen
   } ;
   static const char* lbMsg[uilENV] =
   { //---------|---------|---------|------| <- 37 column width
      "                                    \n" // English
      "Sorry, there are no additional files\n"
      "current open for edit.\n"
      "\n"
      "     List recently edited files?    \n"  // text of this row is centered
      "\n",
     //---------|---------|---------|------| <- 37 column width
      "                                    \n" // Zhongwen
      "抱歉，目前没有其他打开的缓冲区。\n"
      "\n"
      "\n"
      "       列出最近编辑过的文件？      \n"  // text of this row is centered
      "\n",
   } ;

   gString gs ;
   short newRec ;                         // new target record
   bool status = false ;                  // return value

   //* Display the list of currently open buffers *
   if ( selOpt == wcsALT_B )
   {
      if ( (this->reportOpenFiles ( newRec )) )
         status = true ;
   }
   else if ( (selOpt == wcsCAPGUP) || (selOpt == wcsCAPGDN) )
   {
      short curRec = this->fman[AREC].sindx ;
      short step  = 1 ;                   // step toward end of list
      if ( selOpt == wcsCAPGUP )
         step = -1 ;                      // step toward beginning of list
      for ( short r = curRec + step ; r != curRec ; r += step )
      {
         if ( r < ZERO )               { r = MAX_FILES - 1 ; }
         else if ( r >= MAX_FILES )    { r = ZERO ;          }
         if ( this->fman[r].open )
         {
            //* New target found.                                       *
            //* 1) If unsaved mods in focus record, perform auto-save.  *
            //* 2) Copy target record to focus record.                  *
            if ( r != curRec ) // if only one open buffer, do not select it again
            {
               newRec = r ;
               status = true ;
            }
            break ;
         }
      }
   }

   //* If new buffer identified *
   if ( status != false )
   {
      //* Save the current data in the target field, and  *
      //* initialize the field using specified record.    *
      this->loadField ( newRec, this->focus ) ;
      this->updateAuxData () ;
   }

   //* If load failed, alert user *
   if ( !status && (selOpt != wcsALT_B) )
   {
      //* Position within window which has focus.  *
      //* 1) Vertically offset by one row from top *
      //*    of field with focus.                  *
      //* 2) Centered horizontally in target field.*
      short dlgRow = 1 ;   // vertical offset of dialog (fiTEXTfw || fiTEXThu)
      short dlgCol = -1 ;  // horizontal offset of dialog (fiTEXTfw || fiTEXThu || fiTEXThl)
      short fldWid ;
      short winCtr = (this->wInfo.width - 2) / 2 ;
      if ( this->focus == fiTEXThl )
      { dlgRow = this->wInfo.skf->fld[this->focus].orig.row + 1 ; }
      else if ( this->focus == fiTEXTvl )
      {
         fldWid = this->wInfo.skf->fld[this->focus].wid ;
         dlgCol = winCtr - (fldWid / 2 + dlgWid / 2 + 1) ;
      }
      else if ( this->focus == fiTEXTvr )
      {
         fldWid = this->wInfo.skf->fld[this->focus].wid ;
         dlgCol = winCtr + (fldWid / 2 - dlgWid / 2 + 4) ;
      }
      gs = lbMsg[this->lang] ;            // load message text

      //* If user specifies, open the list of recently-edited files.
      if ( (this->infoDialog ( gs, true, lbTitle[this->lang], dlgRow, dlgCol )) )
      {
         this->reportRecentFiles () ;
      }
   }

   return status ;

}  //* End selectBuffer() *

//*************************
//*         undo          *
//*************************
//********************************************************************************
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  : fIndx  : index into the 'fman' array indicating the target file     *
//*                                                                              *
//* Returns:                                                                     *
//********************************************************************************

// CZONE - undo()
bool AcEdit::undo ( short fIndx )
{
   bool status = false ;                  // return value

   return status ;

}  //* End undo() *

//*************************
//*         redo          *
//*************************
//********************************************************************************
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  : fIndx  : index into the 'fman' array indicating the target file     *
//*                                                                              *
//* Returns:                                                                     *
//********************************************************************************

// CZONE - redo()
bool AcEdit::redo ( short fIndx )
{
   bool status = false ;                  // return value

   return status ;

}  //* End redo() *

//*************************
//*         save          *
//*************************
//********************************************************************************
//* Replace the working copy of the file with the contents of the RAM buffer.    *
//*                                                                              *
//* Optionally, save the working copy, overwriting the original source file.     *
//* This is done in steps for safety.                                            *
//*  1) Rename the source file as backup.                                        *
//*  2) Copy the working copy to source filename.                                *
//*  3) If copy is successful, delete the backup copy.                           *
//*                                                                              *
//*                                                                              *
//* Input  : fIndx    : index into the 'fman' array indicating the target file   *
//*          updateSrc: (optional, 'false' by default)                           *
//*                     if 'true', replace source file with working copy         *
//*                                                                              *
//* Returns: 'true' if successful, 'false' if save operation fails               *
//********************************************************************************

// CZONE - save()
bool AcEdit::save ( short fIndx, bool updateSrc )
{
   bool status = false ;                  // return value

   //* Open working file (truncate) *

   //* Write contents of buffer to target file *

   //* If specified, update the source file *
   if ( updateSrc )
   {
   }

   return status ;

}  //* End save() *

//*************************
//*     updateWindow      *
//*************************
//********************************************************************************
//* Redraw the data displayed in the interior of the ACWin object.               *
//* Should be called only if the field with input focus is one of the text edit  *
//* fields (fiTEXTfw, fiTEXThu, fiTEXThl, fiTEXTvl, fiTEXTvr) and won't change.  *
//* The actual fields to be updated is determined by the value of the 'fIndx'    *
//* parameter.                                                                   *
//*                                                                              *
//* 1) Clear the interior of the ACWin object.                                   *
//* 2) Redraw the gutter field.                                                  *
//* 3) Redraw the visible line-number field(s).                                  *
//* 4) Redraw the visible text control(s),                                       *
//*                                                                              *
//* The primary usefulness of this method is when data of the gutter field or    *
//* line number fields is stale, or when text fields have been obscured by a     *
//* menu or dialog. See also updateText(), updateAuxData(),updateLinenum() and   *
//* updateGutter().                                                              *
//*                                                                              *
//*                                                                              *
//* Input  : fIndx : (optional, default: fiFIELDS)                               *
//*                  -- if specified, update only the text field with focus and  *
//*                     the associated line-number field                         *
//*                  -- if not specified, update all _visible_ fields            *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::updateWindow ( fldIndex fIndx )
{
   this->win->ClearWin () ;               // clear interior
   this->updateAuxData () ;               // update the gutter data
   this->win->RefreshWin ( false ) ;      // make the updates visible

}  //* End updateWindow() *

//*************************
//*      updateFocus      *
//*************************
//********************************************************************************
//* Set input focus to the specified textbox control.                            *
//*                                                                              *
//* There are five(5) fields defined for text editing:                           *
//*   fiTEXTfw : covers the entire editing area                                  *
//*                                                                              *
//*   fiTEXThu : and                                                             *
//*   fiTEXThl : (fiLINEhl) splits the editing area horizontally with one        *
//*              control above the other.                                        *
//*                                                                              *
//*   fiTEXTvl : (fiLINEvl) and                                                  *
//*   fiTEXTvr : (fiLINEvr) splits the editing area vertically with the two      *
//*              controls side-by-side.                                          *
//*                                                                              *
//* Any of these five fields may receive the input focus through this method.    *
//* One of these five fields always has the input focus, as indicated by the     *
//* 'focus' member variable. Exceptions:                                         *
//*  a) A menu is open, in which case the menu temporarily handles the input.    *
//*  b) A dialog window is open, in which case the parent window receives        *
//*     no input until the dialog closes.                                        *
//*                                                                              *
//* Input  : trgIndx: (member of enum fldIndex) target index                     *
//*                   If fiFIELDS, then 'hKey' specifies the target control      *
//*                   and this parameter is ignored.                             *
//*          hKey   : (optional, default: null character L'\0')                  *
//*                   hotkey keycode assigned to target control access           *
//*                   wcsALT_0/wcsALT_1, wcsALT_2, wcsALT_3, wcsCAPGUP/wcsCAPGDN *
//*                                                                              *
//* Returns: 'true' if successful                                                *
//*          'false' invalid control index or invalid hot-keycode                *
//*                  (this should never happen)                                  *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* 1) The "hidden" and "read-only" flags determine which controls are currently *
//*    visible.                                                                  *
//* 2) The ACWin GetInputFocus() method reports the control with focus; however, *
//*    it is hoped that the application's 'focus' member will be kept current    *
//*    to avoid having to call the library function.                             *
//*    Therefore, whenever practical, change focus using this method, rather     *
//*    than setting focus directly to avoid tracking data getting out-of-synch.  *
//* 3) The user moves input focus to the desired TextEdit control in one         *
//*    of three ways:                                                            *
//*    A) If the desired control is visible, but currently does not have focus,  *
//*       the wcsAPGUP and wcsAPGDN keys move between the visible controls.      *
//*    B) If the desired control is not currently visible:                       *
//*       a) Select the target control from the 'View' menu.                     *
//*       b) Select the target control via hotkey.                               *
//*          -wcsALT_2 selects fiTEXThu                                          *
//*          -wcsALT_3 selects fiTEXTvl                                          *
//*          -wcsALT_1 (or wcsALT_0) selects fiTEXTfw                            *
//*    The 'tindx' member of the FileMan object tracks the associated            *
//*    text-edit field.                                                          *
//*     -- As a special case, if the target pair of TextEdit fields is already   *
//*        visible, then the wcsALT_2/wcsALT_3 keycode is converted to wcsAPGDN. *
//* 4) Each text-editing field has an associated line-number field.              *
//*    The 'lindx' member of the FileMan object tracks the associated            *
//*    line-number field.                                                        *
//*       Text        Line Num                                                   *
//*      --------     --------    When the specified text edit field receives    *
//*      fiTEXTfw --> fiLINEvl    focus, the associated line-number field is     *
//*      fiTEXTvl --> fiLINEvl    also visible and updated, but is view-only.    *
//*      fiTEXTvr --> fiLINEvr                                                   *
//*      fiTEXThu --> fiLINEhu                                                   *
//*      fiTEXThl --> fiLINEhl                                                   *
//*    The non-member linenumMatch() function returns the index of the matching  *
//*    LineNumber field.                                                         *
//*                                                                              *
//* Technical Note:                                                              *
//* -- The field which currently has the input focus can't be set hidden, so     *
//*    fiGUTTER field _temporarily_ receives focus while the TextEdit and        *
//*    LineNum fields are being reconfigured.                                    *
//* -- There should be no display updates while fiGUTTER has focus; however, the *
//*    AnsiCmd library currently provides no clean method for setting the input  *
//*    focus without a display update, so there may be a slight flicker during   *
//*    the update.                                                               *
//*                                                                              *
//* Programmer's Note: When moving Dual-window modes (Vertical-to-Horizontal or  *
//* Horizontal-to-Vertical) there is a choice to be made:                        *
//*    1) If both visible source windows reference the same file,                *
//*       then both target windows will reference that file.                     *
//*    2) If the visible source windows reference _different_ files,             *
//*       then the choice become:                                                *
//*       a) Both target window reference file in window losing focus.           *
//*       b) New focus window references file in window losing focus,            *
//*          while new inactive window references file in old                    *
//*          inactive window.                                                    *
//* It is not clear which is more user friendly. Implementations logically       *
//* differ by only two lines of code; however, this needs testing.               *
//*                                                                              *
//* Programmer's Rant:                                                           *
//* This method is over 350 lines of code which is impractically large for       *
//* efficient maintenance. It wou be better if broken into segments,             *
//********************************************************************************

bool AcEdit::updateFocus ( fldIndex trgIndx, wchar_t hKey )
{
   #define DEBUG_UF (0)                   // for debugging only
   #if DEBUG_ACE != 0 && DEBUG_UF != 0
   gString gsuf ;                         // text formatting
   short asindx = -1, isindx = -1 ;       // active/inactive source indices
   bool dbVerbose = false ;               // debug output format
   #endif   // DEBUG_ACE && DEBUG_UF

   fldIndex srcIndx = this->focus ;       // remember field with original focus
   fldIndex altIndx = IDLE_BUFF ;         // for target pair, field without focus
   bool trgvis = false,                   // set if target is visible
        status = true ;                   // return value (hope for the best)


   //*****************************************************
   //* Validate Parameters:                              *
   //* a) Convert Hotkey selection to target field Index.*
   //* b) Range check target index.                      *
   //*****************************************************
   if ( trgIndx == fiFIELDS )             // selection via hotkey
   {
      switch ( hKey )
      {
         case wcsALT_2:                   // dual-window, horizontal
            //* If horizontal pair is already visible, *
            //* move focus to the other member of pair.*
            if ( srcIndx == fiTEXThu ) { trgIndx = fiTEXThl ; }
            else { trgIndx = fiTEXThu ; }
            break ;
         case wcsALT_3:                   // dual-window, vertical
            //* If vertical pair is already visible,   *
            //* move focus to the other member of pair.*
            if ( srcIndx == fiTEXTvl ) { trgIndx = fiTEXTvr ; }
            else { trgIndx = fiTEXTvl ; }
            break ;
         case wcsALT_1:                   // single-window
         case wcsALT_0:
            trgIndx = fiTEXTfw ; break ;
         case wcsAPGUP:                   // move between dual windows
         case wcsAPGDN: 
            switch ( srcIndx )
            {
               case fiTEXThu: trgIndx = fiTEXThl ; break ;
               case fiTEXThl: trgIndx = fiTEXThu ; break ;
               case fiTEXTvl: trgIndx = fiTEXTvr ; break ;
               case fiTEXTvr: trgIndx = fiTEXTvl ; break ;
               default: status = false ;  break ;  // target is not visible
            }
            break ;
         default: status = false ;  break ; // unsupported hotkey
      }
   }
   else if ( (trgIndx >= fiTEXTfw) && (trgIndx <= fiTEXThl) ) // selection by index
   { /* nothing to do at this time */ }
   else                                   // invalid target index (unlikely)
      status = false ;

   //* If invalid parameters, or if target == source, *
   //* then caller is an idiot. Note the early return.*
   if ( status && (trgIndx == srcIndx) )
   { this->win->acBeep () ; status = false ; }
   if ( ! status )
      return ( status ) ;

   //*****************************************************
   //* If valid update parameters, set new input focus.  *
   //*****************************************************
   if ( status != false )
   {
      #if DEBUG_ACE != 0 && DEBUG_UF != 0
      if ( ofsdbg.is_open() )
      {
         if ( dbVerbose )
         {
            asindx = this->fman[AREC].sindx ;
            isindx = this->fman[IREC].sindx ;
            this->dumpFileMan ( 4, false, "updateFocusA(Active Group)" ) ;
            this->dumpFileMan ( &this->fman[asindx], 
                                false, false, "(Source Record A)" ) ;
            if ( this->fman[IREC].sindx >= ZERO )
               this->dumpFileMan ( &this->fman[isindx], 
                                   false, false, "(Source Record I)" ) ;
            ofsdbg << endl ;
         }
         else
            this->dumpFileMan ( -1, false, "updateFocus(A)" ) ;
      }
      #endif   // DEBUG_ACE && DEBUG_UF

      //* Target TextEdit field currently visible? *
      if ( !this->wInfo.skf->fld[trgIndx].hide )
         trgvis = true ;

      //*********************************************
      //* Perform backup of dynamic data records to *
      //* their respective source records.          *
      //* If unsaved edits, perform an auto-save.   *
      //*********************************************
      this->saveFileMan () ;
      if ( this->fman[IREC].open )
         saveFileMan ( false ) ;

      //**********************************************************
      //* If target field is already visible, set input focus to *
      //* target and update the associated auxilliary data.      *
      //* Note: Source and target may or may not reference       *
      //*       the same file.                                   *
      //**********************************************************
      if ( trgvis )
      {
         this->swapActiveFileMan () ;
         this->focus = fldIndex(this->win->SetInputFocus ( trgIndx )) ;
         this->updateAuxData () ;
      }

      //**********************************************************
      //* Else, target field is not currently visible.           *
      //* This is where it gets a bit messy.                     *
      //* "This is a blues riff in B. Watch me for the changes,  *
      //*  and try to keep up." -- Marty McFly                   *
      //**********************************************************
      else
      {
         //* Save a copy of the text and IP from field with focus.*
         //* These will be used to initialize the target field(s).*
         skField srcFld ;
         this->win->GetFieldData ( srcIndx, srcFld ) ;
         gString srcText = srcFld.txt ;
         int srcIp = srcFld.ip ;

         //* Because the horizontal fields have less vertical height *
         //* than vertical fields, adjust vertical offset member.    *
         //* Note: Horizontal field pair may not be the same height, *
         //* but in that case, fiTEXThl will be one row less.        *
         if ( (trgIndx == fiTEXThu) && (srcIndx != fiTEXThl) &&
              (this->fman[AREC].voff >= this->wInfo.skf->fld[trgIndx].hgt) )
         { this->fman[AREC].voff = this->wInfo.skf->fld[trgIndx].hgt - 2 ; }

         //*******************************************************
         //* If dual-window mode target, then one TextEdit field *
         //* has focus, and the other is visible, but inactive.  *
         //* Initialize FileMan record for the inactive field.   *
         //*******************************************************
         if ( (altIndx = udfAltField ( srcIndx, trgIndx, trgvis )) != IDLE_BUFF )
         {
            //** Currently in Single-window Mode **
            if ( srcIndx == fiTEXTfw )
            {
               if ( (trgIndx == fiTEXTvl) || (trgIndx == fiTEXThu) )
               {
                  //* Copy fiTEXTfw to BOTH targets:                       *
                  //* fiTEXTvl and fiTEXTvr || fiTEXThu and fiTEXThl.      *
                  //* fman[AREC] already contains the data, so copy it to  *
                  //* IREC, then update IREC 'tindx' and 'lindx' members.  *
                  this->fman[IREC] = this->fman[AREC] ;
                  linenumMatch ( (this->fman[AREC].tindx = trgIndx),
                                  this->fman[AREC].lindx ) ;
                  linenumMatch ( (this->fman[IREC].tindx = altIndx),
                                 this->fman[IREC].lindx ) ;
                  this->fman[IREC].active = false ;
               }
               //* Note that the application currently does not allow focus *
               //* to move from fiTEXTfw to either fiTEXThl. or fiTEXTvr.   *
            }

            //* Currently in Dual-window Mode:                    *
            //* Moving from Dual-window Vertical mode to          *
            //* Dual-window Horizontal (target: fiTEXThu only).   *
            //*    OR                                             *
            //* Moving from Dual-window Horizontal mode to        *
            //* Dual-window Vertical (target: fiTEXTvl only).     *
            else
            {
               #if 1 // (See note in header about selecting source data.)
                     // a) fman[AREC] -> BOTH fman[AREC] && fman[IREC] ||
                     // b) fman[AREC] and fman[IREC] remain in place.
                     //    vl -> hu && vr -> hl (i.e. do nothing)
               this->fman[IREC] = this->fman[AREC] ;
               this->fman[IREC].active = false ;
               #endif   // (see note)
               linenumMatch ( (this->fman[AREC].tindx = trgIndx), 
                               this->fman[AREC].lindx ) ;
               this->fman[IREC].tindx = (trgIndx == fiTEXThu) ? fiTEXThl : fiTEXTvr ;
               linenumMatch ( this->fman[IREC].tindx, this->fman[IREC].lindx ) ;
            }
         }
         //* Moving from Dual-window to Single-window mode.*
         else  // ( trgIndx == fiTEXTfw )
         {
// Do we need to do anything here?
            linenumMatch ( (this->fman[AREC].tindx = fiTEXTfw), 
                            this->fman[AREC].lindx ) ;
            //* Inactive window will become invisible, so reset *
            //* its FileMan record to clear the stale data.     *
            this->fman[IREC].reset() ;
         }

         #if DEBUG_ACE != 0 && DEBUG_UF != 0
         if ( ofsdbg.is_open() )
         {
            if ( dbVerbose )
            {
               this->dumpFileMan ( &this->fman[asindx], 
                                   false, false, "(Saved Source A)" ) ;
               if ( isindx >= ZERO )
                  this->dumpFileMan ( &this->fman[isindx], 
                                      false, false, "(Saved Source I)" ) ;
               ofsdbg << endl ;
               this->dumpFileMan ( 4, false, "udFocusB(Active Group)" ) ;
               ofsdbg << endl ;
            }
         }
         #endif   // DEBUG_ACE && DEBUG_UF

         //* Copy the text data from source to target, *
         //* and if a non-focus field is visible, copy *
         //* the source to it also.                    *
         //*   (display is not updated at this time)   *
         this->win->SetFieldText ( trgIndx, srcText, false ) ;
         if ( srcIp > ZERO ) { this->win->SetFieldIP ( trgIndx, srcIp, false, false ) ; }
         if ( altIndx != IDLE_BUFF )
         {
            this->win->SetFieldText ( altIndx, srcText, false ) ;
            if ( srcIp > ZERO ) { this->win->SetFieldIP ( altIndx, srcIp, false, false ) ; }
         }

         //* Delete text from field which is no longer visible.*
         this->win->SetFieldText ( srcIndx, L"", false ) ;

         this->focus = trgIndx ;       // set the target focus

         //**************************************************************
         //* Update TextEdit field data, and refresh display.           *
         //* Target is not currently visible, so set the 'hide' flag    *
         //* for all text controls and associated line-number fields.   *
         //* Because the field with focus can't be set hidden, fiGUTTER *
         //* _temporarily_ receives focus (see note above).             *
         //**************************************************************

         //* Temporarily move input focus to gutter field *
         //* so other fields can be modified.             *
         this->win->SetFieldHidden ( fiGUTTER, false, false, false ) ;
         this->win->SetInputFocus ( fiGUTTER ) ;

         //* Set all Text-edit and Line-number fields as hidden. *
         //* This includes the horizontal divider field.         *
         for ( short f = fiLINEvl ; f <= fiHDIV ; ++f )
         {
            if ( !this->wInfo.skf->fld[f].hide )
               this->win->SetFieldHidden ( f, true, true, false ) ;
         }

         //*********************************************
         //* Reset the 'hidden' flags for the controls *
         //* which will become visible.                *
         //* To avoid redundant refresh, first refresh *
         //* the fields which will be visible but which*
         //* _will not_ be associated with input focus.*
         //*********************************************
         if ( trgIndx == fiTEXTfw )
         {
            if ( ! trgvis )
            {  //* Make the target (and its companions) visible.*
               this->win->SetFieldHidden ( fiTEXTfw, false, false, false ) ;
               this->win->SetFieldHidden ( fiLINEvl, false, true,  false ) ;
            }
         }
         else if ( (trgIndx == fiTEXThu) || (trgIndx == fiTEXThl) )
         {
            if ( ! trgvis )
            {  //* Make the target (and its companions) visible.*
               this->win->SetFieldHidden ( fiTEXThu, false, false, false ) ;
               this->win->SetFieldHidden ( fiTEXThl, false, false, false ) ;
               this->win->SetFieldHidden ( fiHDIV,   false, true,  false ) ;
               this->win->SetFieldHidden ( fiLINEhu, false, true,  false ) ;
               this->win->SetFieldHidden ( fiLINEhl, false, true,  false ) ;
            }
            //* Make the horizontal divider control visible.*
            this->win->RefreshField ( fiHDIV ) ;

            //* Refresh the alternate text-edit field  *
            //* and the associated auxilliary data.    *
            if ( trgIndx == fiTEXThu )
            {
               this->win->RefreshField ( fiTEXThl ) ;
               this->updateAuxData ( true, true ) ;
            }
         }
         else if ( (trgIndx == fiTEXTvl) || (trgIndx == fiTEXTvr) )
         {
            if ( ! trgvis )
            {  //* Make the target and its companion fields visible.*
               this->win->SetFieldHidden ( fiTEXTvl, false, false, false ) ;
               this->win->SetFieldHidden ( fiTEXTvr, false, false, false ) ;
               this->win->SetFieldHidden ( fiLINEvl, false, true,  false ) ;
               this->win->SetFieldHidden ( fiLINEvr, false, true,  false ) ;
            }

            //* Refresh the alternate text-edit field  *
            //* and the associated auxilliary data.    *
            if ( trgIndx == fiTEXTvl )
            {
               this->win->RefreshField ( fiTEXTvr ) ;
               this->updateAuxData ( true, true ) ;
            }
         }

         //* If fiGUTTER received focus above. (see note in method header) *
         if ( ! this->wInfo.skf->fld[fiGUTTER].ro )
         {
            this->win->SetFieldHidden( fiGUTTER, false, true, false ) ;
            this->focus = fldIndex(this->win->SetInputFocus ( trgIndx )) ;
         }
         //* Set focus to target field (no window update) *
         else
            ;

         //* Refresh the target text-edit field  *
         //* and the associated auxilliary data. *
         this->win->RefreshField ( trgIndx ) ;
         this->updateAuxData () ;

      }  // ( ! trgvis )

      #if DEBUG_ACE != 0 && DEBUG_UF != 0
      if ( dbVerbose)
      {
         this->dumpFileMan ( 4, false, "updateFocusC(Active Group)" ) ;
      }
      else
      {
         gsuf.compose( "updateFocus(B %s --> %s)", fldId[srcIndx], fldId[trgIndx] ) ;
         this->dumpFileMan ( -1, false, gsuf.ustr() ) ;
      }
      ofsdbg << endl ;
      #endif   // DEBUG_ACE && DEBUG_UF
   }

   if ( !status && this->wInfo.skf->beep )
      this->win->acBeep () ;

   return status ;

   #undef DEBUG_UF
}  //* End updateFocus() *

//*************************
//*      udfAltField      *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* Called by updateFocus() (and possibly others) that given the source and      *
//* target field indices, which, if any, non-focus TextEdit field should also    *
//* become visible.                                                              *
//*                                                                              *
//* Input  : srcIndx : index of TextEdit field losing focus                      *
//*          trgIndx : index of TextEdit field to receive focus                  *
//*          trgvis  : 'true' if target field is already visible                 *
//*                    'false' if target field is currently invisible            *
//*                                                                              *
//* Returns: index of non-focus field to become visible (IDLE_BUFF if none)      *
//********************************************************************************

static fldIndex udfAltField ( fldIndex srcIndx, fldIndex trgIndx, bool trgvis )
{
   fldIndex altIndx ;                     // return value

   if ( srcIndx == fiTEXTfw ) // AREC 
   {
      if ( (trgIndx == fiTEXThu) || (trgIndx == fiTEXThl) )
      { altIndx = trgIndx == fiTEXThu ? fiTEXThl : fiTEXThu ;  }
      else
      { altIndx = trgIndx == fiTEXTvl ? fiTEXTvr : fiTEXTvl ;  }
   }
   else if ( (srcIndx == fiTEXThu) || (srcIndx == fiTEXThl) )
   {
      if ( trgvis )
         altIndx = srcIndx == fiTEXThu ? fiTEXThl : fiTEXThu ;
      else
         altIndx = trgIndx == fiTEXTvl ? fiTEXTvr : IDLE_BUFF ;
   }
   else  // ((srcIndx == fiTEXTvl) || (srcIndx == fiTEXTvr))
   {
      if ( trgvis )
         altIndx = srcIndx == fiTEXTvl ? fiTEXTvr : fiTEXTvl ;
      else
         altIndx = trgIndx == fiTEXThu ? fiTEXThl : IDLE_BUFF ;
   }
   return altIndx ;

}  //* End udfAltField() *

//*************************
//*     updateAuxData     *
//*************************
//********************************************************************************
//* For the Textbox control with focus, two fields provide auxilliary            *
//* information:                                                                 *
//*   1) line-number field                                                       *
//*   2) gutter field                                                            *
//* The data displayed in these fields is based upon the position of the text    *
//* within the Textbox,the cursor position within the field and whether there    *
//* are unsaved modification to the text.                                        *
//* These data are located in the FileMan record associated with the Textbox     *
//* which has the focus: this->fman[AREC].                                       *
//*                                                                              *
//* Each of the auxilliary fields is supported by a dedicated update method      *
//* which relies on the accuracy of the data of the FileMan record.              *
//* This method updates the underlying data values and then calls the display    *
//* update method(s) if the data have changed.                                   *
//*                                                                              *
//*                                                                              *
//* a) The updateLinenum() method requires only the index of the target          *
//*    line number field and the integer specifying the top data line displayed  *
//*    in the associated Textbox control.                                        *
//*    The specified FileMan record must be associated with a VISIBLE Textbox    *
//*    control, meaning that the associated line-number field is also visible.   *
//*    -- Calling updateLinenum() directly is discouraged; instead it should     *
//*       be called through this method only.                                    *
//*                                                                              *
//* b) The updateGutter() method performs several different tasks and presents   *
//*    the data is different formats depending on caller's specifications.       *
//*    This method updates only the stats area of the gutter field.              *
//*    The gutter field is always visible and displays stats data ONLY for the   *
//*    Textbox control which currently has the input focus.                      *
//*    -- To display an informational message in the gutter field, call          *
//        updateGutter() directly.                                               *
//*                                                                              *
//* Input  : ln_only: (optional, default: false)                                 *
//*                   if set,   update only the target line-number field         *
//*                   if reset, update both line-number and gutter fields        *
//*          alt_fld: (optional, default:false)                                  *
//*                   if 'false', update info for field with focus, according    *
//*                               to value of 'ln_only' flag                     *
//*                   if 'true',  for non-focus TextEdit field, update the       *
//*                               LineNum field ONLY (stat data is not modified) *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* -- The data source should generally be the FileMan record associated with    *
//*    the TextEdit control which currently has focus (fman[AREC]).              *
//* -- To update the LineNum field for a TextEdit field which _does not_ have    *
//*    focus, set 'alt_fld' flag.                                                *
//* -- If we "know" that top line value of target has not changed, we could      *
//*    avoid unnecessary LineNum update. This may be too difficult to track.     *
//********************************************************************************

void AcEdit::updateAuxData ( bool ln_only, bool alt_fld )
{
   FileMan *fmptr ;                       // pointer to FileMan containing update info
   skField  skf ;                         // copy of text-edit field data
   fldIndex trgTb = this->focus ;         // target TextEdit control
   fldIndex trgLn ;                       // target LineNum control
   short    fmRec = AREC ;                // index of FileMan containing stat data

   //* If inactive field is the target, verify that it is visible.*
   if ( alt_fld && (this->fman[IREC].sindx >= ZERO) )
   {
      ln_only = true ;                    // (avoid writing bad data to gutter)
      trgTb = (fldIndex)this->fman[IREC].tindx ; // index of inactive TextEdit field
      fmRec = IREC ;
   }
   linenumMatch ( trgTb, trgLn ) ;        // get associated LineNum control

   this->win->GetFieldData ( trgTb, skf ) ; // get copy of TextEdit field data
   fmptr= &this->fman[fmRec] ;            // point to (dynamic) tracking data
   fmptr->vtop = skf.mrptr->rtop ;        // update tracking data
   fmptr->voff = skf.mrptr->rcur - skf.mrptr->rtop ;
   fmptr->hoff = skf.ip - skf.mrptr->roff[skf.mrptr->rcur] ;
   if ( ! ln_only )                    // update gutter (stats only)
      this->updateGutter () ;
   this->updateLinenum ( trgLn, (fmptr->vtop + 1) ) ;

}  //* End updateAuxData() *

//*************************
//*     updateGutter      *
//*************************
//********************************************************************************
//* Format and display data for the application's "gutter" line.                 *
//* The gutter is the field at the bottom of the application window.             *
//* It contains _at least_ the following:                                        *
//*  a) Name of the file in the text-edit field which has focus                  *
//*  b) Line and character offset (not column) of visible cursor                 *
//*  c) Token showing whether unsaved modifications have been made               *
//*  d) Token showing state of Insert/Overstrike mode                            *
//*  e) Optionally, an informational or warning message.                         *
//*                                                                              *
//* Input  : stats: (optional, default: true)                                    *
//*                 if 'true;,  update _only_ the stats information:             *
//*                             a) filename           b) cursor position         *
//*                             c) unsaved-mods flag  d) ins/ovr flag            *
//*                 if 'false', update stats _and_ message and attributes        *
//*          wmsg : (optional, default: null pointer)                            *
//*                 if message text is specified, integrate it into the display  *
//*                 (an empty string will clear the message area)                *
//*          warn : (optional, default: false)                                   *
//*                 if 'false', updated using standard color attribute           *
//*                 if 'true',  highlight the message text in alternate color    *
//*          blink: (optional, default: zero) referenced _only_ if 'stats' is    *
//*                             reset and 'wmsg' is a non-null value             *
//*                 if == zero, update displayed data and return immediately     *
//*                 if == one,  display the specified data for a moment, then    *
//*                             restore the original data                        *
//*                 if > one,   number of times to "blink" i.e. display and      *
//*                             then erase the message and then restore the      *
//*                             original data                                    *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Because this method may be called quite often, it is built for speed, not    *
//* compactness. We will revisit this at intervals.                              *
//*                                                                              *
//* Notes on the 'blink' parameter:                                              *
//* -------------------------------                                              *
//* 'blink' parameter is referenced ONLY if 'wmsg' is a non-empty text string.   *
//*  a) A value of zero (default value) indicates that the gutter field data are *
//*     to be updated, and then immediately return to caller.                    *
//*  b) A value of one indicates that the field is to be updated, followed by a  *
//*     short delay so user can read it. Then the previous field message data    *
//*     are restored.                                                            *
//*  c) A value greater than one indicates the number of times the specified     *
//*     data will be displayed then erased. A short delay will be executed       *
//*     for each cycle. Then the previous field message data are restored.       *
//*                                                                              *
//* Retaining the 'warn' flag:                                                   *
//* --------------------------                                                   *
//*  a) For 'stats-only' updates, the 'saveWarn' flag determines the attribute   *
//*     for display of the existing message text (if any) and the state of       *
//*     'saveWarn' is not modified.                                              *
//*  b) For persistent (non-blinking) message updates, the message is displayed  *
//*     according to the 'warn' parameter, and the 'warn' value is saved to      *
//*     'saveWarn'.                                                              *
//*  c) For temporary (blinking) messages, the blinking message attribute is     *
//*     specified by the 'warn' parameter.                                       *
//*     The original message is then restored by according to the state of the   *
//*     'saveWarn' flag.                                                         *
//*  d) The ACWin 'HilightFieldText' method is used to display the message       *
//*     using the warning (fg/bg reversed) attributes.                           *
//*                                                                              *
//********************************************************************************

void AcEdit::updateGutter ( bool stats, const wchar_t *wmsg, bool warn, short blink )
{
   const wchar_t* Mode[2]   = { L"OVR", L"INS" } ;
   const wchar_t* Mods[2]   = { L"---", L"mods" } ;
   const int NAME_WID = 23 ;                 // width of filename display
   static wchar_t sMsg[256] = { L"" } ;      // static copy of current message
   static int sw_beg, sw_len ;               // static copy of hilight delimeters
   static bool saveWarn = false ;            // static copy of 'warn' flag
   int   hl_beg, hl_len ;                    // delimeters for "highlighted" segment
   short ln = this->fman[AREC].vtop +        // line number
              this->fman[AREC].voff + 1,
         ch = this->fman[AREC].hoff + 1,     // horizontal offset
         mi = (this->fman[AREC].mods || this->fman[AREC].wmods) ? 1 : 0, // index of Mods array
         ii = this->wInfo.skf->ins ? 1 : 0 ; // insert/overstrike flag

   if ( stats || (blink < ZERO) ) { blink = ZERO ; } // avoid embarrassment

   //* Format the statistics info *
   gString gsStats( "──── %s    ", this->fman[AREC].tnf.fName ) ;
   gsStats.padCols( NAME_WID ) ;
   gsStats.limitCols( NAME_WID ) ;
   gsStats.append( "│ %3hd:%-3hd │ %S │ %S │", &ln, &ch, Mods[mi], Mode[ii] ) ;
   gString gsOut, gsOrig, gsAlt, gstmp( sMsg ) ;

   //* If both stats and message text will be updated       *
   //* ('stats' flag reset), load and format the specified  *
   //* message text.                                        *
   if ( ! stats )
   {
      gstmp.clear() ;
      if ( wmsg != NULL )
         gstmp = wmsg ;
   }

   //* Adjust width of message text and center it in the message area.*
   short availCols = this->wInfo.txtCols - gsStats.gscols() ;
   gstmp.limitCols( availCols ) ;
   gstmp.padCols ( availCols, SPACE, true ) ;

   //* If stats-only update:                                *
   //* If 'stats-only' update, then saved message has been  *
   //* loaded. ('wmsg' is ignored). If no saved message,    *
   //* the message area will be empty.                      *
   if ( stats )
   {
      //* Concatenate field stats and message text (gstmp), *
      //* and pad text to full width of field.              *
      gsOut.compose( "%S%S", gsStats.gstr(), gstmp.gstr() ) ;
      gsOut.padCols( this->wInfo.txtCols ) ;
      this->win->SetFieldText ( fiGUTTER, gsOut ) ;
      if ( saveWarn )                     // if alternate color attribute specified
         this->win->HilightFieldText ( fiGUTTER, true, sw_beg, sw_len, acaREVERSE ) ;
   }

   //* Update both stats data and message text.*
   else
   {
      //* For persistent messages (blink==zero):      *
      //* a) Concatenate field stats and message text.*
      //* b) Save message text, attribute flag and    *
      //*    highlight delimeters.                    *
      if ( blink <= ZERO )
      {
         gstmp.copy( sMsg, 256 ) ;
         saveWarn = warn ;
         gsOut.compose( "%S%S", gsStats.gstr(), gstmp.gstr() ) ;
         gsOut.padCols( this->wInfo.txtCols ) ;
         this->win->SetFieldText ( fiGUTTER, gsOut ) ;
         if ( saveWarn )
         {
            sw_beg = gsOut.after( gsStats.gstr() ) ;
            sw_len = gsOut.gschars() - sw_beg - 1 ;
            this->win->HilightFieldText ( fiGUTTER, true, sw_beg, sw_len, acaREVERSE ) ;
         }
      }

      //* Blinking temporary message *
      else
      {
         //* Format the data for final (existing) display *
         gsOrig = sMsg ;
         gsOrig.limitCols( availCols ) ;
         gsOrig.padCols ( availCols, SPACE, true ) ;
         gsOrig.insert( gsStats.gstr() ) ;
         gsOrig.padCols( this->wInfo.txtCols ) ;

         gsOut.compose( "%S%S", gsStats.gstr(), gstmp.gstr() ) ;
         gsOut.padCols( this->wInfo.txtCols ) ;
         if ( warn )                      // if alternate color attribute specified
         {
            hl_beg = gsOut.after( gsStats.gstr() ) ;
            hl_len = gsOut.gschars() - hl_beg - 1 ;
         }

         //* Create display data with an empty message area.*
         gsAlt = gsStats.gstr() ;
         gsAlt.padCols( this->wInfo.txtCols ) ;

         // Programmer's Note: The most effective timing of the blink is a 
         // subjective evaluation: with the message visible long enough for 
         // an alert user to read it in a single cycle, yet short enough to 
         // draw attention of a caffine-deprived office drone.
         // The timing of automatic traffic signs may give us a clue.
         uint16_t waxOn  = blink > 1 ? 5 : 16,
                  waxOff = 3 ;
         if ( warn )
         {
            this->win->SetFieldText ( fiGUTTER, gsOut ) ;
            while ( blink > ZERO )
            {
               this->win->HilightFieldText ( fiGUTTER, true, hl_beg, hl_len, acaREVERSE ) ;
               this->win->nsleep ( waxOn ) ;
               this->win->HilightFieldText ( fiGUTTER, false, ZERO, ZERO ) ;
               this->win->nsleep ( waxOff ) ;
               if ( --blink == 1 )
                  waxOn += 2 ;            // extend last iteration a bit longer
            }
         }
         else
         {
            while ( blink > ZERO )
            {
               this->win->SetFieldText ( fiGUTTER, gsOut ) ;
               this->win->nsleep ( waxOn ) ;
               this->win->SetFieldText ( fiGUTTER, gsAlt ) ;
               this->win->nsleep ( waxOff ) ;
               if ( --blink == 1 )
                  waxOn += 2 ;            // extend last iteration a bit longer
            }
         }
         this->win->SetFieldText ( fiGUTTER, gsOrig ) ;  // restore the original data
         if ( saveWarn )                  // if alternate color attribute specified
            this->win->HilightFieldText ( fiGUTTER, true, sw_beg, sw_len, acaREVERSE ) ;
      }
   }  // (update stats AND message text)

}  //* End updateGutter() *

//*************************
//*     updateLinenum     *
//*************************
//********************************************************************************
//* Reinitialize text of the line-number field, based on the 'vtop' member of    *
//* the FileMan class. Note that 'vtop' is the _index_ of the topmost visible    *
//* text row in the target field.                                                *
//*                                                                              *
//* Input  : lnIndx  : member of fldIndex: fiLINEvl. fiLINEvr, fiLINEhu,fiLINEhl *
//*          topline : line number (1-based) displayed in top row                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Note: The fiLINEhl line number field is handled as a special case, in that   *
//* the top row of the field is used as a visual extension to the fiHDIV field   *
//* which seperates the upper and lower horizontal edit fields.                  *
//*                                                                              *
//********************************************************************************

void AcEdit::updateLinenum ( fldIndex lnIndx, int topline )
{
   if ( (lnIndx >= fiLINEvl) && (lnIndx <= fiLINEhl) )
   {
      gString gs ;
      int ln = topline,                            // line number
          rows = this->wInfo.skf->fld[lnIndx].hgt, // field row count
          xln ;      // mod 10K value, prevents overflow for > 4 digits

      if ( lnIndx == fiLINEhl )   // (see note above)
      { gs = L"────\n" ; --rows ; }

      for ( int indx = ZERO ; indx < rows ; ++indx, ++ln )
      {
         if ( ln < 10000 )                   // 4-digut limit
            gs.append( "%4d\n", &ln ) ;
         else                                // line # beyond 10K
         {
            xln = (ln % 1000) ;
            gs.append( "x%03d\n", &xln ) ;
         }
      }
      gs.limitChars( (gs.gschars()) - 2 ) ;  // discard trailing newline
      this->win->SetFieldText ( lnIndx, gs ) ;
   }

}  //* End updateLinenum() *

//*************************
//*      tabEmulator      *
//*************************
//********************************************************************************
//* In response to user pressing the Tab key, for the TextEdit field with focus, *
//* insert spaces from the current cursor position to the next tab-stop.         *
//*                                                                              *
//* Tab characters are incompatible with data in Textbox controls; therefore,    *
//* tab emulation is performed by inserting the number of space characters       *
//* necessary to advance the cursor to the next tab stop.                        *
//*                                                                              *
//* The number of spaces per tab stop is determined by the value in the          *
//* configuration file with a default value of TAB_DFLT.                         *
//* Range: 0 <= tab width <= TAB_MAX                                             *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* Within an skForm object, Tab and Shift-Tab move to next/previous field by    *
//* default; however, when a Tab key is identified when editing text, this       *
//* method inserts space characters up to the next tab stop                      *
//* (regardless of Ins/Over flag state).                                         *
//*                                                                              *
//********************************************************************************

void AcEdit::tabEmulator ( void )
{
   gString gs ;
   skField skf ;
   int     ipVal,                         // copy of IP index
           ipRow,                         // index of 1st char on cursor line
           substrChars ;                  // number of characters in substring
   short   ipCols,                        // columns between ipRow and ipVal
           trgCols,                       // target column offset
           spaces ;                       // number of spaces to be inserted

   this->win->GetFieldData ( this->focus, skf ) ;
   ipVal = skf.ip ;
   ipRow = skf.mrptr->roff[skf.mrptr->rcur] ;
   substrChars = ipVal - ipRow ;
   gs = &skf.txt.gstr()[ipRow] ;
   gs.limitChars( substrChars ) ;
   ipCols = gs.gscols() ;

   for ( trgCols = ZERO ; trgCols <= ipCols ; trgCols += this->tabWid ) ;
   spaces = trgCols - ipCols ;
   if ( ! this->wInfo.skf->ins )          // set the Insert Mode flag
      this->win->KeycodePush ( wcsINSERT ) ;
   for ( short c = ZERO ; c < spaces ; ++c ) // insert the padding spaces
      this->win->KeycodePush ( SPACE ) ;
   if ( ! this->wInfo.skf->ins )          // reset the Insert Mode flag
      this->win->KeycodePush ( wcsINSERT ) ;

}  //* End tabEmulator() *

//*************************
//*      GetExitCode      *
//*************************
//********************************************************************************
//* Report the exit code for the class. Called by main().                        *
//*                                                                              *
//* Returns: member of enum xCode                                                *
//********************************************************************************

xCode AcEdit::GetExitCode ( void )
{

   return ( this->exitCode ) ;

}  //* End GetExitCode() *

//*************************
//*     ComposeTitle      *
//*************************
//********************************************************************************
//* Create a text string including the application title, application version    *
//* number and copywrite years.                                                  *
//*                                                                              *
//* Input  : gs     : (by reference) receives the formatting data                *
//*          withUL : (optional, 'false' by default)                             *
//*                   if 'true',  extend the title and underline it              *
//*                   if 'false', plain title                                    *
//*          withNL : (optional, 'false' by default)                             *
//*                   if 'true',  bracket the string with newline characters     *
//*                   if 'false', create the string using only the template      *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::ComposeTitle ( gString& gs, bool withUL, bool withNL )
{
   gs.compose( titleTemplate, appVersion, crYears ) ;

   if ( withUL )
   {
      gs.padCols( 80, L' ', true ) ;
      gs.append( L'\n' ) ;
      gs.padCols( ((gs.gscols()) * 2), L'=' ) ;
   }
   if ( withNL )
   { gs.insert( NEWLINE ) ; gs.append( NEWLINE ) ; }

}  //* End ComposeTitle() *

//*************************
//*    DisplayTermSize    *
//*************************
//********************************************************************************
//* Display the dimensions of the terminal window,                               *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note: We hesitated to convert this conceptually simple report   *
//* into a multi-lingual report, but you may find it to be a useful exercise in  *
//* creating a cartouche to enclose a block of text. Have fun!                   *
//********************************************************************************

void AcEdit::DisplayTermSize ( void )
{
   const char* dtsTemplate[uilENV] = 
   {
      " Terminal Size: %2hd rows x %3hd columns  ", // English
      " 终端窗口尺寸： %2hd 行 x %3hd 专栏  ",                // Zhongwen - CZONE translate
   } ;
   const int bufChars = 56 ;
   WinSize ws = this->win->acGetTermSize () ;
   gString gs( dtsTemplate[this->lang], &ws.ws_row, &ws.ws_col ) ;
   wchar_t reptxt[bufChars], boxtop[bufChars], boxbot[bufChars] ;
   gs.limitChars( bufChars - 4 ) ;
   int txtCols = gs.gscols() ;
   gs.copy( reptxt, bufChars ) ;
   gs.clear() ;
   gs.padCols( txtCols, wcsHORIZ ) ;
   gs.copy( boxbot, bufChars ) ;
   gs.insert( wcsUL ) ;
   gs.append( "%C\n", &wcsUR ) ;
   gs.copy( boxtop, bufChars ) ;
   gs = boxbot ;
   gs.insert( wcsLL ) ;
   gs.append( "%C\n", &wcsLR ) ;
   gs.copy( boxbot, bufChars ) ;
   gs = reptxt ;
   gs.insert( wcsVERT ) ;
   gs.append( "%C\n", &wcsVERT ) ;
   gs.insert( boxtop )  ;
   gs.append( boxbot ) ;
   this->win->ttyWrite ( gs, true, true ) ;

}  //* End DisplayTermSize() *

//*************************
//*    DisplayVersion     *
//*************************
//********************************************************************************
//* Display the application version number and copyright information.            *
//*                                                                              *
//* Input  : verbose : 'false', display only application version and copyright   *
//*                    'true',  include the AnsiCmd-class version number         *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AcEdit::DisplayVersion ( bool verbose )
{
   static const char* const freeSoftware = 
   "License GPLv3+: GNU GPL version 3 <http://gnu.org/licenses/gpl.html>\n"
   "This is free software: you are free to modify and/or redistribute it under the\n"
   "terms set out in the license.\n"
   "There is NO WARRANTY, to the extent permitted by law.\n\n" ;

   gString gsOut ;
   this->ComposeTitle ( gsOut, true, true ) ;
   if ( verbose )
   {
      // Programmer's Note: This is fancy formatting :-)
      gString gsverb( "AnsiCmd Link Library v:%s", this->win->acVersion () ) ;
      gsverb.append( "  (gString v:%s)", gsverb.Get_gString_Version() ) ;
      gsverb.shiftCols( gsOut.scan() - 1 ) ;
      gsverb.padCols( ((gsOut.gscols()) / 2) ) ;
      gsverb.append( L'\n' ) ;
      gsverb.padCols( (gsOut.gscols()), L'-' ) ;
      gsOut.append( "%S\n", gsverb.gstr() ) ;
   }
   this->win->ttyWrite ( gsOut, false ) ;
   this->win->ttyWrite ( freeSoftware ) ;

}  //* End DisplayVersion() *

//*************************
//*     GetAppVersion     *
//*************************
//********************************************************************************
//* Returns a pointer to the application version string.                         *
//* Used by support modules to retrieve the static data stored in this module.   *
//*                                                                              *
//* Input  : years : (by reference) receives a pointer to copyright years string.*
//*                                                                              *
//* Returns: pointer to version string                                           *
//********************************************************************************

const wchar_t* AcEdit::GetAppVersion ( const wchar_t*& years )
{

   years = crYears ;
   return ( appVersion ) ;

}  //* End GetAppVersion() *

//*************************
//*      DisplayHelp      *
//*************************
//********************************************************************************
//* Display command-line help message.                                           *
//*                                                                              *
//* Input  : userOption:                                                         *
//*               clHelp - display message to stdout                             *
//*               clHelpless - redirect stdout to the 'less' utility             *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//*                                                                              *
//*                                                                              *
//********************************************************************************

void AcEdit::DisplayHelp ( clArgs userOption )
{
    //123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-
   const char* Help1 = 
     "  Usage: ace [OPTIONS] filespec [filespec...]"
   "\n  filespec: Files to be edited may be specified as any of the following:"
   "\n     a) filename of a file in the current directory"
   "\n        Examples: MyApp.cpp   'system-update.pl'   \"Chocolate Cake Recipe.txt\""
   "\n     b) a relative path/filename"
   "\n        Examples: docs/MyApp.texi    '../Utils/system-update.pl'"
   "\n     c) a full filespec"
   "\n        Example : ~/Documents/SoftwareDesign/MyApp/MyApp.cpp"
   "\n "
   "\n  For each option that requires one or more arguments, the option name and"
   "\n  its argument(s) may be separated by either '=' or ' ' (space)."
   "\n  Note that if the argument contains whitespace, then the entire argument"
   "\n  sequence must be enclosed within single-quotes or double-quotes."
   "\n    Examples: --option=xyz"
   "\n              --option 'x y z'"
   "\n              --option=\"xyz\""
   "\n "
    //--------------------------------------------------------------------------------
   "\n --color=BORDER_FG:BORDER_BG[,TEXT_FG:TEXT_BG[,HILITE_FG:HILITE_BG]"
   "\n   Specify the color attributes for:"
   "\n    a) application border.  b) text under edit.  c) highlight (cursor) line."
   "\n   Color specification takes the same form as in configuration file:"
   "\n     Ffg:Fbg (4-bit colors), or Efg:Ebg (8-bit colors) or"
   "\n     Rfg:Rbg (24-bit RGB colors). Example: --color=F01:F12,E16:E48,F01:R201"
   "\n   See documentation for full description and color chart."
   "\n "
   "\n --language=[en | zh | te] Language for user interface:"
   "\n            en == English"
   "\n            zh == 中文 (Zhōngwén - Chinese, simplified)"
   "\n            te == terminal environment (default)"
   "\n            By default, the language is determined by the \"locale\""
   "\n            specified in the terminal environment: \"zh_*\" for Chinese"
   "\n            else English."
   "\n "
//   "\n --wordproc: Configure for word-processor mode. Text will be configured to"
//   "\n             \"flow\" within the edit windows. That is, automatic line breaks"
//   "\n             will be performed to keep the end of each line in visible space."
//   "\n             By default, the application is configured for Text Editor mode."
//   "\n "
   "\n --dimensions: Report the terminal window dimensions (rows and columns)."
   "\n "
   "\n --test=TESTNUM : For development: perform the specified test."
   "\n       Specify a test number 0 <= testnum <= 99. (99==test menu)"
   "\n "
   "\n --help (-h)      Display application help, then exit."
   "\n --helpless (-hl) Display application help using the 'less' utility."
   "\n --version[v]     Display application version and copyright information."
   "\n " ;  // End Help1
    //123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-

// CZONE - Convert help to multi-lingual
   gString gs ;
   this->ComposeTitle ( gs, true, true ) ;
   bool write2stdout = true ;

   //* If specified, pipe the Help text to the 'less' utility.*
   if ( userOption == clHelpless )
   {
      FILE *output = popen ( "less -c", "w" ) ;
      if ( output != NULL )
      {
         fprintf ( output, gs.ustr() ) ;
         fprintf ( output, Help1 ) ;
         pclose ( output ) ;
         write2stdout = false ;
      }
      //* If popen() fails, write to stdout instead.*
   }

   //* Write Help text to stdout *
   if ( write2stdout != false )
   {
      this->win->ttyWrite ( gs, false ) ;
      this->win->ttyWrite ( Help1, false ) ;
      this->win->ttyWrite ( '\n' ) ;
   }

}  //* End DisplayHelp() *

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

