The TYPE Program

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.