/*++ Copyright (c) 1990-2000 Microsoft Corporation Module Name: PAGER Abstract: This module contains the implementations for the PAGER class Author: Ramon Juan San Andres (RamonSA) 15-Apr-1990 Notes: --*/ #include "ulib.hxx" #include "filestrm.hxx" #include "wstring.hxx" #include "bstring.hxx" #include "screen.hxx" #include "stream.hxx" #include "more.hxx" #include "pager.hxx" // // What constitutes a "blank" character (spaces and tabs) // #define BLANKCHARACTERS (LPWSTR)L" \t" #define TAB_ASCII (CHAR)'\t' DEFINE_CONSTRUCTOR( PAGER, OBJECT ); VOID PAGER::Construct ( ) { UNREFERENCED_PARAMETER( this ); } PAGER::~PAGER ( ) { DELETE( _String ); DELETE( _BString ); DELETE( _Blanks ); DELETE( _BlankLine ); } BOOLEAN PAGER::Initialize ( IN PSTREAM Stream, IN PPROGRAM Program ) /*++ Routine Description: Phase 2 of construction for a pager object. It initializes its internal data. Arguments: Stream - Supplies the stream to be paged Program - Supplies pointer to the program doing the paging. Return Value: TRUE - If initialized correctly FALSE - If something when wrong. (check error stack) --*/ { USHORT ScreenRows, RowsInPage; CHNUM i; _Stream = Stream; _CurrentLineNumber = 0; _StandardOutput = Program->GetStandardOutput(); _Screen = SCREEN::Cast( _StandardOutput ); _Position = INVALID_CHNUM; // // Get the page dimensions // if (_Screen) { _Screen->QueryScreenSize( &ScreenRows, &_ColumnsInScreen, &RowsInPage, &_ColumnsInPage ); _RowsInPage = RowsInPage; _ColumnsInPage = _ColumnsInScreen; } else { // If we don't have a screen then we should treat the page // as infinitely long since we have no need to prompt. _RowsInPage = (ULONG) -1; _ColumnsInPage = (USHORT) -1; } if (!(_String = NEW DSTRING) || !(_BString = NEW BDSTRING) || !(_Blanks = NEW DSTRING) || !(_BlankLine = NEW DSTRING) || !_String->Initialize() || !_BString->Initialize() || !_Blanks->Initialize(BLANKCHARACTERS) || !_BlankLine->Resize(_Screen ? _ColumnsInPage : 0)) { return FALSE; } for (i = 0; i < _BlankLine->QueryChCount(); i++) { _BlankLine->SetChAt(' ', i); } return TRUE; } BOOLEAN PAGER::DisplayPage ( IN ULONG LinesInPage, IN BOOLEAN ClearScreen, IN BOOLEAN SqueezeBlankLines, IN BOOLEAN ExpandFormFeed, IN ULONG TabExp ) /*++ Routine Description: Displays a page of the screen Arguments: LinesInPage - Supplies the desired number of lines in the page ClearScreen - Supplies a flag, which if TRUE means that we want to clear the screen before displaying the page SqueezeBlankLines - Supplies squeeze flag ExpandFormFeed - Supplies formfeed expansion flag Return Value: TRUE - If page displayed FALSE otherwise --*/ { ULONG LinesLeft = LinesInPage; BOOLEAN IgnoreBlankLine = FALSE; CHNUM Length; CHNUM FormFeedAt; if ( TabExp > (ULONG)( _ColumnsInPage - 1 ) ) { TabExp = _ColumnsInPage - 1 ; } // // Clear the screen if instructed to do so // if ( ClearScreen && _Screen) { #ifdef FE_SB // v-junm - 08/18/93 // Also reset attributes. _Screen->EraseScreenAndResetAttribute(); #else _Screen->EraseScreen(); #endif _Screen->MoveCursorTo( 0, 0 ); } else { // // Make sure that we start at the beginning of a line // ClearLine(); } // // Display up to LinesLeft lines // while ( LinesLeft > 0 && (ThereIsMoreToPage() || _Position != INVALID_CHNUM) ) { // // Get next string from input // if (!ReadNextString( TabExp )) { return FALSE; } #ifdef FE_SB // v-junm - 08/18/93 // Now QueryChCount returns the correct number of unicode chars. An additional // API, QueryByteCount was added. Length = _String->QueryByteCount() - _Position; #else Length = _String->QueryChCount() - _Position; #endif if ( SqueezeBlankLines ) { if ( _String->Strspn( _Blanks, _Position ) == INVALID_CHNUM ) { // // This is a blank line. We must sqeeze // if ( IgnoreBlankLine ) { // // We ignore the line // _Position = INVALID_CHNUM; continue; } else { // // We will print a blank line and ignore the following // blank lines. // DisplayBlankLine( 1 ); _Position = INVALID_CHNUM; IgnoreBlankLine = TRUE; continue; } } else if (IgnoreBlankLine) { LinesLeft--; IgnoreBlankLine = FALSE; continue; } } if ( ExpandFormFeed ) { // // Look for form feed within line // if ((FormFeedAt = _String->Strchr( FORMFEED,_Position )) != INVALID_CHNUM) { if ( FormFeedAt == _Position ) { // // First character is a form feed. // // We will skip the formfeed character, and the // rest of the screen will be blanked. // if ( SqueezeBlankLines ) { if (!IgnoreBlankLine) { DisplayBlankLine( 1 ); LinesLeft--; IgnoreBlankLine = TRUE; } } else { DisplayBlankLine( LinesLeft ); LinesLeft = 0; } _Position++; continue; } Length = FormFeedAt - _Position; } } // // If the line is too long, we must split it // if (Length > (CHNUM)_ColumnsInPage) { Length = (CHNUM)_ColumnsInPage; } // // Display the string // DisplayString( _String, &_Position, Length ); IgnoreBlankLine = FALSE; LinesLeft--; } return TRUE; } VOID PAGER::ClearLine ( ) /*++ Routine Description: Clears a line Arguments: none Return Value: none --*/ { USHORT ScreenRows, ScreenCols; USHORT WindowRows, WindowCols; CHNUM i; if (_Screen) { _Screen->QueryScreenSize( &ScreenRows, &ScreenCols, &WindowRows, &WindowCols ); // // If the number of columns has changed, re-initialize the // blank line. // if ( ScreenCols != _ColumnsInPage ) { _BlankLine->Resize(ScreenCols); for (i = 0; i < ScreenCols; i++) { _BlankLine->SetChAt(' ', i); } } _RowsInPage = WindowRows; _ColumnsInPage = ScreenCols; _StandardOutput->WriteChar( (WCHAR)CARRIAGERETURN ); _StandardOutput->WriteString( _BlankLine, 0, _ColumnsInPage-1 ); _StandardOutput->WriteChar( (WCHAR)CARRIAGERETURN ); } } VOID PAGER::DisplayBlankLine ( IN ULONG Lines, IN BOOLEAN NewLine ) /*++ Routine Description: Displays a number of blank lines Arguments: Lines - Supplies the number of blank lines to display NewLine - Supplies the newline flag Return Value: none --*/ { CHNUM Position; while ( Lines-- ) { Position = 0; DisplayString( _BlankLine, &Position, _ColumnsInPage-1, (Lines > 0) ? TRUE : NewLine); } } VOID PAGER::DisplayString ( IN PWSTRING String, OUT PCHNUM Position, IN CHNUM Length, IN BOOLEAN NewLine ) /*++ Routine Description: Displays a chunk of the current string Arguments: Length - Supplies the length of the string to display NewLine - Supplies the newline flag Return Value: none --*/ { #ifdef FE_SB // v-junm - 04/19/93 CHNUM UnicodeCharNum, // # of unicode characters to display TempByteCount, // to minimize calls to QueryByteCount index; // loop counter PSTR STRBuffer; // ASCIIZ converted string pointer static CHNUM OldPos; // Keeps old position in # of Unicode BOOL DBCSFlag = FALSE; // Flag for ending leadbyte TempByteCount = String->QueryByteCount(); Length = min( Length, TempByteCount ); // If the length of the string to display is shorter than // the width of the screen, we do not have to do any DBCS // checking. Just print it out. (Can be skipped for CP437) // if ( TempByteCount > _ColumnsInPage ) { // // Initialize # of unicode characters to # // of ASCII chars to display. // UnicodeCharNum = Length; // // Get the string as a ASCIIZ text. // STRBuffer = String->QuerySTR(); if ( STRBuffer != NULL ) { // // Start changing the UnicodeCharNum from the actual // number of bytes to the actual number of characters. // for( index = 0; index < Length; index++ ) { if ( IsLeadByte( *(STRBuffer + index + _Position) ) ) { index++; UnicodeCharNum--; DBCSFlag = TRUE; } else DBCSFlag = FALSE; } DELETE( STRBuffer ); } // // If the following conditions are true, then there // is a Leadbyte at the end of the screen that needs // to be displayed on the next line with it's tail byte. // if ( DBCSFlag == TRUE && // String ends with DBCS. index == (Length - 1) && // Only Leadbyte. Length == (CHNUM)_ColumnsInPage // More rows to display. ) { Length--; UnicodeCharNum--; } } else //fix kksuzuka: #195 //Overflow pagecolumns when writing 0D0A. //UnicodeCharNum = String->QueryChCount(); UnicodeCharNum = min( Length, String->QueryChCount() ); // // When the string does not fit on one line and needs to be truncated, // OldPos keeps the position where the string was truncated in Unicode // location. // // // If true, set to beginning of string. // if ( *Position == 0 ) OldPos = 0; _StandardOutput->WriteString( String, OldPos, UnicodeCharNum ); // // Set to last+1 char displayed in unicode character numbers. // OldPos += UnicodeCharNum; // // Update our position within the string // *Position += Length; // // Check if all the characters have been written. // if ( TempByteCount && (TempByteCount == *Position) && !(TempByteCount % _ColumnsInPage) ) { // // Characters have been written, but there is a LF/CR // character at the that needs to be display that has // not been displayed. (At _ColumnsInPage+1 location) // *Position = INVALID_CHNUM; // _StandardOutput->WriteChar( (WCHAR)CARRIAGERETURN ); // _StandardOutput->WriteChar( (WCHAR)LINEFEED ); } else if ( *Position >= TempByteCount ) *Position = INVALID_CHNUM; if ( ((*Position != INVALID_CHNUM) || NewLine) && ( Length < _ColumnsInScreen || !_Screen ) ) { _StandardOutput->WriteChar( (WCHAR)CARRIAGERETURN ); _StandardOutput->WriteChar( (WCHAR)LINEFEED ); } #else // FE_SB Length = min( Length, String->QueryChCount() ); _StandardOutput->WriteString( String, *Position, Length ); // // Update our position within the string // *Position += Length; if (*Position >= _String->QueryChCount()) { *Position = INVALID_CHNUM; } if ( ((*Position != INVALID_CHNUM) || NewLine) && ( Length < _ColumnsInScreen || !_Screen ) ) { _StandardOutput->WriteChar( (WCHAR)CARRIAGERETURN ); _StandardOutput->WriteChar( (WCHAR)LINEFEED ); } #endif } ULONGLONG PAGER::QueryCurrentByte ( ) /*++ Routine Description: Queries the current Byte number Arguments: none Return Value: The current byte number --*/ { PFILE_STREAM pFileStream; ULONGLONG PointerPosition ; if ((pFileStream = FILE_STREAM::Cast(_Stream)) == NULL ) { return 0; } else { pFileStream->QueryPointerPosition( &PointerPosition ); return PointerPosition; } } USHORT PAGER::QueryLinesPerPage ( ) /*++ Routine Description: Queries the number of lines per page of output Arguments: none Return Value: The number of lines (rows) in a page --*/ { USHORT ScreenRows, ScreenCols; USHORT WindowRows, WindowCols; CHNUM i; // // If Paging to screen, get the current size of the window // if (_Screen) { _Screen->QueryScreenSize( &ScreenRows, &ScreenCols, &WindowRows, &WindowCols ); // // If the number of columns has changed, re-initialize the // blank line. // if ( WindowCols != _ColumnsInPage ) { _BlankLine->Resize(ScreenCols); for (i = 0; i < ScreenCols; i++) { _BlankLine->SetChAt(' ', i); } } _RowsInPage = WindowRows; _ColumnsInPage = ScreenCols; } return (USHORT)_RowsInPage; } BOOLEAN PAGER::ReadNextString ( IN ULONG TabExp ) /*++ Routine Description: Reads in the next string from the input stream. Arguments: TabExp - Supplies the number of blank characters per tab Return Value: TRUE - If string read in FALSE otherwise --*/ { if (_Position == INVALID_CHNUM ) { CHNUM Idx; // // We have to read a new string from the input stream. // if (!_Stream->ReadLine( _String )) { return FALSE; } _CurrentLineNumber++; _Position = 0; // // Expand tabs // Idx = 0; // // Get the string as a ASCIIZ text. // PSTR szBuffer; // ASCIIZ converted string pointer szBuffer = _String->QuerySTR(); if (szBuffer != NULL) { _BString->Initialize(szBuffer); DELETE( szBuffer ); while ( (Idx < _BString->QueryChCount()) && ((Idx = _BString->Strchr( TAB_ASCII, Idx )) != INVALID_CHNUM) ) { if (TabExp) { _BString->ReplaceWithChars( Idx, // AtPosition 1, // AtLength ' ', // Replacement char TabExp - Idx%TabExp // FromLength ); } else { _BString->ReplaceWithChars( Idx, // AtPosition 1, // AtLength ' ', // Replacement char 0 // FromLength ); } // // MJB: If we're eliminating tabs we don't want to advance the // index; removing the previous tab has pulled the next character // in *to* the index, so it's already where we want it. Advancing // regardless can cause every other adjacent tab not to be // elminiated. // if (TabExp > 0) { Idx = _BString->NextChar(Idx); } } szBuffer = _BString->QuerySTR(); if (szBuffer != NULL) { _String->Initialize(szBuffer); DELETE( szBuffer ); } } } return TRUE; } BOOLEAN PAGER::SkipLines ( IN ULONG LinesToSkip, IN ULONG TabExp ) /*++ Routine Description: Skips certain number of lines Arguments: LinesToSkip - Supplies the number of lines to skip TabExp - Supplies number of spaces per tab Return Value: TRUE - If lines skipped FALSE otherwise --*/ { if ( TabExp > (ULONG)( _ColumnsInPage - 1 ) ) { TabExp = _ColumnsInPage - 1; } while ( LinesToSkip-- && ThereIsMoreToPage() ) { if (!ReadNextString( TabExp )) { return FALSE; } _Position = INVALID_CHNUM; } return TRUE; } #ifdef FE_SB // v-junm - 09/24/93 BOOLEAN PAGER::IsLeadByte( IN BYTE c ) /*++ Routine Description: Checks to see if c is a leadbyte of a DBCS character. Arguments: c - character to check to see if leadbyte. Return Value: TRUE - leadbyte of DBCS character. FALSE otherwise --*/ { CPINFO cp; static UINT outputcp = GetConsoleOutputCP(); int i; if ( GetCPInfo( outputcp, &cp ) ) { // // Code page info has been aquired. From code page info, // the leadbyte range can be determined. // for( i = 0; cp.LeadByte[i] && cp.LeadByte[i+1]; i += 2 ) { // // There are leadbytes. Check to see if c falls in // current leadbyte range. // if ( c >= cp.LeadByte[i] && c <= cp.LeadByte[i+1] ) return( TRUE ); } return( FALSE ); } else { // // This will not produce correct results if // 'ConsoleOutputCP != SystemCP && DBCS System' // Just making system conversion the default // when GetCPInfo doesn't work. // return( IsDBCSLeadByte( c ) != FALSE ); } } #endif