|
|
/****************************************************************************
PROGRAM: wterm.c
PURPOSE: Implementation of TermWClass Windows
FUNCTIONS:
COMMENTS:
****************************************************************************/
#include "windows.h"
#include "stdlib.h"
#include "memory.h"
#include "wterm.h"
#define MAX_ROWS 24
#define MAX_COLS 80
typedef struct WData { // Function to execute for processing a menu
MFUNCP pMenuProc;
// Function to execute for processing a single character
CFUNCP pCharProc;
// Function to execute when window is closed (terminated)
TFUNCP pCloseProc;
// Pass on callback
void *pvCallBackData;
BOOL fGotFocus;
BOOL fCaretHidden;
// Rows on the screen
int cRows;
// Columns on the screen
int cCols;
// Row at top of screen
int iTopRow;
// Row at bottom of the screen
int iBottomRow;
// First Column on screen
int iFirstCol;
// Column at bottom of the screen
int iBottomCol;
// Row for next character
int iNextRow;
// Row for next column
int iNextCol;
// Width of character
int cxChar;
// Height of character
int cyChar;
// Memory image of screen this is treated as a circular buffer
TCHAR aImage[MAX_ROWS] [MAX_COLS];
// First row in circular screen buffer
int iBufferTop; } WData;
static HANDLE hInst = 0; TCHAR BlankLine[80];
static int row_diff( int row1, int row2) { return (row2 > row1) ? MAX_ROWS - (row2 - row1) : row1 - row2; }
static void set_vscroll_pos( HWND hwnd, WData *pwdata) { if (pwdata->cRows != 0) { // Save a few indirections by caching cRows
register int cRows = pwdata->cRows;
// calculate distance bottom of screen from top of data buffer
register int top_from_row = row_diff(pwdata->iBottomRow, pwdata->iBufferTop);
// Output position of scroll bar
int new_pos = 0;
if (top_from_row >= cRows) { // Calculate number of screens to display entire buffer
int screens_for_data = MAX_ROWS / cRows + ((MAX_ROWS % cRows != 0) ? 1 : 0);
// Figure out which screen the row falls in
int screen_loc = top_from_row / cRows + ((top_from_row % cRows != 0) ? 1 : 0);
// If the screen is in the last one set box to max
new_pos = (screen_loc == screens_for_data) ? MAX_ROWS : screen_loc * cRows; }
SetScrollPos(hwnd, SB_VERT, new_pos, TRUE); } }
static int calc_row( register int row, WData *pwdata) { register int top = pwdata->iTopRow; static int boopa = 0;
if (top > row) boopa++;
return (row >= top) ? row - top : (MAX_ROWS - (top - row)); }
static void display_text( HWND hwnd, int row, int col, LPTSTR text, int text_len, WData *pWData) { // Get the DC to display the text
HDC hdc = GetDC(hwnd);
// Select Font
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Hide caret while we are printing
HideCaret(hwnd);
// Update the screen
TextOut(hdc, (col - pWData->iFirstCol) * pWData->cxChar, calc_row(row, pWData) * pWData->cyChar, text, text_len);
// Done with DC
ReleaseDC(hwnd, hdc);
// Put the caret back now that we are done
ShowCaret(hwnd); }
static void display_char( HWND hwnd, TCHAR char_to_display, WData *pWData) { // Update image buffer
pWData->aImage[pWData->iNextRow][pWData->iNextCol] = char_to_display;
display_text(hwnd, pWData->iNextRow, pWData->iNextCol, &char_to_display, 1, pWData); }
static void do_backspace( HWND hwnd, WData *pWData) { // Point to the previous character in the line
if (--pWData->iNextCol < 0) { // Can't backspace beyond the current line
pWData->iNextCol = 0; return; }
display_char(hwnd, ' ', pWData);
// Null character for repaint
pWData->aImage[pWData->iNextRow][pWData->iNextCol] = '\0'; }
static int inc_row( int row, int increment) { row += increment;
if (row >= MAX_ROWS) { row -= MAX_ROWS; } else if (row < 0) { row += MAX_ROWS; }
return row; }
void inc_next_row( HWND hwnd, WData *pWData) { if (pWData->iNextRow == pWData->iBottomRow) { // Line is at bottom -- scroll the client area one row
ScrollWindow(hwnd, 0, -pWData->cyChar, NULL, NULL);
// Increment the top & bottom of the screen
pWData->iTopRow = inc_row(pWData->iTopRow, 1); pWData->iBottomRow = inc_row(pWData->iBottomRow, 1); }
// Increment the row
pWData->iNextRow = inc_row(pWData->iNextRow, 1);
if (pWData->iNextRow == pWData->iBufferTop) { // Have to reset circular buffer to next
pWData->iBufferTop = inc_row(pWData->iBufferTop, 1);
// Reset line to nulls for repaint
memset(&pWData->aImage[pWData->iNextRow][0], '\0', MAX_COLS); }
pWData->iNextCol = 0; }
static void do_cr( HWND hwnd, WData *pWData) { // Set position to next row
inc_next_row(hwnd, pWData); pWData->iNextCol = 0;
// Make sure next character is null for repaint of line
pWData->aImage[pWData->iNextRow][pWData->iNextCol] = '\0';
// Update the vertical scroll bar's position
set_vscroll_pos(hwnd, pWData); }
static void do_char( HWND hwnd, WPARAM wParam, WData *pWData) { display_char(hwnd, (TCHAR) wParam, pWData);
// Point to the next character in the line
if (++pWData->iNextCol > MAX_COLS) { // Handle switch to next line
inc_next_row(hwnd, pWData); } }
static void do_tab( HWND hwnd, WData *pWData) { int c = pWData->iNextCol % 8;
if ((pWData->iNextCol + c) <= MAX_COLS) { for ( ; c; c--) { do_char(hwnd, ' ', pWData); } } else { do_cr(hwnd, pWData); } }
static void EchoChar( HWND hwnd, WORD cRepeats, WPARAM wParam, WData *pWData) { for ( ; cRepeats; cRepeats--) { switch (wParam) { // Backspace
case '\b': do_backspace(hwnd, pWData); break;
// Carriage return
case '\n': case '\r': do_cr(hwnd, pWData); break;
// Tab
case '\t': do_tab(hwnd, pWData); break;
// Regular characters
default: do_char(hwnd, wParam, pWData); } }
// The row is guaranteed to be on the screen because we will
// scroll on a CR. However, the next column for input may be
// beyond the window we are working in.
if (pWData->iNextCol > pWData->iBottomCol) { // We are out of the window so scroll the window one
// column to the right.
SendMessage(hwnd, WM_HSCROLL, SB_LINEDOWN, 0L); } else if (pWData->iNextCol < pWData->iFirstCol) { // We are out of the window so repaint the window using
// iNextCol as the first column for the screen.
pWData->iFirstCol = pWData->iNextCol; pWData->iBottomCol = pWData->iFirstCol + pWData->cCols - 1;
// Reset scroll bar
SetScrollPos(hwnd, SB_HORZ, pWData->iFirstCol, TRUE);
// Tell window to update itself.
InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); } else { // Reset Caret's position
SetCaretPos((pWData->iNextCol - pWData->iFirstCol) * pWData->cxChar, calc_row(pWData->iNextRow, pWData) * pWData->cyChar); } }
/****************************************************************************
FUNCTION: WmCreate(HWND)
PURPOSE: Initializes control structures for a TermWClass Window
MESSAGES: WM_CREATE
COMMENTS:
This prepares a window for processing character based I/O. In particular it does stuff like calculate the size of the window needed.
****************************************************************************/ static void WmCreate( HWND hwnd, CREATESTRUCT *pInit) { WData *pData = (WData *) (pInit->lpCreateParams); HDC hdc = GetDC(hwnd); TEXTMETRIC tm;
// Store pointer to window data
SetWindowLong(hwnd, 0, (LONG) pData);
// Set font to system fixed font
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
// Calculate size of a character
GetTextMetrics(hdc, &tm); pData->cxChar = tm.tmAveCharWidth; pData->cyChar = tm.tmHeight; ReleaseDC(hwnd, hdc);
// Set up vertical scroll bars
SetScrollRange(hwnd, SB_VERT, 0, MAX_ROWS, TRUE); SetScrollPos(hwnd, SB_VERT, 0, TRUE);
// Set up horizontal scroll bars
SetScrollRange(hwnd, SB_HORZ, 0, MAX_COLS, TRUE); SetScrollPos(hwnd, SB_HORZ, 0, TRUE); }
/****************************************************************************
FUNCTION: WmSize(HWND, WORD, LONG)
PURPOSE: Processes a size message
MESSAGES:
COMMENTS:
****************************************************************************/ static void WmSize( HWND hwnd, WPARAM wParam, LONG lParam, WData *pwdata) { // Get the new size of the window
int cxClient; int cyClient; int cRowChange = pwdata->cRows; RECT rect;
// Get size of client area
GetClientRect(hwnd, &rect);
// Calculate size of client area
cxClient = rect.right - rect.left; cyClient = rect.bottom - rect.top;
// Calculate size of area in rows
pwdata->cCols = cxClient / pwdata->cxChar; pwdata->cRows = min(MAX_ROWS, cyClient / pwdata->cyChar); pwdata->iBottomCol = min(pwdata->iFirstCol + pwdata->cCols, MAX_COLS); cRowChange = pwdata->cRows - cRowChange;
// Keep input line toward bottom of screen
if (cRowChange < 0) { // Screen has shrunk in size.
if (pwdata->iNextRow != pwdata->iTopRow) { // Has input row moved out of screen?
if (row_diff(pwdata->iNextRow, pwdata->iTopRow) >= pwdata->cRows) { // Yes -- Calculate top new top that puts input line on
// the bottom.
pwdata->iTopRow = inc_row(pwdata->iNextRow, 1 - pwdata->cRows); } } } else { // Screen has gotten bigger -- Display more text if possible
if (pwdata->iTopRow != pwdata->iBufferTop) { pwdata->iTopRow = inc_row(pwdata->iTopRow, -(min(row_diff(pwdata->iTopRow, pwdata->iBufferTop), cRowChange))); } }
// Calculate new bottom
pwdata->iBottomRow = inc_row(pwdata->iTopRow, pwdata->cRows - 1);
InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); }
static void WmSetFocus( HWND hwnd, WData *pwdata) { // save indirections
register int cxchar = pwdata->cxChar; register int cychar = pwdata->cyChar; pwdata->fGotFocus = TRUE; CreateCaret(hwnd, NULL, cxchar, cychar);
if (!pwdata->fCaretHidden) { SetCaretPos(pwdata->iNextCol * cxchar, calc_row(pwdata->iNextRow, pwdata) * cychar); }
ShowCaret(hwnd); }
static void WmKillFocus( HWND hwnd, WData *pwdata) { pwdata->fGotFocus = FALSE;
if (!pwdata->fCaretHidden) { HideCaret(hwnd); }
DestroyCaret(); }
static void WmVscroll( HWND hwnd, WPARAM wParam, LONG lParam, WData *pwdata) { int cVscrollInc = 0; register int top_diff = row_diff(pwdata->iTopRow, pwdata->iBufferTop); register int bottom_diff = MAX_ROWS - (top_diff + pwdata->cRows);
switch(wParam) { case SB_TOP:
if (top_diff != 0) { cVscrollInc = -top_diff; }
break;
case SB_BOTTOM:
if (bottom_diff != 0) { cVscrollInc = bottom_diff; }
break;
case SB_LINEUP:
if (top_diff != 0) { cVscrollInc = -1; }
break;
case SB_LINEDOWN:
if (bottom_diff != 0) { cVscrollInc = 1; }
break;
case SB_PAGEUP:
if (top_diff != 0) { cVscrollInc = - ((top_diff > pwdata->cRows) ? pwdata->cRows : top_diff); }
break;
case SB_PAGEDOWN:
if (bottom_diff != 0) { cVscrollInc = (bottom_diff > pwdata->cRows) ? pwdata->cRows : bottom_diff; }
break;
case SB_THUMBTRACK:
if (LOWORD(lParam) != 0) { cVscrollInc = LOWORD(lParam) - row_diff(pwdata->iTopRow, pwdata->iBufferTop); } }
// Cacluate new top row
if (cVscrollInc != 0) { // Calculate new top and bottom
pwdata->iTopRow = inc_row(pwdata->iTopRow, cVscrollInc); pwdata->iBottomRow = inc_row(pwdata->iTopRow, pwdata->cRows);
// Scroll window
ScrollWindow(hwnd, 0, pwdata->cyChar * cVscrollInc, NULL, NULL);
// Reset scroll bar
set_vscroll_pos(hwnd, pwdata);
// Tell window to update itself.
InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); } }
static void WmHscroll( HWND hwnd, WPARAM wParam, LONG lParam, WData *pwdata) { register int cHscrollInc = 0;
switch(wParam) { case SB_LINEUP:
cHscrollInc = -1; break;
case SB_LINEDOWN:
cHscrollInc = 1; break;
case SB_PAGEUP:
cHscrollInc = -8; break;
case SB_PAGEDOWN:
cHscrollInc = 8; break;
case SB_THUMBTRACK:
if (LOWORD(lParam) != 0) { cHscrollInc = LOWORD(lParam) - pwdata->iFirstCol; } }
if (cHscrollInc != 0) { // Cacluate new first column
register int NormalizedScrollInc = cHscrollInc + pwdata->iFirstCol;
if (NormalizedScrollInc < 0) { cHscrollInc = -pwdata->iFirstCol; } else if (NormalizedScrollInc > MAX_COLS - pwdata->cCols) { cHscrollInc = (MAX_COLS - pwdata->cCols) - pwdata->iFirstCol; }
pwdata->iFirstCol += cHscrollInc; pwdata->iBottomCol = pwdata->iFirstCol + pwdata->cCols - 1;
// Scroll window
ScrollWindow(hwnd, -(pwdata->cxChar * cHscrollInc), 0, NULL, NULL);
// Reset scroll bar
SetScrollPos(hwnd, SB_HORZ, pwdata->iFirstCol, TRUE);
// Tell window to update itself.
InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); } }
static void WmPaint( HWND hwnd, WData *pwdata) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); register int row = pwdata->iTopRow; register int col = pwdata->iFirstCol; int bottom_row = pwdata->iBottomRow; int cxChar = pwdata->cxChar; int cyChar = pwdata->cyChar; int y;
// Select System Font
SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
while (TRUE) { int len = lstrlen(&pwdata->aImage[row][col]);
if (len != 0) { y = calc_row(row, pwdata) * cyChar; TextOut(hdc, 0, y, &pwdata->aImage[row][col], len); }
if (row == bottom_row) { break; }
row = inc_row(row, 1); }
if (pwdata->fGotFocus) { if ((pwdata->iNextCol >= pwdata->iFirstCol) && (row_diff(pwdata->iNextRow, pwdata->iTopRow) < pwdata->cRows)) { if (pwdata->fCaretHidden) { pwdata->fCaretHidden = FALSE; ShowCaret(hwnd); }
SetCaretPos( (pwdata->iNextCol - pwdata->iFirstCol) * pwdata->cxChar, calc_row(pwdata->iNextRow, pwdata) * pwdata->cyChar); } else { if (!pwdata->fCaretHidden) { pwdata->fCaretHidden = TRUE; HideCaret(hwnd); } } }
EndPaint(hwnd, &ps); }
//
// FUNCTION: WmPrintLine
//
// PURPOSE: Print a line on the screen.
//
// Note: this is a user message not an intrinsic Window's message.
//
void WmPrintLine( HWND hwnd, WPARAM wParam, LONG lParam, WData *pTermData) { TCHAR *pBuf = (TCHAR *) lParam;
// MessageBox(hwnd, L"WmPrintLine", L"Debug", MB_OK);
// DebugBreak();
while (wParam--) { // Is character a lf?
if (*pBuf == '\n') { // Convert to cr since that is what this window uses
*pBuf = '\r'; }
// Write the character to the window
EchoChar(hwnd, 1, *pBuf++, pTermData); }
}
//
// FUNCTION: WmPutc
//
// PURPOSE: Print a single character on the screen
//
// Note: this is a user message not an intrinsic Window's message.
//
void WmPutc( HWND hwnd, WPARAM wParam, WData *pTermData) { // Is character a lf?
if (wParam == '\n') { // Convert to cr since that is what this window uses
wParam = '\r'; }
// Write the character to the window
EchoChar(hwnd, 1, wParam, pTermData); }
/****************************************************************************
FUNCTION: TermWndProc(HWND, unsigned, WORD, LONG)
PURPOSE: Processes messages
MESSAGES:
COMMENTS:
****************************************************************************/
long TermWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { WData *pTerm = (WData *) GetWindowLong(hWnd, 0);
switch (message) { case WM_CREATE: WmCreate(hWnd, (CREATESTRUCT *) lParam); break;
case WM_COMMAND: case WM_SYSCOMMAND: // Call procedure that processes the menus
return (*(pTerm->pMenuProc))(hWnd, message, wParam, lParam, pTerm->pvCallBackData);
case WM_SIZE: WmSize(hWnd, wParam, lParam, pTerm); break;
case WM_SETFOCUS: WmSetFocus(hWnd, pTerm); break;
case WM_KILLFOCUS: WmKillFocus(hWnd, pTerm); break;
case WM_VSCROLL: WmVscroll(hWnd, wParam, lParam, pTerm); break;
case WM_HSCROLL: WmHscroll(hWnd, wParam, lParam, pTerm); break;
case WM_CHAR: // Character message echo and put in buffer
return (*(pTerm->pCharProc))(hWnd, message, wParam, lParam, pTerm->pvCallBackData);
case WM_PAINT: WmPaint(hWnd, pTerm); break;
case WM_CLOSE: DestroyWindow(hWnd); break;
case WM_NCDESTROY: // Call close notification procedure
return (*(pTerm->pCloseProc))(hWnd, message, wParam, lParam, pTerm->pvCallBackData);
case WM_PRINT_LINE: WmPrintLine(hWnd, wParam, lParam, pTerm); break;
case WM_PUTC: WmPutc(hWnd, wParam, pTerm); break;
case WM_DESTROY: PostQuitMessage(0); break;
case WM_TERM_WND: DestroyWindow(hWnd); break;
default: /* Passes it on if unproccessed */ return (DefWindowProc(hWnd, message, wParam, lParam)); }
return 0; }
/****************************************************************************
FUNCTION: TermRegisterClass(HANDLE)
PURPOSE: Register a class for a terminal window
COMMENTS:
****************************************************************************/
BOOL TermRegisterClass( HANDLE hInstance, LPTSTR MenuName, LPTSTR ClassName, LPTSTR Icon) { WNDCLASS wc; BOOL retVal;
// Make sure blank line is blank
memset(BlankLine, ' ', 80);
/* Fill in window class structure with parameters that describe the */ /* main window. */
wc.style = 0; wc.lpfnWndProc = TermWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(WData *); wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, Icon); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = MenuName; wc.lpszClassName = ClassName;
/* Register the window class and return success/failure code. */ if (retVal = RegisterClass(&wc)) { // Class got registered -- so finish set up
hInst = hInstance; }
return retVal; }
/****************************************************************************
FUNCTION: TermCreateWindow(LPTSTR, LPTSTR, HMENU, void *, void *, int)
PURPOSE: Create a window of a previously registered window class
COMMENTS:
****************************************************************************/
BOOL TermCreateWindow( LPTSTR lpClassName, LPTSTR lpWindowName, HMENU hMenu, MFUNCP MenuProc, CFUNCP CharProc, TFUNCP CloseProc, int nCmdShow, HWND *phNewWindow, void *pvCallBackData) { HWND hWnd; // Main window handle.
WData *pTermData;
// Allocate control structure for the window
if ((pTermData = malloc(sizeof(WData))) == NULL) { return FALSE; }
// Set entire structure to nulls
memset((TCHAR *) pTermData, '\0', sizeof(WData));
// Initialize function pointers
pTermData->pMenuProc = MenuProc; pTermData->pCharProc = CharProc; pTermData->pCloseProc = CloseProc;
// Initialize callback data
pTermData->pvCallBackData = pvCallBackData;
// Create a main window for this application instance.
hWnd = CreateWindow( lpClassName, lpWindowName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hInst, (LPTSTR) pTermData );
// If window could not be created, return "failure"
if (!hWnd) { free(pTermData); return FALSE; }
SetFocus(hWnd);
// Make the window visible; update its client area; and return "success"
ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); *phNewWindow = hWnd; return (TRUE); }
|