/* screen.c */

#include <windows.h>
#include <commdlg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "telnet.h"
#include "ini.h"

extern char *cInvertedArray;
extern int bMouseDown;
extern int bSelection;

static SCREEN *ScreenList;
static HINSTANCE hInst;
static char szScreenClass[] = "ScreenWClass";
static char szScreenMenu[] = "ScreenMenu";
static char cursor_key[8][4] = {				// Send for cursor keys
	"\x1B[D", "\x1B[A", "\x1B[C", "\x1B[B", 	// Normal mode
	"\x1BOD", "\x1BOA", "\x1BOC", "\x1BOB",		// Numpad on mode
};

void ScreenInit(
	HINSTANCE hInstance)
{
	BOOL b;
	WNDCLASS wc;

	hInst = hInstance;
	
	ScreenList = NULL;

	wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; /* Class style(s) */
	wc.lpfnWndProc = ScreenWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = sizeof(long);
	wc.hInstance = hInstance;
	wc.hIcon = LoadIcon(hInstance, "TERMINAL");
	wc.hCursor = LoadCursor(NULL, IDC_IBEAM);
	wc.hbrBackground = GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName =  szScreenMenu;
	wc.lpszClassName = szScreenClass;

	b = RegisterClass(&wc);
	assert(b);

} /* ScreenInit */


void SetScreenInstance(
	HINSTANCE hInstance)
{
	hInst = hInstance;

} /* SetScreenInstance */


int GetNewScreen(void)
{
	SCREEN *pScr;
	static int id = 0;

	pScr = (SCREEN *) calloc(sizeof(SCREEN), 1);
	if (pScr == NULL)
		return(-1);

	if (ScreenList == NULL) {
		pScr->next = NULL;
		pScr->prev = NULL;
	}
	else {
		if (ScreenList->next == NULL) {
			ScreenList->next = ScreenList;
			ScreenList->prev = ScreenList;
		}
		pScr->next = ScreenList;
		pScr->prev = ScreenList->prev;
		ScreenList->prev->next = pScr;
		ScreenList->prev = pScr;
	}

	ScreenList = pScr;
	return(id++);

} /* GetNewScreen */


SCREENLINE *ScreenNewLine(void) 
{
	SCREENLINE *pScrLine;

	pScrLine = calloc(sizeof(SCREENLINE) + 2*MAX_LINE_WIDTH, 1);
	if (pScrLine == NULL)
		return (NULL);
	pScrLine->text = &pScrLine->buffer[0];
	pScrLine->attrib = &pScrLine->buffer[MAX_LINE_WIDTH];
	return(pScrLine);

} /* ScreenNewLine */


static void MakeWindowTitle(
	char *host,
	int width,
	int height,
	char *title,
	int nchars)
{
	char buf[128];
	int hlen;

	hlen = strlen(host);

	title[0] = 0;

	if (hlen + 1 > nchars)
		return;

	strcpy(title, host);

	wsprintf(buf, " (%dh x %dw)", height, width);

	if ((int) strlen(buf) + hlen + 1 > nchars)
		return;

	strcat(title, buf);

} /* MakeWindowTitle */


SCREEN *InitNewScreen(
	CONFIG *Config)
{
	TEXTMETRIC tm;
	HMENU hMenu = NULL;
	SCREEN *scr = NULL;
	SCREENLINE *pScrLine;
	SCREENLINE *pScrLineLast;
	int id;
	int idx = 0;
	char title[128];
	HDC hDC;
	HFONT hFont;

	id = GetNewScreen();
	if (id == -1)
		return(0);

	scr = ScreenList;
	assert(scr != NULL);

	hMenu = LoadMenu(hInst, szScreenMenu);
	assert(hMenu != NULL);

	scr->title = Config->title;
	MakeWindowTitle(Config->title, Config->width, Config->height, title, sizeof(title));

	scr->hwndTel = Config->hwndTel;  /* save HWND of calling window */
	
	if (Config->backspace) {
		CheckMenuItem(hMenu, IDM_BACKSPACE, MF_CHECKED);
		CheckMenuItem(hMenu, IDM_DELETE, MF_UNCHECKED);
	}
	else {
		CheckMenuItem(hMenu, IDM_BACKSPACE, MF_UNCHECKED);
		CheckMenuItem(hMenu, IDM_DELETE, MF_CHECKED);
	}

	hDC = GetDC(NULL);
	assert(hDC != NULL);

	scr->lf.lfPitchAndFamily = FIXED_PITCH;
	GetPrivateProfileString(INI_FONT, "FaceName", "Courier", scr->lf.
		lfFaceName,	LF_FACESIZE, TELNET_INI);
	scr->lf.lfHeight = (int) GetPrivateProfileInt(INI_FONT, "Height", 0, TELNET_INI);
	scr->lf.lfWidth = (int) GetPrivateProfileInt(INI_FONT, "Width", 0, TELNET_INI);
	scr->lf.lfPitchAndFamily = (BYTE) GetPrivateProfileInt(INI_FONT, "PitchAndFamily", 0, TELNET_INI);
	scr->lf.lfCharSet = (BYTE) GetPrivateProfileInt(INI_FONT, "CharSet", 0, TELNET_INI);
	scr->lf.lfEscapement = (BYTE) GetPrivateProfileInt(INI_FONT, "Escapement", 0, TELNET_INI);
	scr->lf.lfQuality = PROOF_QUALITY;
	scr->hSelectedFont = CreateFontIndirect((LPLOGFONT) &(scr->lf));
	hFont = SelectObject(hDC, scr->hSelectedFont);
	GetTextMetrics(hDC, (LPTEXTMETRIC) &tm);
	SelectObject(hDC, hFont);
	scr->cxChar = tm.tmAveCharWidth;
	scr->cyChar = tm.tmHeight + tm.tmExternalLeading;

	ReleaseDC(NULL, hDC);

	scr->width = Config->width;
	scr->height = Config->height;
	scr->ID = id;
	scr->x = 0;    
	scr->y = 0;
	scr->Oldx = 0;
	scr->Oldy = 0;
	scr->attrib = 0;
	scr->DECAWM = 1;
	scr->bWrapPending = FALSE;
	scr->top = 0;
	scr->bottom = scr->height-1;
	scr->parmptr = 0;
	scr->escflg = 0;
	scr->bAlert = FALSE;
	scr->numlines = 0;
	scr->maxlines = 150;

	cInvertedArray = calloc(scr->width * scr->height, 1);

	pScrLineLast = ScreenNewLine();
	if (pScrLineLast == NULL)
		return(NULL);
	scr->screen_top = scr->buffer_top = pScrLineLast;

	for (idx = 0; idx < scr->height - 1; idx++) {
		pScrLine = ScreenNewLine();
		if (pScrLine == NULL)
			return(NULL);
		pScrLine->prev = pScrLineLast;
		pScrLineLast->next = pScrLine;
		pScrLineLast = pScrLine;
	}

	scr->screen_bottom = scr->buffer_bottom = pScrLine;

	scr->hWnd = CreateWindow(szScreenClass, title, WS_OVERLAPPEDWINDOW | WS_VSCROLL,
		CW_USEDEFAULT, CW_USEDEFAULT,
		scr->cxChar * scr->width + FRAME_WIDTH,
		scr->cyChar * scr->height + FRAME_HEIGHT,
		NULL, hMenu, hInst, scr);
	assert(scr->hWnd != NULL);
	
	ShowWindow(scr->hWnd, SW_SHOW);

	CreateCaret(scr->hWnd, NULL, scr->cxChar, 2);
	SetCaretPos(scr->x*scr->cxChar, (scr->y+1) * scr->cyChar);
	ShowCaret(scr->hWnd);

	return(ScreenList);

} /* InitNewScreen */


void DeleteTopLine(
	SCREEN *pScr)
{
	assert(pScr->buffer_top != NULL);

	pScr->buffer_top = pScr->buffer_top->next;
	assert(pScr->buffer_top != NULL);

	pScr->buffer_top->prev = NULL;

	pScr->numlines--;

} /* DeleteTopLine */


static void SetScreenScrollBar(
	SCREEN *pScr)
{
	if (pScr->numlines <= 0) {
		SetScrollRange(pScr->hWnd, SB_VERT, 0, 100, FALSE);
		SetScrollPos(pScr->hWnd, SB_VERT, 0, TRUE);
		EnableScrollBar(pScr->hWnd, SB_VERT, ESB_DISABLE_BOTH);
	}
	else {
		SetScrollRange(pScr->hWnd, SB_VERT, 0, pScr->numlines, FALSE);
		SetScrollPos(pScr->hWnd, SB_VERT, pScr->numlines, TRUE);
		EnableScrollBar(pScr->hWnd, SB_VERT, ESB_ENABLE_BOTH);
	}

} /* SetScreenScrollBar */


int ScreenScroll(
	SCREEN *pScr)
{
	SCREENLINE *pScrLine;
	SCREENLINE *pPrev;
	SCREENLINE *pNext;
	SCREENLINE *pScrollTop;
	SCREENLINE *pScrollBottom;
	BOOL bFullScreen = TRUE;
	HDC hDC;
	RECT rc;

	Edit_ClearSelection(pScr);

	pScrollTop = GetScreenLineFromY(pScr, pScr->top);

	pScrollBottom = GetScreenLineFromY(pScr, pScr->bottom);

	if (pScrollTop != pScr->screen_top) {
		bFullScreen = FALSE;
		rc.left = 0;
		rc.right = pScr->cxChar * pScr->width;
		rc.top = pScr->cyChar * (pScr->top);
		rc.bottom = pScr->cyChar * (pScr->bottom+1);

		pNext = pScrollTop->next;
		pPrev = pScrollTop->prev;

		pPrev->next = pNext;        
		pNext->prev = pPrev;

		pScrLine = pScrollTop;
		ScreenClearLine(pScr, pScrLine);
	}
	else {
		pScr->numlines++;                
		pScrLine = ScreenNewLine(); 
		if (pScrLine == NULL)
			return(0);
		pScr->screen_top = pScrollTop->next;
	}    

	if (pScrLine == NULL)
		return(0);

	pNext = pScrollBottom->next;
	pScrollBottom->next = pScrLine;
	pScrLine->next = pNext;
	pScrLine->prev = pScrollBottom;
	if (pNext != NULL)
	    pNext->prev = pScrLine;

	if (pScrollBottom != pScr->screen_bottom) {
		bFullScreen = FALSE;
		rc.left = 0;
		rc.right = pScr->cxChar * pScr->width;
		rc.top = pScr->cyChar * pScr->top;
		rc.bottom = pScr->cyChar * (pScr->bottom+1);
	}
	else {
		if (pScr->screen_bottom == pScr->buffer_bottom)
			pScr->buffer_bottom = pScrLine;
		pScr->screen_bottom = pScrLine;
	}

//	CheckScreen(fpScr);

	pScr->y++;

	if (pScr->y > pScr->bottom)
		pScr->y = pScr->bottom;

	hDC = GetDC(pScr->hWnd);
	assert(hDC != NULL);

	if (bFullScreen)
		ScrollDC(hDC, 0, -pScr->cyChar, NULL, NULL, NULL, NULL);
	else 
		ScrollDC(hDC, 0, -pScr->cyChar, &rc, &rc, NULL, NULL);

	PatBlt(hDC, 0, pScr->bottom * pScr->cyChar,
		pScr->width * pScr->cxChar, pScr->cyChar, WHITENESS);
		
	ReleaseDC(pScr->hWnd, hDC);

	if (pScr->numlines == pScr->maxlines)
		DeleteTopLine(pScr);
	else
		SetScreenScrollBar(pScr);

	return(1);

} /* ScreenScroll */


int DrawTextScreen(
	RECT rcInvalid,
	SCREEN *pScr,
	HDC hDC)
{
	SCREENLINE *pScrLineTmp;
	SCREENLINE *pScrLine;
	int x = 0;
	int y = 0;
	int left = 0;
	int right = 0;
	int i;
	int len;
	char attrib;
	#define YPOS (y*pScr->cyChar)

	pScrLine = pScr->screen_top;

	for (y = 0; y < pScr->height; y++) {
		if (!pScrLine)
			continue;

		if (YPOS >= rcInvalid.top - pScr->cyChar && 
			YPOS <= rcInvalid.bottom + pScr->cyChar) {

			if (y < 0)
				y = 0;

			if (y >= pScr->height)
				y = pScr->height - 1;

			left = (rcInvalid.left / pScr->cxChar) - 1;

			right = (rcInvalid.right / pScr->cxChar) + 1;

			if (left < 0)
				left = 0;

			if (right > pScr->width - 1)
				right = pScr->width - 1;

			x = left;

			while (x <= right) {
				if (!pScrLine->text[x]) {
					x++;
					continue;
				}

				if (SCR_isrev(pScrLine->attrib[x])) {
					SelectObject(hDC, pScr->hSelectedFont);                
					SetTextColor(hDC, RGB(255, 255, 255));
					SetBkColor(hDC, RGB(0, 0, 0));
				}
				else if (SCR_isblnk(pScrLine->attrib[x])) {
					SelectObject(hDC, pScr->hSelectedFont);                
					SetTextColor(hDC, RGB(255, 0, 0));
					SetBkColor(hDC, RGB(255, 255, 255));
				}
				else if (SCR_isundl(pScrLine->attrib[x])) {
					SetTextColor(hDC, RGB(255, 0, 0));
					SetBkColor(hDC, RGB(255, 255, 255));
					SelectObject(hDC, pScr->hSelectedULFont);
				}
				else {
					SelectObject(hDC,pScr->hSelectedFont);
					SetTextColor(hDC, RGB(0, 0, 0));
					SetBkColor(hDC, RGB(255, 255, 255));
				}

				len = 1;
				attrib = pScrLine->attrib[x];
				for (i = x + 1; i <= right; i++) {
					if (pScrLine->attrib[i] != attrib || !pScrLine->text[i])
						break;
					len++;
				}

				TextOut(hDC, x*pScr->cxChar, y*pScr->cyChar, &pScrLine->text[x], len);
				x += len;
			}
		}                
		pScrLineTmp = pScrLine->next;
		pScrLine = pScrLineTmp;
	}

	return(0);

} /* DrawTextScreen */


static BOOL SetInternalScreenSize(
	SCREEN *pScr,
	int width,
	int height)
{
	RECT rc;
	char *p;
	int idx;
	int n;
	int newlines;
	SCREENLINE *pNewLine;
	SCREENLINE *pTopLine;
	SCREENLINE *pBottomLine;
	#if 0
		int col;
		int row;
		int dydestbottom;
	#endif

	GetClientRect(pScr->hWnd, &rc);

	width = (rc.right - rc.left) / pScr->cxChar;
	height = (rc.bottom - rc.top) / pScr->cyChar;

	if (pScr->height == height && pScr->width == width)
		return(FALSE);

	pScr->Oldx = 0;
	pScr->Oldy = 0;
	pScr->attrib = 0;

	/*
	Reallocate the inverted array of bytes and copy the values
	from the old screen to the new screen.
	*/
	p = calloc(width * height, 1);

	ScreenCursorOff(pScr);

	#if 0	/* Copy inversion array to desitination */
		for (col = 0; col < width; col++) {
			for (row = 0; row < height; row++) {
				dydestbottom = height - 1 - row;
				if (col < pScr->width && dydestbottom < pScr->height - 1)
					p[row * width + col] =
						cInvertedArray[(pScr->height - 1 - dydestbottom) * pScr->width + col];
			}
		}
	#endif

	free(cInvertedArray);
	cInvertedArray = p;

	/*
	Append any new lines which need to be added to accomodate the new
	screen size.
	*/
	pBottomLine = pScr->buffer_bottom;
	newlines = height - (pScr->height + pScr->numlines);

	if (newlines > 0) {
		pScr->y += pScr->numlines;
		pScr->numlines = 0;

		for (idx = 0; idx < newlines; idx++) {
			pNewLine = ScreenNewLine();
			if (pNewLine == NULL)
				return(FALSE);
			pNewLine->prev = pBottomLine;
			if (pBottomLine == NULL)
				return(FALSE);
			pBottomLine->next = pNewLine;
			pBottomLine = pNewLine;
		}
	}

	/*
	If we already have plenty of lines, then we need to get rid of the
	scrollback lines, if too many exist.  The cursor should end up
	the same distance from the bottom of the screen as is started out
	in this instance.
	*/
	if (newlines < 0) {
		pScr->y = (height - 1) - (pScr->bottom - pScr->y);
		if (pScr->y < 0)
			pScr->y = 0;
		pScr->numlines = -newlines;
		n = pScr->numlines - pScr->maxlines;
		for (idx = 0; idx < n; idx++)
			DeleteTopLine(pScr);
	}
		
	/*
	Calculate the position of the buffer relative to the screen.
	*/
	pScr->screen_bottom = pBottomLine;
	pScr->buffer_bottom = pBottomLine;

	pTopLine = pBottomLine;

	for (idx = 1; idx < height; idx++) {
		pTopLine = pTopLine->prev;
	}

	pScr->screen_top = pTopLine;
	pScr->width = width;
	pScr->height = height;
	pScr->top = 0;
	pScr->bottom = height - 1;

	if (pScr->x >= width)
		pScr->x = width - 1;

	if (pScr->y >= height)
		pScr->y = height - 1;

	SetScreenScrollBar(pScr);
	ScreenCursorOn(pScr);
	return(TRUE);

} /* SetInternalScreenSize */


static int ScreenAdjustUp(
	SCREEN *pScr,
	int n)
{
	int idx;
	SCREENLINE *pLine1;
	SCREENLINE *pLine2;

	for (idx = 0; idx < n; idx++) {
	    if (pScr->screen_top == pScr->buffer_top)
			return(-idx);
	   	pLine1 = pScr->screen_top->prev;
		if (pLine1 == NULL)
			return(-idx);
		pLine2 = pScr->screen_bottom->prev;
		if (pLine2 == NULL)
			return(-idx);
		pScr->screen_top = pLine1;
		pScr->screen_bottom = pLine2;
	}

	return(idx);

} /* ScreenAdjustUp */


static int ScreenAdjustDown(
	SCREEN *pScr,
	int n)
{
	int idx;
	SCREENLINE *pLine1;
	SCREENLINE *pLine2;

	for (idx = 0; idx < n; idx++) {
		if (pScr->screen_bottom == pScr->buffer_bottom)
			return(-idx);
		pLine1 = pScr->screen_top->next;
		if (pLine1 == NULL)
			return(-idx);
		pLine2 = pScr->screen_bottom->next;
		if (pLine2 == NULL)
			return(-idx);
		pScr->screen_top = pLine1;
		pScr->screen_bottom = pLine2;
	}

	return(idx);

} /* ScreenAdjustDown */


long FAR PASCAL ScreenWndProc(
	HWND hWnd,
	UINT message,
	WPARAM wParam,
	LPARAM lParam)
{
	MINMAXINFO *lpmmi;
	SCREEN *pScr;                              
	HMENU hMenu;
	PAINTSTRUCT ps;    
	int x = 0;
	int y = 0;
	int ScrollPos;
	int tmpScroll = 0;
	int idx;
	HDC hDC;
	RECT rc;
	char title[128];
	static int bDoubleClick = FALSE;
		 
	switch (message) {

	case WM_COMMAND:
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);

		switch (wParam) {               

		case IDM_BACKSPACE:
			hMenu = GetMenu(hWnd);
			CheckMenuItem(hMenu, IDM_BACKSPACE, MF_CHECKED);
			CheckMenuItem(hMenu, IDM_DELETE, MF_UNCHECKED);
			SendMessage(pScr->hwndTel, WM_MYSCREENCHANGEBKSP, VK_BACK, (LPARAM) pScr);
			break;

		case IDM_DELETE:
			hMenu = GetMenu(hWnd);
			CheckMenuItem(hMenu, IDM_BACKSPACE, MF_UNCHECKED);
			CheckMenuItem(hMenu, IDM_DELETE, MF_CHECKED);
			SendMessage(pScr->hwndTel, WM_MYSCREENCHANGEBKSP, 0x7f, (LPARAM) pScr);
			break;

		case IDM_FONT:
			ScreenCursorOff(pScr);
			ProcessFontChange(hWnd);
			ScreenCursorOn(pScr);
			break;

		case IDM_COPY:
			Edit_Copy(hWnd);
			hMenu=GetMenu(hWnd);
			Edit_ClearSelection(pScr);                
			break;

		case IDM_PASTE:
			Edit_Paste(hWnd);
			break;

		case IDM_ABOUT:
	        #ifdef KRB4
				strcpy(strTmp, "        Kerberos 4 for Windows\n");
			#endif
			#ifdef KRB5
				strcpy(strTmp, "        Kerberos 5 for Windows\n");
			#endif
			strcat(strTmp, "\n                Version 1.00\n\n");
			strcat(strTmp, "          For support, contact:\n");
			strcat(strTmp, "Cygnus Support");
			strcat(strTmp, " - (415) 903-1400");
			MessageBox(NULL, strTmp, "Kerberos", MB_OK);
			break;

		#if ! defined(NDEBUG)
			case IDM_DEBUG:
				CheckScreen(pScr);
				break;
		#endif
		}        

		break;

	case WM_NCCREATE:
		pScr = (SCREEN *) ((LPCREATESTRUCT) lParam)->lpCreateParams;
		pScr->hWnd = hWnd;
		SetWindowLong(hWnd, SCREEN_HANDLE, (LONG) pScr);
		SetScrollRange(hWnd, SB_VERT, 0, 100, FALSE);
		SetScrollPos(hWnd, SB_VERT, 0, TRUE);
		EnableScrollBar(hWnd, SB_VERT, ESB_DISABLE_BOTH);
		return(TRUE);
		
	case WM_VSCROLL:
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);

		ScreenCursorOff(pScr);

		switch(wParam) {

		case SB_LINEDOWN:
			if (ScreenAdjustDown(pScr, 1) <= 0)
				break;
			hDC = GetDC(hWnd);
			assert(hDC != NULL);
			rc.left = 0;
			rc.right = pScr->cxChar * pScr->width;
			rc.top = 0;
			rc.bottom = pScr->cyChar * (pScr->bottom + 1);
			ScrollDC(hDC, 0, -pScr->cyChar, &rc, &rc, NULL, NULL);
			ReleaseDC(hWnd, hDC);
			rc.top = pScr->cyChar * pScr->bottom;
			InvalidateRect(hWnd, &rc, TRUE);
			ScrollPos = GetScrollPos(hWnd, SB_VERT);
			SetScrollPos(hWnd, SB_VERT, ScrollPos + 1, TRUE);
			UpdateWindow(hWnd);
			break;

		case SB_LINEUP:
			if (ScreenAdjustUp(pScr, 1) <= 0)
				break;
			hDC = GetDC(hWnd);
			assert(hDC != NULL);
			rc.left = 0;
			rc.right = pScr->cxChar * pScr->width;
			rc.top = 0;
			rc.bottom = pScr->cyChar * (pScr->bottom + 1);
			ScrollDC(hDC, 0, pScr->cyChar, &rc, &rc, NULL, NULL);
			ReleaseDC(hWnd, hDC);
			rc.bottom = pScr->cyChar;
			InvalidateRect(hWnd, &rc, TRUE);
			ScrollPos = GetScrollPos(pScr->hWnd, SB_VERT);
			SetScrollPos(hWnd,SB_VERT, ScrollPos - 1, TRUE);
			UpdateWindow(hWnd);
			break;

		case SB_PAGEDOWN:
			idx = abs(ScreenAdjustDown(pScr, pScr->height));
			hDC = GetDC(hWnd);
			assert(hDC != NULL);
			rc.left = 0;
			rc.right = pScr->cxChar * pScr->width;
			rc.top = 0;
			rc.bottom = pScr->cyChar * (pScr->bottom+1);
			ScrollDC(hDC, 0, -idx * pScr->cyChar, &rc, &rc, NULL, NULL);
			ReleaseDC(hWnd, hDC);
			rc.top = pScr->cyChar * (pScr->bottom - idx + 1);
			InvalidateRect(hWnd, &rc, TRUE);
			ScrollPos=GetScrollPos(hWnd, SB_VERT);
			SetScrollPos(hWnd, SB_VERT, ScrollPos + idx, TRUE);
			break;

		case SB_PAGEUP:
			idx = abs(ScreenAdjustUp(pScr, pScr->height));
			hDC = GetDC(hWnd);
			assert(hDC != NULL);
			rc.left = 0;
			rc.right = pScr->cxChar * pScr->width;
			rc.top = 0;
			rc.bottom = pScr->cyChar * (pScr->bottom + 1);
			ScrollDC(hDC, 0, idx * pScr->cyChar, &rc, &rc, NULL, NULL);
			ReleaseDC(hWnd, hDC);
			rc.bottom = idx * pScr->cyChar;
			InvalidateRect(hWnd, &rc, TRUE);
			ScrollPos=GetScrollPos(hWnd, SB_VERT);
			SetScrollPos(hWnd, SB_VERT, ScrollPos - idx, TRUE);
			break;

		case SB_THUMBPOSITION:
		case SB_THUMBTRACK:
			ScrollPos = GetScrollPos(hWnd, SB_VERT);
			tmpScroll = ScrollPos - LOWORD(lParam);
			if (tmpScroll == 0)
				break;
			if (tmpScroll > 0)
				ScreenAdjustUp(pScr, tmpScroll);
			else
				ScreenAdjustDown(pScr, -tmpScroll);
			if (abs(tmpScroll) < pScr->height) {
				hDC = GetDC(hWnd);
				assert(hDC != NULL);
				rc.left = 0;
				rc.right = pScr->cxChar * pScr->width;
				rc.top = 0;
				rc.bottom = pScr->cyChar * (pScr->bottom + 1);
				ScrollDC(hDC, 0, tmpScroll * pScr->cyChar, &rc, &rc, NULL, NULL);
				ReleaseDC(hWnd, hDC);
				if (tmpScroll > 0) {
					rc.bottom = tmpScroll * pScr->cyChar;
					InvalidateRect(hWnd, &rc, TRUE);
				}
				else {
					rc.top = (pScr->bottom + tmpScroll + 1) * pScr->cyChar;
					InvalidateRect(hWnd, &rc, TRUE);
				}
			}
			else
				InvalidateRect(hWnd, NULL, TRUE);

			SetScrollPos(hWnd, SB_VERT, LOWORD(lParam), TRUE);
			UpdateWindow(hWnd);
			break;
		}

		ScreenCursorOn(pScr);
		break;
	
	case WM_KEYDOWN:
		if (wParam == VK_INSERT) {
			if (GetKeyState(VK_SHIFT) < 0) 
				PostMessage(hWnd, WM_COMMAND, IDM_PASTE, NULL);
			else if (GetKeyState(VK_CONTROL) < 0)
				PostMessage(hWnd, WM_COMMAND, IDM_COPY, NULL);
			break;
		}
		/*
		** Check for cursor keys. With control pressed, we treat as
		** keyboard equivalents to scrolling. Otherwise, we send
		** a WM_MYCURSORKEY message with the appropriate string
		** to be sent. Sending the actual string allows the upper
		** level to be ignorant of keyboard modes, etc.
		*/
		if (wParam < VK_PRIOR || wParam > VK_DOWN) // Is it a cursor key?
			break;

		if (GetKeyState (VK_CONTROL) >= 0) {	// No control key
			if (wParam >= VK_LEFT && wParam <= VK_DOWN) {
				pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
				assert (pScr != NULL);
				wParam = wParam - VK_LEFT + (pScr->DECCKM ? 4 : 0);
				SendMessage (pScr->hwndTel, WM_MYCURSORKEY,
					strlen(cursor_key[wParam]),
					(LPARAM) (char FAR *) cursor_key[wParam]);
			}
		} else {								// Control is down
			switch (wParam) {
			case VK_PRIOR:						/* Page up   */
				SendMessage(hWnd, WM_VSCROLL, SB_PAGEUP, NULL);
				break;
			case VK_NEXT:						/* Page down */
				SendMessage(hWnd, WM_VSCROLL, SB_PAGEDOWN, NULL);
				break;
			case VK_UP:							/* Line up   */
				SendMessage(hWnd, WM_VSCROLL, SB_LINEUP, NULL);
				break;
			case VK_DOWN:						/* Line down */
				SendMessage(hWnd, WM_VSCROLL, SB_LINEDOWN, NULL);
				break;
			}
		}
		UpdateWindow(hWnd);
		break;
		
	case WM_CHAR:
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);
		SendMessage(pScr->hwndTel, WM_MYSCREENCHAR, wParam, (LPARAM) pScr);
		break;

	case WM_SYSCHAR:
		if (wParam == 'c' || wParam == 'e')
			return (DefWindowProc(hWnd, message, wParam, lParam));
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);
		SendMessage(pScr->hwndTel, WM_MYSYSCHAR, wParam, (LPARAM) pScr);
		break;

	case WM_INITMENU:
		if (IsClipboardFormatAvailable(CF_TEXT))
			EnableMenuItem((HMENU) wParam, IDM_PASTE, MF_ENABLED);
		else
			EnableMenuItem((HMENU) wParam, IDM_PASTE, MF_GRAYED);
		if (bSelection)
			EnableMenuItem((HMENU) wParam, IDM_COPY, MF_ENABLED);
		else
			EnableMenuItem((HMENU) wParam, IDM_COPY, MF_GRAYED);
		break;
							  
	case WM_GETMINMAXINFO:
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		if (pScr == NULL) // Used on creation when window word not set
			pScr = ScreenList;
	    lpmmi = (MINMAXINFO *) lParam;
		if (FRAME_WIDTH + MAX_LINE_WIDTH * pScr->cxChar < lpmmi->ptMaxSize.x)
			lpmmi->ptMaxSize.x = FRAME_WIDTH + MAX_LINE_WIDTH * pScr->cxChar;
		lpmmi->ptMaxTrackSize.x = lpmmi->ptMaxSize.x;
		lpmmi->ptMinTrackSize.x = FRAME_WIDTH + 20 * pScr->cxChar;
		lpmmi->ptMinTrackSize.y = FRAME_HEIGHT + 4 * pScr->cyChar;
		break;
	
	case WM_LBUTTONDOWN: 
		if (bDoubleClick)
			Edit_TripleClick(hWnd, lParam);
		else
			Edit_LbuttonDown(hWnd, lParam);
		break;
	
	case WM_LBUTTONUP:
		Edit_LbuttonUp(hWnd, lParam);
		break;

	case WM_LBUTTONDBLCLK:
		bDoubleClick = TRUE;
		SetTimer(hWnd, TIMER_TRIPLECLICK, GetDoubleClickTime(), NULL);
		Edit_LbuttonDblclk(hWnd, lParam);
		break;

	case WM_TIMER:
		if (wParam == TIMER_TRIPLECLICK)
			bDoubleClick = FALSE;
		break;

	case WM_RBUTTONUP:
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);
		Edit_Copy(hWnd);
		Edit_ClearSelection(pScr);
		Edit_Paste(hWnd);
		break;
							
	case WM_MOUSEMOVE:
		if (bMouseDown)
			Edit_MouseMove(hWnd, lParam);
		break;

	case WM_RBUTTONDOWN:
		#if 0
			pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
			assert (pScr != NULL);
			wsprintf(strTmp,"fp->x=%d fp->y=%d text=%s \r\n", 
				pScr->screen_top->x, pScr->screen_top->y, pScr->screen_top->text);
			OutputDebugString(strTmp);
		#endif
		break;
			
	case WM_PAINT:        
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);
		BeginPaint (hWnd, &ps);
		SelectObject(ps.hdc, pScr->hSelectedFont);
		if (pScr->screen_bottom != NULL)
			DrawTextScreen(ps.rcPaint, pScr, ps.hdc);
		else
			OutputDebugString("screen_bottom is NULL.\r\n");
		EndPaint(hWnd, &ps);
		break;         

	case WM_CLOSE:
		if (MessageBox(hWnd, "Terminate this connection?", "Telnet", MB_OKCANCEL) == IDOK) {
			pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
			assert (pScr != NULL);
			SendMessage(pScr->hwndTel, WM_MYSCREENCLOSE, NULL, (LPARAM) pScr);
			return (DefWindowProc(hWnd, message, wParam, lParam));
		}    
		break;

	case WM_DESTROY:
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		if (pScr != NULL)
			DeleteObject(pScr->hSelectedFont);
		return (DefWindowProc(hWnd, message, wParam, lParam));

	case WM_ACTIVATE:
		if (wParam != WA_INACTIVE) {
			pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
			assert (pScr != NULL);
			if (pScr->bAlert) {
				char strTitle[128];
				int idx;

				GetWindowText(hWnd, strTitle, sizeof(strTitle));
				if (strTitle[0] == ALERT) {
					idx = lstrlen(strTitle);
					strTitle[idx - 2] = 0;
					SetWindowText(hWnd, &strTitle[2]);
					pScr->bAlert = FALSE;
				}
			}
		}
		return (DefWindowProc(hWnd, message, wParam, lParam));

	case WM_SIZE:
		if (wParam == SIZE_MINIMIZED)
			break;

		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);

		if (SetInternalScreenSize(pScr, LOWORD(lParam), HIWORD(lParam))) {
			SendMessage(pScr->hwndTel, WM_MYSCREENSIZE, 0,
				MAKELONG(pScr->width, pScr->height));
		}
		MakeWindowTitle(pScr->title, pScr->width, pScr->height, title, sizeof(title));
		SetWindowText(hWnd, title);
		break;

	case WM_SETFOCUS:
		pScr = (SCREEN *) GetWindowLong(hWnd, SCREEN_HANDLE);
		assert (pScr != NULL);
		CreateCaret(hWnd, NULL, pScr->cxChar, 2);
		ScreenCursorOn(pScr);
		break;

	case WM_KILLFOCUS:
		DestroyCaret();
		break;

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

	return(NULL);

} /* ScreenWndProc */


void ScreenBell(
	SCREEN *pScr)
{
	char strTitle[128];
	int idx;

	MessageBeep(MB_ICONEXCLAMATION);
	if (pScr->hWnd != GetActiveWindow()) {
		FlashWindow(pScr->hWnd, TRUE);
		if (!pScr->bAlert) {
			strTitle[0] = ALERT;
			strTitle[1] = SPACE;
			GetWindowText(pScr->hWnd, &strTitle[2], sizeof(strTitle) - 2);
			idx = lstrlen(strTitle);
			strTitle[idx] = SPACE;
			strTitle[idx+1] = ALERT;
			strTitle[idx+2] = 0;
			SetWindowText(pScr->hWnd, strTitle);
		}
		FlashWindow(pScr->hWnd, FALSE);
		pScr->bAlert = TRUE;
	}

} /* ScreenBell */


void ScreenBackspace(SCREEN *pScr)
{
	RECT rc;
	
	pScr->bWrapPending = FALSE;
	rc.left = pScr->x * pScr->cxChar;
	rc.right = (pScr->x + 1) * pScr->cxChar;
	rc.top = pScr->cyChar * pScr->y;
	rc.bottom = pScr->cyChar * (pScr->y + 1);
	InvalidateRect(pScr->hWnd, &rc, TRUE);
	pScr->x--;
	if (pScr->x < 0)
		pScr->x = 0;
	UpdateWindow(pScr->hWnd);

} /* ScreenBackspace */


void ScreenTab(
	SCREEN *pScr)
{
	int num_spaces;
	int idx;
	SCREENLINE *pScrLine;
	int iTest = 0;
	HDC hDC;

	num_spaces = TAB_SPACES - (pScr->x % TAB_SPACES);
	if (pScr->x + num_spaces >= pScr->width)
	   num_spaces = pScr->width - pScr->x;
	pScrLine = GetScreenLineFromY(pScr, pScr->y);
	if (pScrLine == NULL)
		return;
	for (idx = 0; idx < num_spaces; idx++, pScr->x++) {
		if (!pScrLine->text[pScr->x])
			iTest=1;
		if (iTest)
			pScrLine->text[pScr->x] = SPACE;
	}
	hDC = GetDC(pScr->hWnd);
	assert(hDC != NULL);
	SelectObject(hDC, pScr->hSelectedFont);
	TextOut(hDC, (pScr->x - num_spaces) * pScr->cxChar, pScr->y * pScr->cyChar,
		pScrLine->text + pScr->x - num_spaces, num_spaces);
	ReleaseDC(pScr->hWnd, hDC);
	if (pScr->x >= pScr->width)
		pScr->x = pScr->width - 1;
	pScr->bWrapPending = FALSE;

} /* ScreenTab */


void ScreenCarriageFeed(
	SCREEN *pScr)
{
	pScr->bWrapPending = FALSE;
	pScr->x = 0;

} /* ScreenCarriageFeed */
