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