//********************************************************************************
//* File       : AnsiCmdWix.cpp                                                  *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2022-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice below                               *
//* Date       : 14-Mar-2026                                                     *
//* Version    : (see AnsiCmdVersion string in AnsiCmd.cpp)                      *
//*                                                                              *
//* Description: ACWin class implementation. ACWin is derived from the           *
//* AnsiCmd class and defines a high-level "window" object with user-interaction *
//* implemented via the skForm and skField user-interface objects.               *
//* This module is an annex to the main AnsiCmdWin.cpp module which had grown    *
//* to 8,500 lines (incl. comments) which the author considers too large for     *
//* convenient maintenance.                                                      *
//********************************************************************************

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

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

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

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

//********************
//* Local prototypes *
//********************


//*************************
//*        isPrint        *
//*************************
//********************************************************************************
//* A locale-aware version of the 'std:isprint' function.                        *
//*                                                                              *
//* The locale-aware version of 'isprint' takes into account all characters      *
//* of all languages supported by the Unicode standard. This means that          *
//* whatever input language is being used for the application, all characters    *
//* of that language will be handled as expected.                                *
//*                                                                              *
//* Space L' ', Enter L'\n' and Tab '\t' keycodes:                               *
//* ----------------------------------------------                               *
//* Canon says that these and similar keycodes are classed as whitespace         *
//* rather than graphing characters; however, canon is wrong on this issue.      *
//* This method recognizes these as graphing characters simply because           *
//* when written, they cause updates to the display.                             *
//* (How hard is that to comprehend?)                                            *
//*                                                                              *
//* Keycodes reserved by the AnsiCmd library:                                    *
//* -----------------------------------------                                    *
//* A small range of Unicode codepoints is reserved by the library for           *
//* assignment to decoded ANSI escape sequence, control codes "conditioned"      *
//* keys (ex: CTRL+DownArrow), and ALT-key combinations.                         *
//* These reserved keycodes consist primarily of arrows, geometric shapes,       *
//* and typographic symbols. These keycodes are not integral to any written      *
//* language, and should seldom, if ever be needed in daily use.                 *
//*                                                                              *
//* The range of keycodes in this group are technically graphing characters      *
//* but have been reassigned by the AnsiCmd library, and will not be reported    *
//* as graphing characters by this method.                                       *
//* The majority of these are assigned to:                                       *
//*   wcsALT_A - wcsALT_RB                                                       *
//*   wcsCTL_A - wcsCTL_7                                                        *
//*   wcsF01   - wcsCF12                                                         *
//*   various "conditioned" keys, cursor-movement keys, etc.                     *
//* The exclusion range is defined by the constants RESKEY_MIN and RESKEY_MAX    *
//* which correspond to keycode assignments wcsF01 through wcsALT_RB.            *
//* Not all keycodes within this range are currently assigned. Some are          *
//* reserved for future development.                                             *
//*                                                                              *
//* Defined line-drawing characters, bullet characters and similar fall          *
//* outside the reserved range, and therefore ARE reported as graphing           *
//* characters.                                                                  *
//*                                                                              *
//* If a keycode or keycodes within the reserved range is needed for a           *
//* specific project, this should not be a problem because none of these         *
//* keycodes is associated with any specific keyboard key for any language,      *
//* so the AnsiCmd user-interface methods will never see it.                     *
//*                                                                              *
//* It may be educational to study the full Unicode character table which        *
//* can be found at: https://www.utf8-chartable.de/                              *
//*                                                                              *
//* Input  : keycode to be tested                                                *
//*                                                                              *
//* Returns: true if a "graphing" character, else 'false'                        *
//********************************************************************************

bool ACWin::isPrint ( wchar_t wkey )
{
   bool graphing = false ;                // return value

   if ( (wkey == wcsENTER) || (wkey == SPACE) ||
        (wkey == wcsTAB)   || (wkey == L'\t') )
   { graphing = true ; }
   else if ( !((wkey >= RESKEY_MIN) && (wkey <= RESKEY_MAX)) &&
              (isprint ( wkey, *this->ioLocale )) )
   { graphing = true ; }

   return graphing ;

}  //* End isPrint() *

//*************************
//*       ResizeWin       *
//*************************
//********************************************************************************
//* Resize the window object and certain controls within the window.             *
//* Dimensions limits are:                                                       *
//*                    minDIM <= newHgt <= termRows                              *
//*                    minDIM <= newWid <= termCols                              *
//*                                                                              *
//*NOTE: It may be more manageable to have only two options:                     *
//*      1) Full screen (i.e. full terminal window)                              *
//*      2) Revert to original (non-full-screen) dimensions.                     *
//*                                                                              *
//*                                                                              *
//* When resized, the window will be positioned at the top row of the terminal   *
//* window, and centered horizontally within the terminal window.                *
//* If a different position is desired, then call MoveWin() after resize is      *
//* complete.                                                                    *
//*                                                                              *
//* Input  : newHgt  : target height in rows                                     *
//*                                                                              *
//*                    -- a negative value indicates full height of terminal     *
//*                       window                                                 *
//*                                                                              *
//*          newWid  : target width in columns                                   *
//*                                                                              *
//*                    -- a negative value indicates full width of terminal      *
//*                       window                                                 *
//*          newRow  : offset from top of terminal window                        *
//*                                                                              *
//*                    -- A negative value indicates that the window should be   *
//*                       centered vertically                                    *
//*          newCol  : offset from left edge of terminal window                  *
//*                                                                              *
//*                    -- A negative value indicates that the window should be   *
//*                       centered horizontally                                  *
//*                                                                              *
//*                                                                              *
//*          newSkf  : (optional, default:null pointer)                          *
//*                    pointer to an skForm object containing new positions      *
//*                    and dimensions of the controls within the window.         *
//*                    -- Only the 'hgt', 'wid' and 'orig' members are referenced*
//*                    -- Only Textbox control (ftTB) dimensions may be modified.*
//*                       Other control types contain static data and cannot     *
//*                       be resized.                                            *
//*                    -- Position of all controls can be adjust within the      *
//*                       limits imposed by window definition: inside window     *
//*                       borders and no overlap of controls.                    *
//*                                                                              *
//* Returns: new window position within the terminal window                      *
//********************************************************************************

#if 0    // ResizeWin - Design is not complete
WinPos ResizeWin ( short newHgt, short newWid, short newRow, short newCol,
                   const skForm* newSkf = NULL ) ;
WinPos ACWin::ResizeWin ( short newHgt, short newWid, short newRow, short newCol,
                          const skForm* newSkf )
{
   
}  //* End ResizeWin() *
#endif   // Design is not complete

//*************************
//*       ShellOut        *
//*************************
//********************************************************************************
//* Go to command shell.                                                         *
//*   1) Save the application window display data.                               *
//*   2) Clear the terminal window.                                              *
//*      Currently, terminal default color attributes are used                   *
//*      ('textColor' parameter is ignored).                                     *
//*   3) Return control of the Input stream:                                     *
//*      a) buffered input, b) terminal echo, c) blocking read                   *
//*   4) invoke the specified application (if any)                               *
//*      a) If no application specified, invoke 'bash' (or current shell program)*
//*         In this case, user will need to type 'exit' to return to application.*
//*   5) Return control of the Input stream to the ACWin object.                 *
//*   6) Redraw the application window.                                          *
//*      As a side effect, this also restores the color attributes.              *
//*                                                                              *
//* Important Note:                                                              *
//*   This method should be called ONLY from the main application window.        *
//*   AND there should be no sub-dialogs open.                                   *
//*   If invoked from a sub-dialog, then the primary window's display data       *
//*   will not be properly saved.                                                *
//*                                                                              *
//* Input  : shellCmd : (by reference)                                           *
//*                     invoke the specified shell builtin command, shell        *
//*                     program or console utility program                       *
//*                     - if an empty string, then invoke a copy of 'bash' shell *
//*                       NOTE: If the default shell IS NOT 'bash', then         *
//*                             'shellCmd' should be used to specify the target  *
//*                             shell program.                                   *
//*          textColor: (optional, default: acaUSE_DFLT) [CURRENTLY IGNORED]     *
//*                     specify an alternate foreground/background color for     *
//*                     text data written to standard output.                    *
//*                                                                              *
//* Returns: exit code of child process                                          *
//*           Ideally, this is the exit code of the last utility run in the      *
//*           child process, but this cannot be guaranteed.                      *
//********************************************************************************

int ACWin::ShellOut ( const gString& shellCmd, acAttr textColor )
{
   gString gsCmd( shellCmd ) ;            // working copy of command
   int status = ZERO ;                    // return value
   EchoOpt orig_inpEch = this->tset->inpEch ; // saved configuration
   bool    orig_inpBuf = this->tset->inpBuf,
           orig_inpBlk = this->tset->inpBlk ;

   //* Save the application window *
   this->HideWin () ;

   //* Set terminal color attributes *
   this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;

   //* Clear the terminal window and make cursor visible. *
   //* Note that the window refresh, below, will set the  *
   //* cursor invisible again.                            *
   this->acClearScreen () ;
   this->acSetCursorStyle ( csShow ) ;

   //* Return I/O control to terminal window *
   this->acBufferedInput ( true, termEcho, true ) ;

  //* If no command was specified, invoke the default shell program.*
   //* 1) Query the environment for the shell filespec.              *
   //* 2) Isolate the shell filename.                                *
   //* 3) Construct the invocation string with message.              *
   // Programmer's Note: If the "SHELL" environment variable is not defined, 
   // "bash" will be invoked.
   if ( (gsCmd.gschars()) == 1 )
   {
      char shellProg[64] ;
      gsCmd = secure_getenv ( "SHELL" ) ;
      if ( (gsCmd.gschars()) == 1 ) { gsCmd = "bash" ; }
      int indx = ((gsCmd.findlast( L'/' )) + 1) ;
      if ( indx > ZERO )
         gsCmd.shiftChars( -(indx) ) ;
      gsCmd.copy( shellProg, 64 ) ;
      gsCmd.compose( 
         "echo \"****************************\n"
                "** Type 'exit' to return. **\n"
                "****************************\""
                ";%s", shellProg ) ;
   }

   //* Invoke the external application or shell program *
   status = system ( gsCmd.ustr() ) ;

   //* Re-establish application control over the terminal I/O streams. *
   this->acBufferedInput ( orig_inpBuf, orig_inpEch, orig_inpBlk ) ;

   //* Clear any garbage from the terminal window *
   this->acClearScreen () ;

   //* Restore the application window *
   this->RefreshWin () ;

   return status ;

}  //* End ShellOut() *

//****************************
//*     LaunchDefaultApp     *
//****************************
//********************************************************************************
//* Launch the system default application for handling the specified type        *
//* of file.                                                                     *
//*                                                                              *
//* This is done by creating a child process to perform the actual launch,       *
//* while the parent process continues execution of the application code.        *
//*                                                                              *
//* Input  : targFile : filespec of file to be processed by the external         *
//*                     application                                              *
//*          redirect : (optional, 'true' by default)                            *
//*                     if 'true',  redirect stdout and stderr to temp files     *
//*                     if 'false', allow stdout and stderr to write to the      *
//*                                 terminal window                              *
//*                                                                              *
//* Returns: a) the child process ID                                             *
//*          b) -1 if child process not created                                  *
//********************************************************************************
//* Note: If temp files are created here, then the parent process will pause for *
//* a few seconds for the external program to be launched, then the temporary    *
//* files are deleted.                                                           *
//*                                                                              *
//* If launch failes, some information about the failure may have been written   *
//* to the temporary files. Testing for and reporting this information is        *
//* a relatively expensive operation, so is not currently parformed.             *
//********************************************************************************

int ACWin::LaunchDefaultApp ( const char* targFile, bool redirect )
{
   gString stdoutTemp, stderrTemp ;       // stdout/stderr target files
   bool stdoutOk = false,                 // set if stdout redirection file created
        stderrOk = false ;                // set if stderr redirection file created
   int soDesc = ZERO,                     // stdout target descriptor
       seDesc = ZERO ;                    // stderr target descriptor
       pid_t fpid = -1 ;                  // process ID

   if ( redirect )
   {
      stdoutOk = this->acCreateTempfile ( stdoutTemp ) ; // stdout redirection file
      stderrOk = this->acCreateTempfile ( stderrTemp ) ; // stderr redirection file
   }

   if ( !redirect || (redirect && stdoutOk && stderrOk) )
   {
      fpid = vfork () ;                      // create the child process

      if ( fpid == ZERO )
      {  //* The child process (if created) executes here.                  *
         //* Create temp files as targets for redirecting stdout and stderr.*
         if ( redirect )
         {
            soDesc = open ( stdoutTemp.ustr(), O_WRONLY | O_CREAT | O_TRUNC, 
                              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) ;
            seDesc = open ( stderrTemp.ustr(), O_WRONLY | O_CREAT | O_TRUNC, 
                              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) ;
            dup2 ( soDesc, STDOUT_FILENO) ;  // copy the descriptor
            dup2 ( seDesc, STDERR_FILENO) ;  // copy the descriptor
            close ( soDesc ) ;               // close the disused descriptor
            close ( seDesc ) ;               // close the disused descriptor
         }

         execlp ( "xdg-open", "xdg-open", targFile, NULL ) ;

         //* In case the exec call fails: child process MUST NOT return.*
         _exit ( 0 ) ;
      }
      else
      {  //* The parent process continues execution here *
         if ( fpid > ZERO )      // child successfully launched
         {
            //* Waits for a few seconds, then delete the temp *
            //* files used for redirecting stdin/stderr.      *
            if ( redirect )
            {
               this->nsleep ( 30 ) ;
               if ( stdoutOk ) { this->acDeleteTempfile ( stdoutTemp ) ; }
               if ( stderrOk ) { this->acDeleteTempfile ( stderrTemp ) ; }
            }
         }
      }
   }
   return fpid ;

}  //* End LaunchDefaultApp() *

//*************************
//*      KeycodePush      *
//*************************
//********************************************************************************
//* Push a keycode into the input stream.                                        *
//* All valid wchar_t (wide) keycodes are accepted. The maximum number of        *
//* keycodes which can be pushed into the input stream FIFO queue without an     *
//* intervening read is defined as KEYPUSH_MAX (currently 16).                   *
//*                                                                              *
//* Input  : wKey   : keycode to be placed into the queue                        *
//*                                                                              *
//* Returns: 'true'  if keycode successfully pushed into the input queue         *
//*          'false' if input queue is full                                      *
//********************************************************************************

bool ACWin::KeycodePush ( wchar_t wKey )
{

   return ( (this->kpb.keyPush ( wKey )) ) ;

}  //* End KeycodePush() *

//*************************
//*        winRead        *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Capture a single keystroke from the user.                                    *
//*                                                                              *
//* This method implements access to the input stream pushback FIFO.             *
//* Called ONLY from within the user interface methods for ACWin and ACDlg       *
//* objects.                                                                     *
//*                                                                              *
//* 1) Because a keycode pushed into the queue has bypassed the input filters    *
//*    _including_ the soft-echo test, if the keycode is a printing character and*
//*    NOT a special keycode, then we echo the key to the output stream (stdout).*
//*    This is done via a call to the AnsiCmd softWrite() method.                *
//* 2) All input to the ACWin and ACDlg classes is through an _unbuffered_       *
//*    input stream. A buffered input stream would not see the pushback queue.   *
//* 3) Within EditForm() and as a consequence, within editDialog() key input     *
//*    is captured in noEcho mode.                                               *
//*    This means that printing characters entered while focus is on a Textbox   *
//*    control will be explicity copied to the target control and the display    *
//*    will then be updated. This method _does not_ update the display, neither  *
//*    directly nor indirectly.                                                  *
//* 4) Note that the AnsiCmd class is not aware of the key-input FIFO; therefore,*
//*    the acFlushStreams/acFlushIn methods DO NOT clear the FIFO queue.         *
//*                                                                              *
//*                                                                              *
//* Input  : nothing                                                             *
//*                                                                              *
//* Returns: keycode, either from keyboard or FIFO queue.                        *
//*          If keyPushback class lies to us, a null ('\0') will be returned,    *
//*          but this should never happen.                                       *
//********************************************************************************

wchar_t ACWin::winRead ( void )
{
   wchar_t wkey ;                         // return value

   if ( (this->kpb.keyAvail()) )
   {
      this->kpb.keyPull ( wkey ) ;
   }
   else
      wkey = this->acRead () ;

   return wkey ;

}  //* End winRead() *


//****************************
//*** For Development Only ***
//****************************
#if DEBUG_ANSICMD != 0 && DEBUG_KPUSH != 0
//*************************
//*   KeycodePush_Clear   *
//*************************
//********************************************************************************
//* For Debugging Only:                                                          *
//* -------------------                                                          *
//* Clear (reset) the KecodePush FIFO queue.                                     *
//* All keycodes in the queue will be discarded. Note that this does not affect  *
//* data in the system key input stream.                                         *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void ACWin::KeycodePush_Clear ( void )
{

   this->kpb.keyReset () ;

}  //* End KeycodePush_Clear() *

//*************************
//*  KeycodePush_Report   *
//*************************
//********************************************************************************
//* For Debugging Only:                                                          *
//* -------------------                                                          *
//* Return the number of keycodes currently in the keycode pushback FIFO queue.  *
//* Optionally, return a copy of the current contents of the queue.              *
//*                                                                              *
//*            Used primarily during development, with                           *
//*            little practical value in the real world.                         *
//*            wchar_t fifoCopy[KEYCODE_MAX] ;                                   *
//*            short kpCount = win->KeycodePush_Report ( fifoCopy ) ;            *
//*                                                                              *
//*                                                                              *
//* Input  : kfifo  : (optional, default: null pointer)                          *
//*                   if specified, pointer to an array of wchar_t to receive    *
//*                   a copy of current FIFO queue contents:                     *
//*                                                                              *
//* Returns: number of keycodes currently in the queue                           *
//********************************************************************************

short ACWin::KeycodePush_Report ( wchar_t* kfifo )
{
   if ( kfifo != NULL )
   {
      for ( short k = ZERO ; k < KEYPUSH_MAX ; ++k )
      {
         if ( k < this->kpb.kCount )
            kfifo[k] = this->kpb.kPush[k] ;
         else
            kfifo[k] = NULLCHAR ;
      }
   }
   return ( this->kpb.kCount ) ;

}  //* End KeycodePush_Report() *

//*************************
//*   KeycodePush_Pull    *
//*************************
//********************************************************************************
//* For Debugging Only:                                                          *
//* -------------------                                                          *
//* Extract a keycode from the keycode pushback FIFO queue.                      *
//*                                                                              *
//* Input  : nothing                                                             *
//*                                                                              *
//* Returns: keycode, either from FIFO queue, or if queue is empty,              *
//*          waits for a key from the keyboard.                                  *
//********************************************************************************

wchar_t ACWin::KeycodePush_Pull ( void )
{

   return ( (this->winRead ()) ) ;

}  //* End KeycodePush_Pull() *
#endif   // DEBUG_ANSICMD && DEBUG_KPUSH


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

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

