// ****************** START OF TEXTWIND.CPP ******************
//
//
//
// This file contains all of the code needed to support the
// Text Windows used for example programs in this book.  These
// are "quick and dirty" text windows, using the BIOS for most of
// the work.

#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include <conio.h>
#include <ctype.h>
#include <bios.h>
#include "textwind.h"

// This static data member keeps track of how many windows are
// open.  When the count drops to zero, cleanup work can be done.

int TextWindow::count = 0;

// write_char uses the BIOS function to write the current
// character using the window attribute.  This function is a
// protected member function, and is used by the << operator.

inline void TextWindow::write_char( unsigned char c )
{
    union REGS r;

    r.h.ah = 9;
    r.h.al = c;
    r.h.bl = attribute;
    r.h.bh = 0;
    r.x.cx = 1;
    int86( 0x10, &r, &r );
}

// This protected function uses the BIOS parameter to write a
// single character repeatedly.  This is much faster than writing
// individual characters in a loop.  It is used when writing the
// border and clearing the screen.

inline void TextWindow::write_repeated_chars( unsigned char c, 
                                              int count )
{
    union REGS r;

    r.h.ah = 9;
    r.h.al =  c;
    r.h.bl =  attribute;
    r.x.cx = count;
    r.h.bh = 0;
    int86( 0x10, &r, &r );
}

// This protected member function positions the cursor at the
// desired position relative to the text window.  The protected
// function doesn't check for validity of the row and column
// values.  It also doesn't set the row and column data
// members.

inline void TextWindow::position( int r, int c )
{
    union REGS rin;

    r += start_row;
    c += start_col;
    rin.h.ah = 2;
    rin.h.dh = (unsigned char) r;
    rin.h.dl = (unsigned char) c;
    rin.h.bh = 0;
    int86( 0x10, &rin, &rin );
}

// This public member function positions the cursor in a
// window.  It also stores the new position in the row and column
// data members.

int TextWindow::SetPosition( int r, int c )
{
    union REGS rin;

    if ( r < 0 || c < 0 )
        return 0;
    if ( r >= (int) height || c >= (int) width )
        return 0;
    row = (unsigned char) r;
    col = (unsigned char) c;
    r += start_row;
    c += start_col;
    rin.h.ah = 2;
    rin.h.dh = (unsigned char) r;
    rin.h.dl = (unsigned char) c;
    rin.h.bh = 0;
    int86( 0x10, &rin, &rin );
    return 1;
}

// This public member function positions the cursor in the current
// location of the window defined by *this.

void TextWindow::Goto( void )
{
    union REGS rin;

    rin.h.ah = 2;
    rin.h.dh = (unsigned char) ( row + start_row );
    rin.h.dl = (unsigned char) ( col + start_col );
    rin.h.bh = 0;
    int86( 0x10, &rin, &rin );
}

// This public function uses the BIOS to scroll a text window.

inline void TextWindow::Scroll( unsigned char line_count )
{
    union REGS r;

    r.h.ah = 6;
    r.h.al = line_count;
    r.h.ch = start_row;
    r.h.cl = start_col;
    r.h.dh = (unsigned char) ( start_row + height - 1 );
    r.h.dl = (unsigned char) ( start_col + width - 1 );
    r.h.bh = attribute;
    int86( 0x10, &r, &r );
}

// This routine writes a formated character to the output
// window.  Special processing is performed for the CR, LF, and
// BS keys.

BaseWindow& TextWindow::operator<<( char c )
{
    switch ( c ) {
        case '\r' :
            col = 0;
            break;
        case '\n' :
            col = 0;
        row += 1;
            break;

        case '\b' :
            if ( col > 0 )
                col--;
            break;
        default :
            position( row, col );
            write_char( c );
            col++;
            if ( col >= width ) {
                if ( wrap ) {
                    col = 0;
                    row++;
                } else
                    col--;
            }
    }
    if ( row >= height ) {
        row = (unsigned char) ( height - 1 );
        Scroll( 1 );
    }
    position( row, col );
    return *this;
}

// This routine writes a formated string to the output
// window.  Special processing is performed for the CR, LF, and
// BS keys.

BaseWindow& TextWindow::operator<<( char *s )
{
    unsigned char c;

    while ( ( c = *s++ ) != '\0' ) {
        switch ( c ) {
            case '\r' :
                col = 0;
                break;
            case '\n' :
                col = 0;
                row += 1;
                break;
            case '\b' :
                if ( col > 0 )
                    col--;
                break;
            default :
                position( row, col );
                write_char( c );
                col++;
                if ( col >= width ) {
                    if ( wrap ) {
                        col = 0;
                        row += 1;
                    } else
                        col--;
                }
                break;
        }
        if ( row >= height ) {
            row = (unsigned char) ( height - 1 );
            Scroll( 1 );
        }
        position( row, col );
    }
    return *this;
}

BaseWindow& TextWindow::operator<<( int c )
{
    return operator<<( (char) c );
}

// The only constructor for a text window just defines the
// starting row and column, and the width and height.
// Constructing a window doesn't actually draw anything on the
// screen.

TextWindow::TextWindow( int r, int c, int w, int h )
{
    count++;
    start_row = (unsigned char ) r;
    start_col = (unsigned char ) c;
    width = (unsigned char) w;
    height = (unsigned char) h;
    border = 0;
    save_buffer = 0;
    row = 0;
    col = 0;
}

// The destructor for a text window restores any saved data
// that was under the window.  It also repositions the cursor if
// the last window was just closed.

TextWindow::~TextWindow( void )
{
    if ( save_buffer ) {
        restore_window();
        if ( border )
            restore_border();
        delete[] save_buffer;
    }
    count--;
    if ( count == 0 ) {
        start_row = 0;
        start_col = 0;
        position( 23, 0 );
    }
}

// Clearing the window is easy.

void TextWindow::Clear( void )
{
    Scroll( 0 );
}

// This function writes the border out around the window.  If
// a save buffer has been established, the data under the border
// is saved.

void TextWindow::AddBorder( void )
{
    unsigned char r;
    SaveCursor a;

    if ( save_buffer && border == 0 )
        save_border();
    border = 1;
    position( -1, -1 );
    write_char( 218 );
    position( -1, width );
    write_char( 191 );
    position( height, -1 );
    write_char( 192 );
    position( height, width );
    write_char( 217 );
    for ( r = 0 ; r < height ; r++ ) {
        position( r, -1 );
        write_char( 179 );
        position( r, width );
        write_char( 179 );
    }
    position( -1, 0 );
    write_repeated_chars( 196, width );
    position( height, 0 );
    write_repeated_chars( 196, width );
}

// The title just gets displayed on top of the border.

void TextWindow::DisplayTitle( char *s )
{
    int col = 0;

    if ( ( strlen( s ) + 2 ) > width )
        return;
    if ( !border )
        AddBorder();
    SaveCursor save_it;
    position( -1, col++ );
    write_char( 180 );
    while ( *s ) {
        position( -1, col++ );
        write_char( *s++ );
    }
    position( -1, col );
    write_char( 195 );
}

// Popup windows save the area under the window.  This
// function saves the main window, another one saves the border
// area.

void TextWindow::save_window( void )
{
    union REGS rpos;
    union REGS rread;
    union REGS rout;
    int i;
    SaveCursor save_it;

// Allocate enough space for the window plus the border.  To 
// simplify the storage issues, I treat the save buffer as the 
// screen array followed by a border array.  The border array 
// will have weird indexing, but the screen array won't.

    if ( save_buffer == 0 )
        save_buffer = new unsigned int[(width+2)*(height+2)];
    if ( save_buffer ) {
        i = 0;
        rpos.h.ah = 2;
        rpos.h.bh = 0;
        rread.h.ah = 8;
        rread.h.bh = 0;
        for ( rpos.h.dh = start_row ;
              rpos.h.dh < (unsigned char) (start_row+height);
              rpos.h.dh++ )
            for ( rpos.h.dl = start_col ;
                  rpos.h.dl < (unsigned char) (start_col+width);
                  rpos.h.dl++ ) {
                int86( 0x10, &rpos, &rout );
                int86( 0x10, &rread, &rout );
                save_buffer[ i++ ] = rout.x.ax;
            }
    }
}

// When a popup window is destroyed, the area under it is
// restored.  This function restores the text under the window,
// but doesn't do anything about the border.

void TextWindow::restore_window( void )
{
    union REGS rpos;
    union REGS rwrite;
    union REGS rout;
    int i;
    SaveCursor save_it;

    if ( !save_buffer )
        return;
    i = 0;
    rpos.h.ah = 2;
    rpos.h.bh = 0;
    rwrite.h.ah = 9;
    rwrite.h.bh = 0;
    rwrite.x.cx = 1;
    for ( rpos.h.dh = start_row ;
          rpos.h.dh < (unsigned char) ( start_row + height );
          rpos.h.dh++ )
        for ( rpos.h.dl = start_col ;
              rpos.h.dl < (unsigned char) ( start_col + width );
              rpos.h.dl++ ) {
            int86( 0x10, &rpos, &rout );
            rwrite.h.al = (unsigned char) save_buffer[ i ];
            rwrite.h.bl = (unsigned char)(save_buffer[i++]>>8);
            int86( 0x10, &rwrite, &rout );
        }
}

// When a popup window is saved, the border has to be saved as
// well.  The main window is saved in a standard row by column
// order in the save buffer.  The border area is stored somewhat
// more haphazardly, right here.

void TextWindow::save_border( void )
{
    union REGS rpos;
    union REGS rread;
    union REGS rout;
    int i;
    SaveCursor save_it;

    if ( save_buffer == 0 )
        return;
    i = width * height;
    rpos.h.ah = 2;
    rpos.h.bh = 0;
    rread.h.ah = 8;
    rread.h.bh = 0;
    for ( rpos.h.dl = (unsigned char) ( start_col - 1 );
          rpos.h.dl < (unsigned char) ( start_col + width + 1 ) ;
          rpos.h.dl++ ) {
        rpos.h.dh = (unsigned char) ( start_row - 1 );
        int86( 0x10, &rpos, &rout );
        int86( 0x10, &rread, &rout );
        save_buffer[ i++ ] = rout.x.ax;
        rpos.h.dh = (unsigned char) ( start_row + height );
        int86( 0x10, &rpos, &rout );
        int86( 0x10, &rread, &rout );
        save_buffer[ i++ ] = rout.x.ax;
    }
    for ( rpos.h.dh = start_row ;
          rpos.h.dh < (unsigned char) ( start_row + height ) ;
          rpos.h.dh++ ) {
        rpos.h.dl = (unsigned char) ( start_col - 1 );
        int86( 0x10, &rpos, &rout );
        int86( 0x10, &rread, &rout );
        save_buffer[ i++ ] = rout.x.ax;
        rpos.h.dl = start_col;
        rpos.h.dl = (unsigned char)( rpos.h.dl + width );
        int86( 0x10, &rpos, &rout );
        int86( 0x10, &rread, &rout );
        save_buffer[ i++ ] = rout.x.ax;
    }
}

// This protected function restores the border in the same odd
// order that it was saved.

void TextWindow::restore_border( void )
{
    union REGS rpos;
    union REGS rwrite;
    union REGS rout;
    int i;
    SaveCursor save_it;

    if ( save_buffer == 0 )
        return;
    i = width * height;
    rpos.h.ah = 2;
    rpos.h.bh = 0;
    rwrite.h.ah = 9;
    rwrite.h.bh = 0;
    rwrite.x.cx = 1;
    for ( rpos.h.dl = (unsigned char) ( start_col - 1 );
          rpos.h.dl < (unsigned char)( start_col + width + 1 );
          rpos.h.dl++ ) {
        rpos.h.dh = (unsigned char) ( start_row - 1 );
        int86( 0x10, &rpos, &rout );
        rwrite.h.al = (unsigned char) save_buffer[ i ];
        rwrite.h.bl = (unsigned char)( save_buffer[ i++ ] >> 8 );
        int86( 0x10, &rwrite, &rout );
        rpos.h.dh = (unsigned char) ( start_row + height );
        int86( 0x10, &rpos, &rout );
        rwrite.h.al = (unsigned char) save_buffer[ i ];
        rwrite.h.bl = (unsigned char)( save_buffer[ i++ ] >> 8 );
        int86( 0x10, &rwrite, &rout );
    }
    for ( rpos.h.dh = start_row;
          rpos.h.dh < (unsigned char) ( start_row + height ) ;
          rpos.h.dh++ ) {
        rpos.h.dl = (unsigned char) ( start_col - 1 );
        int86( 0x10, &rpos, &rout );
        rwrite.h.al = (unsigned char) save_buffer[ i ];
        rwrite.h.bl = (unsigned char) ( save_buffer[ i++ ] >> 8 );
        int86( 0x10, &rwrite, &rout );
        rpos.h.dl = (unsigned char) ( start_col + width );
        int86( 0x10, &rpos, &rout );
        rwrite.h.al = (unsigned char) save_buffer[ i ];
        rwrite.h.bl = (unsigned char) ( save_buffer[ i++ ] >> 8 );
        int86( 0x10, &rwrite, &rout );
    }
}

// A popup window is just like a text window except that it
// has an automatic save buffer and save cursor.  The constructor
// doesn't do anything except make a text window and then save
// it.

PopupWindow::PopupWindow( int r, int c, int w, int h )
         : TextWindow( r, c, w, h )
{
    save_window();
}

// This internal utility routine hides the cursor by moving it
// off the screen.  It is used when in the menu code.

inline void hide_cursor( void )
{
    union REGS r;
    r.h.ah = 2;
    r.h.dh = 25;
    r.h.dl = 0;
    r.h.bh = 0;
    int86( 0x10, &r, &r );
}

int Menu( char *menu[] )
{
    int items;
    int maxlen;
    int length;
    int i;
    int c;
    int selection;

    items = 0;
    maxlen = 0;
    while ( ( length = strlen( menu[ items ] ) ) != 0 ) {
        items++;
        if ( length > maxlen )
            maxlen = length;
    }
    if ( items == 0 )
        return -1;
    PopupWindow w( 5, 25, maxlen + 2, items );
    w.Clear();
    w.AddBorder();
    for ( i = 0 ; i < items ; i++ ) {
        w.SetPosition( i, 1 );
        w << menu[ i ];
    }
    selection = 0;
    for ( ; ; ) {
        w.SetAttribute( REVERSE_ATTRIBUTE );
        w.SetPosition( selection, 1 );
        w << menu[ selection ];
        hide_cursor();
        while ( ( c = w.ReadKey() ) == 0 )
            ;
        w.SetAttribute( NORMAL_ATTRIBUTE );
        w.SetPosition( selection, 1 );
        w << menu[ selection ];
        switch ( c ) {
            case 27 : //Escape
                return( -1 );
            case 13 : // CR
                return( selection );
            case HOME :
                selection = 0;
                break;
            case END :
                selection = items - 1;
                break;
            case DOWN :
                if ( selection < ( items - 1 ) )
                   selection++;
                break;
            case UP :
                if ( selection > 0 )
                    selection--;
                break;
            default :
                for ( i = ( selection + 1 ) % items ;
                      i != selection ;
                      i = ( i + 1 ) % items ) {
                    if (toupper(c)==toupper(menu[i][0]))
                    {
                        selection = i;
                        break;
                    }
                }
                break;
        }
    }
#ifdef _MSC_VER
    return -1;// To pass /W4 under Microsoft, not really needed
#endif
}

int ReadLine( char *prompt, char *buffer, int length )
{
    int c;
    int count;

    PopupWindow w( 5, 10, 60, 1 );
    w.Clear();
    w.AddBorder();
    w << prompt << ' ';
    count = 0;

    for ( ; ; ) {
        while ( ( c = w.ReadKey() ) == 0 )
            ;
        switch ( c ) {
            case ESC :
                return 0;
            case CR :
                buffer[ count ] = '\0';
                return 1;
            case BS :
                if ( count > 0 ) {
                    count--;
                    w << BS;
                    w << ' ';
                    w << BS;
                }
                break;
            default :
               if ( c >= ' ' && c <= 0x7f ) {
                   if ( count < length ) {
                       buffer[ count++ ] = (char) c;
                       w << c;
                   }
               }
        }
    }
#ifdef _MSC_VER
    return -1; // To pass /W4 under Microsoft, not really needed
#endif
}

// ReadKey returns a 0 if no key is ready.  The _bios_keybrd()
// routine under Borland C++ has trouble if a Ctrl-Break has been
// pressed, so I replace it with the equivalet int86() function
// call for that compiler only.

int TextWindow::ReadKey( void )
{
#ifdef __TURBOC__
    union REGS r;
    r.h.ah = 1;
    int86( 0x16, &r, &r );
    if ( r.x.flags & 0x40 )  // Test for the zero flag
        return 0;
    r.h.ah = 0;
    int86( 0x16, &r, &r );
    if ( r.h.al != 0 )
        return r.h.al;
    else
        return r.x.ax;
#else  // #ifdef __TURBOC__
    int c;

// Under Borland C, this function will never return true if CTRL-BREAK
// has been pressed, effectively closing the keyboard.

    if ( _bios_keybrd( _KEYBRD_READY ) == 0 )
       return 0;
    c = _bios_keybrd( _KEYBRD_READ );
    if ( ( c & 0xff ) == 0 )  // A normal ASCII key
        return c;
    else
        return c & 0xff;      // A function or other extended key
#endif // #ifdef __TURBOC__ ... #else
}

// This is an unsophisticated way to change to 43 line mode, or 50
// line mode on a VGA.  Note that it doesn't disable EGA cursor
// emulation, so if you try to change the cursor size, strange things
// might happen.  All this routine does to change modes is select the
// appropriate size font, 8x8 for 43 line mode, else 8x14 or 8x16.

void Set43LineMode( int control )
{
    union REGS r;

    if ( control ) {
        r.h.ah = 0x11;
        r.h.al = 0x12;
        r.h.bl = 0;
        int86( 0x10, &r, &r );
    } else {
        r.h.ah = 0x14;  // Change to 11 for EGA
        r.h.al = 0x12;
        r.h.bl = 0;
        int86( 0x10, &r, &r );
        r.h.ah = 0;
        r.h.al = 3; // Change to 2 for BW80
        int86( 0x10, &r, &r );
    }
}

// ****************** END OF TEXTWIND.CPP ******************
