/* --- Copyright University of Sussex 1996. All rights reserved. ----------
 * File:			C.win32/extern/src/edit.c
 * Purpose:			A simple Poplog editor window class, sufficient to
					support Ved
 * Author:			Robert John Duncan, Jul 14 1994 (see revisions)
 */

#include "popcore.h"
#include "popwin.h"

const TCHAR POP_EDIT_CLASS_NAME[] = TEXT("PopEdit");

static BOOL edit_alloc_buffer(POP_EDIT_WINDOW_DATA *pe, int nrows, int ncols)
	/*	Allocate the initial buffer for an edit window
	*/
{
	int nchars, i, j;
	TCHAR **newrows, *newbuff, *newrow;

	nchars = nrows * ncols;
	newrows = (TCHAR**)pop_alloc(nrows*sizeof(TCHAR**));
	if (newrows == NULL) {
		return FALSE;
	}
	newbuff = (TCHAR*)pop_alloc(nchars*sizeof(TCHAR));
	if (newbuff == NULL) {
		pop_free(newrows);
		return FALSE;
	}

	/* initialise the buffer with spaces */
	newrow = newbuff;
	for (i = 0; i < nrows; i++) {
		newrows[i] = newrow;
		for (j = 0; j < ncols; j++) {
			*newrow++ = TEXT(' ');
		}
	}

	pe->buffer_nrows = nrows;
	pe->buffer_ncols = ncols;
	pe->rows = newrows;
	pe->buffer = newbuff;

	return TRUE;
}

static BOOL edit_resize_buffer(POP_EDIT_WINDOW_DATA *pe, int nrows, int ncols)
	/*	Adjust the buffer to fit a display size of nrows x ncols.
		(NB: the buffer size never decreases)
	*/
{
	int nchars, i, j;
	TCHAR **newrows, *newbuff, *newrow;

	if (ncols <= pe->buffer_ncols) {
		/* leave number of buffer columns unchanged */
		ncols = pe->buffer_ncols;
		if (nrows <= pe->buffer_nrows) {
			/* nothing to do */
		}
		else {
			/* add space for extra rows */
			nchars = nrows * ncols;
			newrows = (TCHAR**)pop_realloc(pe->rows, nrows*sizeof(TCHAR**));
			newbuff = (TCHAR*)pop_realloc(pe->buffer, nchars*sizeof(TCHAR));
			if (newrows == NULL || newbuff == NULL) {
				/* failed -- no change */
				return FALSE;
			}

			for (i = 0; i < pe->buffer_nrows; i++) {
				newrows[i] = newbuff + (newrows[i] - pe->buffer);
			}
			newrow = newbuff + pe->buffer_nrows * ncols;
			for (i = pe->buffer_nrows; i < nrows; i++) {
				newrows[i] = newrow;
				for (j = 0; j < ncols; j++) {
					*newrow++ = TEXT(' ');
				}
			}

			pe->buffer_nrows = nrows;
			pe->rows = newrows;
			pe->buffer = newbuff;
		}
	}
	else {
		if (nrows <= pe->buffer_nrows) {
			nchars = pe->buffer_nrows * ncols;
			newrows = pe->rows;
			newbuff = (TCHAR*)pop_alloc(nchars*sizeof(TCHAR));
			if (newbuff == NULL) {
				return FALSE;
			}

			newrow = newbuff;
			for (i = 0; i < nrows; i++) {
				TCHAR *row = newrows[i];
				newrows[i] = newrow;
				for (j = 0; j < pe->ncols; j++) {
					*newrow++ = *row++;
				}
				for (j = pe->ncols; j < ncols; j++) {
					*newrow++ = TEXT(' ');
				}
			}

			pop_free(pe->buffer);

			pe->buffer_ncols = ncols;
			pe->buffer = newbuff;
		}
		else {
			nchars = nrows * ncols;
			newrows = (TCHAR**)pop_realloc(pe->rows, nrows*sizeof(TCHAR**));
			newbuff = (TCHAR*)pop_alloc(nchars*sizeof(TCHAR));
			if (newrows == NULL || newbuff == NULL) {
				return FALSE;
			}

			newrow = newbuff;
			for (i = 0; i < pe->buffer_nrows; i++) {
				TCHAR *row = newrows[i];
				newrows[i] = newrow;
				for (j = 0; j < pe->ncols; j++) {
					*newrow++ = *row++;
				}
				for (j = pe->ncols; j < ncols; j++) {
					*newrow++ = TEXT(' ');
				}
			}
			for (i = pe->buffer_nrows; i < nrows; i++) {
				newrows[i] = newrow;
				for (j = 0; j < ncols; j++) {
					*newrow++ = TEXT(' ');
				}
			}

			pop_free(pe->buffer);

			pe->buffer_nrows = nrows;
			pe->buffer_ncols = ncols;
			pe->rows = newrows;
			pe->buffer = newbuff;
		}
	}

	return TRUE;
}

static int edit_resize(POP_EDIT_WINDOW_DATA *pe)
	/*	Adjust display after font or window size change
	*/
{
	int nrows, ncols;

	/* round up to a whole number of characters */
	nrows = (pe->clienth + pe->charh - 1) / pe->charh;
	ncols = (pe->clientw + pe->charw - 1) / pe->charw;

	/* adjust the buffer size accordingly */
	if (!edit_resize_buffer(pe, nrows, ncols)) {
		return -1;
	}

	/* save new size */
	pe->nrows = nrows;
	pe->ncols = ncols;

	/* adjust the caret position */
	if (pe->caretx >= ncols) {
		pe->caretx = ncols - 1;
	}
	if (pe->carety >= nrows) {
		pe->carety = nrows - 1;
	}

	/* adjust the refresh region */
	if (pe->refresh.left > ncols) {
		pe->refresh.left = ncols;
		pe->refresh.right = 0;
	}
	else if (pe->refresh.right > ncols) {
		pe->refresh.right = ncols;
	}
	if (pe->refresh.top > nrows) {
		pe->refresh.top = nrows;
		pe->refresh.bottom = 0;
	}
	else if (pe->refresh.bottom > nrows) {
		pe->refresh.bottom = nrows;
	}

	/* set the scrolling region to full screen */
	pe->region_top = 0;
	pe->region_bottom = pe->nrows;

	return 0;
}

static void edit_repaint(
		POP_EDIT_WINDOW_DATA *pe,
		HDC hdc,
		int left,
		int top,
		int right,
		int bottom)
	/*	Redraw a region of the display bounded by the character rectangle
		with corners (left,top) and (right,bottom)
	*/
{
	int i, x, y, nchars;
	HFONT old_font;

	old_font = (HFONT)SelectObject(hdc, (HGDIOBJ)pe->font);
	x = left * pe->charw;		/* starting x-coord (pixels) */
	y = top * pe->charh;		/* starting y-coord (pixels) */
	nchars = right - left;		/* number of chars to draw in each row */
	for (i = top; i < bottom; i++) {
		TextOut(hdc, x, y, pe->rows[i]+left, nchars);
		y += pe->charh;			/* move to next row */
	}
	SelectObject(hdc, (HGDIOBJ)old_font);
}

static void edit_refresh(POP_EDIT_WINDOW_DATA *pe)
	/*	Bring the display in line with the buffer contents
	*/
{
	if (pe->refresh.left < pe->refresh.right
	&& pe->refresh.top < pe->refresh.bottom)
	{
		HDC hdc;

		/* redraw the invalid region */
		HideCaret(pe->self);
		hdc = GetDC(pe->self);
		edit_repaint(
			pe, hdc,
			pe->refresh.left, pe->refresh.top,
			pe->refresh.right, pe->refresh.bottom);
		ReleaseDC(pe->self, hdc);
		ShowCaret(pe->self);

		/* make it valid again */
		pe->refresh.left = pe->ncols;
		pe->refresh.top = pe->nrows;
		pe->refresh.right = 0;
		pe->refresh.bottom = 0;
	}
	SetCaretPos(pe->caretx * pe->charw, pe->carety * pe->charh);
}

static void edit_needs_refresh(
		POP_EDIT_WINDOW_DATA *pe,
		int left,
		int top,
		int right,
		int bottom)
	/*	Invalidate the character rectangle with corners (left,top)
		and (right,bottom)
	*/
{
	if (left < pe->refresh.left) {
		pe->refresh.left = left;
	}
	if (top < pe->refresh.top) {
		pe->refresh.top = top;
	}
	if (right > pe->refresh.right) {
		pe->refresh.right = right;
	}
	if (bottom > pe->refresh.bottom) {
		pe->refresh.bottom = bottom;
	}
}

static void edit_set_caret_pos(POP_EDIT_WINDOW_DATA *pe, int x, int y)
	/*	Reposition the caret, ensuring that it doesn't stray outside
		the display area
	*/
{
	if (x < 0) {
		x = 0;
	}
	else if (x >= pe->ncols) {
		x = pe->ncols - 1;
	}
	if (y < 0) {
		y = 0;
	}
	else if (y >= pe->nrows) {
		y = pe->nrows - 1;
	}
	pe->caretx = x;
	pe->carety = y;
}

static void edit_set_scroll_region(POP_EDIT_WINDOW_DATA *pe, int top, int bottom)
	/*	Set the scrolling region between lines top and bottom
	*/
{
	if (top < 0) {
		top = 0;
	}
	else if (top >= pe->nrows) {
		top = pe->nrows;
	}
	if (bottom < top) {
		bottom = top;
	}
	else if (bottom >= pe->nrows) {
		bottom = pe->nrows;
	}
	pe->region_top = top;
	pe->region_bottom = bottom;
}

static void edit_add_chars(
		POP_EDIT_WINDOW_DATA *pe,
		int x,
		int y,
		TCHAR* text,
		int nchars)
	/*	Add text string at position (x,y)
	*/
{
	if (y >= 0 && y < pe->nrows) {

		/* restrict character range to what's visible */
		if (x < 0) {
			nchars += x;
			text -= x;
			x = 0;
		}
		if (x + nchars > pe->ncols) {
			nchars = pe->ncols - x;
		}

		if (nchars > 0) {
			int j, lim = x + nchars, changed = -1;
			TCHAR *row = pe->rows[y];
			if (pe->insert_mode) {
				/* shift trailing characters along */
				for (j = pe->ncols - 1; j >= lim; j--) {
					TCHAR c = row[j-nchars];
					if (c != row[j]) {
						row[j] = c;
						if (changed < 0) changed = j;
					}
				}
			}
			for (j = lim - 1; j >= x; j--) {
				TCHAR c = text[j-x];
				if (c != row[j]) {
					row[j] = c;
					if (changed < 0) changed = j;
				}
			}
			if (changed >= 0) {
				edit_needs_refresh(pe, x, y, changed+1, y+1);
			}
		}
	}
}

static void edit_add_char(POP_EDIT_WINDOW_DATA *pe, int x, int y, TCHAR newc)
	/*	Add a single character at position (x,y)
	*/
{
	if (x >= 0 && x < pe->ncols && y >= 0 && y < pe->nrows) {
		int changed = -1;
		TCHAR *row = pe->rows[y];
		if (pe->insert_mode) {
			int j;
			for (j = x; j < pe->ncols; j++) {
				TCHAR c = row[j];
				if (c != newc) {
					row[j] = newc;
					changed = j;
					newc = c;
				}
			}
		}
		else {
			if (row[x] != newc) {
				row[x] = newc;
				changed = x;
			}
		}
		if (changed >= 0) {
			edit_needs_refresh(pe, x, y, changed+1, y+1);
			edit_refresh(pe);
		}
	}
}

static void edit_delete_char(POP_EDIT_WINDOW_DATA *pe, int x, int y)
	/*	Delete the character at position (x,y)
	*/
{
	if (x >= 0 && x < pe->ncols && y >= 0 && y < pe->nrows) {
		int j, changed = -1;
		TCHAR newc = TEXT(' ');
		TCHAR *row = pe->rows[y];
		for (j = pe->ncols - 1; j >= x; j--) {
			TCHAR c = row[j];
			if (c != newc) {
				row[j] = newc;
				if (changed < 0) changed = j;
				newc = c;
			}
		}
		if (changed >= 0) {
			edit_needs_refresh(pe, x, y, changed+1, y+1);
		}
	}
}

static void edit_insert_line(POP_EDIT_WINDOW_DATA *pe, int y)
	/*	Open a blank line at row y
	*/
{
	if (y >= pe->region_top && y < pe->region_bottom) {
		int i, last = pe->region_bottom - 1;
		TCHAR *row = pe->rows[last];
		for (i = last; i > y; i--) {
			pe->rows[i] = pe->rows[i-1];
		}
		pe->rows[y] = row;
		for (i = 0; i < pe->ncols; i++) {
			row[i] = TEXT(' ');
		}
		edit_needs_refresh(pe, 0, y, pe->ncols, pe->region_bottom);
	}
}

static void edit_delete_line(POP_EDIT_WINDOW_DATA *pe, int y)
	/*	Delete row y
	*/
{
	if (y >= pe->region_top && y < pe->region_bottom) {
		int i, last = pe->region_bottom - 1;
		TCHAR *row = pe->rows[y];
		for (i = y; i < last; i++) {
			pe->rows[i] = pe->rows[i+1];
		}
		pe->rows[last] = row;
		for (i = 0; i < pe->ncols; i++) {
			row[i] = TEXT(' ');
		}
		edit_needs_refresh(pe, 0, y, pe->ncols, pe->region_bottom);
	}
}

static void edit_clear(POP_EDIT_WINDOW_DATA *pe, int x, int y)
	/*	Clear region from point (x,y)
	*/
{
	int i, j;
	TCHAR *row;

	if (x < 0) x = 0;
	if (y < pe->region_top) y = pe->region_top;
	for (i = y; i < pe->region_bottom; i++) {
		row = pe->rows[i];
		for (j = x; j < pe->ncols; j++) {
			row[j] = TEXT(' ');
		}
		x = 0;
	}
	edit_needs_refresh(pe, 0, y, pe->ncols, pe->region_bottom);
}

static void edit_clear_eol(POP_EDIT_WINDOW_DATA *pe, int x, int y)
	/*	Clear from point (x,y) to the end of the row
	*/
{
	int j;
	TCHAR *row;

	if (y >= 0 && y < pe->nrows) {
		if (x < 0) x = 0;
		row = pe->rows[y];
		for (j = x; j < pe->ncols; j++) {
			row[j] = TEXT(' ');
		}
		edit_needs_refresh(pe, x, y, pe->ncols, y+1);
	}
}

static LRESULT edit_on_create(HWND hwnd, CREATESTRUCT *cs)
	/*	Process WM_CREATE
	*/
{
	POP_EDIT_WINDOW_DATA *pe;
	HDC hdc;
	RECT client;
	TEXTMETRIC tm;

	/* make space for the window data */
	pe = (POP_EDIT_WINDOW_DATA*)pop_alloc(sizeof(POP_EDIT_WINDOW_DATA));
	if (pe == NULL)
		return -1;
	SetWindowLong(hwnd, 0, (LONG)pe);

	/* save window handles and client area size */
	pe->self = hwnd;
	pe->parent = GetParent(hwnd);
	GetClientRect(hwnd, &client);
	pe->clientw = client.right;
	pe->clienth = client.bottom;

	/* home the cursor */
	pe->caretx = pe->carety = 0;

	/* select a standard font and save the character size */
	hdc = GetDC(hwnd);
	pe->font = (HFONT)GetStockObject(SYSTEM_FIXED_FONT);
	SelectObject(hdc, (HGDIOBJ)pe->font);
	GetTextMetrics(hdc, &tm);
	ReleaseDC(hwnd, hdc);
	pe->charh = tm.tmHeight;
	pe->charw = tm.tmAveCharWidth;

	/* determine the number of visible characters */
	pe->nrows = (pe->clienth + pe->charh - 1) / pe->charh;
	pe->ncols = (pe->clientw + pe->charw - 1) / pe->charw;

	/* set refresh region */
	pe->refresh.left = pe->ncols;
	pe->refresh.top = pe->nrows;
	pe->refresh.right = 0;
	pe->refresh.bottom = 0;

	/* set scrolling region to full screen */
	pe->region_top = 0;
	pe->region_bottom = pe->nrows;

	/* default to overstrike mode */
	pe->insert_mode = FALSE;

	/* allocate buffer space */
	return edit_alloc_buffer(pe, pe->nrows, pe->ncols) ? 0 : -1;
}

static LRESULT edit_on_destroy(HWND hwnd)
	/*	Process WM_DESTROY
	*/
{
	POP_EDIT_WINDOW_DATA *pe;

	pe = (POP_EDIT_WINDOW_DATA*)SetWindowLong(hwnd, 0, 0);
	if (pe != NULL) {
		pop_free(pe->buffer);
		pop_free(pe->rows);
		pop_free(pe);
	}

	return 0;
}

static LRESULT edit_on_setfocus(HWND hwnd, HWND hwndLoseFocus)
	/*	Process WM_SETFOCUS
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	/* create and display the caret at its current position */
	CreateCaret(hwnd, (HBITMAP)0, 2*GetSystemMetrics(SM_CXBORDER), pe->charh);
	SetCaretPos(pe->caretx*pe->charw, pe->carety*pe->charh);
	ShowCaret(hwnd);

	return 0;
}

static LRESULT edit_on_killfocus(HWND hwnd, HWND hwndGetFocus)
	/*	Process WM_KILLFOCUS
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	/* destroy the caret */
	HideCaret(hwnd);
	DestroyCaret();

	return 0;
}

static LRESULT edit_on_size(HWND hwnd, WORD nWidth, WORD nHeight)
	/*	Process WM_SIZE
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	/* save new client area size */
	pe->clientw = nWidth;
	pe->clienth = nHeight;

	/* resize display to match */
	return edit_resize(pe);
}

static LRESULT edit_on_setfont(HWND hwnd, HFONT hfont, LPARAM redraw)
	/*	Process WM_SETFONT
	*/
{
	HDC hdc;
	HFONT old_font;
	TEXTMETRIC tm;
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	if (hfont == NULL) {
		/* use system default */
		hfont = GetStockObject(SYSTEM_FIXED_FONT);
	}

	/* determine character size of new font */
	hdc = GetDC(hwnd);
	old_font = (HFONT)SelectObject(hdc, (HGDIOBJ)hfont);
	GetTextMetrics(hdc, &tm);
	SelectObject(hdc, (HGDIOBJ)old_font);
	ReleaseDC(hwnd, hdc);
	if (tm.tmPitchAndFamily & TMPF_FIXED_PITCH) {
		/* not fixed pitch -- don't change */
		return 0;
	}
	pe->font = hfont;
	pe->charh = tm.tmHeight;
	pe->charw = tm.tmAveCharWidth;

	/* resize display to match */
	if (edit_resize(pe) < 0) {
		/* resize failed */
		return -1;
	}

	/* redraw if required */
	if (redraw != 0) {
		edit_needs_refresh(pe, 0, 0, pe->ncols, pe->nrows);
		edit_refresh(pe);
	}

	return 0;
}

static LRESULT edit_on_getfont(HWND hwnd)
	/*	Process WM_GETFONT
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	/* report the current font (NULL means the system font) */
	return (LRESULT)(pe->font == GetStockObject(SYSTEM_FIXED_FONT)
		? NULL : pe->font);
}

static LRESULT edit_on_char(HWND hwnd, TCHAR newc, LPARAM lKeyData)
	/*	Process WM_CHAR
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	/* character key press: redirect to parent */
	/* NB: char is always truncated to a single byte, even for UNICODE */
	SendMessage(pe->parent, POPM_EDIT_CHAR, (WPARAM)(unsigned char)newc,
		(LPARAM)hwnd);

	return 0;
}

static LRESULT edit_on_keydown(HWND hwnd, int nVirtKey, LPARAM lKeyData)
{
	int code;
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	/* general key press: identify special keys and redirect */
	code = nVirtKey & 0xff;
	switch (code) {
		case VK_CANCEL:
			/* Ctrl+Break -- interrupt */
			SendMessage(pe->parent, POPM_EDIT_INTERRUPT, 0, (LPARAM)hwnd);
			break;

		case VK_ADD:
		case VK_SUBTRACT:
		case VK_MULTIPLY:
		case VK_DIVIDE:
		case VK_SEPARATOR:
			/* These will all map to WM_CHAR so send just keypad prefix */
			SendMessage(pe->parent, POPM_EDIT_FUNCTION,
				(WPARAM)VK_NUMLOCK, (LPARAM)hwnd);
			break;

		case VK_RETURN:
			/* Keypad Enter key has bit 24 set in lKeyData */
			/* see Win32 KB Q */
			if (lKeyData & 0x1000000L) {
				/* keypad enter key -- treat as above */
				SendMessage(pe->parent, POPM_EDIT_FUNCTION,
					(WPARAM)VK_NUMLOCK, (LPARAM)hwnd);
				break;
			}
			/* else fall through */

		case VK_PRIOR:
		case VK_NEXT:
		case VK_END:
		case VK_HOME:
		case VK_LEFT:
		case VK_UP:
		case VK_RIGHT:
		case VK_DOWN:
		case VK_INSERT:
		case VK_DELETE:
		case VK_HELP:
		case VK_F1:
		case VK_F2:
		case VK_F3:
		case VK_F4:
		case VK_F5:
		case VK_F6:
		case VK_F7:
		case VK_F8:
		case VK_F9:
		case VK_F10:
		case VK_F11:
		case VK_F12:
		case VK_F13:
		case VK_F14:
		case VK_F15:
		case VK_F16:
		case VK_F17:
		case VK_F18:
		case VK_F19:
		case VK_F20:
		case VK_F21:
		case VK_F22:
		case VK_F23:
		case VK_F24:
			if (GetKeyState(VK_CONTROL) < 0)
				code |= (1 << 8);
			if (GetKeyState(VK_SHIFT) < 0)
				code |= (1 << 9);
			SendMessage(pe->parent, POPM_EDIT_FUNCTION, (WPARAM)code,
				(LPARAM)hwnd);
			break;

		default:
			break;
	}

	return 0;
}

static LRESULT edit_on_lbuttondown(HWND hwnd, POINTS posn, DWORD keys)
	/* Process WM_LBUTTONDOWN
	*/
{
	int x, y;
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	x = posn.x / pe->charw;
	if (x >= pe->ncols)
		x = pe->ncols-1;
	y = posn.y / pe->charh;
	if (y >= pe->nrows)
		y = pe->nrows-1;

	SendMessage(pe->parent, POPM_EDIT_MOUSE, (WPARAM)(x << 16 | y),
		(LPARAM)hwnd);

	return 0;
}

static LRESULT edit_on_paint(HWND hwnd)
	/*	Process WM_PAINT
	*/
{
	HDC hdc;
	PAINTSTRUCT ps;
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);

	hdc = BeginPaint(hwnd, &ps);
	edit_repaint(
		pe, hdc,
		ps.rcPaint.left / pe->charw,					/* left */
		ps.rcPaint.top / pe->charh,						/* top */
		(ps.rcPaint.right+pe->charw-1) / pe->charw,		/* right */
		(ps.rcPaint.bottom+pe->charh-1) / pe->charh);	/* bottom */
	EndPaint(hwnd, &ps);

	return 0;
}

static LRESULT edit_on_command(HWND hwnd, BYTE *command, int len)
	/*	Process POPM_EDIT_COMMAND
	*/
{
	int nchars = 0;
	TCHAR text[128];
	BYTE *limit;

	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA *)GetWindowLong(hwnd, 0);

	/* process command string */
	limit = command + len;
	while (command < limit) {
		BYTE c = *command++;
		if (c < ' ') {
			/* control character: flush any insertions */
			if (nchars > 0) {
				edit_add_chars(pe, pe->caretx, pe->carety, text, nchars);
				edit_set_caret_pos(pe, pe->caretx + nchars, pe->carety);
				nchars = 0;
			}
			switch (c) {
				case EDIT_CMD_CHAR_UP:
					edit_set_caret_pos(pe, pe->caretx, pe->carety - 1);
					break;
				case EDIT_CMD_CHAR_DOWN:
					edit_set_caret_pos(pe, pe->caretx, pe->carety + 1);
					break;
				case EDIT_CMD_CHAR_LEFT:
				case EDIT_CMD_BACKSPACE:
					edit_set_caret_pos(pe, pe->caretx - 1, pe->carety);
					break;
				case EDIT_CMD_CHAR_RIGHT:
					edit_set_caret_pos(pe, pe->caretx + 1, pe->carety);
					break;
				case EDIT_CMD_RETURN:
					edit_set_caret_pos(pe, 0, pe->carety);
					break;
				case EDIT_CMD_GOTO: {
					int x = *command++;
					int y = *command++;
					edit_set_caret_pos(pe, x, y);
					break;
				}
				case EDIT_CMD_LINEFEED: {
					int y = pe->carety + 1;
					if (y >= pe->region_bottom) {
						/* off the bottom of the region -- scroll up */
						edit_delete_line(pe, pe->region_top);
					}
					edit_set_caret_pos(pe, 0, y);
					break;
				}
				case EDIT_CMD_SCROLL_UP:
					edit_delete_line(pe, pe->region_top);
					break;
				case EDIT_CMD_SCROLL_DOWN:
					edit_insert_line(pe, pe->region_top);
					break;
				case EDIT_CMD_SET_SCROLL_REGION: {
					int top = *command++;
					int bottom = *command++;
					edit_set_scroll_region(pe, top, bottom);
					break;
				}
				case EDIT_CMD_INSERT_CHAR:
					text[0] = (TCHAR)(*command++);
					nchars = 1;
					break;
				case EDIT_CMD_TAB: {
					int i, nspaces = 8 - (pe->caretx % 8);
					for (i = 0; i < nspaces; i++) {
						text[i] = TEXT(' ');
					}
					nchars = nspaces;
					break;
				}
				case EDIT_CMD_DELETE_CHAR:
					edit_delete_char(pe, pe->caretx, pe->carety);
					break;
				case EDIT_CMD_INSERT_LINE:
					edit_insert_line(pe, pe->carety);
					break;
				case EDIT_CMD_DELETE_LINE:
					edit_delete_line(pe, pe->carety);
					break;
				case EDIT_CMD_CLEAR_EOL:
					edit_clear_eol(pe, pe->caretx, pe->carety);
					break;
				case EDIT_CMD_CLEAR:
					edit_clear(pe, 0, 0);
					edit_set_caret_pos(pe, 0, 0);
					break;
				case EDIT_CMD_INSERT_MODE:
					pe->insert_mode = TRUE;
					break;
				case EDIT_CMD_REPLACE_MODE:
					pe->insert_mode = FALSE;
					break;
				case EDIT_CMD_BELL:
				default:
					MessageBeep(MB_OK);
					break;
			}
		}
		else {
			if (nchars == (sizeof text / sizeof(TCHAR))) {
				edit_add_chars(pe, pe->caretx, pe->carety, text, nchars);
				edit_set_caret_pos(pe, pe->caretx + nchars, pe->carety);
				nchars = 0;
			}
			text[nchars++] = (TCHAR)c;
		}
	}

	/* flush outstanding output */
	if (nchars > 0) {
		edit_add_chars(pe, pe->caretx, pe->carety, text, nchars);
		edit_set_caret_pos(pe, pe->caretx + nchars, pe->carety);
		nchars = 0;
	}

	/* bring display up to date */
	edit_refresh(pe);

	return 0;
}

static LRESULT edit_on_insert_char(HWND hwnd, TCHAR c)
	/*	Process POPM_EDIT_INSERT_CHAR
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);
	edit_add_char(pe, pe->caretx, pe->carety, c);
	edit_set_caret_pos(pe, pe->caretx + 1, pe->carety);
	/* bring display up to date */
	edit_refresh(pe);
	return 0;
}

static LRESULT edit_on_goto(HWND hwnd, int x, int y)
	/*	Process POPM_EDIT_GOTO
		(NB: result not visible until next refresh)
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);
	edit_set_caret_pos(pe, x, y);
	return 0;
}

static LRESULT edit_on_set_scroll_region(HWND hwnd, int top, int bottom)
	/*	Process POPM_EDIT_SET_SCROLL_REGION
	*/
{
	POP_EDIT_WINDOW_DATA *pe = (POP_EDIT_WINDOW_DATA*)GetWindowLong(hwnd, 0);
	edit_set_scroll_region(pe, top, bottom);
	return 0;
}

static LRESULT CALLBACK PopEditWindowProc(
		HWND	hwnd,		/* window handle */
		UINT	message,	/* message identifier */
		WPARAM	wParam,		/* first message parameter */
		LPARAM	lParam)		/* second message parameter */
{
	switch (message) {

		case WM_CREATE:
			return edit_on_create(hwnd, (CREATESTRUCT*)lParam);
			break;

		case WM_DESTROY:
			return edit_on_destroy(hwnd);
			break;

		case WM_SETFOCUS:
			return edit_on_setfocus(hwnd, (HWND)wParam);
			break;

		case WM_KILLFOCUS:
			return edit_on_killfocus(hwnd, (HWND)wParam);
			break;

		case WM_SIZE:
			return edit_on_size(hwnd, LOWORD(lParam), HIWORD(lParam));
			break;

		case WM_SETFONT:
			return edit_on_setfont(hwnd, (HFONT)wParam, lParam);
			break;

		case WM_GETFONT:
			return edit_on_getfont(hwnd);
			break;

		case WM_CHAR:
			return edit_on_char(hwnd, (TCHAR)wParam, lParam);
			break;

		case WM_KEYDOWN:
			return edit_on_keydown(hwnd, (int)wParam, lParam);
			break;

		case WM_LBUTTONDOWN:
			return edit_on_lbuttondown(hwnd, MAKEPOINTS(lParam), wParam);
			break;

		case WM_PAINT:
			return edit_on_paint(hwnd);
			break;

		case POPM_EDIT_COMMAND:
			return edit_on_command(hwnd, (BYTE*)lParam, (int)wParam);
			break;

		case POPM_EDIT_INSERT_CHAR:
			return edit_on_insert_char(hwnd, (TCHAR)wParam);
			break;

		case POPM_EDIT_GOTO:
			return edit_on_goto(hwnd, LOWORD(wParam), HIWORD(wParam));
			break;

		case POPM_EDIT_SET_SCROLL_REGION:
			return edit_on_set_scroll_region(hwnd, LOWORD(wParam), HIWORD(wParam));
			break;

		default:
			return DefWindowProc(hwnd, message, wParam, lParam);
			break;
	}

	return 0;
}

ATOM popwin_init_edit_window_class(void)
	/*	Register the PopEdit window class
	*/
{
	WNDCLASS vwc = {
		CS_HREDRAW|CS_VREDRAW,			/* style */
		PopEditWindowProc,				/* window procedure */
		0,								/* extra bytes for the class */
		sizeof(LONG),					/* extra bytes per window */
		GetModuleHandle(NULL),			/* application instance */
		NULL,							/* icon -- none */
		LoadCursor(NULL, IDC_IBEAM),	/* text cursor */
		(HBRUSH)(COLOR_WINDOW+1),		/* background colour */
		NULL,							/* menu -- none */
		POP_EDIT_CLASS_NAME,			/* class name */
	};

	return RegisterClass(&vwc);
}

/* --- Revision History ---------------------------------------------------
--- Robert Duncan, May 16 1996
		Fixed edit_on_char to pass char value as unsigned
--- Robert Duncan, Mar 21 1996
		Changed to use a thicker caret
--- Robert John Duncan, Feb  7 1996
		Added support for keypad keys (which send VK_NUMLOCK (!) as a
		recognisable prefix) and for Ctrl+Break as an interrupt key.
--- Robert John Duncan, Jan  8 1996
		Changed to report LBUTTONDOWN events, for positioning the cursor
 */
