//
//  WIN32TERM.CPP
//
//  Source code from:
//
//  Serial Communications: A C++ Developer's Guide, 2nd Edition
//  by Mark Nelson, M&T Books, 1999
//
//  Please see the book for information on usage.
//
// This header file contains the implementation of
// class Win32Term. This class creates a window that
// can be used as part of a Win32 terminal emulator. 
//

#include "Win32Term.h"
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cstring>

//
// With Visual Studio 5.0 SP3, min and max
// quit working when used in conjunction with
// class vector<T>! This code ws required to
// fix the strange problem.
//
#undef max
#undef min

inline int max( int a, int b )
{
    if ( a > b )
        return a;
    else
        return b;
}

inline int min( int a, int b )
{
    if ( a < b )
        return a;
    else
        return b;
}

//
// Static variable to make sure I only 
// register this class with Windows once.
//
bool Win32Term::m_bClassRegistered = false;

//
// The constructor for a Win32 object has to
// pull double duty. It has to create all the
// data structures and so on that will be used
// in the class, then it has to create the 
// window as well. Once this is done, the window
// has been created, the message loop is running,
// and the whole thing is ready to use.
//
// Note that the number of rows and columns in
// the virtual screen is decided here in the 
// constructor, and can't be changed after the
// window is constructed. The values are stored
// in a const Pair, so no amount of coding is 
// going to let them be modified.
//
Win32Term::Win32Term( HWND hParent,
                      const char *window_name,
                      int rows, 
                      int cols ) : m_VirtualSize( cols, rows )
{
    //
    // This code sets the two dimensional arrays to the
    // correct size, and copies default data into 
    // every cell. Much less code here than the equivalent
    // that would be needed for standard C 2-D arrays.
    //
    m_ScreenText.resize( rows, vector<char>( cols ) );
    m_ScreenColor.resize( rows, vector<TextColor>( cols ) );
    m_hParent        = 0;
    m_hWnd           = 0;
    m_bShowingCursor = false;
    m_bWrap          = true;
    m_VisibleSize    = Pair( 0, 0 );
    m_ScrollRange    = Pair( 0, 0 );
    m_Position       = Pair( 0, rows - 1 );
    //
    // setup default font information
    // 
    m_lfFont.lfHeight =         12 ;
    m_lfFont.lfWidth =          0 ;
    m_lfFont.lfEscapement =     0 ;
    m_lfFont.lfOrientation =    0 ;
    m_lfFont.lfWeight =         0 ;
    m_lfFont.lfItalic =         0 ;
    m_lfFont.lfUnderline =      0 ;
    m_lfFont.lfStrikeOut =      0 ;
    m_lfFont.lfCharSet =        OEM_CHARSET ;
    m_lfFont.lfOutPrecision =   OUT_DEFAULT_PRECIS ;
    m_lfFont.lfClipPrecision =  CLIP_DEFAULT_PRECIS ;
    m_lfFont.lfQuality =        DEFAULT_QUALITY ;
    m_lfFont.lfPitchAndFamily = FIXED_PITCH | FF_MODERN ;
    strcpy( m_lfFont.lfFaceName, "FixedSys" ) ;
    SetFont( m_lfFont );
    SetForegroundColor( RGB( 0, 0, 0 ) );
    SetBackgroundColor( RGB( 255, 255, 255 ) );
    Clear();
    //
    // Now I create the actual window
    //
    if ( !m_bClassRegistered ) {
        WNDCLASS wc = { 0 };
        wc.lpfnWndProc      = WindowProc;
        wc.hInstance        = GetModuleHandle( NULL );
        wc.hCursor          = LoadCursor( NULL, IDC_IBEAM );
        wc.hbrBackground    = ::CreateSolidBrush( RGB( 255, 255, 255 ) );
        wc.lpszClassName    = "Win32TermClass";
        wc.lpszMenuName     = NULL;
        wc.hIcon            = NULL;
        wc.cbWndExtra       = sizeof( Win32Term * );
        RegisterClass( &wc );
        m_bClassRegistered  = true;
    }
    m_BorderColor = RGB( 255, 255, 255 );
    m_hParent = hParent;
    //
    // I have to pass a pointer to myself to the window
    // so that it can store the pointer in the window long
    // word. Data that is going to be passed to a 
    // WM_CREATE handler has to go in this funny structure.
    //
    CreateData data = { sizeof( Win32Term * ), 
                        this 
                      };
    CreateWindow( "Win32TermClass", 
                  window_name, 
                  WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL,
                  0,0,
                  0,0,
                  m_hParent, 
                  0, 
                  GetModuleHandle( NULL ), 
                  &data );
}

//
// If the user manages to pass me a good
// LOGFONT, this should all go fairly
// smoothly. The old font is deleted, the
// new one is created, we get some info
// about it, then we redo the screen size,
// offsets, scroll ranges, etc. Finally,
// we do an invalidate to force the whole
// thing to be redrawn.
//
void Win32Term::SetFont( LOGFONT LogFont )
{
    TEXTMETRIC tm;
    HDC  hDC;
    if ( m_hFont )
        DeleteObject( m_hFont );

    m_lfFont = LogFont;
    m_hFont = CreateFontIndirect( &m_lfFont );

    hDC = GetDC( m_hParent ) ;
    SelectObject( hDC, m_hFont ) ;
    GetTextMetrics( hDC, &tm ) ;
    ReleaseDC( m_hParent, hDC ) ;

    m_CharSize.x = tm.tmAveCharWidth;
    m_CharSize.y = tm.tmHeight + tm.tmExternalLeading;
    m_iCharDescent = tm.tmDescent;

    m_Offset.x = 0 ;
    m_Offset.y = m_CharSize.y * m_Position.y;
    Size( m_VisibleSize.x, m_VisibleSize.y );
    ::InvalidateRect( m_hWnd, NULL, TRUE );
    if ( m_hWnd ) {
        KillFocus();
        SetFocus();
    }
}

//
// The routine has a lot to do. Please be sure
// to refer to the explanation in the book for
// a comprehensive discussion fo this routine.
// You really need to understand the member
// variables that are used in this routine to
// follow the code.
//
BOOL Win32Term::Paint()
{
    PAINTSTRUCT  ps;

    HDC hDC = BeginPaint( m_hWnd, &ps ) ;
    HFONT hOldFont = (HFONT) SelectObject( hDC, m_hFont );
    //
    // The first thing we do is figure out the 
    // minimum and maximum row in the update 
    // rectangle, along with the minumum and 
    // maximum column. We aren't going to try
    // to display any characters outside of that
    // cell, because there wouldn't be any point.
    // This makes the routine quite a bit more
    // efficient.
    RECT rect = ps.rcPaint;
    int nRow = min( m_VirtualSize.y - 1, 
                    max( 0, 
                         (rect.top + m_Offset.y ) / m_CharSize.y
                       ) 
                  );
    int nEndRow = min( m_VirtualSize.y - 1,
                       ( (rect.bottom + m_Offset.y - 1 ) / m_CharSize.y ) 
                     );
    int nCol = min( m_VirtualSize.x - 1,
                    max( 0, 
                         ( rect.left + m_Offset.x ) / m_CharSize.x 
                       ) 
                  );
    int nEndCol = min( m_VirtualSize.x - 1,
                       ( ( rect.right + m_Offset.x - 1 ) / m_CharSize.x ) 
                     );
    //
    // Given that info, we now enter a big for loop 
    // that is going to update the display of one row
    // at a time. We print each row in blocks of text
    // that are all the same color. Printing out a
    // string of characters is much more efficient
    // than just doing a single character at a time,
    // so its worth it to do the work of figuring out
    // which ones can be done in a single shot.
    //
    for ( ; nRow <= nEndRow; nRow++ )
    {
        int nVertPos = (nRow * m_CharSize.y ) - m_Offset.y;
        //
        // For each row, we perform an ExtTextOut() for each of
        // the contiguous blocks of the same color.
        //
        for ( int start_col = nCol; start_col <= nEndCol ; ) {
            TextColor current_color = m_ScreenColor[ nRow ][ start_col ];
            for ( int end_col = start_col ; end_col <= nEndCol ; end_col++ )
                if ( current_color != m_ScreenColor[ nRow ][ end_col ] )
                    break;
            end_col--;
            //
            // At this point I can print all the columns between 
            // start_col and end_col using the same color
            //
            int count = end_col - start_col + 1;
            int nHorzPos = (start_col * m_CharSize.x ) - m_Offset.x;
            rect.top = nVertPos ;
            rect.bottom = nVertPos + m_CharSize.y;
            rect.left = nHorzPos;
            rect.right = nHorzPos + m_CharSize.x * count;
            SetTextColor( hDC, m_ScreenColor[ nRow ][ start_col ].m_Foreground ) ;
            SetBkColor( hDC, m_ScreenColor[ nRow ][ start_col ].m_Background ) ;
            SetBkMode( hDC, OPAQUE );
            ExtTextOut( hDC, 
                        nHorzPos, 
                        nVertPos, 
                        ETO_OPAQUE | ETO_CLIPPED, 
                        &rect,
                        (LPSTR)( m_ScreenText[ nRow ].begin() + start_col ),
                        count, 
                        NULL );
            start_col = end_col + 1;
        }
    }
    SelectObject( hDC, hOldFont );
    EndPaint( m_hWnd, &ps );
    UpdateCursor();
    return TRUE;
}

//
// If we currently have the focus, I'll update
// the position of the cursor when this is called.
// If it is called when we don't have the focus,
// I won't try to move the caret. That would
// cause a lot of trouble.
//
void Win32Term::UpdateCursor()
{
   if ( m_bShowingCursor )
      SetCaretPos( 
                   ( m_Position.x * m_CharSize.x ) - m_Offset.x,
                   ( m_Position.y * m_CharSize.y ) - m_Offset.y 
                 );
   
}

//
// This is the handler that is called when this
// window gest the focus. If we didn't already
// have the cursor up, we put it up here.
//
void Win32Term::SetFocus()
{
   if ( !m_bShowingCursor )
   {
      CreateCaret( m_hWnd, NULL, m_CharSize.x, m_CharSize.y ) ;
      ShowCaret( m_hWnd ) ;
      m_bShowingCursor = true;
   }
   UpdateCursor();
}

//
// When we lose the focus we have to destroy
// the cursor. There is only one caret per 
// system, hogging it is very bad.
//
void Win32Term::KillFocus()
{
   if ( m_bShowingCursor )
   {
      HideCaret( m_hWnd );
      DestroyCaret() ;
      m_bShowingCursor = false;
   }
}


//
// The two output routines are nearly identical, The
// string output routine simply sits in a loop 
// repeating the code in the character routine over
// and over until the string is gone.
//
void Win32Term::Output( char c )
{
    switch ( c ) {
    //
    // The alert or BEL character gets special
    // handling. In this case, special means
    // that we play a beep sound to annoy the
    // user.
    //
    case '\a' :
        MessageBeep( 0 ) ;
        break ;

    //
    // The backspace key doesn't display anything
    // on the screen, it just backs up the cursor
    // by one cell if possible. 
    //
    case '\b' :                 
        if ( m_Position.x > 0 )
            m_Position.x-- ;
        break ;

    //
    // The Carriage Return doesn't do anything
    // to the contents of the screen either. It
    // just returns the cursor to the first column.
    // This is usually matched up with a line feed
    // immediately following.
    //
    case '\r' :                 
        m_Position.x = 0 ;
		break;

    //
    // The line feed character causes the cursor to
    // go to the next row. Normally this is not a
    // big deal, but if we were already on the last 
    // row of the screen, we have to scroll the whole
    // thing up, which is a big deal.
    //
    case '\n' :                 
        if ( m_Position.y++ == ( m_VirtualSize.y - 1 ) )
        {
            for ( int j = 0 ; j < ( m_VirtualSize.y - 1 ) ; j++ ) {
                m_ScreenText[ j ] = m_ScreenText[ j + 1 ];
                m_ScreenColor[ j ] = m_ScreenColor[ j + 1 ];
            }
            fill( m_ScreenText.back().begin(),
                  m_ScreenText.back().end(),
                  ' ' );
            fill( m_ScreenColor.back().begin(),
                  m_ScreenColor.back().end(),
                  m_CurrentColor );
            InvalidateRect( m_hWnd, NULL, FALSE );
            m_Position.y--;
        }
        break;

    //
    // Normal character processing is straightforward.
    // We stuff the character and its color into the
    // screen at the current insertion point, then update
    // the cursor position. We also invalidate the tiny
    // bit of the screen that we just modified so that it
    // will get repainted.
    //
    default:
        {
            RECT rect;
            m_ScreenText[ m_Position.y ][ m_Position.x ] = c;
            m_ScreenColor[ m_Position.y ][ m_Position.x ] = m_CurrentColor;
            rect.left = ( m_Position.x * m_CharSize.x ) - m_Offset.x;
            rect.right = rect.left + m_CharSize.x;
            rect.top = ( m_Position.y * m_CharSize.y ) - m_Offset.y;
            rect.bottom = rect.top + m_CharSize.y;
            InvalidateRect( m_hWnd, &rect, FALSE ) ;
            // 
            // If we reach the end of the line, we
            // might have to wrap to the next line.
            //
            if ( m_Position.x < ( m_VirtualSize.x - 1)  )
                m_Position.x++;
            else if ( m_bWrap ) 
                Output( "\r\n" ) ;
            break;
        }
    }
    UpdateCursor();
}

//
// See the previous routine for docs, it is nearly
// identical.
//
void Win32Term::Output( const char *pBuf, int length /* = -1 */ )
{
    if ( length == -1 )
        length = strlen( pBuf );
    for ( int i = 0 ; i < length ; i++ ) {
        switch ( pBuf[ i ] ) {
        case '\a' :                
            MessageBeep( 0 ) ;
            break ;

        case '\b' : 
            if ( m_Position.x > 0 )
                m_Position.x-- ;
            break ;

        case '\r' :
            m_Position.x = 0 ;
            break;

        case '\n' : 
            if ( m_Position.y++ == ( m_VirtualSize.y - 1 ) )
            {
                for ( int j = 0 ; j < ( m_VirtualSize.y - 1 ) ; j++ ) {
                    m_ScreenText[ j ] = m_ScreenText[ j + 1 ];
                    m_ScreenColor[ j ] = m_ScreenColor[ j + 1 ];
                }
                fill( m_ScreenText.back().begin(),
                      m_ScreenText.back().end(),
                      ' ' );
                fill( m_ScreenColor.back().begin(),
                      m_ScreenColor.back().end(),
                      m_CurrentColor );
                    InvalidateRect( m_hWnd, NULL, FALSE );
                    m_Position.y--;
            }
            break;

        default:
            {
                RECT rect;
                m_ScreenText[ m_Position.y ][ m_Position.x ] = pBuf[ i ];
                m_ScreenColor[ m_Position.y ][ m_Position.x ] = m_CurrentColor;
                rect.left = ( m_Position.x * m_CharSize.x ) - m_Offset.x;
                rect.right = rect.left + m_CharSize.x;
                rect.top = ( m_Position.y * m_CharSize.y ) - m_Offset.y;
                rect.bottom = rect.top + m_CharSize.y;
                ::InvalidateRect( m_hWnd, &rect, FALSE ) ;

                // 
                // Check to see if we wrapped
                //
                if ( m_Position.x < ( m_VirtualSize.x - 1)  )
                    m_Position.x++ ;
                else if ( m_bWrap )
                    Output( "\r\n", 2 ) ;                
                break;
            }
        }
    }
    UpdateCursor();
}

//
// There are a big bunch of possible messages that
// we can get from the scroll bars. All we have to
// do in the handler is determine how far to scroll
// the screen based on the input we receive in the
// form of a message. Note that the total span of
// the scroll bar is stored in m_ScrollRange.
//
void Win32Term::VerticalScroll( WPARAM wParam )
{
    int ScrollCommand = LOWORD( wParam );
    int ScrollPosition = HIWORD( wParam );
    int  nScrollAmt;

    switch (  ScrollCommand )
    {
        case SB_TOP :
            nScrollAmt = -m_Offset.y;
            break;

        case SB_BOTTOM :
            nScrollAmt = m_ScrollRange.y - m_Offset.y;
            break;

        case SB_PAGEUP :
            nScrollAmt = -m_VisibleSize.y;
            break;

        case SB_PAGEDOWN :
            nScrollAmt = m_VisibleSize.y;
            break;

        case SB_LINEUP:
            nScrollAmt = -m_CharSize.y;
            break;

        case SB_LINEDOWN:
            nScrollAmt = m_CharSize.y;
            break;

        case SB_THUMBPOSITION:
            nScrollAmt = ScrollPosition - m_Offset.y;
            break;

        default:
           return;
    }

    if ( ( m_Offset.y + nScrollAmt ) > m_ScrollRange.y )
        nScrollAmt = m_ScrollRange.y - m_Offset.y;
    if ( ( m_Offset.y + nScrollAmt ) < 0 )
        nScrollAmt = -m_Offset.y;
    ::ScrollWindowEx( m_hWnd, 
                      0, 
                      -nScrollAmt, 
                      NULL, 
                      NULL, 
                      NULL, 
                      NULL, 
                      SW_INVALIDATE | SW_ERASE );

    m_Offset.y += nScrollAmt;
    SetScrollPos( m_hWnd, SB_VERT, m_Offset.y, TRUE ) ;
}

void Win32Term::HorizontalScroll( WPARAM wParam )
{
    int ScrollCommand = LOWORD( wParam );
    int ScrollPosition = HIWORD( wParam );
    int nScrollAmt;

    switch ( ScrollCommand )
    {
        case SB_TOP:
            nScrollAmt = -m_Offset.x;
            break;

        case SB_BOTTOM:
            nScrollAmt = m_ScrollRange.x - m_Offset.x;
            break ;

        case SB_PAGEUP:
            nScrollAmt = -m_VisibleSize.x;
            break;

        case SB_PAGEDOWN:
            nScrollAmt = m_VisibleSize.x;
            break ;

        case SB_LINEUP:
            nScrollAmt = -m_CharSize.x;
            break ;

        case SB_LINEDOWN:
            nScrollAmt = m_CharSize.x;
            break;

        case SB_THUMBPOSITION:
            nScrollAmt = ScrollPosition - m_Offset.x;
            break;

        default:
            return;
    }
    if ( ( m_Offset.x + nScrollAmt ) > m_ScrollRange.x )
        nScrollAmt = m_ScrollRange.x - m_Offset.x;
    if ( ( m_Offset.x + nScrollAmt ) < 0 )
        nScrollAmt = -m_Offset.x;
    ScrollWindowEx( m_hWnd, 
                    -nScrollAmt, 
                    0, 
                    NULL, 
                    NULL, 
                    NULL, 
                    NULL, 
                    SW_INVALIDATE | SW_ERASE );
    m_Offset.x = m_Offset.x + nScrollAmt;
    ::SetScrollPos( m_hWnd, SB_HORZ, m_Offset.x, TRUE );
}

//
// See the text in the book for details on the Size()
// message handler. In principle this routine should
// not be too tricky, but it is complicated by the
// fact that scroll bars can pop up or disappear as
// things change.
//
void Win32Term::Size( int x, int y )
{
    //
    // First, we will try to do everything with no scroll bars.
    // If there are scroll bars, we're going to give their space
    // back, at least temporarily
    //
    long style = ::GetWindowLong( m_hWnd, GWL_STYLE );
    if ( style & WS_VSCROLL )
        x += ::GetSystemMetrics( SM_CXVSCROLL );
    if ( style & WS_HSCROLL )
        y += ::GetSystemMetrics( SM_CYHSCROLL );
    //
    // adjust vertical settings
    //
    m_VisibleSize.y = y;
    m_ScrollRange.y = max( 0, (m_VirtualSize.y * m_CharSize.y ) + m_iCharDescent - m_VisibleSize.y ) ;
    int nScrollAmt = min( m_ScrollRange.y, m_Offset.y ) - m_Offset.y;
    m_Offset.y = m_Offset.y + nScrollAmt;
 
    //
    // adjust horz settings
    //
    m_VisibleSize.x = x;
    m_ScrollRange.x = max( 0, (m_VirtualSize.x *  m_CharSize.x ) - m_VisibleSize.x );
    nScrollAmt = min( m_ScrollRange.x, m_Offset.x ) - m_Offset.x;
    m_Offset.x = m_Offset.x + nScrollAmt;
    //
    // If we created a vertical scrollbar, we need to go back and adjust the horizontal
    //
    if ( m_ScrollRange.y > 0 ) {
        m_VisibleSize.x = x - ::GetSystemMetrics( SM_CXVSCROLL );
        m_ScrollRange.x = max( 0, (m_VirtualSize.x *  m_CharSize.x ) - m_VisibleSize.x );
        nScrollAmt = min( m_ScrollRange.x, m_Offset.x ) - m_Offset.x;
        m_Offset.x = m_Offset.x + nScrollAmt;
    } 
    //
    // If we created a horzontal scrollbar, we need to go back and adjust the vertical
    //
    if ( m_ScrollRange.x > 0 ) {
        m_VisibleSize.y = y - ::GetSystemMetrics( SM_CYHSCROLL );
        m_ScrollRange.y = max( 0, (m_VirtualSize.y * m_CharSize.y ) + m_iCharDescent - m_VisibleSize.y ) ;
        int nScrollAmt = min( m_ScrollRange.y, m_Offset.y ) - m_Offset.y;
        m_Offset.y = m_Offset.y + nScrollAmt;
        //
        // And it's actually still possible that we need to readjust the X scrollbar
        //
        m_VisibleSize.x = x - ::GetSystemMetrics( SM_CXVSCROLL );
        m_ScrollRange.x = max( 0, (m_VirtualSize.x *  m_CharSize.x ) - m_VisibleSize.x );
        nScrollAmt = min( m_ScrollRange.x, m_Offset.x ) - m_Offset.x;
        m_Offset.x = m_Offset.x + nScrollAmt;
    }
    //
    // Now we do the actual scrolling. Note that at this
	// point, the number i m_ScrollRange represents the
	// difference between the pixels needed to display the
	// Virtual screen and the number of pixels in the visible
	// window.
    //
    ScrollWindow( m_hWnd, 0, -nScrollAmt, NULL, NULL );
    SetScrollRange( m_hWnd, SB_VERT, 0, m_ScrollRange.y, TRUE );
    SetScrollPos( m_hWnd, SB_VERT, m_Offset.y, FALSE );
    //
    ScrollWindow( m_hWnd, nScrollAmt, 0, NULL, NULL );
    SetScrollRange( m_hWnd, SB_HORZ, 0, m_ScrollRange.x, FALSE ) ;
    SetScrollPos( m_hWnd, SB_HORZ, m_Offset.x, TRUE ) ;
    
    InvalidateRect( m_hWnd, NULL, FALSE ) ;   // redraw entire window
}

//
// This is the dispatcher for messages coming into
// this window. This is the C++ equivaelnt of a 
// WinProc, and in fact is called by the WinProc
// for this window class.
//

LRESULT Win32Term::Dispatch( HWND hWnd, 
                             UINT uMsg, 
                             WPARAM wParam, 
                             LPARAM lParam )
{
    switch( uMsg ) {
        case WM_VSCROLL:
            VerticalScroll( wParam );
            break;

        case WM_HSCROLL:
            HorizontalScroll( wParam );
            break ;

        case WM_SIZE:
            Size( LOWORD( lParam ), HIWORD( lParam ) );
            break;

        case WM_PAINT:
            Paint();
            break;

        case WM_CHAR:
            Output( (char) wParam );
            break;

        case WM_SETFOCUS:
            SetFocus();
            break ;

        case WM_KILLFOCUS:
            KillFocus();
            break;

        case WM_MOUSEACTIVATE:
            ::SetFocus( hWnd );
            return MA_ACTIVATE;
            break;

        case WM_DESTROY :
            SetWindowLong( hWnd, 0, 0 );
			m_hWnd = 0;
            break;
        default:
            return DefWindowProc( hWnd, uMsg, wParam, lParam );
    }
    return 0L;
}

//
// The WinProc for this class is what Windows
// will call when any messages are to be dispatched.
// The only time we actually do anything in this 
// routine is when the window is first created, 
// because then we get a special pointer to the
// C++ object passed in. We extract that pointer
// and store it in a window long word so that all
// subsequent calls to ths routine can get a pointer
// to the C++ object. Thus, all subseuqent calls can
// also be handled by the C++ member function 
// Dispatch().
//
// Why am I so eager to have commands handled by a
// C++ member function? Most importantly, it gives
// me the opportunity to derive new classes from
// Win32Term, and let them write their own versions
// of Dispatch, allowing them to override some or
// all of the behavior of the class. This is how the
// Chapter 13 example works.
//
LRESULT CALLBACK Win32Term::WindowProc( HWND hWnd, 
                                        UINT uMsg, 
                                        WPARAM wParam, 
                                        LPARAM lParam )
{
    Win32Term *p = (Win32Term *) ::GetWindowLong( hWnd, 0 );
    if ( p )
        return p->Dispatch( hWnd, uMsg, wParam, lParam );
    //
    // If we haven't defined the window yet, I want to make sure I don't do 
    // anything that requires the pointer to the object
    //
    if ( uMsg == WM_CREATE ) 
    {
        CreateData UNALIGNED *pData = (CreateData UNALIGNED *) ((LPCREATESTRUCT) lParam)->lpCreateParams;
        pData->pTerm->m_hParent = ::GetParent( hWnd );
        pData->pTerm->m_hWnd = hWnd;
        SetWindowLong( hWnd, 0, (LONG) pData->pTerm );
    } 
    return DefWindowProc( hWnd, uMsg, wParam, lParam );
}

//
// Normally clearing the screen is very simple. We
// just fill the screen with blanks of a certain color.
// However, sometimes it is nice to have calibration data
// on the screen so that we can do some experimentation.
// By changing the "#if 1" to "#if 0", we can turn that
// code on. It isn't pretty, but sometimes it is very 
// handy.
//
void Win32Term::Clear()
{
#if 1	
    for ( int row = 0 ; row < m_VirtualSize.y ; row++ ) {
        fill( m_ScreenText[ row ].begin(),
              m_ScreenText[ row ].end(),
              ' ' );
        fill( m_ScreenColor[ row ].begin(),
              m_ScreenColor[ row ].end(),
              m_CurrentColor );
    }
#else
    TextColor bars[ 2 ] = {
		TextColor( RGB(   0,   0,   0 ), RGB( 224, 224, 224 ) ),
		TextColor( RGB( 255, 255, 255 ), RGB( 0,   0,   0 ) )
	};
	bool temp_wrap = m_bWrap;
	m_bWrap = false;
	int temp_row; 
	int temp_col;
	GetCursorPosition( temp_row, temp_col );
    for ( int row = 0 ; row < m_VirtualSize.y ; row++ ) {
        fill( m_ScreenText[ row ].begin(),
              m_ScreenText[ row ].end(),
              ' ' );
        fill( m_ScreenColor[ row ].begin(),
              m_ScreenColor[ row ].end(),
              bars[ row & 1 ] );
		SetForegroundColor( bars[ row & 1 ].m_Foreground );
		SetBackgroundColor( bars[ row & 1 ].m_Background );
		ostringstream s1;
		ostringstream s2;
		s1 << "*** Row " << setw( 2 ) << row;
		s2 << "Row " << setw( 2 ) << row << " ***";
		SetCursorPosition( row, 0 );
		Output( s1.str().c_str() );
		SetCursorPosition( row, m_VirtualSize.x - s2.str().size() );
		Output( s2.str().c_str() );
    }
	m_bWrap = temp_wrap;
	SetCursorPosition( temp_row, temp_col );
#endif
    ::InvalidateRect( m_hWnd, NULL, TRUE );
}

//
// The border color is truly just the background
// color for the window. When we get a new
// color in for the background, we just create
// a brush for that color and stuff it into the
// class data for this window. The rest of it
// is automatic, we never have to actually draw
// the border, it's done automatically as part
// of the paint process.
//
void Win32Term::SetBorderColor( COLORREF color )
{
    HBRUSH brush = (HBRUSH) ::GetClassLong( m_hWnd, GCL_HBRBACKGROUND );
    if ( brush )
        DeleteObject( brush );
    brush = ::CreateSolidBrush( color );
    ::SetClassLong( m_hWnd, GCL_HBRBACKGROUND, (LONG) brush );
    m_BorderColor = color;
    ::InvalidateRect( m_hWnd, NULL, TRUE );
}

//
// Done just like you might think, with a bit
// of error checking
//
int Win32Term::SetCursorPosition( int row, int col )
{
    if ( row < 0 || col < 0 )
        return 0;
    if ( row >= m_VirtualSize.y )
        return 0;
    if ( col >= m_VirtualSize.x )
        return 0;
    m_Position.x = col;
    m_Position.y = row;
    UpdateCursor();
    return 1;
}

void Win32Term::GetCursorPosition( int &row, int &col )
{
    row = m_Position.y;
    col = m_Position.x;
}

//EOF Win32Term.cpp

