// // 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 #include #include #include #include // // With Visual Studio 5.0 SP3, min and max // quit working when used in conjunction with // class vector! 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( cols ) ); m_ScreenColor.resize( rows, vector( 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 = 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