//********************************************************************************
//* File       : AnsiCmdDlg.cpp                                                  *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2022-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice below                               *
//* Date       : 30-Mar-2026                                                     *
//* Version    : (see AnsiCmdVersion string in AnsiCmd.cpp)                      *
//*                                                                              *
//* Description: ACDlg class implementation                                      *
//*              The AnsiCmd Dialog is a dialog window within, and controlled by *
//*              the parent ACWin-class object.                                  *
//*                                                                              *
//********************************************************************************

//*****************
//* Include Files *
//*****************
#include "AnsiCmd.hpp"     //* AnsiCmd-class definition
#include "AnsiCmdWin.hpp"  //* ACWin-class definition

//*----------------------------------------------------------------------------*
#if INCLUDE_API != 0 //* Build the API methods as part of the AnsiCmd library. *
//*----------------------------------------------------------------------------*

//***************
//* Definitions *
//***************

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

//* For dialogs which specify callback functions, this variable is set on   *
//* entry to editDialog(), and reset before return. This variable is a      *
//* pointer to the ACDlg object currently under edit, and allows the        *
//* application's callback function a limited access to the dialog controls.*
static ACDlg* activeDlg = NULL ;

//********************
//* Local prototypes *
//********************
extern bool char2Hotkey ( wchar_t& wkey ) ;
static bool adjustDlgWidth ( gString& gsTxt, short maxWidth, short trgHeight, int& trindx ) ;
static bool adjustDlgHeight ( gString& gsTxt, short maxHeight, short trgWidth, int& trindx ) ;
static void formatDlgTitle ( gString& gsTit, short dlgWid, LineType ltype ) ;


//*************************
//*     QuickDialog       *
//*************************
//********************************************************************************
//* Generate a "quick" dialog window within the application's ACWin-class window.*
//* Either of two formats may be specified:                                      *
//*  1) Information Dialog: Display a dialog with one Pushbutton control and     *
//*     an informational message.                                                *
//*  2) Decision Dialog: Display a dialog with two Pushbutton controls.          *
//*     The user must decide between the options presented, Yes/No,              *
//*     Continue/Cancel, Save/Discard, and similar.                              *
//*                                                                              *
//* For a fully-configurable dialog, see the Dialog method which accepts all     *
//* necessary parameters.                                                        *
//*                                                                              *
//*                                                                              *
//* Input  : gsMsg  : (by reference) contains the message(s) to be displayed.    *
//*          query  : 'true'  indicates a decision dialog, and user is prompted  *
//*                           for a yes/no response                              *
//*                   'false' indicates an information dialog, and user is       *
//*                           prompted to press Enter (or Space) to exit.        *
//*          title  : (optional, default: null pointer)                          *
//*                   If specified, this is the dialog title text which will     *
//*                   be centered in the top border of the dialog.               *
//*          height : (optional, zero (0) by default) height of dialog window    *
//*                   in rows.                                                   *
//*                   A zero value indicates that the dialog is automatically    *
//*                   sized to accomodate dimensions of the message text (gsMsg) *
//*          width  : (optional, zero (0) by default) width of the dialog        *
//*                   window in columns.                                         *
//*                   A zero value indicates that the dialog is automatically    *
//*                   sized to accomodate the message text (gsMsg)               *
//*          vOff   : (optional, default: 0) vertical offset into the parent     *
//*                   window's text area (0-based)                               *
//*                   A negative value indicates that the dialog is to be        *
//*                   centered vertically in the parent window.                  *
//*          hOff   : (optional, default: -1) horizontal offset into the parent  *
//*                   window's text area (0-based)                               *
//*                   A negative value indicates that the dialog is to be        *
//*                   centered horizontally in the parent window.                *
//*          timeout: (optional, zero by default) automatic timeout of dialog    *
//*                   display in seconds                                         *
//*                   -- By default, the dialog is closed when one of the        *
//*                      pushbutton controls is activated.                       *
//*                   -- If a positive value (in seconds) is specified,          *
//*                      the dialog will close automatically after the specified *
//*                      number of seconds has elapsed OR when any keycode is    *
//*                      received, whichever comes first.                        *
//*          dbg    : (optional, false by default) for debugging only:           *
//*                   if reset, refresh the parent window on return              *
//*                   if set,   dialog remains visible on return                 *
//*                                                                              *
//* Returns: if 'query' flag is set, then the user response is returned:         *
//*                     'true'  if control at index zero(0) selected             *
//*                     'false' if control at index one(1) selected              *
//*          if 'query' flag is reset, i.e. dialog has only one control, then    *
//*                     return value is always 'true'.                           *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* A) Only two(2) parameters are required:                                      *
//*    1) the message to be displayed                                            *
//*    2) specify the type of user interaction: query (two Pushbutton controls)  *
//*       or informational (one Pushbutton control).                             *
//*    All other parameters use default values unless otherwise specified.       *
//*                                                                              *
//*    The dimensions (height and width) of the dialog are based on the text     *
//*    rows and columns required to display the formatted message data           *
//*                              _unless_                                        *
//*    text would extend beyond the available vertical or horizontal space _or_  *
//*    would extend beyond the dimensions specified by the optional parameters,  *
//*    in which case, the text will be automatically reformatted.                *
//*    - Note that if the text must be reformatted, the flag enabling line       *
//*      breaks on hyphen-like characters is set.                                *
//*                                                                              *
//* --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---  *
//*                                                                              *
//* Note On Positioning:                                                         *
//* --------------------                                                         *
//* All positions within a dialog are relative to the 'txtBase' member of the    *
//* parent window. 'txtBase' is the 1-based, offset from the origin (upper-left  *
//* corner of the terminal window. The dialog saves this value in the ACDlg      *
//* member, 'dBase'.                                                             *
//* The 'skf->base' member steps inward from 'dBase' by one row and column to    *
//* reference the upper-left corner of the dialog's interior. This allows all    *
//* member fields of the dialog to be drawn as if they were being drawn directly *
//* to the interior of the parent window, which in fact, they are.               *
//*                                                                              *
//********************************************************************************

bool ACWin::QuickDialog ( const gString& gsMsg, bool query, const char* title, 
                          short height, short width, short vOff, short hOff, 
                          short timeout, bool dbg )
{
   #define DEBUG_QDialog (0)     // for debugging only

   const short LANGS = 4 ;
   const wchar_t *ymsg[LANGS] =  // default pushbutton messages
   {
      L"  ^OK  ",                // English
      L" ^.好的 ",               // Zhongwen (Hǎo de)
      L" De ^Acuerdo ",          // Espanol
      L" ^Tốt "                  // Tieng Viet
   } ;
   const wchar_t *nmsg[LANGS] = 
   {
      L" ^CANCEL ",              // English
      L"  ^,取消  ",               // Zhongwen (Qǔxiāo)
      L" ^Cancelar ",            // Espanol
      L" ^Hủy Bỏ  "             // Tieng Viet
   } ;

   gString gsm = gsMsg ;                  // working copy of message text
   gString gst( title ) ;                 // working copy of title text
   gString gspb ;                         // format pushbutton display
   short  winRows   = this->height,       // height of parent window
          winCols   = this->width,        // width of parent window
          maxHeight = this->txtRows,      // maximum vertical display area available
          maxWidth  = this->txtCols,      // maximum horizontal display area available
          minHeight = dvMIN + 1,          // minimum dialog height
                                          // (borders + control row + 1 message row)
          minWidth,                       // minimum dialog width
          boxRows   = minHeight,          // number of dialog rows (init to min)
          boxCols   = ZERO,               // number of dialog columns
          msgHeight = 1,                  // number of output rows in message
          msgWidth  = ZERO,               // maximum number of columns in a message row
          pbwidth,                        // number of columns for pushbutton display
          pbut_cols,                      // columns in pushbutton control (not incl. caret)
          langi = ZERO ;                  // index for language selection
   bool resp = true ;                     // user response, return value

   //* Determine the language to use for control text *
   gspb.compose( "%s", this->acGetLocale () ) ;  // get the current locale
   if  ( (gspb.find( L"en_", ZERO, false )) == ZERO )
      langi = ZERO ;
   else if ( (gspb.find( L"zh_", ZERO, false )) == ZERO )
      langi = 1 ;
   else if ( (gspb.find( L"es_", ZERO, false )) == ZERO )
      langi = 2 ;
   else if ( (gspb.find( L"vi_", ZERO, false )) == ZERO )
      langi = 3 ;

   //* Minimum dialog width is width of controls (plus padding) *
   if ( query )
      gspb.compose( "%S  %S", ymsg[langi], nmsg[langi] ) ;
   else
      gspb = ymsg[langi] ;
   pbwidth = gspb.gscols() - (query ? 2 : 1) ; // width of caret(s) excluded
   minWidth = pbwidth + dwPAD ;

   //* Range check caller's dimension parameters *
   if ( height > ZERO )             // if height is not auto-dimensioned
   {
      if ( height > maxHeight )        { height = maxHeight ; }
      else if ( height < minHeight )   { height = minHeight ; }
   }
   if ( width > ZERO )              // if width is not auto-dimensioned
   {
      if ( width > maxWidth )          { width = maxWidth ; }
      else if ( width < minWidth )     { width = minWidth ; }
   }

   //* Calculate the height and width of the source text.   *
   //* If source text is unformatted i.e. if it contains no *
   //* newline characters, then height==1 and width is set  *
   //* to total width of text.                              *
   //* Else height == number of text rows (>= 1), and width *
   //* is the number of columns in the widest row.          *
   //* Defensive programming (empty string not allowed).    *
   if ( (gsm.gschars() == 1) || (gsm.gscols() == ZERO) ) { gsm = " " ; }
   this->ParagraphSize ( gsm, msgHeight, msgWidth ) ;

   //* If text height or width is greater than maximum space*
   //* available, or greater than the specified dimensions, *
   //* then reformat the text to fit the available space.   *
   if ( ((msgHeight + dvPAD) > maxHeight) ||
        ((msgWidth  + dwPAD) > maxWidth)  ||
        ((height > ZERO) && (msgHeight + dvPAD) > height) ||
        ((width  > ZERO) && (msgWidth  + dwPAD) > width) )
   {
      short paraRows = height > ZERO ? height : maxHeight - dvPAD,
            paraCols = width  > ZERO ? width  : maxWidth  - dwPAD ;
      int   truncindx ;
      gsm.formatParagraph ( paraRows, paraCols, false, true, &truncindx ) ;
      this->ParagraphSize ( gsm, msgHeight, msgWidth ) ;  // recalculate message dimensions

      #if DEBUG_ANSICMD != 0 && DEBUG_QDialog != 0
      gs.replace( L' ', L'_', ZERO, false, true ) ; // replace all spaces
      #endif   // DEBUG_ANSICMD && DEBUG_QDialog

      //* If formatted text is too large to fit within the *
      //* specified area, truncate the text.               *
      if ( truncindx > ZERO )
         gsm.limitChars( truncindx ) ;
   }

   //* - If automatic dialog dimension calculation specified,   *
   //*   set dialog dimensions to the height and width of the   *
   //*   text _plus_ vertical and horizontal padding.           *
   //* - If fixed dimension specified, and text fits within     *
   //*   the dimension, use fixed dimension.                    *
   //* - Else, set dialog dimension according to text dimension.*
   //*   (Calculation assumes at least one message line and )   *
   //*   (at least minimum width.                           )   *
   if ( height <= ZERO)                   // auto-height
      boxRows = minHeight + msgHeight - 1 ;
   else                                   // specified height
   {
      if ( (boxRows = height) < minHeight )
         boxRows = minHeight ;
      else if ( boxRows > maxHeight )
         boxRows = maxHeight ;
   }
   if ( width <= ZERO )                   // auto-width
      boxCols = msgWidth + dwPAD ;        // msg width plus padding
   else                                   // specified width
      boxCols = (width >= (msgWidth + dwPAD)) ? width : (msgWidth + dwPAD) ;

   //* If specified, center dialog in parent window. Else, *
   //* validate offset(s) and adjust them if necessary.    *
   if ( vOff < ZERO )                     // centered vertically
   {
      if ( (vOff = winRows / 2 - boxRows / 2 - 1) < ZERO )
         vOff = ZERO ;
   }
   else if ( vOff > ZERO )                // vertical offset specified
   {
      if ( (boxRows + vOff) > maxHeight )
         if ( (vOff = maxHeight - boxRows) < ZERO )
            vOff = ZERO ;
   }
   if ( hOff < ZERO )                     // centered horizontally
   {
      if ( (hOff = winCols / 2 - boxCols / 2 - 1) < ZERO )
         hOff = ZERO ;
   }
   else if ( hOff > ZERO )                // horizontal offset specified
   {
      if ( (boxCols + hOff) > maxWidth )
         if ( (hOff = maxWidth - boxCols) < ZERO )
            hOff = ZERO ;
   }

   //* Validate minimum dialog width.*
   if ( boxCols < minWidth )
      boxCols = minWidth ;

   const acAttr pbnAttr = acAttr(acaFG_RED | acaBG_GREY ) ;  // pushbutton (non-focus) attribute
   const acAttr pbfAttr = acAttr(acaFG_INDEX | acaBGb_RED) ; // pushbutton (focus) attribute
   gspb = ymsg[langi] ;
   pbut_cols = gspb.gscols() - 1 ;        // width of control (not incl. caret)

   //* Define the controls.                                 *
   //* a) one or two pushbutton controls                    *
   //* b) base of form == base of parent window's interior  *
   //*    PLUS specified vertical and horizontal offsets.   *
   skForm* skfp = new skForm( (query ? 2 : 1) ) ;
   skfp->base = { short(vOff + 1), short(hOff + 1) } ;
   skfp->faca = pbfAttr ;                 // control focus color
   skfp->dlg = true ;                     // signal that container is a dialog

   skfp->fld[0].type = ftPB ;
   skfp->fld[0].orig.row = (boxRows - 3) ; // (1 row above bottom border)
   skfp->fld[0].orig.col = (boxCols / 2) - (pbwidth / 2) - 1 ; // centered in dialog
   skfp->fld[0].hgt  = 1 ;
   skfp->fld[0].wid  = pbut_cols ;
   skfp->fld[0].txt  = ymsg[langi] ;
   skfp->fld[0].aca.decode( pbnAttr ) ;
   if ( query && (timeout <= ZERO) )
   {
      skfp->fld[1].type = ftPB ;
      skfp->fld[1].orig.row = skfp->fld[0].orig.row ;
      skfp->fld[1].orig.col = skfp->fld[0].orig.col + pbut_cols + 2 ;
      skfp->fld[1].hgt  = 1 ;
      skfp->fld[1].txt  = nmsg[langi] ;
      skfp->fld[1].wid  = skfp->fld[1].txt.gscols() - 1 ;
      skfp->fld[1].aca.decode( pbnAttr ) ;
   }

   //* If a dialog title was specified, verify that text will fit *
   //* width of dialog and add title delimeters.                  *
   if ( (gst.gschars()) > 1 )
      formatDlgTitle ( gst, boxCols, ltSINGLE ) ;

   //* If automatic timeout specified, make control invisible. *
   if ( timeout > ZERO )
   {
      skfp->fld[0].txt.clear() ;
      skfp->fld[0].aca = this->txtAttr ;
      resp = false ;    // if timeout, return value is always "no"
   }

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_QDialog != 0
   if ( ofsdbg.is_open() )
   {
      gsdbg.compose( "QuickDialog( boxRows:%02hd boxCols:%02hd vOff:%02hd hOff:%02hd skfp->base:%02hd,%02hd )\n"
                     "             fld0.orig:%02hd,%02hd fld.orig:%02hd,%02hd",
                     &boxRows, &boxCols, &vOff, &hOff, &skfp->base.row, &skfp->base.col, 
                     &skfp->fld[0].orig.row, &skfp->fld[0].orig.col, 
                     &skfp->fld[1].orig.row, &skfp->fld[1].orig.col ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_QDialog

   //* Reformat the text data for the controls to support the hotkeys. *
   this->init_Hotkeys ( skfp ) ;

   ACDlg* dlg = new ACDlg( boxRows, boxCols, vOff, hOff,
                           skfp, gsm, gst, ltSINGLE, 
                           this->bdrAttr.acaVal, this->txtAttr.acaVal ) ;
   //* Because this is a 'protected' member, we must copy it explicitly. *
   dlg->skf->dlg = skfp->dlg ;

   //* Open the dialog and get user's response *
   if ( (this->genericDialog ( dlg, timeout )) > ZERO )
      resp = false ;

   delete skfp ;                          // delete the temporary object
   delete dlg ;                           // delete the dialog object

   if ( ! dbg ) { this->RefreshWin () ; } // redraw contents of parent window

   return resp ;                          // return user's response

   #undef DEBUG_QDialog
}  //* End QuickDialog() *

//*************************
//*        Dialog         *
//*************************
//********************************************************************************
//* Generate a dialog window within the application ACWin window.                *
//* The dimensions, position and color attributes are specified directly, while  *
//* the dialog's controls are defined by passing a pointer to a fully-initialized*
//* skForm-class object.                                                         *
//* The referenced skForm object is updated in response to user interaction,     *
//* so that when the dialog is closed, caller may interpret any changes the      *
//* user has made to the controls' status. (see notes below)                     *
//*                                                                              *
//* Note that verification of all parameters is performed by this method, and    *
//* adjustments will be made to the position and dimensions of the dialog if     *
//* necessary to make the dialog fit within the parent window.                   *
//* In addition, the display text, and the position of the controls will be      *
//* adjusted if necessary to fit within the dialog.                              *
//* Keep in mind however, that it is impractical to test for all varieties of    *
//* foolishness, such as overlapping controls, so construct dialogs with care.   *
//*                                                                              *
//*                                                                              *
//* Input  : dlgHgt: dialog height (rows)                                        *
//*                  A value of zero (0) indicates that the height of the dialog *
//*                  is based on the height (rows) of the PREFORMATTED text it   *
//*                  contains, plus necessary padding (typically an additional   *
//*                  three(3) rows).                                             *
//*                  The height of the dialog may be calculated as the rows      *
//*                  needed by the controls plus the height of the static text   *
//*                  data, plus top and bottom borders. Some additional space is *
//*                  recommended to present a pleasing and easily-readable       *
//*                  display.                                                    *
//*          dlgWid: dialog width (columns)                                      *
//*                  A value of zero (0) indicates that the width of the dialog  *
//*                  is based on the width (columns) of the PREFORMATTED text it *
//*                  contains, plus necessary padding (typically an additional   *
//*                  four(4) columns).                                           *
//*                  The width of the dialog may be calculated as the columns    *
//*                  needed by the widest row of controls, or the width of the   *
//*                  static text, whichever is greatest, plus the right and left *
//*                  borders. Some additional space is recommended to present    *
//*                  a pleasing and easily-readable display.                     *
//*          formDef:pointer to user-interface control definitions               *
//*                  (skForm object)                                             *
//*                  -- At least one of the defined controls MUST BE a Pushbutton*
//*                     control; otherwise it is an unrecoverable error.         *
//*                  -- Certain member values are overridden by the values in    *
//*                     the skForm object of the parent window.                  *
//*                  -- Invalid or out-of-range members will be silently         *
//*                     corrected to maintain dialog integrity.                  *
//*                  -- See note below regarding data modified before return     *
//*                     to caller.                                               *
//*          sTxt  : static text written to be written to dialog interior        *
//*                  (a null pointer or an empty string indicates that there is  *
//*                   no static message)                                         *
//*          tTxt  : dialog title, if specified, will be written centered        *
//*                  in the top border of the dialog.                            *
//*                  (a null pointer or an empty string indicates that           *
//*                   the dialog will have no title)                             *
//*          vOff  : vertical offset (0-based) in rows from top of parent window *
//*                  (optional, default: -1) A value of -1 specifies that        *
//*                  dialog is centered vertically in the parent window.         *
//*          hOff  : horizontal offset (0-based) in columns, from left edge of   *
//*                  parent window.                                              *
//*                  (optional, default: -1) A value of -1 specifies that        *
//*                  dialog is centered horizontally in the parent window.       *
//*          lType : border style (optional, default: ltSINGLE)                  *
//*                  Member of enum LineType (ltSINGLE or ltDUAL)                *
//*          bAttr : border color attributes                                     *
//*                  (optional, default: color attributes are taken from those   *
//*                  of the parent window.                                       *
//*          tAttr : text (dialog interior) color attributes                     *
//*                  (optional, default: color attributes are taken from those   *
//*                  of the parent window.                                       *
//*          tvOff : static text position, vertical offset                       *
//*                  (optional, default: 0, top row of dialog)                   *
//*          thOff : static text position. horizontal offset                     *
//*                  (optional, default: 1, inset one column from left edge      *
//*                   of dialog)                                                 *
//*          callbk: (optional, default: null pointer) pointer to a non-member   *
//*                  callback function. (See typedef CUPTR for details.)         *
//*                  If specified, this function will be called by the EditDialog*
//*                  method each time the user presses a key.                    *
//*          dbg   : (optional, false by default) for debugging only:            *
//*                  if reset, refresh the parent window on return               *
//*                  if set,   dialog remains visible on return                  *
//*                                                                              *
//* Returns: index of control with focus when dialog is closed                   *
//*          (this is the same value contained in formDef->fi, 'fieldindex')     *
//*                                                                              *
//*          The structure referenced by 'formDef' may have been updated         *
//*          to contain additional information:                                  *
//*          a) Text controls (ftTB): The text of the field may have been        *
//*             modified by user.                                                *
//*          b) Radiobutton controls (ftRB): The 'set' flag which indicates      *
//*             the binary state of the control (set or reset) may have changed. *
//*          c) Menus (ftMB): If user selects an item from the menu, the item    *
//*             number will be available by calling the skField-class getItem()  *
//*             method. The value returned is a 0-based index of the menu item:  *
//*              a) item which was "selected", OR                                *
//*              b) item highlighted when dialog was closed.                     *
//*          d) Scrollbox controls (ftSB) typically contain plain,               *
//*             paragraph-oriented text data, not itemized data records; however,*
//*             if desired, the application may define each display row of the   *
//*             Scrollbox control as a data item. The skField-class getItem()    *
//*             method may be used to retrieve the item:                         *
//*              a) item which was "selected", OR                                *
//*              b) item highlighted when dialog was closed.                     *
//*                                                                              *
//*                                                                              *
//* Initialization Error(s):                                                     *
//* ------------------------                                                     *
//* If the formDef object as received has been malformed, certain                *
//* parameters will have been corrected if possible; or if corrections           *
//* are not possible:                                                            *
//* a) formDef will be ignored,                                                  *
//* b) a warning will be displayed, and                                          *
//* c) a minus one (-1) will be returned.                                        *
//********************************************************************************
//* Notes:                                                                       *
//* ======                                                                       *
//* A) At least one(1) Pushbutton (ftPB) control must be defined.                *
//*    Selecting any Pushbutton triggers closing the dialog and returning        *
//*    to the application level, UNLESS there is an active callback function.    *
//* B) Textbox (ftTB), Scrollbox (ftSB) and Radiobutton (ftRB) controls are      *
//*    supported.                                                                *
//* C) Menu controls (ftMB) are supported, but are limited to 'context' menus    *
//*    (multi-item stand-alone menus) only, AND unlike context menus defined in  *
//*    the parent window, the menu will be set as always visible.                *
//*    Override of initial values is performed for the following data members.   *
//*    ('grp' == noGROUP, 'lock' flag set, 'ro' flag reset, 'hide' flag reset).  *
//* D) Control outlines and external labels for control types that support them: *
//*    The data members that define outlines and labels are 'protected' members; *
//*    that is, they may not be directly modified at the application level.      *
//*    Two specialized public methods of the skField class are available to      *
//*    configure these options:                                                  *
//*    a) labelConfig: Example: Label text, color attributes and 'center' flag.  *
//*       skfp->fld[2].labelConfig ( "Sample Label",                             *
//*                                  acAttr(acaFG_BLUE | acaBG_CYAN), true ) ;   *
//*    b) outlineConfig: Example: Line type and color attributes.                *
//*       skfp->fld[3].outlineConfig( ltSINGLE, acAttr(acaFG_BLUE | acaBG_CYAN));*
//*                                                                              *
//* Please see the docs for additional notes on defining a dialog window.        *
//*                                                                              *
//*                 ---  ---  ---  ---  ---  ---  ---  ---                       *
//* Dimensioning logic:                                                          *
//* ===================                                                          *
//* A. Dialog height, width, vertical offset and horizontal offset may be        *
//*    specified directly, or may be dynamically calculated based upon the       *
//*    static text to be displayed.                                              *
//*    1) A height of zero (0) indicates that the vertical dimension should      *
//*       be derived from the height of the static text plus top and bottom      *
//*       borders (plus one row which is assumed to be occupied by one or        *
//*       more controls).                                                        *
//*       a) If the dimensions of the received text will not fit within the      *
//*          available space, the text will be automatically reformatted         *
//*          based on the specified dialog width and vertical/horizontal offsets *
//*          (if specified) and the space available for the dialog.              *
//*    2) If a VALID height is specified (ZERO < dlgHgt < maxHgt), then that     *
//        value will anchor the dimensioning calculations.                       *
//*       a) If specified height is out-of-range, it will be set to the          *
//*          available height.                                                   *
//*    3) A width of zero (0) indicates that the horizontal dimension should     *
//*       be derived from the width of the static text plus left and right       *
//*       borders (plus 2 columns for left/right indentation).                   *
//*    4) If a VALID width is specified (ZERO < dlgWid < maxWid),                *
//*                                                                              *
//*                 ---  ---  ---  ---  ---  ---  ---  ---                       *
//* Programmer's Note: During development the use of multi-row text fields was   *
//* validated; however, at this time, dialogs text fields are limited to         *
//* single-row controls. This simplifies the range check on field dimensions.    *
//********************************************************************************

short ACWin::Dialog ( short dlgHgt, short dlgWid, skForm* formDef, 
                      const char* sTxt, const char* tTxt, 
                      short vOff, short hOff, LineType lType, 
                      acAttr bAttr, acAttr tAttr, short tvOff, short thOff, 
                      CUPTR callbk, bool dbg )
{
   #define DEBUG_Dialog (0)               // for debugging only
   #define DEBUG_ADJ (0)                  // for debugging adjustment of dialog dimensions

   gString gs ;                           // text analysis
   gString gsStatic( sTxt ) ;             // working copy of static-message text
   gString gsTitle( tTxt ) ;              // working copy of dialog title
   gString gspb ;                         // format pushbutton display
   skForm* skfp     = NULL ;              // pointer to temporary skForm object
   short  maxHgt    = this->txtRows,      // maximum vertical display area available
          maxTHgt   = maxHgt - dvPAD,     // maximum height of static text
          maxWid    = this->txtCols,      // maximum horizontal display area available
          maxTWid   = maxWid - dwPAD,     // maximum width of static text
          minHgt    = dvMIN,              // minimum dialog height (3)
          minWid    = 4 + dwPAD,          // minimum dialog width (8)
          msgHeight = 1,                  // number of output rows in message
          msgWidth  = ZERO,               // maximum number of columns in a message row
          specWid,                        // specified dialog width
          trgHgt,                         // target for message dimensions
          trgWid ; 
   int    trindx ;                        // message truncation index

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
   short tmpmH, tmpmW ;
   this->ParagraphSize ( gsStatic, tmpmH, tmpmW ) ;
   gsdbg.compose( "  raw dimensions : dlgHgt:%02hd msgHeight:%02hd dlgWid:%02hd msgWidth:%02hd",
      &dlgHgt, &tmpmH, &dlgWid, &tmpmW ) ;
   ofsdbg << gsdbg.ustr() << endl ;
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ

   //* Gross range-check of dimensions and offsets. *
   if ( dlgHgt > maxHgt )     { dlgHgt = maxHgt ; }
   else if ( (dlgHgt > ZERO) && (dlgHgt < minHgt) )
      dlgHgt = minHgt ;
   if ( dlgWid > maxWid )   { dlgWid = maxWid ; }
   else if ( (dlgWid > ZERO) && (dlgWid < minWid) )
      dlgWid = minWid ;
   specWid = dlgWid ;                     // specified dialog width
   if ( (vOff > ZERO) && ((maxHgt - vOff) < minHgt) )
      vOff = maxHgt - minHgt ;
   if ( (hOff > ZERO) && ((maxWid - hOff) < minWid) )
      hOff = maxWid - minWid ;

   //* Negative offsets for static text are not allowed, *
   //* (but see note above about the special case above).*
   if ( tvOff < ZERO )        { tvOff = ZERO ; }
   if ( thOff < ZERO )        { thOff = ZERO ; }

   //* Verify border style is one of the supported types.*
   if ( lType != ltDUAL )     { lType = ltSINGLE ; }

   //* If specified, use color attributes from parent window.*
   if ( bAttr == acaUSEDFLT )
      bAttr = this->bdrAttr.acaVal ;
   if ( tAttr == acaUSEDFLT )
      tAttr = this->txtAttr.acaVal ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_Dialog != 0
   if ( ofsdbg.is_open() )
   {
      gsdbg.compose( 
         "Dialog( dlgHgt:%02hd(max:%02hd) dlgWid:%02hd(max:%02hd) vOff:%02hd hOff:%02hd tvOff:%02hd thOff:%02hd callbk:%p )",
         &dlgHgt, &maxHgt, &dlgWid, &maxWid, &vOff, &hOff, &tvOff, &thOff, callbk ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_Dialog

   //**----------------------------------------------------------**
   //** Validate Dialog Dimensions and Static Text Dimensions    **
   //**----------------------------------------------------------**

   //* Calculate dimensions of static text (if any). *
   if ( (gsStatic.gschars()) > 1 )
   {
      this->ParagraphSize ( gsStatic, msgHeight, msgWidth ) ;

      //* If default dialog dimensions specified, set dimensions *
      //* according to text dimensions (plus padding).           *
      if ( dlgHgt <= ZERO )
      {
         if ( ((dlgHgt = msgHeight + dvMIN) + vOff) > maxHgt )
            dlgHgt = maxHgt - vOff ;
      }
      if ( dlgWid <= ZERO )   { dlgWid = msgWidth  + dwPAD ; }

      //* a) If the width of the static text is greater than the     *
      //*    width of the parent window (unformatted text), OR       *
      //* b) If sum of static text width plus horizontal offset      *
      //*    (plus padding) is greater than the available space, OR  *
      //* c) If a fixed dialog width is specified, but the static    *
      //*    text is wider than the specified dialog width:          *
      //* Reformat the text and if necessary, resize the dialog.     *
      if ( (msgWidth > maxTWid) ||
           ((hOff > ZERO) && ((msgWidth + dwPAD + hOff) > maxWid)) ||
           ((dlgWid > ZERO) && (msgWidth > (dlgWid - dwPAD))) )
      {
         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
         gsdbg.compose( 
            "  horiz. overflow: maxWid:%02hd maxTWid:%02hd msgWid:%02hd dlgWid:%02hd",
            &maxWid, &maxTWid, &msgWidth, &dlgWid ) ;
         ofsdbg << gsdbg.ustr() << endl ;
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ

         //* Set target dialog height to space available between  *
         //* vertical offset (if specified) and bottom border.    *
         trgHgt = maxHgt - (vOff > ZERO ? vOff : ZERO) ;
         //* If valid (>zero)dialog width specified, use it UNLESS*
         //* it is larger than available space. Else set width to *
         //* fill available space.                                *
         short tmp_hoff = hOff > ZERO ? hOff : ZERO ;
         if ( (specWid > ZERO) && (specWid <= (maxWid - tmp_hoff)) )
            dlgWid = specWid ;
         else
            dlgWid = maxWid - tmp_hoff ;
         //* Set target text width to fit within dialog width.    *
         trgWid = dlgWid - dwPAD ;

         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
         gsdbg.compose( "                   trgHgt:%02hd trgWid:%02hd dlgWid:%02hd\n",
                        &trgHgt, &trgWid, &dlgWid ) ;
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ

         //* Adjust dimensions of static text to fit the space.   *
         adjustDlgHeight ( gsStatic, trgHgt, trgWid, trindx ) ;
         this->ParagraphSize ( gsStatic, msgHeight, msgWidth ) ;
         if ( trindx >= ZERO )
         {
            gsStatic.limitChars( trindx ) ;
            this->ParagraphSize ( gsStatic, msgHeight, msgWidth ) ;
         }
         //* Dialog height adjustment will leave room for a clean  *
         //* row for controls UNLESS that would violate max height.*
         if ( (dlgHgt = msgHeight + dvMIN) > trgHgt ) { dlgHgt = trgHgt ; }

         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
         gsdbg.append( 
            "                   dlgHgt:%02hd msgHgt:%02hd dlgWid:%02hd msgWid:%02hd",
            &dlgHgt, &msgHeight, &dlgWid, &msgWidth ) ;
         if ( trindx >= ZERO )
            gsdbg.append( " (trindx:%02hd)", &trindx ) ;
         ofsdbg << gsdbg.ustr() << endl ;
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ
      }  // adjust dialog width

      //**--------------------------------------**
      //** Validate Static Text Vertical Offset **
      //**--------------------------------------**
      //* If specified vertical offset combined with the height      *
      //* of the static text, would push the dialog into the bottom  *
      //* border, resize the dialog to fit within the space between  *
      //* the vertical offset and the bottom border.                 *
      //* Reformat the text to fit the new dimensions.               *
      //*   Note: If necessary, we override fixed height to avoid    *
      //*   truncation of the text. Text is truncated ONLY if        *
      //*   necessary to preserve vertical offset.                   *
      else if ( (msgHeight > (maxTHgt - (vOff > ZERO ? vOff : ZERO))) ||
                (msgHeight > (dlgHgt - dvPAD)) )
      {
         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
         //short av = maxTHgt - (vOff > ZERO ? vOff : ZERO) ;
         //short dv = (dlgHgt - dvPAD) ; 
         gsdbg.compose( 
            "  vert. overflow : maxHgt:%02hd maxTHgt:%02hd msgHgt:%02hd dlgHgt:%02hd dlgWid:%02hd",
            &maxHgt, &maxTHgt, &msgHeight, &dlgHgt, &dlgWid ) ;
         ofsdbg << gsdbg.ustr() << endl ;
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ

         if ( vOff < ZERO )               // if vertical centering specified
            vOff = (maxHgt / 3) ;
         else if ( (maxHgt - vOff) < minHgt )
            vOff = maxHgt - minHgt ;
         trgHgt = maxHgt - vOff - dvPAD ;
         trgWid = msgWidth ;

         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
         gsdbg.compose( "                   trgWid:%02hd maxWid:%02hd trgHgt:%02hd\n",
                        &trgWid, &maxWid, &trgHgt  ) ;
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ

         adjustDlgWidth ( gsStatic, maxWid, trgHgt, trindx ) ;
         if ( trindx >= ZERO )
            gsStatic.limitChars( trindx ) ;
         this->ParagraphSize ( gsStatic, msgHeight, msgWidth ) ;
         dlgHgt = msgHeight + dvMIN ;
         if ( dlgHgt > (maxHgt - vOff) ) { dlgHgt = maxHgt - vOff ; }
         dlgWid = msgWidth + dwPAD ;

         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
         gsdbg.append( 
            "                   dlgHgt:%02hd msgHgt:%02hd dlgWid:%02hd msgWid:%02hd",
            &dlgHgt, &msgHeight, &dlgWid, &msgWidth ) ;
         if ( trindx >= ZERO )
            gsdbg.append( " (trindx:%02hd)", &trindx ) ;
         ofsdbg << gsdbg.ustr() << endl ;
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ
      }  // adjust dialog height

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_ADJ != 0
      else  // Dimensions accepted as received.
      {
         gsdbg.compose( 
            "      unmodified : dlgHgt:%02hd msgHeight:%02hd dlgWid:%02hd msgWidth:%02hd",
            &dlgHgt, &msgHeight, &dlgWid, &msgWidth ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_ADJ

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_Dialog != 0
      if ( ofsdbg.is_open() )
      {
         gsdbg.compose( "        TXT5:dlgHgt:%02hd msgHeight:%02hd dlgWid:%02hd msgWidth:%02hd",
                        &dlgHgt, &msgHeight, &dlgWid, &msgWidth ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_Dialog
   }  // static text dimensioning
   else
   {
      //* Application programmer _should_ set dialog dimensions   *
      //* either directly or via dimensions of the static text;   *
      //* however, if neither static text nor dialog dimension    *
      //* specified, we arbitrarily set that dimension to half of *
      //* the available space. Size and position of dialog        *
      //* controls will be adjusted to fit that space.            *
      if ( dlgHgt <= ZERO )   { dlgHgt = maxHgt / 2 ; }
      if ( dlgWid <= ZERO )   { dlgWid = maxWid / 2 ; }
   }

   //* If a dialog title was specified, verify that text will fit *
   //* width of dialog and add title delimeters.                  *
   if ( (gsTitle.gschars()) > 1 )
      formatDlgTitle ( gsTitle, dlgWid, lType ) ;

   //* Range check number of controls. (note defensive programming)        *
   //* If control specification is invalid (or missing) we can't continue. *
   if ( (formDef == NULL) || ((formDef->fCnt < 1) || formDef->fCnt > (MAX_DLG_CTRLS)) )
   {
      gString gs = "\n"
           "     Application Error:      \n"
           "Invalid dialog configuration." ;
      this->QuickDialog ( gs, false, NULL, 7, 0, -1, -1 ) ;
      this->RefreshWin () ;
      return -1 ; // Note the early return and the return value indicating an error.
   }

   //**--------------------------------------------------------**
   //**              Validate Dialog Controls                  **
   //**              ------------------------                  **
   //** Dialog dimensions and positioning have been adjusted,  **
   //** as necessary, to accomodate the static text.           **
   //** Create a working copy of the skForm object, then       **
   //** verify that the dialog controls are properly positioned**
   //** within the dialog AND that they remain entirely within **
   //** the dialog borders. (see notes above)                  **
   //**--------------------------------------------------------**

   skfp = new skForm ( formDef->fCnt ) ;  // create the form
   *skfp = formDef ;                      // copy caller's data to our form
   skField* fptr ;                        // pointer to skField object
   short maxVOff = dlgHgt - 3,            // max vertical offset
         cntr = (dlgWid - 2) / 2,         // horiz. center offset
         maxCtrlHgt = dlgHgt - 2,         // vertical interior space
         #if 1 // no horizontal padding
         maxCtrlWid = dlgWid - 2 ;        // horizontal interior space (minus padding)
         #else // with horizontal padding
         maxCtrlWid = dlgWid - 4 ;        // horizontal interior space (minus padding)
         #endif // horizontal padding
   bool  exitRamp = false ;               // set if a Pushbutton is found

   if ( (skfp->fi < ZERO) || (skfp->fi >= skfp->fCnt) ) // range-check
      skfp->fi = ZERO ;
   // Programmer's Note: If 'faca' contains the placeholder value, then 
   // pushbuttons and radiobuttons will swap fo/bg when they gain/lose focus.
   // If non-placeholder value specified, it will be the focus attribute.
   if ( skfp->faca.acaVal != acaUSEDFLT )       // if non-placeholder value,
      skfp->faca.decode( skfp->faca.acaVal ) ;  // decode the bitfield
   skfp->base = { short(vOff + 1), short(hOff + 1) } ; // interior corner
   skfp->seli = skfp->fCnt ;              // reset
   skfp->pKey = NULLCHAR ;                // reset
   skfp->foot = false ;                   // disable
   skfp->dlg  = true ;                    // signal that container is a dialog
   skfp->togg = this->skf->togg ;         // inherit values from parent window
   skfp->icur = this->skf->icur ;
   skfp->ocur = this->skf->ocur ;
   skfp->insc = this->skf->insc ;
   skfp->togg = this->skf->togg ;
   skfp->beep = this->skf->beep ;
   // skfp->ins flag accepted as-is

   //* Examine each control *
   for ( short f = ZERO ; f < skfp->fCnt ; ++f )
   {
      fptr = &skfp->fld[f] ;              // pointer to field

      //* Validate field type. Other field types not currently supported.*
      //* Menu field configurations are limited, see note above.         *
      if ( !((fptr->type == ftPB) || (fptr->type == ftTB) || (fptr->type == ftRB) || 
             (fptr->type == ftSB) || (fptr->type == ftMB)) )
      { fptr->type = ftPB ; }

      //* For PB/RB controls, width is text width (minus caret). *
      //* For TB controls, width is: 1<=wid<=(dlgWid-2)          *
      if ( (fptr->type == ftPB) || (fptr->type == ftRB) )
      {
         fptr->hgt = 1 ;                  // fixed height
         if ( (fptr->wid = fptr->txt.gscols()) > maxCtrlWid )
         {
            fptr->txt.limitCols( maxCtrlWid ) ;
            fptr->wid = fptr->txt.gscols() ;
         }
         if ( (fptr->txt.find( CARET )) >= ZERO )
            --fptr->wid ;
         else if ( fptr->wid <= ZERO ) // no text specified (stupid, but possible)
         { fptr->type = ftPB ; fptr->txt = " PB " ; fptr->wid = 4 ; }
         if ( fptr->type == ftPB ) { exitRamp = true ; }
         else if (fptr->type == ftRB)  // color attributes for Radiobutton label
         {
            if ( fptr->lattr == acaUSEDFLT ) // if attribute not specified,
            fptr->lattr = tAttr ;            // use dialog interior color
         }
      }

      else if ( (fptr->type == ftTB) || (fptr->type == ftSB) )
      {
         //* Range check control dimensions *
         short minCtrlHgt = fptr->type == ftTB ? 1 : 2, // (rows)
               minCtrlWid = 4 ;                         // (columns)
         if ( fptr->hgt < minCtrlHgt )    //range check dimensions
            fptr->hgt = minCtrlHgt ;
         else if ( fptr->hgt > maxCtrlHgt )
            fptr->hgt = maxCtrlHgt ;
         if ( fptr->wid < minCtrlWid )
            fptr->wid = minCtrlWid ;
         else if ( fptr->wid > maxCtrlWid )
            fptr->wid = maxCtrlWid ;
         if ( fptr->type == ftTB && fptr->hgt == 1 ) // enforce 'floShift'
            fptr->flow = floShift ;
         if ( fptr->type == ftSB )        // format the scrollbox data
            this->init_Scrollbox ( fptr ) ;
         // Programmer's Note: init_Scrollbox() assumes that the control is in 
         // the parent window, so we must perform maximum-dimension check and 
         // positioning tests here.
      }

      else if ( fptr->type == ftMB )
      {
         // Note on menu configuration: The 'operator=' has copied the interesting 
         // members: 'btype', 'battr', 'lab', 'lattr'.
         // In addition, if centered label was specified, 'lpos.col' will be '-1'.
         // Else 'lpos' will contain the default: '0,0'.

         //* Range check control dimensions *
         short minCtrlHgt = 4,            // (rows) top/bottom border,and items >= 2
               minCtrlWid = 4 ;           // (columns) left/right border,and 2 columns
         if ( fptr->hgt < minCtrlHgt )
            fptr->hgt = minCtrlHgt ;
         else if ( fptr->hgt > maxCtrlHgt )
            fptr->hgt = maxCtrlHgt ;
         if ( fptr->wid < minCtrlWid )
            fptr->wid = minCtrlWid ;
         else if ( fptr->wid > maxCtrlWid )
            fptr->wid = maxCtrlWid ;
         fptr->ro   = false ;             // user accessible
         fptr->lock = true ;              // always visible
         fptr->grp  = noGROUP ;           // menu groups not allowed
         fptr->bdr  = false ;             // no external border
         fptr->flow = floShift ;          // (but shift _should_ never happen)
         this->init_Menu ( fptr ) ;       // add borders and calculate stats
      }

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_Dialog != 0
      if ( ofsdbg.is_open() )
      {
         gsdbg.compose( "        f:%02hd) hgt:%02hd wid:%02hd orig:%02hd/%02hd bot:%02hd ", 
                        &f, &fptr->hgt, &fptr->wid, &fptr->orig.row, &fptr->orig.col, &maxVOff ) ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_Dialog

      //* Positive offset from top border       *
      //* (vertical offset of 0 is not allowed) *
      if ( fptr->orig.row >= ZERO )
      {
         if ( fptr->orig.row > maxVOff ) { fptr->orig.row = maxVOff ; }
      }
      //* Negative offset from bottom border *
      else 
      {
         if ( (fptr->orig.row += maxVOff + 1) < ZERO )
            fptr->orig.row = ZERO ;
      }

      //* Test for overrun of bottom border, and if necessary, *
      //* shift the control upward.                            *
      while ( ((fptr->hgt + fptr->orig.row) > maxCtrlHgt) && (fptr->orig.row >ZERO) )
         --fptr->orig.row ;

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_Dialog != 0
      if ( ofsdbg.is_open() )
      {
         gsdbg.append( "orig.row:%02hd\n"
                       "              cntr:%02hd %+02hd == ",
                       &fptr->orig.row, &cntr, &fptr->orig.col ) ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_Dialog

      //* Centered horizontally *
      if ( fptr->orig.col == ZERO )
      {
         fptr->orig.col = cntr - (fptr->wid / 2) ;
      }
      //* Offset to right of center *
      else if ( fptr->orig.col > ZERO )
      {
         fptr->orig.col += cntr ;
      }
      //* Offset to left of center *
      else
      {
         if ( (fptr->orig.col = cntr + fptr->orig.col) < ZERO )
            fptr->orig.col = ZERO ;
      }

      //* Test for overrun of right border, and if necessary, *
      //* shift the control leftward.                         *
      while ( ((fptr->wid + fptr->orig.col) > (maxCtrlWid + 1)) && (fptr->orig.col > 1) )
         --fptr->orig.col ;

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_Dialog != 0
      if ( ofsdbg.is_open() )
      {
         gsdbg.append( "orig.col:%02hd", &fptr->orig.col ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_Dialog

      //* If default attribute specified for Fgnd or Bgnd,     *
      //* substitute the dialog interior attribute(s).         *
      if( fptr->aca.acaVal == acaUSEDFLT ) { fptr->aca.acaVal = acaATTR_DFLT ; }
      if ( (fptr->aca.acaVal & acaFG_DFLT) || (fptr->aca.acaVal & acaBG_DFLT) )
      {
         acAttr fgndAttr, bgndAttr ;      // separate the fg/bg bitfields
         if ( fptr->aca.acaVal & acaFG_DFLT )
            fgndAttr = acAttr(tAttr & acaFG_MASK) ;
         else
            fgndAttr = acAttr(fptr->aca.acaVal & acaFG_MASK) ;
         if ( fptr->aca.acaVal & acaBG_DFLT )
            bgndAttr = acAttr(tAttr & acaBG_MASK) ;
         else
            bgndAttr = acAttr(fptr->aca.acaVal & acaBG_MASK) ;
         fptr->aca.acaVal = acAttr(fgndAttr | bgndAttr) ;
      }
      //* Decode the field attributes *
      fptr->aca.decode( fptr->aca.acaVal ) ;
   }  // for(;;)

   //* At least one of the defined controls MUST BE a Pushbutton control. *
   //* If not, then there is no way for the user to exit the dialog.      *
   if ( ! exitRamp )
   {
      skfp->fld[0].type = ftPB ;          // first control becomes a pushbutton
      skfp->fld[0].txt = "  RTFM^!  " ;   // set the message text
      skfp->fld[0].aca.decode( acAttr(acaFGb_BROWN | acaBGb_RED) ) ; // set color
   }

   //* Extract the hotkey information (if any). *
   //* and format the field label for display.  *
   this->init_Hotkeys ( skfp ) ;

   //* If vertical centering of dialog specified *
   if ( vOff < ZERO )
      vOff = maxHgt / 2 - dlgHgt / 2 ;

   //* If horizontal centering of dialog specified *
   if ( hOff < ZERO )
      hOff = maxWid / 2 - dlgWid / 2 ;

   //* Window-relative offset to interior of dialog *
   skfp->base = { short(vOff + 1), short(hOff + 1) } ;

   //* Construct the ACDlg object. *
   ACDlg* dlg = new ACDlg( dlgHgt, dlgWid, vOff, hOff, skfp, gsStatic, gsTitle,
                           lType, bAttr, tAttr, tvOff, thOff, callbk ) ;
   //* Because this is a 'protected' member, we must copy it explicitly. *
   dlg->skf->dlg = skfp->dlg ;

   //* For Textbox controls, parse the text. *
   for ( short f = ZERO ; f < dlg->skf->fCnt ; ++f )
   {
      if ( dlg->skf->fld[f].type == ftTB )
      {
         if ( dlg->skf->fld[f].flow == floShift )
            this->hshiftFieldText ( dlg->skf, f, ZERO ) ;
         else
            this->formatFieldText ( dlg->skf, f ) ;
      }
   }

   //*********************************************
   //** Open the Dialog and Interact With User. **
   //**        (Watch out! They bite!)          **
   //*********************************************
   short ctrlIndx = this->genericDialog ( dlg ) ;

   //* Copy final text, binary states and item numbers *
   //* and field index to caller's form.               *
   formDef->fi = dlg->skf->fi ;
   for ( short f = ZERO ; f < skfp->fCnt ; ++f )
      formDef->fld[f] = dlg->skf->fld[f] ;

   delete skfp ;                          // delete the dynamic allocation
   delete dlg ;                           // delete the dialog object

   if ( ! dbg ) { this->RefreshWin () ; } // redraw contents of parent window

   return ctrlIndx ;

   #undef DEBUG_ADJ
   #undef DEBUG_Dialog
}  //* End Dialog() *

//*************************
//*    genericDialog      *
//*************************
//********************************************************************************
//* Private Method                                                               *
//* --------------                                                               *
//* Display the dialog window defined by the 'dlg' parameter, then wait for      *
//* user to select one of the controls and return the index of the selected      *
//* control.                                                                     *
//*                                                                              *
//* Input  : dlg: (by reference) contains dialog configuration information       *
//*          timeout: (optional, zero by default) automatic timeout of dialog    *
//*                   display in seconds                                         *
//*             -- By default, the dialog is closed when one of the pushbutton   *
//*                controls is activated.                                        *
//*             -- If a positive value (in seconds) is specified, the dialog     *
//*                will close automatically after the specified number of        *
//*                seconds has elapsed OR when any keycode is received,          *
//*                whichever comes first.                                        *
//*                                                                              *
//* Returns: index of dialog control selected by user                            *
//********************************************************************************

short ACWin::genericDialog ( ACDlg* dlg, short timeout )
{
   EchoOpt orig_echo = this->tset->inpEch ;  // application's echo option
   bool curvis = this->tset->curVis ;        // initial cursor visibility

   //* Set cursor to invisible while inside a dialog, *
   //* unless inside a dialog text control.           *
   if ( curvis )
      this->acSetCursorStyle ( csHide ) ;
   if ( orig_echo != noEcho )
      this->acSetEchoOption ( noEcho ) ;

   //* Draw the dialog window *
   this->refreshDialog ( dlg ) ;

   //* Interact with the user (filthy beasts, as they may be). *
   if ( ! timeout )
      this->editDialog ( dlg ) ;

   //* Execute a pause for the specified number of seconds.  *
   //* If a keycode is received during the pause interval,   *
   //* terminate the loop and return immediately.            *
   //* Programmer's Note: The specified timing is empirical. *
   //* For a one-second interation of the loop:              *
   //*    0.900s + 30ms + 30ms == 960ms plus overhead.       *
   //* Note that acReadNB() does not pause after the last    *
   //* iteration of it's input loop, so it pauses only 30ms. *
   else
   {
      wchar_t wkey ;
      bool orig_inpBlk = this->tset->inpBlk ;
      if ( orig_inpBlk )                  // set non-blocking read
         this->acBlockingInput ( false ) ;

      do
      {
         this->nsleep ( 9, 30 ) ;
         if ( (this->acReadNB ( wkey, 2, 30 )) ) { break ; }
      }
      while ( --timeout > ZERO ) ;

      if ( orig_inpBlk )                  // restore original setting
         this->acBlockingInput ( orig_inpBlk ) ;
   }

   if ( curvis )                          // restore original cursor visibility state
      this->acSetCursorStyle ( csShow ) ;
   if ( this->tset->inpEch != orig_echo ) // return to original echo option
      this->acSetEchoOption ( orig_echo ) ;

   return dlg->skf->fi ;                  // return index of field with focus

}  //* End genericDialog() *

//*************************
//*     refreshDialog     *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Erase and redraw the dialog window.                                          *
//*                                                                              *
//*                                                                              *
//* Input  : dlg  : pointer to fully-initialized dialog object.                  *
//*          full : (optional, 'true' by default)                                *
//*                 if 'true',  erase the area where dialog will be written,     *
//*                             then redraw both the border and interior         *
//*                 if 'false', erase and re-draw the interior of the dialog     *
//*                             border is not updated                            *
//*                                                                              *
//* Returns: 'true'                                                              *
//********************************************************************************

bool ACWin::refreshDialog ( ACDlg* dlg, bool full )
{
   bool status = true ;                   // return value

   //* The save-to-FIFO flag is reset when drawing the dialog, *
   //* and is restored when display update is complete.        *
   bool fifoFlag   = this->saveWrite ;
   this->saveWrite = false ;

   //* If full refresh, draw the dialog border. *
   if ( full )
   {
      this->DrawBox ( dlg->dBase, dlg->dHeight, dlg->dWidth, // draw the border
                      dlg->lType, dlg->bdrAttr, true ) ;
      //* If a title was specified, position it in the top border *
      if ( dlg->dTitle.gschars() > 1 )
      {
         //* Center of top border row *
         WinPos wpt( dlg->dBase.row, (dlg->dBase.col + (dlg->dWidth / 2)) ) ;
         wpt.col -= dlg->dTitle.gscols() / 2 ;
         this->Write ( wpt, dlg->dTitle, dlg->bdrAttr ) ;
      }

      //* If specified interior attribute is not the same *
      //* as interior for DrawBox(), clear the interior   *
      //* using specified attribute.                      *
      if ( dlg->txtAttr != this->txtAttr.acaVal )
      {
         WinPos wpInt( this->winBase, dlg->dBase ) ;
         wpInt.row += 2 ; wpInt.col += 2 ;
         this->acClearArea ( wpInt, (dlg->dHeight - 2), (dlg->dWidth - 2), 
                             wcsSPACE, dlg->txtAttr ) ;
      }
   }
   else
   {
      WinPos dbase( this->txtBase, dlg->dBase ) ;
      dbase.row += 1 ; dbase.col += 1 ;
      this->acClearArea ( dbase, (dlg->dHeight - 2), (dlg->dWidth - 2), 
                          wcsSPACE, dlg->txtAttr ) ;
   }

   //* Display the static text (if any). *
   if ( dlg->dText.gschars() > 1 )
      this->Write ( dlg->dtxtPos, dlg->dText, dlg->txtAttr ) ;

   //* Draw the controls. *
   this->refreshField ( dlg->skf, dlg->skf->fi, true ) ;

   this->saveWrite = fifoFlag ;           // restore the FIFO flag to original state

      return status ;

}  //* End refreshDialog() *

//*************************
//*      editDialog       *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Called by the dialog methods to get a response from the user.                *
//*                                                                              *
//* -- This method is modelled after the EditForm() method, with the major       *
//*    difference being that the application's callback is not invoked.          *
//*    Additionally, there is no "exit keycode" defined. Exit occurs when any    *
//*    pushbutton control is selected.                                           *
//* -- Pushbuttons control the user-interface loop. A Pushbutton is selected     *
//*    via the Enter key or Space key (or via associated hotkey if specified).   *
//*    The index of the selected control is returned to caller.                  *
//*    -- At least one Pushbutton control must be present and available to the   *
//*       user, so that user is able to close the dialog.                        *
//* -- Radiobutton controls are binary controls and the current state of each    *
//*    is returned in the 'set' flag for each field.                             *
//* -- Textbox controls can collect and edit text data. The data are returned    *
//*    in the 'txt' member of the control.                                       *
//*    -- Copy/Cut/Paste operations are available within Textbox controls.       *
//*    -- The Insert/Overstrike key is available when editing Textbox controls.  *
//* -- Scrollbox controls may not be edited, so contents are viewed, not changed.*
//* -- Any control may be set as read-only, meaning that it is visible, but not  *
//*    accessible to the user.                                                   *
//* -- See documentation for more information.                                   *
//*                                                                              *
//*                                                                              *
//* Input  : dlg : pointer to dialog object, including user-interface controls.  *
//*                                                                              *
//* Returns: index of the control which was activated to trigger return to caller*
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* There are some differences between the dialog user interface and editing     *
//* the skForm in the parent window (EditForm).                                  *
//* A) QuickDialog objects:                                                      *
//*    -- Callbacks to the application layer _are not_ available.                *
//*    -- Dialog has no "exit key". To close the dialog, press either of the     *
//*       defined Pushbutton controls.                                           *
//*                                                                              *
//* B) Full Dialog objects.                                                      *
//*    -- Callbacks to the application layer are possible, but not mandatory.    *
//*       -- If no callback is specified, then the dialog is automatically       *
//*          closed when any Pushbutton control is selected.                     *
//*       -- When a callback is active, the automatic closure is disabled.       *
//*          The callback must signal when to exit the dialog by returning       *
//*          the exit code: wcsCTL_4.                                            *
//*    -- If a callback is specified, then a limited interaction between the     *
//*       application and the dialog controls is available.                      *
//*       The non-member variable 'activeDlg'at the top of this module contains  *
//*       a pointer to the dialog currently under edit, and specialized methods  *
//*       provide the callback with a limited access to the data in the dialog's *
//*       controls.                                                              *
//*       a) Retrieve or modify contents of Textbox controls.                    *
//*                                                                              *
//*       b) Retrieve the item number and item data for Menu controls.           *
//*                                                                              *
//*       c) Retrieve the item number and item data for Scrollbox controls.      *
//*                                                                              *
//*       d) Retrieve the binary state of Radiobutton controls.                  *
//*                                                                              *
//********************************************************************************

short ACWin::editDialog ( ACDlg* dlg )
{
   #define DEBUG_ED_CLIP (0)     // for debugging only
   #if DEBUG_ANSICMD != 0 && DEBUG_ED_CLIP != 0
   //* Display initial clipboard contents. *
   gString gsClip ;
   this->GetClipboardText ( gsClip ) ;
   gsClip.erase( NEWLINE, ZERO, false, true ) ; // strip newlines
   gsClip.limitCols( dlg->dWidth - 8 ) ;        // limit columns
   gsClip.insert( L"CB:'" ) ;
   gsClip.append( L"'" ) ;
   WinPos wpdbg( short(dlg->dBase.row + dlg->dHeight - 1), short(dlg->dBase.col + 1) ) ;
   this->Write ( wpdbg, gsClip ) ;
   this->refreshField ( dlg->skf, dlg->skf->fi ) ; // reposition cursor
   #endif   // DEBUG_ANSICMD && DEBUG_ED_CLIP


   skField* fptr = &dlg->skf->fld[dlg->skf->fi] ; // pointer to target field
   wchar_t wkey ;                // captured keycodes
   wchar_t cbKey ;               // value returned from callback method
   wchar_t ncKey ;               // value returned from normalkeyConvert method
   short   hotIndx ;             // hotkey target index
   short   menuIndx = dlg->skf->fCnt ; // index of menu control
   FldType ftype = fptr->type ;  // field type (current field)
   bool isSpecial ;              // set if "special" key received
   bool cbCalled = false ;       // prevent double-invocation of callback
   bool done = false ;           // loop control

   //* If a callback function was specified, initialize it *
   //* and set the pointer at the top of this module for   *
   //* callback access to the dialog controls (see notes)  *
   if ( dlg->cbptr != NULL )
   {
      dlg->cbptr ( ZERO, wchar_t(ZERO), true ) ;
      activeDlg = dlg ;
   }

   //***************************
   //* Interact with the user. *
   //***************************
   while ( ! done )
   {
      wkey = this->winRead () ;
      cbCalled = false ;                  // track whether callback invoked

      //*****************************************
      //* Compare to list of "hotkey keycodes". *
      //* Hotkeys set focus to associated field.*
      //*****************************************
      // Programmer's Note: Dialog callback is still under development, and 
      // the conditional will be removed when the code is stable.
      #define DLG_CALLBACK (1)
      if ( bool((hotIndx = this->skpIsHotkey ( dlg->skf, wkey )) < dlg->skf->fCnt) )
      {
         #if DLG_CALLBACK != 0   // EditDialog-callback
         FldType ftType = dlg->skf->fld[hotIndx].type ; // target field type
         ftype = dlg->skf->fld[this->skf->fi].type ;    // local copy of current field type
         #endif   // DLG_CALLBACK

         //* If a valid hotkey has been defined, AND if     *
         //* specified target field is not current field,   *
         //* move the input focus to the associated field.  *
         //* If hotkey references the current control,      *
         //* then focus does not change.                    *
         if ( hotIndx != dlg->skf->fi )
         {
            #if DLG_CALLBACK != 0   // EditDialog-callback
            //* This invocation of the callback function alerts the  *
            //* application when a hotkey has been pressed.          *
            if ( (dlg->cbptr != NULL) && (ftType != ftPB) && (ftType != ftRB) &&
                 ((ftype != ftTB) ||
                 ((ftype == ftTB) && !((wkey == wcsCOPY) || (wkey == wcsCUT)))) )
            {
               cbKey = dlg->cbptr ( dlg->skf->fi, wkey, false ) ;
               if ( cbKey == NULLCHAR ) { continue ; }      // keycode handled by caller
               else if ( cbKey == wcsCTL_4 ) { wkey = cbKey ; break ; }    // return to caller
               else if ( cbKey != wkey ) { wkey = cbKey ; } // substitute returned value
               cbCalled = true ;
            }
            #endif   // DLG_CALLBACK

            if ( ! fptr->ro )             // if target is accessible
            {
               this->setInputFocus ( dlg->skf, hotIndx ) ;
               fptr = &dlg->skf->fld[dlg->skf->fi] ;
               ftype = fptr->type ;
            }
         }
         //* If new (or existing) field has focus and if it is  *
         //* a binary-state control, set 'wkey' to Enter, which *
         //* indicates that control has been "selected".        *
         if ( (dlg->skf->fi == hotIndx) && ((ftype == ftPB) || (ftype == ftRB)) )
            wkey = wcsENTER ;
         else
            continue ;
      }

      #if DLG_CALLBACK != 0 // EditDialog-callback
      //* If callback has been established, but not yet    *
      //* invoked, invoke it now.                          *
      //* Exceptions:                                      *
      //* 1) Copy/Cut operations require that data be on   *
      //*    the clipboard before the callback.            *
      //* 2) Note: Non-text controls associated with       *
      //*    external events require specific timing for   *
      //*    the events, so callback invoked below instead.*
      ftype = dlg->skf->fld[dlg->skf->fi].type ;
      if ( (dlg->cbptr != NULL) && !cbCalled && 
           (!((ftype == ftTB) || (ftype == ftMB)) ||
             ((ftype == ftTB) && !((wkey == wcsCOPY) || (wkey == wcsCUT)))) )
      {
         cbKey = dlg->cbptr ( dlg->skf->fi, wkey, false ) ;
         if ( cbKey == NULLCHAR ) { continue ; }      // keycode handled by caller
         else if ( cbKey == wcsCTL_4 ) { wkey = cbKey ; break ; }    // return to caller
         else if ( cbKey != wkey ) { wkey = cbKey ; } // substitute returned value 
         cbCalled = true ;
      }
      #endif   // DLG_CALLBACK

      //**************************************************
      //* Compare keycode to list of "special keycodes". *
      //**************************************************
      if ( (isSpecial = this->specialkeyTest ( wkey )) )
      {
          //* Convert certain keycodes dependent upon the   *
          //* field definitions within the skForm object.   *
          if ( (wkey = this->specialkeyConvert ( dlg->skf, wkey )) == NULLCHAR )
             continue ;
      }

      //* If Textbox control, interpret the keycode received from user. *
      if ( (fptr->type == ftTB) )
      {
         //* For non-special keycodes that nevertheless require special *
         //* handling for ftTB controls, convert to a special key.      *
         if ( !isSpecial )
         {
            if ( (ncKey = this->normalkeyConvert ( dlg->skf, wkey )) != wkey )
            { wkey = ncKey ; isSpecial = true ; }
         }

         //**************************************
         //* Perform the operation associated   *
         //* with the specified special keycode.*
         //**************************************
         if ( isSpecial && (wkey != wcsCTL_4) )
         {
            this->specialkeyProc ( dlg->skf, wkey ) ;

            #if DLG_CALLBACK != 0   // EditDialog-callback
            //* Special Case: For certain keycodes, the callback method *
            //* is called AFTER the keycode has been processed.         *
            if ( (dlg->cbptr != NULL) && !cbCalled &&
                 ((wkey == wcsCOPY) || (wkey == wcsCUT)) )
            {
               cbKey = dlg->cbptr ( dlg->skf->fi, wkey, false ) ;
               if ( cbKey == NULLCHAR ) { continue ; }    // keycode handled by caller
               else if ( cbKey == wcsCTL_4 ) { break ; }  // return to caller
            }
            #endif   // DLG_CALLBACK
         }

         //********************************************************************
         //* Else if 'wkey' is a normal (graphing/printing) character, insert *
         //* the character into the field text and update the display.        *
         //* -- Note that if an unhandled control key (CTRL+A - CTRL+Z etc.)  *
         //*    or an unhandled GUI key (ALT+A - ALT+Z etc.), it will be      *
         //*    discarded and NOT processed as a graphing character.          *
         //********************************************************************
         else if ( (wkey != NEWLINE) && (wkey != NULLCHAR) )
         { this->normalkeyProc ( dlg->skf, wkey ) ; }

         //* If an unrecognized or unhandled keycode: Alert user. *
         else if ( this->skf->beep ) { this->acBeep () ; }
      }     // textbox controls

      //* If Scrollbox control *
      else if ( fptr->type == ftSB )
      {
         //**************************************
         //* Perform the operation associated   *
         //* with the specified special keycode.*
         //**************************************
         if ( isSpecial || (wkey == wcsENTER) || (wkey == wcsSPACE) )
            this->specialkeyProc ( dlg->skf, wkey ) ;

         //* If an unrecognized or unhandled keycode: Alert user. *
         else if ( this->skf->beep ) { this->acBeep () ; }
      }  // scrollbox controls

      //* If Menu control - (context menus only, always visible) *
      else if ( dlg->skf->fld[dlg->skf->fi].type == ftMB )
      {
         //* If keycode is not a special key, it may be a menu-item     *
         //* hotkey. If so, move IP to the associated line item, then   *
         //* substitute wcsENTER key to signal that the item has been   *
         //* "selected".                                                *
         if ( !isSpecial && (wkey != wcsENTER) && (wkey != wcsSPACE) )
            wkey = this->menuHotkeyConvert ( &dlg->skf->fld[dlg->skf->fi], wkey ) ;

         //* Please Note: Even though all keycodes associated with   *
         //* ftMB controls are sent for "special" processing, menu   *
         //* controls process ONLY "special keys", NOT "normal"      *
         //* (graphing) keycodes. Exceptions:                        *
         //* a) wcsENTER and SPACE "select" the menu item.           *
         //* b) "hotkeys" embedded within the individual menu items  *
         //*    are invoked via ordinary graphing keycodes. These    *
         //*    are interpreted as command codes which select the    *
         //*    menu item by moving IP to the specified menu item.   *
         if ( isSpecial || (wkey == wcsENTER) || (wkey == wcsSPACE) )
         {
            //* Remember index of field with focus. It    *
            //* may be the target menu or another control.*
            menuIndx = dlg->skf->fi ;

            this->specialkeyProc ( dlg->skf, wkey ) ;

            #if DLG_CALLBACK != 0 // EditDialog-callback
            //* Special Case: For certain keycodes, the callback method *
            //* is called AFTER the keycode has been processed.         *
            //* Note that callback should generally ignore wcsESCAPE    *
            //* keycode because menu has closed without a selection.    *
            if ( (dlg->cbptr != NULL) && !cbCalled &&
                 ((wkey == wcsENTER) || (wkey == wcsSPACE) || (wkey == wcsESCAPE)) )
            {
               cbKey = dlg->cbptr ( menuIndx, wkey, false ) ;
               if ( cbKey == NULLCHAR ) { continue ; }    // keycode handled by caller
               else if ( cbKey == wcsCTL_4 ) { break ; }  // return to caller
            }
            #endif   // DLG_CALLBACK

            //* If the special-key has moved the focus to another field, *
            //* and menu has been closed, refresh the dialog.            *
            if ( dlg->skf->fi != menuIndx )
            {
               //* If menu losing focus was set as hidden during call to *
               //* specialkeyProc(), refresh the entire dialog interior. *
               //* Note that currently, (v:0.0.06) all dialog menus are  *
               //* locked as visible, so this will not be executed.      *
               if ( ! fptr->lock )
                  this->refreshDialog ( dlg, false ) ;
            }

         }
      }  // menubox controls

      //* If Pushbutton or Radiobutton controls                *
      //* These controls process ONLY "special keys".          *
      //* Exceptions: wcsENTER and SPACE "select" the control. *
      //*   a) If no active callback, and the selected control *
      //*      is a pushbutton, prepare to exit.               *
      //*   b) If the selected control is a radiobutton,       *
      //*      toggle the "state" of the control.              *
      else if ( (fptr->type == ftPB) || (fptr->type == ftRB) )
      {
         if ( isSpecial || (wkey == wcsENTER) || (wkey == wcsSPACE) )
         {
            //* Remember field with focus for post-processing callback invocation.*
            short focusIndex = this->skf->fi ;

            if ( wkey == wcsSPACE ) { wkey = wcsENTER ; } // consolidate selection keycodes

            //* If a callback is active, it will tell us when to  *
            //* exit the loop. Otherwise, if a Pushbutton has     *
            //* been "selected", prepare to exit the loop.        *
            if ( (dlg->cbptr == NULL) && (fptr->type == ftPB) && (wkey == wcsENTER) )
            { done = true ; }

            //**************************************
            //* Perform the operation associated   *
            //* with the special keycode.          *
            //**************************************
            else if ( (this->specialkeyProc ( dlg->skf, wkey )) )
            {
               //* Special Case: For certain keycodes, the callback method *
               //* is called AFTER the keycode has been processed.         *
               //* This allows the callback to perform the operation       *
               //* associated with the control which has focus.            *
               if ( dlg->cbptr != NULL && !cbCalled )
               {
                  cbKey = dlg->cbptr ( focusIndex, wkey, false ) ;
                  if ( cbKey == NULLCHAR ) { continue ; }    // keycode handled by caller
                  else if ( cbKey == wcsCTL_4 ) { break ; }  // return to caller
               }
            }

            //* Keycode invalid in this context. *
            else if ( this->skf->beep ) { this->acBeep () ; }
         }     // special key
         else if ( this->skf->beep ) { this->acBeep () ; }
      }  // pushbutton and radiobutton controls

      //* Else is a control type which is currently not supported. *
      //*               (this should not happen)                   *
      else
      { this->acBeep ( 2, 2 ) ; }

      fptr = &dlg->skf->fld[dlg->skf->fi] ;  // refresh field pointer
   }  // while()

   //* Reset the external-access pointer *
   if ( dlg->cbptr != NULL )
      activeDlg = NULL ;

   return dlg->skf->fi ;                  // return index of control with focus

   #undef DEBUG_ED_CLIP
}  //* End editDialog() *

//*************************
//*    Dialog_GetText     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For Textbox controls within a dialog, retrievve a copy of current text data. *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          txtData  : (by reference) receives a copy of the Textbox text       *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_GetText ( short ctrlIndx, gString& txtData )
{
   bool status = false ;

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      if ( activeDlg->skf->fld[ctrlIndx].type == ftTB )
      {
         txtData = activeDlg->skf->fld[ctrlIndx].txt ;
         status = true ;
      }
   }
   return status ;

}  //* End Dialog_GetText() *

//*************************
//*    Dialog_SetText     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* Textbox amd Scrollbox controls only.                                         *
//*  a) Replace existing text.                                                   *
//*  b) Optionally, set new base color attribute for the control.                *
//*                                                                              *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          txtData  : (by reference) text which will replace the current text  *
//*          color    : (optional, default: acaUSEDFLT)                          *
//*                     if specified this is the new color attribute for the     *
//*                     control, else control attribute is unchanged             *
//*          refresh  : (optional, default: true)                                *
//*                     if 'true',  refresh the display after update             *
//*                     if 'false', display is not refreshed                     *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_SetText ( short ctrlIndx, const gString& txtData, 
                             acAttr color, bool refresh )
{
   bool status = false ;

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      skField* fptr = &activeDlg->skf->fld[ctrlIndx] ;
      if ( fptr->type == ftTB )
      {
         //* Initialize target field with new text.              *
         //* (Newlines will be stripped from single-row fields.) *
         fptr->ip = fptr->off = ZERO ; fptr->shft = ZERO ;
         fptr->cur = { 0, 0 } ;
         fptr->txt = txtData ;
         this->formatFieldText ( activeDlg->skf, ctrlIndx ) ;

         //* If a new color attribute is specified *
         if ( color != acaUSEDFLT )
         {
            fptr->aca.decode( color ) ;

            #if 0    // Textbox controls do not have multicolor data.
            //* Release the dynamic attribution allocation *
            if ( fptr->iAttr != NULL )
            { delete [] fptr->iAttr ; fptr->iAttr = NULL ; }
            #endif   // Textbox controls do not have multicolor data.
         }

         if ( refresh )                   // make changes visible
            this->refreshField ( activeDlg->skf, ctrlIndx ) ;
//         this->refreshDialog ( activeDlg, false ) ; // make the changes visible
         status = true ;                  // return success
      }
      else if ( fptr->type == ftSB )
      {
         fptr->ip = fptr->off = ZERO ; fptr->shft = ZERO ;
         fptr->cur = { 0, 0 } ;
         fptr->txt = txtData ;
         this->init_Scrollbox ( fptr ) ;

         //* If a new color attribute is specified *
         if ( color != acaUSEDFLT )
         {
            fptr->aca.decode( color ) ;

            //* Release the dynamic attribution allocation *
            if ( fptr->iAttr != NULL )
            { delete [] fptr->iAttr ; fptr->iAttr = NULL ; }
         }

         if ( refresh )                   // make changes visible
            this->refreshField ( activeDlg->skf, ctrlIndx ) ;
         status = true ;                  // return success
      }
   }
   return status ;

}  //* End Dialog_SetText() *

//*************************
//*    Dialog_SetAttr     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* Specify an array of color attributes, one for each item (row) displayed      *
//* in the control. Only certain control types accept multi-color attributes:    *
//*  Textbox controls (ftTB)                                                     *
//*  Multi-row Menus (ftMB)                                                      *
//* Note that this method _does not_ refresh the display.                        *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          acaArray : pointer to an array of acAttr color attributes, one      *
//*                     attribute for each text row of the control.              *
//*          acaCnt   : number of attribute values in the 'rowAttr' array (>zero)*
//*                     The minimum _practical_ value is two(2).                 *
//*          refresh  : (optional, default: true)                                *
//*                     if 'true',  refresh the display after update             *
//*                     if 'false', display is not refreshed                     *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_SetAttr ( short ctrlIndx, const acAttr* acaArray, 
                             short acaCnt, bool refresh )
{
   bool status = false ;

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      skField* fptr = &activeDlg->skf->fld[ctrlIndx] ;
      status = fptr->colorConfig ( acaArray, acaCnt ) ;
      if ( refresh )                      // make changes visible
         this->refreshField ( activeDlg->skf, ctrlIndx ) ;
   }
   return status ;

}  //* End Dialog_SetAttr() *

//*************************
//*     Dialog_Scroll     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For controls with a visible cursor position: Textbox (ftTB), Scrollbox (ftSB)*
//* or Context Menus (ftMB) only.                                                *
//* Textbox Controls:                                                            *
//* Scroll horizontally, right or left, one character per step.                  *
//* Scrollbox Controls:                                                          *
//* Scroll vertically, up or down, one line per step.                            *
//* Menu Controls:                                                               *
//* Scroll vertically, up or down, one item per step. Wrap-around will not occur.*
//* Note that only "context" menus (multi-row, stand-alone menus) are supported  *
//* by this method.                                                              *
//*                                                                              *
//* Scrolling is managed by the control's 'ip' (Insertion Point) member.         *
//* Position of the highlight (Menus or Scrollbox controls) or visible cursor    *
//* (Textbox controls) is calculated based on the value of 'ip'.                 *
//* Step value is range checked to prevent the insertion point from overruning   *
//* the data.                                                                    *
//*                                                                              *
//* The 'head' and 'tail' flags override the value in 'steps', and the 'head'    *
//* flag overrides the 'tail' flag.                                              *
//*                                                                              *
//* Special case: If both @t{'head'} and @t{'tail'} are reset and if @t{'steps'} *
//* is set to zero, then the current position is reported, but not modified.     *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          steps    : (by reference) number of steps to scroll                 *
//*                     This value is ignored if 'head' or 'tail' flag is set.   *
//*                       scroll == 0, no scrolling will occur, but on return,   *
//*                                    value will be _current_ item/IP.          *
//*                       scroll >  0, scroll forward toward tail of data        *
//*                       scroll <  0, scroll backward toward head of data       *
//*          head     : (optional, default: false) if specified, move to head    *
//*                     of data (overrides value in 'steps')                     *
//*          tail     : (optional, default: false) if specified, move to tail    *
//*                     of data (overrides value in 'steps')                     *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*                 On return, 'steps' parameter will contain the currently      *
//*                 referenced item number, or for Textbox controls, the IP.     *
//*                                                                              *
//*          else 'false' ('steps' is not modified)                              *
//********************************************************************************

bool ACWin::Dialog_Scroll ( short ctrlIndx, int& steps, bool head, bool tail )
{
   bool status = false ;

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      skField* fptr = &activeDlg->skf->fld[ctrlIndx] ;
      int ni = fptr->txt.gschars() - 1 ;  // index the null terminator
      FldType ftype = fptr->type ;

      //* Validate the type of target control *
      if ( !((ftype == ftTB) || (ftype == ftSB) || 
             ((ftype == ftMB) && (fptr->grp == noGROUP) && (fptr->hgt > 1))) )
         return status ;                  // note the early return

      //* If a position request only *
      if ( (steps == ZERO) && !head && !tail )
      {
         status = true ;                  // declare success
         if ( ftype == ftTB )             // Textbox
            steps = fptr->ip ;
         else if ( (ftype == ftSB) || (ftype == ftMB) ) // Scrollbox or Context Menu
         {
            gString gs ;
            short   itemNum ;
            if ( (activeDlg->skf->fld[ctrlIndx].getItem ( gs, itemNum, false )) )
               steps = itemNum ;
         }
         return status ;                  // note the early return
      }

      if ( fptr->type == ftTB )
      {
         //* Calculate the target IP value, then calculate the    *
         //* number of horizontal steps needed to make it visible.*
         // Programmer's Note: This calculation is intended ONLY for single-row 
         // Textboxes, although it would PROBABLY also work for multi-row controls.
         status = true ;                  // provisional success
         int trgIp ;
         if ( head )       { trgIp = ZERO ; }
         else if ( tail )  { trgIp = ni ; }
         else if ( steps != ZERO )
         {
            trgIp = fptr->ip + steps ;
            if ( trgIp < ZERO )     { trgIp = ZERO ; }
            else if ( trgIp > ni )  { trgIp = ni ; }
         }
         else                             // invalid parameter(s)
            status = false ;
         if ( status )
         {
            //* If requested shift != zero *
            if ( (steps = trgIp - fptr->ip) != ZERO )
            {
               this->hshiftFieldText ( activeDlg->skf, ctrlIndx, steps ) ;
               steps = fptr->ip ;
            }
            else                          // position not changed
               status = false ;
         }

      }
      else if ( fptr->type == ftSB )
      {
         //* Calculate steps from current position to top of data.*
         if ( head )      { steps = -(fptr->mrp.rcur) ; }
         //* Calculate steps from current position to last data item.*
         else if ( tail ) { steps = fptr->mrp.rlst - fptr->mrp.rcur ; }
         else                             // move specified number of steps
         { /* Called method does the range check.*/ }
         if ( steps != ZERO )             // scroll to the target position
            status = this->scrollFieldText ( activeDlg->skf, ctrlIndx, steps ) ;
         steps = fptr->mrp.rcur ;         // return the current position
      }
      else if ( fptr->type == ftMB )
      {
         //* Calculate steps from current position to first *
         //* data item. (This excludes the top border.)     *
         if ( head )
            steps = -(fptr->mrp.rcur) + 1 ;
         //* Calculate steps from current position to last  *
         //* data item. (This excludes the bottom border.)  *
         else if ( tail )
            steps = fptr->mrp.rlst - fptr->mrp.rcur - 1 ;
         else                             // move specified number of steps
         { /* Called method does the range check.*/ }
         if ( steps != ZERO )             // scroll to the target position
            status = this->scrollFieldText ( activeDlg->skf, ctrlIndx, steps ) ;
         steps = fptr->mrp.rcur ;         // return the current position
      }
   }
   return status ;

}  //* End Dialog_Scroll() *

//*************************
//*     Dialog_Write      *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* Write one line of text to the interior of the dialog.                        *
//*   a) Text will be truncated if necessary to fit within the available space.  *
//*   b) Static text may not occupy the same space as a control object.          *
//*      If it does, the text will be obscured by the control.                   *
//*   c) Note that the text is written directly to the display, and neither      *
//*      the text nor the color attribute is stored.                             *
//*                                                                              *
//* Input  : txt    : text to be written to dialog interior.                     *
//*                   Either: wchar_t* or char*                                  *
//*          yOff   : position: offset in rows from top of dialog interior       *
//*                   with zero(0) being the top row.                            *
//*          xOff   : position: offset in columns from left edge of dialog       *
//*                   Note that all dialog text is automatically indented by     *
//*                   one column from both the left and right borders, so an     *
//*                   xOff value of zero will display text starting in column 1. *
//*          clear  : (optional, default: false) clear the target row before     *
//*                   writing the data.                                          *
//*          color  : (optional, default: acaUSEDFLT)                            *
//*                   if specified this is the color attribute used to write     *
//*                   the text. Otherwise, text will be written using the        *
//*                   dialog's interior attribute.                               *
//*                                                                              *
//* Returns: 'true' if a dialog is currently under edit                          *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_Write ( const wchar_t* txt, short yOff, short xOff, 
                           bool clear, acAttr color )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback *
   if ( activeDlg != NULL )
   {
      gString gs( txt ) ;                 // working copy of text data
      acAttr txtAttr = (color == acaUSEDFLT ? activeDlg->txtAttr : color) ;
      WinPos wp( activeDlg->dtxtPos ) ;   // upper left of dialog interior (with X indent)
      short maxVOff   = activeDlg->dHeight - 3 ;// max vertical offset
      short availCols = activeDlg->dWidth - 4 ; // total display columns available
      gs.erase( NEWLINE, ZERO, false, true ) ;  // strip newlines
      gs.limitCols( availCols ) ;               // truncate to fit available space
      short maxHOff = availCols - gs.gscols() ; // max horizontal offset for text width

      //* Range Check *
      if ( yOff < ZERO )
         yOff = ZERO ;
      else if ( yOff > maxVOff )
         yOff = maxVOff ;
      if ( xOff < ZERO ) 
         xOff = ZERO ;
      else if ( xOff > availCols )
         xOff = availCols ;
      //* If (additional) truncation is necessary *
      if ( xOff > maxHOff )
         gs.limitCols( availCols - xOff ) ;

      //* If specified, clear the target line *
      if ( clear )
      {
         gString gsClr ;
         WinPos wpClr( short(wp.row + yOff), short(wp.col - 1) ) ;
         gsClr.padCols( availCols + 2 ) ;
         this->Write ( wpClr, gsClr, activeDlg->txtAttr ) ;
      }
      wp.row += yOff ;                    // add the offset for message start
      wp.col += xOff ;

      if ( (gs.gscols()) > ZERO )         // write message to dialog window
         this->Write ( wp, gs, txtAttr ) ;
      status = true ;                     // return success
   }
   return status ;

}  //* End Dialog_SetText() *
bool ACWin::Dialog_Write ( const char* txt, short yOff, short xOff, bool clear, acAttr color )
{
   gString gs( txt ) ;
   return ( (this->Dialog_Write ( gs.gstr(), yOff, xOff, clear, color )) ) ;

}  //* End Dialog_SetText() *

//*************************
//*    Dialog_Refresh     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* Refresh (redraw) the dialog. This method may be called from within the       *
//* application-level callback to restore the display in case the dialog has     *
//* been obscured by another operation.                                          *
//*                                                                              *
//* If the parent window has also been obscured by the operation, refresh the    *
//* parent window first, then refresh the dialog window.                         *
//*                                                                              *
//* Note: Any text written to the interior of the dialog by the Dialog_Write()   *
//*       method will be erased. The original static text (if any) of the        *
//*       dialog will be restored.                                               *
//*                                                                              *
//* Input  : full  : (optional, default: false)                                  *
//*                  if 'false' redraw the interior of the dialog only           
//*                             this includes the static text data and           *
//*                             all controls                                     *
//*                  if 'true', redraw the dialog border, then redraw            
//*                             the dialog interior                              *
//*                                                                              *
//* Returns: 'true' if a dialog is currently under edit, and refresh successful  *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_Refresh ( bool full )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback *
   if ( activeDlg != NULL )
   {
      status = this->refreshDialog ( activeDlg, full ) ;
   }
   return status ;

}

//*************************
//*    Dialog_GetItem     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For Menus and Scrollbox controls within a dialog, the user can select an     *
//* "item" using the Enter or Space key. For the user-selected item, this method *
//* returns the item number and the item text.                                   *
//* 1) An "item" is the data in the text under the cursor.                       *
//* 2) The "item number" is the 0-based index of the of the row containing the   *
//*    item.                                                                     *
//*    a) The optional 'select' parameter specifies whether the "selected"       *
//*       item (if any) should be returned or if the highlighted item should     *
//*       be returned.                                                           *
//*       A "selected" item is an item for which the user has highlighted an     *
//*       item  and then pressed the Enter or Space key.                         *
//* 3) The "item text" is:                                                       *
//*    a) For Menu controls, this is the display text _excluding_ the border     *
//*       characters, any newline character and leading/trailing whitespace.     *
//*         Example: "│Clear Bookmarks  │\n" becomes                             *
//*                  "Clear Bookmarks"                                           *
//*       If the menu item includes a 'hotkey indicator' it will be displayed as:*
//*                  "Clear ^Bookmarks"                                          *
//*    b) For Scrollbox controls, this is the display text _excluding_ the       *
//*       newline character and any leading/trailing whitespace.                 *
//*         Example: " The cow in the meadow goes moo.     \n" becomes           *
//*                  "The cow in the meadow goes moo."                           *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          itemText : (by reference) receives text of the highlighted line item*
//*          itemNum  : (by reference) receives the (0-based) item number        *
//*                     of the highlighted item (or 'selected' item)             *
//*          select   : (optional, default: true)                                *
//*                     if 'true'  return the user 'selected' item (if any)      *
//*                                else, return highlighted item                 *
//*                     if 'false' return the highlighted item                   *
//*          clear    : (optional, default: false)                               *
//*                     if 'true'  clear (reset any current "selection" and      *
//*                                refresh the control to erase the selection    *
//*                                indicator.                                    *
//*                     if 'false' selection is not modified                     *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*          else 'false'                                                        *
//********************************************************************************
//* Note on the 'clear' parameter:                                               *
//* There is a timing issue for clearing the 'sitem' member. If this method is   *
//* called from within a callback in response to selection keystroke, then the   *
//* actual selection has not yet occurred. In this case, the callback would      *
//* need to prevent the selection by returning an alternate keycode instead of   *
//* the wcsENTER/wcsSPACE. Suggestions: return NULLCHAR to indicate that the     *
//* keycode has been handled, or return wcsTAB to exit the field.                *
//********************************************************************************

bool ACWin::Dialog_GetItem ( short ctrlIndx, gString& itemText, 
                             short& itemNum, bool select, bool clear )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      if ( (activeDlg->skf->fld[ctrlIndx].type == ftMB) ||
           (activeDlg->skf->fld[ctrlIndx].type == ftSB) )
      {
         if ( (activeDlg->skf->fld[ctrlIndx].getItem ( itemText, itemNum, select )) )
            status = true ;                  // return success
         if ( clear )
         {
            activeDlg->skf->fld[ctrlIndx].sitem = -1 ;
            this->refreshField ( activeDlg->skf, ctrlIndx ) ;
         }
      }
   }
   return status ;

}  //* End Dialog_GetItem() *

//*************************
//*    Dialog_SetItem     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For Menus and Scrollbox controls within a dialog: set the "selected item"    *
//* explicitly. This will draw user's attention to the item and indicate the     *
//* current selection.                                                           *
//* The target control must not have input focus when this method is called.     *
//* If successful, control is refreshed to visually indicate the selected item.  *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          itemNum  : index (0-based) of the item to be set as selected        *
//*                     Note: If value is out-of-range, then nearest _valid_     *
//*                           item will be selected.                             *
//*          refresh  : (optional, default: true)                                *
//*                     if 'true'  refresh the display                           *
//*                     if 'false' display is not updated                        *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*                     c) the target control _does not_ have input focus        *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_SetItem ( short ctrlIndx, short itemNum, bool refresh )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) &&
        (ctrlIndx != activeDlg->skf->fi) )
   {
      skField* fptr = &activeDlg->skf->fld[ctrlIndx] ;
      FldType ftype = fptr->type ;

      if ( itemNum < ZERO )               // silently correct stupidity
         itemNum = ZERO ;

      if ( ftype == ftSB )                // Scrollbox control
      {
         if ( itemNum > fptr->mrp.rlst )  // max item
            itemNum = fptr->mrp.rlst ;
         fptr->ip = fptr->mrp.roff[itemNum] ; // set the IP
         fptr->sitem = itemNum ;          // initialize 'selected item' member
         this->parseFieldText ( fptr ) ;
         if ( refresh )
            this->refreshCtrl ( activeDlg->skf, ctrlIndx, false ) ;
         status = true ;                  // success
      }
      else if ( (ftype == ftMB) && (fptr->grp == noGROUP) && (fptr->hgt > 1) )
      {
         if ( itemNum >= fptr->mrp.rlst ) // max item
            itemNum = fptr->mrp.rlst - 1 ; // last item (not incl. bottom border)
         fptr->ip = fptr->mrp.roff[itemNum + 1] ; // set the IP
         fptr->sitem = itemNum ;          // initialize 'selected item' member
         this->parseFieldText ( fptr ) ;
         if ( refresh )
            this->refreshCtrl ( activeDlg->skf, ctrlIndx, false ) ;
         status = true ;                  // success
      }
   }
   return status ;

}  //* End Dialog_SetItem() *

//*************************
//*    Dialog_GetField    *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For controls within a dialog, return a pointer to the specified control      *
//* object. This is a const pointer, meaning that the public data may be         *
//* analyzed, but no data within the object may be modified.                     *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          skfPtr   : (by reference) receives a pointer to _constant_ skField  *
//*                     object. Access to all public data members is available.  *
//*                     Access to 'const' methods (if any) is also available.    *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_GetField ( short ctrlIndx, const skField*& skfPtr )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      skfPtr = &activeDlg->skf->fld[ctrlIndx] ;
      status = true ;                     // return success
   }
   return status ;

}  //* End Dialog_GetField() *

//*************************
//*     Dialog_Hilite     *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For Textbox controls only (ftTB), highlight a segment of text                *
//* using one of the text-modifier attributes.                                   *
//* If the target field is currently visible, it will be refreshed before return.*
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          set   : if set, highlighted the text segment                        *
//*                  if reset, clear highlight                                   *
//*          begin : index of first (leftmost) character to highlight            *
//*                  (ignored if !set)                                           *
//*          len   : number of characters in highlighted segment                 *
//*                  Note: If segment spans rows, be sure to include             *
//*                        the newlines in the count.                            *
//*                  (ignored if !set)                                           *
//*          txtmod: (optional, default:acaUSEDFLT)                              *
//*                  text modifier attribute for highlighted text segment        *
//*                  - If specified, text modifier attribute for highlighted     *
//*                    text segment. One of the following:                       *
//*                     acaBOLD      bold-text modifier                          *
//*                     acaITALIC    italic-text modifier                        *
//*                     acaUNDERLINE underline-text modifier                     *
//*                     acaOVERLINE  overline-text modifier                      *
//*                     acaXOUT      x-out (strikethrough) text modifier         *
//*                     acaBLINK     blinking-text modifier                      *
//*                     acaCONCEAL   concealed (invisible) text                  *
//*                     acaREVERSE   reversed fgnd/bkgnd (default)               *
//*                     Note: OR'd combinations of these modifiers not allowed.  *
//*                  - If not specified, then use previously-specified modifier  *
//*                    (if any), else acaREVERSE (fg/bg attributes reversed)     *
//*          center: (optional, default: true)                                   *
//*                  if set, assure that highlight is within visible space by    *
//*                          adjusting the IP if necessary.                      *
//*                  if reset, do not adjust IP (highlighted text may or may not *
//*                          be visible).                                        *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*                     c) highlight set/cleared                                 *
//*          'false' if: a) 'fIndx' references an upsupported control type       *
//*                      b) parameter out-of-range                               *
//********************************************************************************

bool ACWin::Dialog_Hilite ( short ctrlIndx, bool set, int begin, int len, 
                            acAttr txtmod, bool center )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) &&
        (activeDlg->skf->fld[ctrlIndx].type == ftTB) )
   {
      status = this->hilightFieldText ( activeDlg->skf, ctrlIndx, set, begin, len, 
                                        txtmod, center ) ;
   }
   return status ;

}  //* End Dialog_Hilite() *

//*************************
//*    Dialog_SetFocus    *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For controls within a dialog, set the input focus to the specified control.  *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*                     c) target control is not set as read-only                *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_SetFocus ( short ctrlIndx )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, and if *
   //* the index is within range, AND if target not set as read-only. *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) &&
        (!activeDlg->skf->fld[ctrlIndx].ro) )
   {
      if ( ctrlIndx != activeDlg->skf->fi )  // if not already focused on target
      {
         if ( (this->setInputFocus ( activeDlg->skf, ctrlIndx )) == ctrlIndx )
            status = true ;
      }
      else
         status = true ;
   }
   return status ;

}  //* End Dialog_SetFocus() *

//*************************
//*    Dialog_GetState    *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For binary-state controls within a dialog, return the current 'state'        *
//* flag value.                                                                  *
//* Currently, the binary state flag applies only to Radiobutton controls.       *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          rbState  : (by reference) receives the current state of the control *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_GetState ( short ctrlIndx, bool& rbState )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      if ( activeDlg->skf->fld[ctrlIndx].type == ftRB )
      {
         rbState = activeDlg->skf->fld[ctrlIndx].state ;
         status = true ;                  // return success
      }
   }
   return status ;

}  //* End Dialog_GetState() *

//*************************
//*  Dialog_GetStateXOR   *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For binary-state controls within a dialog which are configured as XOR group  *
//* members, return the index of the control whose binary state is 'set'.        *
//* If the target is not a member of an XOR group, then the Dialog_GetState()    *
//* method should be called instead.                                             *
//*                                                                              *
//* Currently, the binary state flag applies only to Radiobutton controls.       *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          rbIndx   : (by reference) receives index of XOR group member        *
//*                     whose state flag is currently  set                       *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*                     c) the target control is a member of an XOR group        *
//*                 'rbIndx' receives the index of group member whose flag is set*
//*          else 'false' ('rbIndx' receives 'fCnt')                             *
//********************************************************************************

bool ACWin::Dialog_GetStateXOR ( short ctrlIndx, short& rbIndx )
{
   wchar_t grp ;                          // group identifier
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, *
   //* and if the index is within range.                       *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) )
   {
      //* If target control defined for binary state, and *
      //* control is a member if an XOR group.            *
      if ( (activeDlg->skf->fld[ctrlIndx].type == ftRB) &&
           ((grp = activeDlg->skf->fld[ctrlIndx].grp) != noGROUP) )
      {
         //* Get the state flag of the target *
         if ( activeDlg->skf->fld[ctrlIndx].state )
         { rbIndx = ctrlIndx ; status = true ; }   // return success

         //* If the target's state IS NOT set, then scan the other    *
         //* members of the group to find the one whose state is set. *
         //* Technical Note: The library is designed such tha exactly *
         //* one control of the group is in the 'set' state; however, *
         //* we return success _only_ if that control is identified.  *
         else
         {
            rbIndx = activeDlg->skf->fCnt ;        // initialize caller's parameter
            for ( short f = ZERO ; f < activeDlg->skf->fCnt ; ++f )
            {
               if ( (activeDlg->skf->fld[f].type == ftRB) &&
                    (activeDlg->skf->fld[f].grp == grp) &&
                    (activeDlg->skf->fld[f].state != false) )
               {
                  rbIndx = f ;
                  status = true ;
                  break ;
               }
            }
         }
      }
   }
   return status ;

}  //* End Dialog_GetStateXOR() *

//*************************
//*  Dialog_SetReadOnly   *
//*************************
//********************************************************************************
//* Important Note: This method is active _only_ when a dialog (ACDlg object)    *
//* is open; and _only_ if a callback method was specified when the dialog       *
//* was created. See documentation for details.                                  *
//*                                                                              *
//* For controls within a dialog, set or reset the read-only flag..              *
//* If the read-only flag is set, then control cannot receive the input focus.   *
//*                                                                              *
//* Input  : ctrlIndx : index of target control within the dialog                *
//*          read_only: if 'true',  set field as read-only                       *
//*                                 (cannot receive input focus)                 *
//*                     if 'false', reset the read-only flag                     *
//*                                 (can receive input focus)                    *
//*                                                                              *
//* Returns: 'true' if: a) a dialog is currently under edit,                     *
//*                     b) 'ctrlIndx' refrences a control of the correct type    *
//*                        for the operation                                     *
//*                     c) target control does not currently have input focus    *
//*          else 'false'                                                        *
//********************************************************************************

bool ACWin::Dialog_SetReadOnly ( short ctrlIndx, bool read_only )
{
   bool status = false ;                  // return value

   //* If there is currently a dialog with an active callback, and if *
   //* the index is within range, AND if target not set as read-only. *
   if ( (activeDlg != NULL) && 
        (ctrlIndx >= ZERO) && (ctrlIndx < activeDlg->skf->fCnt) &&
        (ctrlIndx != activeDlg->skf->fi) )
   {
      activeDlg->skf->fld[ctrlIndx].ro = read_only ;
      status = true ;
   }
   return status ;

}  //* End Dialog_SetReadOnly() *

//*************************
//*    adjustDlgWidth     *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* Calculate the optimal width for the static text of the dialog and format     *
//* the text to the calculated dimensions.                                       *
//*                                                                              *
//* Input  : gsTxt     : (by reference) source text to be formatted              *
//*          maxWidth  : maximum horizontal space available                      *
//*          trgHeight : target dialog height                                    *
//*          trindx    : (by reference) receives the index at which the text     *
//*                      needs to be truncated                                   *
//*                      (a negative value indicates that no truncation needed)  *
//*                                                                              *
//* Returns: 'true' if caller will need to truncate the text to fit dialog.      *
//*          'false' if all text fits within the specified space.                *
//********************************************************************************

static bool adjustDlgWidth ( gString& gsTxt, short maxWidth, short trgHeight, int& trindx )
{
   const short loopy = 8 ;                // loop control
   gString gsTmp = gsTxt ;                // unmodified text
   short availTwid = maxWidth - dwPAD,    // maximum text width
         wstep = availTwid / loopy,       // width step value
         trgWidth ;                       // target text width

   for ( short mulval = 2 ; mulval <= loopy ; ++mulval )
   {
      if ( (trgWidth = wstep * mulval) > availTwid )
      { trgWidth = availTwid ; mulval = loopy ; }
      else if ( mulval == loopy )
      { trgWidth = availTwid ; }

      gsTxt.formatParagraph ( trgHeight, trgWidth, false, false, &trindx ) ;
      if ( (trindx < ZERO) || (mulval == loopy) )
         break ;
      gsTxt = gsTmp ;
   }
   return ( bool(trindx >= ZERO) ) ;

}  //* End adjustDlgWidth() *

//*************************
//*   adjustDlgHeight     *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* Calculate the optimal height for the static text of the dialog and format    *
//* the text to the calculated dimensions.                                       *
//*                                                                              *
//* Input  : gsTxt     : (by reference) source text to be formatted              *
//*          maxHeight : maximum vertical space available                        *
//*          trgWidth  : target dialog width                                     *
//*          trindx    : (by reference) receives the index at which the text     *
//*                      needs to be truncated                                   *
//*                      (a negative value indicates that no truncation needed)  *
//*                                                                              *
//* Returns: 'true' if caller will need to truncate the text to fit dialog.      *
//*          'false' if all text fits within the specified space.                *
//********************************************************************************

static bool adjustDlgHeight ( gString& gsTxt, short maxHeight, short trgWidth, int& trindx )
{
   const short loopy = 6 ;                // loop conntrol
   gString gsTmp = gsTxt ;                // unmodified text
   short availThgt = maxHeight - dvPAD,   // maximum text height
         hstep = availThgt / loopy,       // height step value
         trgHeight ;                      // target text height

   for ( short mulval = 2 ; mulval <= loopy ; ++mulval )
   {
      if ( (trgHeight = hstep * mulval) > availThgt )
      { trgHeight = availThgt ; mulval = loopy ; }
      else if ( mulval == loopy )
      { trgHeight = availThgt ; }

      gsTxt.formatParagraph ( trgHeight, trgWidth, false, false, &trindx ) ;
      if ( (trindx < ZERO) || (mulval == loopy) )
         break ;
      gsTxt = gsTmp ;
   }
   return ( bool(trindx >= ZERO) ) ;

}  //* End adjustDlgHeight() *

//*************************
//*    formatDlgTitle     *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* Format the specified text as the dialog title.                               *
//*   a) Verify that width of text is less than width of window.                 *
//*      Width must be at least six(6) columns less that the dialog with.        *
//*   b) Insert delimiter characters at both ends of text.                       *
//*      These are right and left T characters matching the dialog line type.    *
//*                                                                              *
//* Input  : gsTit  : (by reference) title text to be formatted                  *
//*          dlgWid : width of dialog in columns                                 *
//*          ltype  : member of enum LineType (ltSINGLE or ltDUAL only)          *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

static void formatDlgTitle ( gString& gsTit, short dlgWid, LineType ltype )
{
   //* Specify title delimiters based on border line type *
   wchar_t lt = wcsLTEEs, rt = wcsRTEEs ; // single-line delimeters (default)
   if ( ltype == ltDUAL )                 // dual line delimeters
   { lt = wcsLTEEd ; rt = wcsRTEEd ; }

   if ( (gsTit.gscols()) > (dlgWid - 6) ) // truncate if necessary
      gsTit.limitCols( dlgWid - 6) ;

   gsTit.insert( rt ) ;                   // insert delimeters
   gsTit.append( lt ) ;

}  //* End formatDlgTitle() *

//*************************
//*         ACDlg         *
//*************************
//********************************************************************************
//* Initialization constructor.                                                  *
//* Called by the ACWin object (QuickDialog and Dialog methods).                 *
//* Not directly accessible from the application.                                *
//*                                                                              *
//* Caller must verify that dialog dimensions are large enough to enclose the    *
//* specified controls and the static text. Range-checking is not performed here.*
//*                                                                              *
//* Input  : hgt  : height of dialog in rows. Dialog height is limited to height *
//*                 of the parent window's interior.                             *
//*          wid  : width of dialog in columns. Dialog width is limited to width *
//*                 of the parent window's interior.                             *
//*          vOff : vertical offset from top of parent window                    *
//*                 zero-based offset from top row of window interior            *
//*          hOff : horizontal offset from left of parent window                 *
//*                 zero-based offset from left column of window interior        *
//*          form : pointer to an initialized skForm object describing the       *
//*                 user-interface controls.                                     *
//*                 Note that control positions have been initialized to         *
//*                 window-relative offsets.                                     *
//*          text : (by reference) static text (if any) to be displayed in dialog*
//*          title: (by reference) dialog title text (if any)                    *
//*          lType: (optional, ltSINGLE by default)                              *
//*                 border type, member of enum LineType (ltSINGLE or ltDUAL)    *
//*          bAttr: (optional, default: acaUSEDFLT, use attributes of parent)    *
//*                 border color attributes                                      *
//*          tAttr: (optional, default: acaUSEDFLT, use attributes of parent)    *
//*                 text (interior) color attributes                             *
//*          tVoff: (optional, default: zero) vertical offset for static text    *
//*          tHoff: (optional, default: 1) horizontal offset for static text     *
//*          callbk:(optional, default: null pointer)                            *
//*                 pointer to callback function                                 *
//*                                                                              *
//* Returns: implicitly returns pointer to object                                *
//********************************************************************************

ACDlg::ACDlg ( short hgt, short wid, short vOff, short hOff, const skForm* form, 
               const gString& text, const gString& title, LineType lnType, 
               acAttr bAttr, acAttr tAttr, short tVOff, short tHOff, CUPTR callbk )
{
   this->dHeight = hgt ;
   this->dWidth  = wid ;
   this->dBase   = { vOff, hOff } ;
   this->dText   = text ;
   this->dTitle  = title ;
   this->lType   = lnType ;
   this->bdrAttr = bAttr ;
   this->txtAttr = tAttr ;
   this->cbptr   = callbk ;

   //* Convert text offset from dialog-relative to window-relative. *
   this->dtxtPos = { short(this->dBase.row + 1 + tVOff), 
                     short(this->dBase.col + 1 + tHOff) } ;

   //* Copy caller's form to the local form. *
   this->skf = new skForm( form->fCnt ) ;
   *this->skf = form ;

}  //* End ACDlg() *

//*-----------------------*
#endif   //* INCLUDE_API  *
//*-----------------------*

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

