/****************************************************************************
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);
}