📄 win32term.cpp
字号:
//
// 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 = 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 ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -