The TYPE program shown in Figure 3-5 (beginning on the following page) brings together much of what we've learned in this chapter. You can think of TYPE as an extremely rudimentary text editor. You can type in the window, move the cursor (I mean caret) around with the cursor movement (or are they caret movement?) keys, and erase the contents of the window by pressing Escape. The contents of the window are also erased when you resize the window. There's no scrolling, no search and replace, no way to save files, and no spell checker, but it's a start.
To make things easy for myself, TYPE uses SYSTEM_FIXED_FONT. Writing a text editor for a proportional font is, as you might imagine, much more difficult. The program obtains a device context in several places: during the WM_CREATE message, the WM_KEYDOWN message, the WM_CHAR message, and the WM_PAINT message. Each time, calls to GetStockObject and SelectObject select the fixed-pitch font.
During the WM_SIZE message, TYPE calculates the character width and height of the window and saves these values in the variables cxBuffer and cyBuffer. It then uses malloc to allocate a buffer to hold all the characters that can be typed in the window. The xCaret and yCaret variables store the character position of the caret.
During the WM_SETFOCUS message, TYPE calls CreateCursor to create a cursor that is the width and height of a character, SetCaretPos to set the caret position, and ShowCaret to make the caret visible. During the WM_KILLFOCUS message, TYPE calls HideCaret and DestroyCaret.
TYPE.MAK
#--------------------
# TYPE.MAK make file
#--------------------
type.exe : type.obj type.def
link type, /align:16, NUL, /nod slibcew libw, type
rc type.exe
type.obj : type.c
cl -c -Gsw -Ow -W2 -Zp type.c
TYPE.C
/*-------------------------------------
TYPE.C -- Typing Program
(c) Charles Petzold, 1990
-------------------------------------*/
#include <windows.h>
#include <stdlib.h>
#define BUFFER(x, y) *(pBuffer + y * cxBuffer + x)
long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
static char szAppName[] = "Type" ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
if (!hPrevInstance)
{
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
RegisterClass (&wndclass) ;
}
hwnd = CreateWindow (szAppName, "Typing Program",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, nCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
{
static char *pBuffer = NULL ;
static int cxChar, cyChar, cxClient, cyClient, cxBuffer, cyBuffer,
xCaret, yCaret ;
HDC hdc ;
int x, y, i ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE :
hdc = GetDC (hwnd) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cyChar = tm.tmHeight ;
ReleaseDC (hwnd, hdc) ;
return 0 ;
case WM_SIZE :
// obtain window size in pixels
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
// calculate window size in characters
cxBuffer = max (1, cxClient / cxChar) ;
cyBuffer = max (1, cyClient / cyChar) ;
// allocate memory for buffer and clear it
if (pBuffer != NULL)
free (pBuffer) ;
if ((LONG) cxBuffer * cyBuffer > 65535L ||
(pBuffer = malloc (cxBuffer * cyBuffer)) == NULL)
MessageBox (hwnd, "Window too large. Cannot "
"allocate enough memory.", "Type",
MB_ICONEXCLAMATION | MB_OK) ;
else
for (y = 0 ; y < cyBuffer ; y++)
for (x = 0 ; x < cxBuffer ; x++)
BUFFER(x, y) = ' ' ;
// set caret to upper left corner
xCaret = 0 ;
yCaret = 0 ;
if (hwnd == GetFocus ())
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
return 0 ;
case WM_SETFOCUS :
// create and show the caret
CreateCaret (hwnd, NULL, cxChar, cyChar) ;
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
ShowCaret (hwnd) ;
return 0 ;
case WM_KILLFOCUS :
// hide and destroy the caret
HideCaret (hwnd) ;
DestroyCaret () ;
return 0 ;
case WM_KEYDOWN :
switch (wParam)
{
case VK_HOME :
xCaret = 0 ;
break ;
case VK_END :
xCaret = cxBuffer - 1 ;
break ;
case VK_PRIOR :
yCaret = 0 ;
break ;
case VK_NEXT :
yCaret = cyBuffer - 1 ;
break ;
case VK_LEFT :
xCaret = max (xCaret - 1, 0) ;
break ;
case VK_RIGHT :
xCaret = min (xCaret + 1, cxBuffer - 1) ;
break ;
case VK_UP :
yCaret = max (yCaret - 1, 0) ;
break ;
case VK_DOWN :
yCaret = min (yCaret + 1, cyBuffer - 1) ;
break ;
case VK_DELETE :
for (x = xCaret ; x < cxBuffer - 1 ; x++)
BUFFER (x, yCaret) = BUFFER (x + 1, yCaret) ;
BUFFER (cxBuffer - 1, yCaret) = ' ' ;
HideCaret (hwnd) ;
hdc = GetDC (hwnd) ;
SelectObject (hdc,
GetStockObject (SYSTEM_FIXED_FONT)) ;
TextOut (hdc, xCaret * cxChar, yCaret * cyChar,
& BUFFER (xCaret, yCaret),
cxBuffer - xCaret) ;
ShowCaret (hwnd) ;
ReleaseDC (hwnd, hdc) ;
break ;
}
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
return 0 ;
case WM_CHAR :
for (i = 0 ; i < LOWORD (lParam) ; i++)
{
switch (wParam)
{
case '\b' : // backspace
if (xCaret > 0)
{
xCaret-- ;
SendMessage (hwnd, WM_KEYDOWN,
VK_DELETE, 1L) ;
}
break ;
case '\t' : // tab
do
{
SendMessage (hwnd, WM_CHAR, ' ', 1L) ;
}
while (xCaret % 8 != 0) ;
break ;
case '\n' : // linefeed
if (++yCaret == cyBuffer)
yCaret = 0 ;
break ;
case '\r' : // carriage return
xCaret = 0 ;
if (++yCaret == cyBuffer)
yCaret = 0 ;
break ;
case '\x1B' : // escape
for (y = 0 ; y < cyBuffer ; y++)
for (x = 0 ; x < cxBuffer ; x++)
BUFFER (x, y) = ' ' ;
xCaret = 0 ;
yCaret = 0 ;
InvalidateRect (hwnd, NULL, FALSE) ;
break ;
default : // character codes
BUFFER (xCaret, yCaret) = (char) wParam ;
HideCaret (hwnd) ;
hdc = GetDC (hwnd) ;
SelectObject (hdc,
GetStockObject (SYSTEM_FIXED_FONT)) ;
TextOut (hdc, xCaret * cxChar, yCaret * cyChar,
& BUFFER (xCaret, yCaret), 1) ;
ShowCaret (hwnd) ;
ReleaseDC (hwnd, hdc) ;
if (++xCaret == cxBuffer)
{
xCaret = 0 ;
if (++yCaret == cyBuffer)
yCaret = 0 ;
}
break ;
}
}
SetCaretPos (xCaret * cxChar, yCaret * cyChar) ;
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
for (y = 0 ; y < cyBuffer ; y++)
TextOut (hdc, 0, y * cyChar, & BUFFER(0, y), cxBuffer) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
TYPE.DEF
;---------------------------------
; TYPE.DEF module definition file
;---------------------------------
NAME TYPE
DESCRIPTION 'Typing Program (c) Charles Petzold, 1990'
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
STACKSIZE 8192
EXPORTS WndProc
The processing of the WM_KEYDOWN and WM_CHAR messages is more extensive. The WM_KEYDOWN processing mostly involves the cursor movement keys. Home and End send the caret to the beginning and end of a line respectively, and Page Up and Page Down send the caret to the top and bottom of the window. The arrow keys work as you would expect. For the Delete key, TYPE must move everything left in the buffer from the next caret position to the end of the line and then display a blank at the end of the line.
The WM_CHAR processing handles the Backspace, Tab, Linefeed (Ctrl-Enter), Enter, Escape, and character keys. Notice I've used Repeat Count in lParam when processing the WM_CHAR message (under the assumption that every character the user types is important) but not during the WM_KEYDOWN message (to prevent inadvertent overscrolling). The Backspace and Tab processing is simplified somewhat by the use of the SendMessage function. Backspace is emulated by the Delete logic, and Tab is emulated by a series of spaces.
As I mentioned earlier, you should hide the cursor when drawing on the window during messages other then WM_PAINT. The program does this when processing the WM_KEYDOWN message for the Delete key and the WM_CHAR message for character keys. In both these cases, TYPE alters the contents of the buffer and then draws the new character or characters on the window.
I use TYPE when working on speeches, as shown in Figure 3-6.