#ifndef lint
static char *RCSid = "$Id: wtext.c%v 3.38.2.74 1993/02/19 01:19:51 woo Exp woo $";
#endif

/* GNUPLOT - win/wtext.c */
/*
 * Copyright (C) 1992   Russell Lang
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted, 
 * provided that the above copyright notice appear in all copies and 
 * that both that copyright notice and this permission notice appear 
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the modified code.  Modifications are to be distributed 
 * as patches to released version.
 *  
 * This software is provided "as is" without express or implied warranty.
 * 
 *
 * AUTHORS
 * 
 *   Russell Lang
 * 
 * Send your comments or suggestions to 
 *  info-gnuplot@dartmouth.edu.
 * This is a mailing list; to join it send a note to 
 *  info-gnuplot-request@dartmouth.edu.  
 * Send bug reports to
 *  bug-gnuplot@dartmouth.edu.
 */

/* WARNING: Do not write to stdout/stderr with functions not listed 
   in win/wtext.h */

#define STRICT
#include <windows.h>
#include <windowsx.h>
#if WINVER >= 0x030a
#include <commdlg.h>
#endif
#include <string.h>	/* use only far items */
#ifndef __MSC__
#include <mem.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include <dos.h>

#include "wgnuplib.h"
#include "wresourc.h"
#include "wcommon.h"

/* font stuff */
#define TEXTFONTSIZE 9
#define TEXTFONTNAME "Terminal"

#define EOF -1		/* instead of using <stdio.h> */
/* limits */
#define MAXSTR 256
POINT ScreenMinSize = {16,4};
LRESULT CALLBACK _export WndParentProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK _export WndTextProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void ReadTextIni(LPTW lptw);
void LimitMark(LPTW lptw, POINT FAR *lppt);

char szNoMemory[] = "out of memory";
COLORREF TextColorTable[16] = { 
	RGB(0,0,0),			/* black */
	RGB(0,0,128),		/* dark blue */
	RGB(0,128,0),		/* dark green */
	RGB(0,128,128),		/* dark cyan */
	RGB(128,0,0),		/* dark red */
	RGB(128,0,128),		/* dark magenta */
	RGB(128,128,0),		/* dark yellow */
	RGB(128,128,128),	/* dark grey */
	RGB(192,192,192),	/* light grey */
	RGB(0,0,255),		/* blue */
	RGB(0,255,0),		/* green */
	RGB(0,255,255),		/* cyan */
	RGB(255,0,0),		/* red */
	RGB(255,0,255),		/* magenta */
	RGB(255,255,0),		/* yellow */
	RGB(255,255,255),	/* white */
};
#define NOTEXT 0xF0
#define MARKFORE RGB(255,255,255)
#define MARKBACK RGB(0,0,128)
#define TextFore(attr) TextColorTable[(attr) & 15]
#define TextBack(attr) TextColorTable[(attr>>4) & 15]


void WDPROC
TextMessage(void)
{
    MSG msg;

    while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    return;
}



void
CreateTextClass(LPTW lptw)
{
WNDCLASS wndclass;
	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndTextProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = 2 * sizeof(void FAR *);
	wndclass.hInstance = lptw->hInstance;
	wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = GetStockBrush(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szTextClass;
	RegisterClass(&wndclass);

	wndclass.style = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc = WndParentProc;
	wndclass.cbClsExtra = 0;
	wndclass.cbWndExtra = sizeof(void FAR *);
	wndclass.hInstance = lptw->hInstance;
	if (lptw->hIcon)
		wndclass.hIcon = lptw->hIcon;
	else
		wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = GetStockBrush(WHITE_BRUSH);
	wndclass.lpszMenuName = NULL;
	wndclass.lpszClassName = szParentClass;
	RegisterClass(&wndclass);
}


/* make text window */
int WDPROC
TextInit(LPTW lptw)
{
	RECT rect;
	HMENU sysmenu;
	HGLOBAL hglobal;
	char buf[80];
	
	if (!lptw->hPrevInstance)
		CreateTextClass(lptw);

	if (lptw->KeyBufSize == 0)
		lptw->KeyBufSize = 256;

	if (lptw->ScreenSize.x < ScreenMinSize.x)
		lptw->ScreenSize.x = ScreenMinSize.x;
	if (lptw->ScreenSize.y < ScreenMinSize.y)
		lptw->ScreenSize.y = ScreenMinSize.y;

	lptw->CursorPos.x = lptw->CursorPos.y = 0;
	lptw->bFocus = FALSE;
	lptw->bGetCh = FALSE;
	lptw->CaretHeight = 0;
	if (!lptw->nCmdShow)
		lptw->nCmdShow = SW_SHOWNORMAL;
	if (!lptw->Attr)
		lptw->Attr = 0xf0;	/* black on white */

	hglobal = GlobalAlloc(GHND, lptw->ScreenSize.x * lptw->ScreenSize.y);
	lptw->ScreenBuffer = (BYTE FAR *)GlobalLock(hglobal);
	if (lptw->ScreenBuffer == (BYTE FAR *)NULL) {
		MessageBox((HWND)NULL,szNoMemory,(LPSTR)NULL, MB_ICONHAND | MB_SYSTEMMODAL);
		return(1);
	}
	_fmemset(lptw->ScreenBuffer, ' ', lptw->ScreenSize.x * lptw->ScreenSize.y);
	hglobal = GlobalAlloc(GHND, lptw->ScreenSize.x * lptw->ScreenSize.y);
	lptw->AttrBuffer = (BYTE FAR *)GlobalLock(hglobal);
	if (lptw->AttrBuffer == (BYTE FAR *)NULL) {
		MessageBox((HWND)NULL,szNoMemory,(LPSTR)NULL, MB_ICONHAND | MB_SYSTEMMODAL);
		return(1);
	}
	_fmemset(lptw->AttrBuffer, NOTEXT, lptw->ScreenSize.x * lptw->ScreenSize.y);
	hglobal = GlobalAlloc(LHND, lptw->KeyBufSize);
	lptw->KeyBuf = (BYTE FAR *)GlobalLock(hglobal);
	if (lptw->KeyBuf == (BYTE FAR *)NULL) {
		MessageBox((HWND)NULL,szNoMemory,(LPSTR)NULL, MB_ICONHAND | MB_SYSTEMMODAL);
		return(1);
	}
	lptw->KeyBufIn = lptw->KeyBufOut = lptw->KeyBuf;

	ReadTextIni(lptw);

	lptw->hWndParent = CreateWindow(szParentClass, lptw->Title,
		  WS_OVERLAPPEDWINDOW,
		  lptw->Origin.x, lptw->Origin.y,
		  lptw->Size.x, lptw->Size.y,
		  NULL, NULL, lptw->hInstance, lptw);
	if (lptw->hWndParent == (HWND)NULL) {
		MessageBox((HWND)NULL,"Couldn't open parent text window",(LPSTR)NULL, MB_ICONHAND | MB_SYSTEMMODAL);
		return(1);
	}
	ShowWindow(lptw->hWndParent, lptw->nCmdShow);
	GetClientRect(lptw->hWndParent, &rect);

	lptw->hWndText = CreateWindow(szTextClass, lptw->Title,
		  WS_CHILD | WS_VSCROLL | WS_HSCROLL,
		  0, lptw->ButtonHeight,
		  rect.right, rect.bottom-lptw->ButtonHeight,
		  lptw->hWndParent, NULL, lptw->hInstance, lptw);
	if (lptw->hWndText == (HWND)NULL) {
		MessageBox((HWND)NULL,"Couldn't open text window",(LPSTR)NULL, MB_ICONHAND | MB_SYSTEMMODAL);
		return(1);
	}

	lptw->hPopMenu = CreatePopupMenu();
	AppendMenu(lptw->hPopMenu, MF_STRING, M_COPY_CLIP, "&Copy to Clipboard");
	AppendMenu(lptw->hPopMenu, MF_STRING, M_PASTE, "&Paste");
#if WINVER >= 0x030a
	AppendMenu(lptw->hPopMenu, MF_STRING, M_CHOOSE_FONT, "Choose &Font...");
#endif
	if (lptw->IniFile != (LPSTR)NULL) {
		wsprintf(buf,"&Update %s",lptw->IniFile);
		AppendMenu(lptw->hPopMenu, MF_STRING, M_WRITEINI, (LPSTR)buf);
	}

	sysmenu = GetSystemMenu(lptw->hWndParent,0);	/* get the sysmenu */
	AppendMenu(sysmenu, MF_SEPARATOR, 0, NULL);
	AppendMenu(sysmenu, MF_POPUP, (UINT)lptw->hPopMenu, "&Options");
	AppendMenu(sysmenu, MF_STRING, M_ABOUT, "&About");

	if (lptw->lpmw)
		LoadMacros(lptw);

	ShowWindow(lptw->hWndText, SW_SHOWNORMAL);
	BringWindowToTop(lptw->hWndText);
	SetFocus(lptw->hWndText);
	TextMessage();
	return(0);
}

/* close a text window */
void WDPROC
TextClose(LPTW lptw)
{
	HGLOBAL hglobal;

	/* close window */
	if (lptw->hWndParent)
		DestroyWindow(lptw->hWndParent);
	TextMessage();

	hglobal = (HGLOBAL)GlobalHandle( SELECTOROF(lptw->ScreenBuffer) );
	if (hglobal) {
		GlobalUnlock(hglobal);
		GlobalFree(hglobal);
	}
	hglobal = (HGLOBAL)GlobalHandle( SELECTOROF(lptw->AttrBuffer) );
	if (hglobal) {
		GlobalUnlock(hglobal);
		GlobalFree(hglobal);
	}
	hglobal = (HGLOBAL)GlobalHandle( SELECTOROF(lptw->KeyBuf) );
	if (hglobal) {
		GlobalUnlock(hglobal);
		GlobalFree(hglobal);
	}

	if (lptw->lpmw)
		CloseMacros(lptw);
	lptw->hWndParent = (HWND)NULL;
}
	
void
WriteTextIni(LPTW lptw)
{
	RECT rect;
	LPSTR file = lptw->IniFile;
	LPSTR section = lptw->IniSection;
	char profile[80];
	int iconic;

	
	if ((file == (LPSTR)NULL) || (section == (LPSTR)NULL))
		return;
	
	iconic = IsIconic(lptw->hWndParent);
	if (iconic)
		ShowWindow(lptw->hWndParent, SW_SHOWNORMAL);
	GetWindowRect(lptw->hWndParent,&rect);
	wsprintf(profile, "%d %d", rect.left, rect.top);
	WritePrivateProfileString(section, "TextOrigin", profile, file);
	wsprintf(profile, "%d %d", rect.right-rect.left, rect.bottom-rect.top);
	WritePrivateProfileString(section, "TextSize", profile, file);
	wsprintf(profile, "%d", iconic);
	WritePrivateProfileString(section, "TextMinimized", profile, file);
	wsprintf(profile, "%s,%d", lptw->fontname, lptw->fontsize);
	WritePrivateProfileString(section, "TextFont", profile, file);
	if (iconic)
		ShowWindow(lptw->hWndParent, SW_SHOWMINIMIZED);
	return;
}

void
ReadTextIni(LPTW lptw)
{
	LPSTR file = lptw->IniFile;
	LPSTR section = lptw->IniSection;
	char profile[81];
	LPSTR p;
	BOOL bOKINI;

	bOKINI = (file != (LPSTR)NULL) && (section != (LPSTR)NULL);
	profile[0] = '\0';

	if (bOKINI)
	  GetPrivateProfileString(section, "TextOrigin", "", profile, 80, file);
	if ( (p = GetInt(profile, &lptw->Origin.x)) == NULL)
		lptw->Origin.x = CW_USEDEFAULT;
	if ( (p = GetInt(p, &lptw->Origin.y)) == NULL)
		lptw->Origin.y = CW_USEDEFAULT;
	if ( (file != (LPSTR)NULL) && (section != (LPSTR)NULL) )
	  GetPrivateProfileString(section, "TextSize", "", profile, 80, file);
	if ( (p = GetInt(profile, &lptw->Size.x)) == NULL)
		lptw->Size.x = CW_USEDEFAULT;
	if ( (p = GetInt(p, &lptw->Size.y)) == NULL)
		lptw->Size.y = CW_USEDEFAULT;

	if (bOKINI)
	  GetPrivateProfileString(section, "TextFont", "", profile, 80, file);
	{
		char FAR *size;
		size = _fstrchr(profile,',');
		if (size) {
			*size++ = '\0';
			if ( (p = GetInt(size, &lptw->fontsize)) == NULL)
				lptw->fontsize = TEXTFONTSIZE;
		}
		_fstrcpy(lptw->fontname, profile);
		if (lptw->fontsize == 0)
			lptw->fontsize = TEXTFONTSIZE;
		if (!(*lptw->fontname))
			_fstrcpy(lptw->fontname,TEXTFONTNAME);
	}

	if (bOKINI) {
		int iconic;
		GetPrivateProfileString(section, "TextMinimized", "", profile, 80, file);
		if ( (p = GetInt(profile, &iconic)) == NULL)
			iconic = 0;
		if (iconic)
			lptw->nCmdShow = SW_SHOWMINIMIZED;
	}
}


/* Bring Cursor into text window */
void WDPROC
TextToCursor(LPTW lptw)
{
int nXinc=0;
int nYinc=0;
int cxCursor;
int cyCursor;
	cyCursor = lptw->CursorPos.y * lptw->CharSize.y;
	if ( (cyCursor + lptw->CharSize.y > lptw->ScrollPos.y + lptw->ClientSize.y) 
	  || (cyCursor < lptw->ScrollPos.y) ) {
		nYinc = max(0, cyCursor + lptw->CharSize.y - lptw->ClientSize.y) - lptw->ScrollPos.y;
		nYinc = min(nYinc, lptw->ScrollMax.y - lptw->ScrollPos.y);
	}
	cxCursor = lptw->CursorPos.x * lptw->CharSize.x;
	if ( (cxCursor + lptw->CharSize.x > lptw->ScrollPos.x + lptw->ClientSize.x)
	  || (cxCursor < lptw->ScrollPos.x) ) {
		nXinc = max(0, cxCursor + lptw->CharSize.x - lptw->ClientSize.x/2) - lptw->ScrollPos.x;
		nXinc = min(nXinc, lptw->ScrollMax.x - lptw->ScrollPos.x);
	}
	if (nYinc || nXinc) {
		lptw->ScrollPos.y += nYinc;
		lptw->ScrollPos.x += nXinc;
		ScrollWindow(lptw->hWndText,-nXinc,-nYinc,NULL,NULL);
		SetScrollPos(lptw->hWndText,SB_VERT,lptw->ScrollPos.y,TRUE);
		SetScrollPos(lptw->hWndText,SB_HORZ,lptw->ScrollPos.x,TRUE);
		UpdateWindow(lptw->hWndText);
	}
}

void
NewLine(LPTW lptw)
{
	lptw->CursorPos.x = 0;
	lptw->CursorPos.y++;
	if (lptw->CursorPos.y >= lptw->ScreenSize.y) {
	    int i =  lptw->ScreenSize.x * (lptw->ScreenSize.y - 1);
		_fmemmove(lptw->ScreenBuffer, lptw->ScreenBuffer+lptw->ScreenSize.x, i);
		_fmemset(lptw->ScreenBuffer + i, ' ', lptw->ScreenSize.x);
		_fmemmove(lptw->AttrBuffer, lptw->AttrBuffer+lptw->ScreenSize.x, i);
		_fmemset(lptw->AttrBuffer + i, NOTEXT, lptw->ScreenSize.x);
		lptw->CursorPos.y--;
		ScrollWindow(lptw->hWndText,0,-lptw->CharSize.y,NULL,NULL);
		lptw->MarkBegin.y--;
		lptw->MarkEnd.y--;
		LimitMark(lptw, &lptw->MarkBegin);
		LimitMark(lptw, &lptw->MarkEnd);
		UpdateWindow(lptw->hWndText);
	}
	if (lptw->CursorFlag)
		TextToCursor(lptw);
	TextMessage();
}

/* Update count characters in window at cursor position */
/* Updates cursor position */
void
UpdateText(LPTW lptw, int count)
{
HDC hdc;
int xpos, ypos;
	xpos = lptw->CursorPos.x*lptw->CharSize.x - lptw->ScrollPos.x;
	ypos = lptw->CursorPos.y*lptw->CharSize.y - lptw->ScrollPos.y;
	hdc = GetDC(lptw->hWndText);
	SetTextColor(hdc, TextFore(lptw->Attr));
	SetBkColor(hdc, TextBack(lptw->Attr));
	SelectFont(hdc, lptw->hfont);
	TextOut(hdc,xpos,ypos,
		(LPSTR)(lptw->ScreenBuffer + lptw->CursorPos.y*lptw->ScreenSize.x + 
		lptw->CursorPos.x), count);
	(void)ReleaseDC(lptw->hWndText,hdc);
	lptw->CursorPos.x += count;
	if (lptw->CursorPos.x >= lptw->ScreenSize.x)
		NewLine(lptw);
}

int WDPROC
TextPutCh(LPTW lptw, BYTE ch)
{
int pos;
	switch(ch) {
		case '\r':
			lptw->CursorPos.x = 0;
			if (lptw->CursorFlag)
				TextToCursor(lptw);
			break;
		case '\n':
			NewLine(lptw);
			break;
		case 7:
			MessageBeep(-1);
			if (lptw->CursorFlag)
				TextToCursor(lptw);
			break;
		case '\t':
			{
			int n;
				for ( n = 8 - (lptw->CursorPos.x % 8); n>0; n-- )
					TextPutCh(lptw, ' ');
			}
			break;
		case 0x08:
		case 0x7f:
			lptw->CursorPos.x--;
			if (lptw->CursorPos.x < 0) {
				lptw->CursorPos.x = lptw->ScreenSize.x - 1;
				lptw->CursorPos.y--;
			}
			if (lptw->CursorPos.y < 0)
				lptw->CursorPos.y = 0;
			break;
		default:
			pos = lptw->CursorPos.y*lptw->ScreenSize.x + lptw->CursorPos.x;
			lptw->ScreenBuffer[pos] = ch;
			lptw->AttrBuffer[pos] = lptw->Attr;
			UpdateText(lptw, 1);
	}
	return ch;
}

void 
TextPutStr(LPTW lptw, LPSTR str)
{
BYTE FAR *p, FAR *pa;
int count, limit;
	while (*str) {
		p = lptw->ScreenBuffer + lptw->CursorPos.y*lptw->ScreenSize.x + lptw->CursorPos.x;
		pa = lptw->AttrBuffer + lptw->CursorPos.y*lptw->ScreenSize.x + lptw->CursorPos.x;
		limit = lptw->ScreenSize.x - lptw->CursorPos.x;
		for (count=0; (count < limit) && *str && (isprint(*str) || *str=='\t'); count++) {
			if (*str=='\t') {
				int n;
				for ( n = 8 - ((lptw->CursorPos.x+count) % 8); (count < limit) & (n>0); n--, count++ ) {
					*p++ = ' ';
					*pa++ = lptw->Attr;
				}
				str++;
				count--;
			}
			else {
				*p++ = *str++;
				*pa++ = lptw->Attr;
			}
		}
		if (count>0) {
			UpdateText(lptw, count);
		}
		if (*str=='\n') {
			NewLine(lptw);
			str++;
		}
		else if (*str && !isprint(*str) && *str!='\t') {
			TextPutCh(lptw, *str++);
		}
	}
}


void
LimitMark(LPTW lptw, POINT FAR *lppt)
{
	if (lppt->x < 0)
		lppt->x = 0;
	if (lppt->y < 0) {
		lppt->x = 0;
		lppt->y = 0;
	}
	if (lppt->x > lptw->ScreenSize.x)
		lppt->x = lptw->ScreenSize.x;
	if (lppt->y >= lptw->ScreenSize.y) {
		lppt->x = 0;
		lppt->y = lptw->ScreenSize.y;
	}
}

void
ClearMark(LPTW lptw, POINT pt)
{
RECT rect1, rect2, rect3;
int tmp;
  if ((lptw->MarkBegin.x != lptw->MarkEnd.x) || 
      (lptw->MarkBegin.y != lptw->MarkEnd.y) ) {
	if (lptw->MarkBegin.x > lptw->MarkEnd.x) {
		tmp = lptw->MarkBegin.x;
		lptw->MarkBegin.x = lptw->MarkEnd.x;
		lptw->MarkEnd.x = tmp;
	}
	if (lptw->MarkBegin.y > lptw->MarkEnd.y) {
		tmp = lptw->MarkBegin.y;
		lptw->MarkBegin.y = lptw->MarkEnd.y;
		lptw->MarkEnd.y = tmp;
	}
	/* calculate bounding rectangle in character coordinates */
	if (lptw->MarkBegin.y != lptw->MarkEnd.y) {
		rect1.left = 0;
		rect1.right = lptw->ScreenSize.x;
	}
	else {
		rect1.left = lptw->MarkBegin.x;
		rect1.right = lptw->MarkEnd.x + 1;
	}
	rect1.top = lptw->MarkBegin.y;
	rect1.bottom = lptw->MarkEnd.y + 1;
	/* now convert to client coordinates */
	rect1.left   = rect1.left   * lptw->CharSize.x - lptw->ScrollPos.x;
	rect1.right  = rect1.right  * lptw->CharSize.x - lptw->ScrollPos.x;
	rect1.top    = rect1.top    * lptw->CharSize.y - lptw->ScrollPos.y;
	rect1.bottom = rect1.bottom * lptw->CharSize.y - lptw->ScrollPos.y;
	/* get client rect and calculate intersection */
	GetClientRect(lptw->hWndText, &rect2);
	IntersectRect(&rect3,  &rect1, &rect2);
	/* update window if necessary */
	if (!IsRectEmpty(&rect3)) {
		InvalidateRect(lptw->hWndText, &rect3, TRUE);
	}
  }
  LimitMark(lptw, &pt);
  lptw->MarkBegin.x = lptw->MarkEnd.x = pt.x;
  lptw->MarkBegin.y = lptw->MarkEnd.y = pt.y;
  UpdateWindow(lptw->hWndText);
}


/* output a line including attribute changes as needed */
void
DoLine(LPTW lptw, HDC hdc, int xpos, int ypos, int offset, int count)
{
	BYTE FAR *pa, attr;
	int idx, num;
	pa = lptw->AttrBuffer + offset;
if ((offset < 0) || (offset >= lptw->ScreenSize.x*lptw->ScreenSize.y))
MessageBox((HWND)NULL, "panic", "panic", MB_OK | MB_ICONEXCLAMATION);
	idx = 0;
	num = count;
	while (num > 0) {
		attr = *pa;
		while ((num > 0) && (attr == *pa)) {
			/* skip over bytes with same attribute */
			num--;
			pa++;
		}
		SetTextColor(hdc, TextFore(attr));
		SetBkColor(hdc, TextBack(attr));
		TextOut(hdc,xpos,ypos, (LPSTR)(lptw->ScreenBuffer + offset + idx),
			count-num-idx);
		xpos += lptw->CharSize.x * (count-num-idx);
		idx = count-num;
	}
}

void
DoMark(LPTW lptw, POINT pt, POINT end, BOOL mark)
{
int xpos, ypos;
HDC hdc;
int count;
int offset;
	offset = lptw->ScreenSize.x * pt.y + pt.x;
	hdc = GetDC(lptw->hWndText);
	SelectFont(hdc, lptw->hfont);
	SetTextColor(hdc, MARKFORE);
	SetBkColor(hdc, MARKBACK);
	while (pt.y < end.y) {
		/* multiple lines */
		xpos = pt.x*lptw->CharSize.x - lptw->ScrollPos.x;
		ypos = pt.y*lptw->CharSize.y - lptw->ScrollPos.y;
		count = lptw->ScreenSize.x - pt.x;
		if (mark)
			TextOut(hdc,xpos,ypos, (LPSTR)(lptw->ScreenBuffer + offset), count);
		else {
			DoLine(lptw, hdc, xpos, ypos, offset, count);
			SetTextColor(hdc, MARKFORE);
			SetBkColor(hdc, MARKBACK);
		}
		offset += count;
		pt.y++;
		pt.x=0;
	}
	/* partial line */
	xpos = pt.x*lptw->CharSize.x - lptw->ScrollPos.x;
	ypos = pt.y*lptw->CharSize.y - lptw->ScrollPos.y;
	count = end.x - pt.x;
	if (end.y != lptw->ScreenSize.y) {
		if (mark)
			TextOut(hdc,xpos,ypos, (LPSTR)(lptw->ScreenBuffer + offset), count);
		else
			DoLine(lptw, hdc, xpos, ypos, offset, count);
	}
	(void)ReleaseDC(lptw->hWndText,hdc);
}

void
UpdateMark(LPTW lptw, POINT pt)
{
int begin, point, end;
	LimitMark(lptw, &pt);
	begin = lptw->ScreenSize.x*lptw->MarkBegin.y + lptw->MarkBegin.x;
	point = lptw->ScreenSize.x*pt.y + pt.x;
	end   = lptw->ScreenSize.x*lptw->MarkEnd.y + lptw->MarkEnd.x;

	if (begin <= end) {
		/* forward mark */
		if (point >= end) {
			/* extend marked area */
			DoMark(lptw, lptw->MarkEnd, pt, TRUE);
		}
		else if (point >= begin) {
			/* retract marked area */
			DoMark(lptw, pt, lptw->MarkEnd, FALSE);
		}
		else {	/* retract and reverse */
			DoMark(lptw, lptw->MarkBegin, lptw->MarkEnd, FALSE);
			DoMark(lptw, pt, lptw->MarkBegin, TRUE);
		}
	}
	else {
		/* reverse mark */
		if (point <= end) {
			/* extend marked area */
			DoMark(lptw, pt, lptw->MarkEnd, TRUE);
		}
		else if (point <= begin) {
			/* retract marked area */
			DoMark(lptw, lptw->MarkEnd, pt, FALSE);
		}
		else {	/* retract and reverse */
			DoMark(lptw, lptw->MarkEnd, lptw->MarkBegin, FALSE);
			DoMark(lptw, lptw->MarkBegin, pt, TRUE);
		}
	}
	lptw->MarkEnd.x = pt.x;
	lptw->MarkEnd.y = pt.y;
}


#if WINVER >= 0x030a
/* Windows 3.1 drag-drop feature */
char szFile[80];
void
DragFunc(LPTW lptw, HDROP hdrop)
{
	int i, cFiles;
	LPSTR p;
	if ( (lptw->DragPre==(LPSTR)NULL) || (lptw->DragPost==(LPSTR)NULL) )
		return;
	cFiles = DragQueryFile(hdrop, 0xffff, (LPSTR)NULL, 0);
	for (i=0; i<cFiles; i++) {
		DragQueryFile(hdrop, i, szFile, 80);
		for (p=lptw->DragPre; *p; p++)
			SendMessage(lptw->hWndText,WM_CHAR,*p,1L);
		for (p=szFile; *p; p++)
			SendMessage(lptw->hWndText,WM_CHAR,*p,1L);
		for (p=lptw->DragPost; *p; p++)
			SendMessage(lptw->hWndText,WM_CHAR,*p,1L);
	}
	DragFinish(hdrop);
}
#endif


void
TextCopyClip(LPTW lptw)
{
	int size, count;
	HGLOBAL hGMem;
	LPSTR cbuf, cp;
	POINT pt, end;
	TEXTMETRIC tm;
	UINT type;
	HDC hdc;

	if ((lptw->MarkBegin.x == lptw->MarkEnd.x) && 
	    (lptw->MarkBegin.y == lptw->MarkEnd.y) ) {
		/* copy user text */
		return;
	}

	size = (lptw->MarkEnd.y - lptw->MarkBegin.y + 1) 
		* (lptw->ScreenSize.x + 2) + 1;
	hGMem = GlobalAlloc(GMEM_MOVEABLE, (DWORD)size);
	cbuf = cp = (LPSTR)GlobalLock(hGMem);
	if (cp == (LPSTR)NULL)
		return;
	
	pt.x = lptw->MarkBegin.x;
	pt.y = lptw->MarkBegin.y;
	end.x   = lptw->MarkEnd.x;
	end.y   = lptw->MarkEnd.y;

	while (pt.y < end.y) {
		/* copy to global buffer */
		count = lptw->ScreenSize.x - pt.x;
		_fmemcpy(cp, lptw->ScreenBuffer + lptw->ScreenSize.x*pt.y+pt.x, count);
		/* remove trailing spaces */
		for (count=count-1; count>=0; count--) {
			if (cp[count]!=' ')
				break;
			cp[count] = '\0';
		}
		cp[++count] = '\r';
		cp[++count] = '\n';
		cp[++count] = '\0';
		cp += count;
		pt.y++;
		pt.x=0;
	}
	/* partial line */
	count = end.x - pt.x;
	if (end.y != lptw->ScreenSize.y) {
		_fmemcpy(cp, lptw->ScreenBuffer + lptw->ScreenSize.x*pt.y+pt.x, count);
		cp[count] = '\0';
	}
	size = _fstrlen(cbuf) + 1;
	GlobalUnlock(hGMem);
	hGMem = GlobalReAlloc(hGMem, (DWORD)size, GMEM_MOVEABLE);
	/* find out what type to put into clipboard */
	hdc = GetDC(lptw->hWndText);
	SelectFont(hdc, lptw->hfont);
	GetTextMetrics(hdc,(TEXTMETRIC FAR *)&tm);
	if (tm.tmCharSet == OEM_CHARSET)
		type = CF_OEMTEXT;
	else
		type = CF_TEXT;
	ReleaseDC(lptw->hWndText, hdc);
	/* give buffer to clipboard */
	OpenClipboard(lptw->hWndParent);
	EmptyClipboard();
	SetClipboardData(type, hGMem);
	CloseClipboard();
}

void
TextMakeFont(LPTW lptw)
{
	LOGFONT lf;
	TEXTMETRIC tm;
	LPSTR p;
	HDC hdc;

	hdc = GetDC(lptw->hWndText);
	_fmemset(&lf, 0, sizeof(LOGFONT));
	_fstrncpy(lf.lfFaceName,lptw->fontname,LF_FACESIZE);
	lf.lfHeight = -MulDiv(lptw->fontsize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
	lf.lfPitchAndFamily = FIXED_PITCH;
	lf.lfCharSet = DEFAULT_CHARSET;
	if ( (p = _fstrstr(lptw->fontname," Italic")) != (LPSTR)NULL ) {
		lf.lfFaceName[ (unsigned int)(p-lptw->fontname) ] = '\0';
		lf.lfItalic = TRUE;
	}
	if ( (p = _fstrstr(lptw->fontname," Bold")) != (LPSTR)NULL ) {
		lf.lfFaceName[ (unsigned int)(p-lptw->fontname) ] = '\0';
		lf.lfWeight = FW_BOLD;
	}
	if (lptw->hfont != 0)
		DeleteFont(lptw->hfont);
	lptw->hfont = CreateFontIndirect((LOGFONT FAR *)&lf);
	/* get text size */
	SelectFont(hdc, lptw->hfont);
	GetTextMetrics(hdc,(TEXTMETRIC FAR *)&tm);
	lptw->CharSize.y = tm.tmHeight;
	lptw->CharSize.x = tm.tmAveCharWidth;
	lptw->CharAscent = tm.tmAscent;
	if (lptw->bFocus)
		CreateCaret(lptw->hWndText, 0, lptw->CharSize.x, 2+lptw->CaretHeight);
	ReleaseDC(lptw->hWndText, hdc);
	return;
}

void
TextSelectFont(LPTW lptw) {
#if WINVER >= 0x030a
	LOGFONT lf;
	CHOOSEFONT cf;
	HDC hdc;
	char lpszStyle[LF_FACESIZE]; 
	LPSTR p;

	/* Set all structure fields to zero. */
	_fmemset(&cf, 0, sizeof(CHOOSEFONT));
	_fmemset(&lf, 0, sizeof(LOGFONT));
	cf.lStructSize = sizeof(CHOOSEFONT);
	cf.hwndOwner = lptw->hWndParent;
	_fstrncpy(lf.lfFaceName,lptw->fontname,LF_FACESIZE);
	if ( (p = _fstrstr(lptw->fontname," Bold")) != (LPSTR)NULL ) {
		_fstrncpy(lpszStyle,p+1,LF_FACESIZE);
		lf.lfFaceName[ (unsigned int)(p-lptw->fontname) ] = '\0';
	}
	else if ( (p = _fstrstr(lptw->fontname," Italic")) != (LPSTR)NULL ) {
		_fstrncpy(lpszStyle,p+1,LF_FACESIZE);
		lf.lfFaceName[ (unsigned int)(p-lptw->fontname) ] = '\0';
	}
	else
		_fstrcpy(lpszStyle,"Regular");
	cf.lpszStyle = lpszStyle;
	hdc = GetDC(lptw->hWndText);
	lf.lfHeight = -MulDiv(lptw->fontsize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
	ReleaseDC(lptw->hWndText, hdc);
	lf.lfPitchAndFamily = FIXED_PITCH;
	cf.lpLogFont = &lf;
	cf.nFontType = SCREEN_FONTTYPE;
	cf.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY | CF_INITTOLOGFONTSTRUCT | CF_USESTYLE;
	if (ChooseFont(&cf)) {
		RECT rect;
		_fstrcpy(lptw->fontname,lf.lfFaceName);
		lptw->fontsize = cf.iPointSize / 10;
		if (cf.nFontType & BOLD_FONTTYPE)
			lstrcat(lptw->fontname," Bold");
		if (cf.nFontType & ITALIC_FONTTYPE)
			lstrcat(lptw->fontname," Italic");
		TextMakeFont(lptw);
		/* force a window update */
		GetClientRect(lptw->hWndText, (LPRECT) &rect);
		SendMessage(lptw->hWndText, WM_SIZE, SIZE_RESTORED, 
			MAKELPARAM(rect.right-rect.left, rect.bottom-rect.top));
		GetClientRect(lptw->hWndText, (LPRECT) &rect);
		InvalidateRect(lptw->hWndText, (LPRECT) &rect, 1);
		UpdateWindow(lptw->hWndText);
	}
#endif
}


/* parent overlapped window */
LRESULT CALLBACK _export
WndParentProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	LPTW lptw;

	lptw = (LPTW)GetWindowLong(hwnd, 0);

	switch(message) {
		case WM_SYSCOMMAND:
			switch(LOWORD(wParam))
			{
				case M_COPY_CLIP:
				case M_PASTE:
				case M_CHOOSE_FONT:
				case M_WRITEINI:
				case M_ABOUT:
				  SendMessage(lptw->hWndText, WM_COMMAND, wParam, lParam);
			}
			break;
		case WM_SETFOCUS: 
			if (IsWindow(lptw->hWndText)) {
				SetFocus(lptw->hWndText);
				return(0);
			}
			break;
		case WM_GETMINMAXINFO:
			{
			POINT far * MMinfo = (POINT far *)lParam;
			TEXTMETRIC tm;
			hdc = GetDC(hwnd);
			SelectFont(hdc, GetStockFont(OEM_FIXED_FONT));
			GetTextMetrics(hdc,(LPTEXTMETRIC)&tm);
			ReleaseDC(hwnd,hdc);
			/* minimum size */
			MMinfo[3].x = ScreenMinSize.x*tm.tmAveCharWidth
				+ GetSystemMetrics(SM_CXVSCROLL) + 2*GetSystemMetrics(SM_CXFRAME);
			MMinfo[3].y = ScreenMinSize.y*tm.tmHeight
				+ GetSystemMetrics(SM_CYHSCROLL) + 2*GetSystemMetrics(SM_CYFRAME)
				+ GetSystemMetrics(SM_CYCAPTION);
			}
			return(0);
		case WM_SIZE:
			SetWindowPos(lptw->hWndText, (HWND)NULL, 0, lptw->ButtonHeight,
				LOWORD(lParam), HIWORD(lParam)-lptw->ButtonHeight, 
				SWP_NOZORDER | SWP_NOACTIVATE);
			return(0);
		case WM_COMMAND:
			if (IsWindow(lptw->hWndText))
				SetFocus(lptw->hWndText);
			SendMessage(lptw->hWndText, message, wParam, lParam); /* pass on menu commands */
			return(0);
		case WM_PAINT:
			{
			hdc = BeginPaint(hwnd, &ps);
			if (lptw->ButtonHeight) {
				HBRUSH hbrush;
				GetClientRect(hwnd, &rect);
				hbrush = CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));
				rect.bottom = lptw->ButtonHeight-1;
				FillRect(hdc, &rect, hbrush);
				DeleteBrush(hbrush);
				SelectPen(hdc, GetStockPen(BLACK_PEN));
				MoveTo(hdc, rect.left, lptw->ButtonHeight-1);
				LineTo(hdc, rect.right, lptw->ButtonHeight-1);
			}
			EndPaint(hwnd, &ps);
			return 0;
			}
#if WINVER >= 0x030a
		case WM_DROPFILES:
			{
			WORD version = LOWORD(GetVersion());
			if ((LOBYTE(version)*100 + HIBYTE(version)) >= 310)
				DragFunc(lptw, (HDROP)wParam);
			}
			break;
#endif
		case WM_CREATE:
			{
			RECT crect, wrect;
			TEXTMETRIC tm;
			lptw = ((CREATESTRUCT FAR *)lParam)->lpCreateParams;
			SetWindowLong(hwnd, 0, (LONG)lptw);
			lptw->hWndParent = hwnd;
			/* get character size */
			TextMakeFont(lptw);
			hdc = GetDC(hwnd);
			SelectFont(hdc, lptw->hfont);
			GetTextMetrics(hdc,(LPTEXTMETRIC)&tm);
			lptw->CharSize.y = tm.tmHeight;
			lptw->CharSize.x = tm.tmAveCharWidth;
			lptw->CharAscent = tm.tmAscent;
			ReleaseDC(hwnd,hdc);
			GetClientRect(hwnd, &crect);
			if ( (lptw->CharSize.y*lptw->ScreenSize.y < crect.bottom)
			  || (lptw->CharSize.x*lptw->ScreenSize.x < crect.right) ) {
				/* shrink size */
			    GetWindowRect(lptw->hWndParent,&wrect);
			    MoveWindow(lptw->hWndParent, wrect.left, wrect.top,
				 wrect.right-wrect.left + (lptw->CharSize.x*lptw->ScreenSize.x - crect.right),
				 wrect.bottom-wrect.top + (lptw->CharSize.y*lptw->ScreenSize.y+lptw->ButtonHeight - crect.bottom),
				 TRUE);
			}
			}
#if WINVER >= 0x030a
			{
			WORD version = LOWORD(GetVersion());
			if ((LOBYTE(version)*100 + HIBYTE(version)) >= 310)
				if ( (lptw->DragPre!=(LPSTR)NULL) && (lptw->DragPost!=(LPSTR)NULL) )
					DragAcceptFiles(hwnd, TRUE);
			}
#endif
			break;
		case WM_DESTROY:
#if WINVER >= 0x030a
			{
			WORD version = LOWORD(GetVersion());
			if ((LOBYTE(version)*100 + HIBYTE(version)) >= 310)
				DragAcceptFiles(hwnd, FALSE);
			}
#endif
			DeleteFont(lptw->hfont);
			lptw->hfont = 0;
			break;
		case WM_CLOSE:
			if (lptw->shutdown) {
				FARPROC lpShutDown = lptw->shutdown;
				(*lpShutDown)();
			}
			break;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}

/* child text window */
LRESULT CALLBACK _export
WndTextProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	int nYinc, nXinc;
	LPTW lptw;

	lptw = (LPTW)GetWindowLong(hwnd, 0);

	switch(message) {
		case WM_SETFOCUS: 
			lptw->bFocus = TRUE;
			CreateCaret(hwnd, 0, lptw->CharSize.x, 2+lptw->CaretHeight);
			SetCaretPos(lptw->CursorPos.x*lptw->CharSize.x - lptw->ScrollPos.x,
				lptw->CursorPos.y*lptw->CharSize.y + lptw->CharAscent
				 - lptw->CaretHeight - lptw->ScrollPos.y);
			if (lptw->bGetCh)
				ShowCaret(hwnd);
			break;
		case WM_KILLFOCUS: 
			DestroyCaret();
			lptw->bFocus = FALSE;
			break;
		case WM_SIZE:
			lptw->ClientSize.y = HIWORD(lParam);
			lptw->ClientSize.x = LOWORD(lParam);

			lptw->ScrollMax.y = max(0, lptw->CharSize.y*lptw->ScreenSize.y - lptw->ClientSize.y);
			lptw->ScrollPos.y = min(lptw->ScrollPos.y, lptw->ScrollMax.y);

			SetScrollRange(hwnd, SB_VERT, 0, lptw->ScrollMax.y, FALSE);
			SetScrollPos(hwnd, SB_VERT, lptw->ScrollPos.y, TRUE);

			lptw->ScrollMax.x = max(0, lptw->CharSize.x*lptw->ScreenSize.x - lptw->ClientSize.x);
			lptw->ScrollPos.x = min(lptw->ScrollPos.x, lptw->ScrollMax.x);

			SetScrollRange(hwnd, SB_HORZ, 0, lptw->ScrollMax.x, FALSE);
			SetScrollPos(hwnd, SB_HORZ, lptw->ScrollPos.x, TRUE);

			if (lptw->bFocus && lptw->bGetCh) {
				SetCaretPos(lptw->CursorPos.x*lptw->CharSize.x - lptw->ScrollPos.x,
					lptw->CursorPos.y*lptw->CharSize.y + lptw->CharAscent 
					- lptw->CaretHeight - lptw->ScrollPos.y);
				ShowCaret(hwnd);
			}
			return(0);
		case WM_VSCROLL:
			switch(LOWORD(wParam)) {
				case SB_TOP:
					nYinc = -lptw->ScrollPos.y;
					break;
				case SB_BOTTOM:
					nYinc = lptw->ScrollMax.y - lptw->ScrollPos.y;
					break;
				case SB_LINEUP:
					nYinc = -lptw->CharSize.y;
					break;
				case SB_LINEDOWN:
					nYinc = lptw->CharSize.y;
					break;
				case SB_PAGEUP:
					nYinc = min(-1,-lptw->ClientSize.y);
					break;
				case SB_PAGEDOWN:
					nYinc = max(1,lptw->ClientSize.y);
					break;
				case SB_THUMBPOSITION:
					nYinc = LOWORD(lParam) - lptw->ScrollPos.y;
					break;
				default:
					nYinc = 0;
				}
			if ( (nYinc = max(-lptw->ScrollPos.y, 
				min(nYinc, lptw->ScrollMax.y - lptw->ScrollPos.y)))
				!= 0 ) {
				lptw->ScrollPos.y += nYinc;
				ScrollWindow(hwnd,0,-nYinc,NULL,NULL);
				SetScrollPos(hwnd,SB_VERT,lptw->ScrollPos.y,TRUE);
				UpdateWindow(hwnd);
			}
			return(0);
		case WM_HSCROLL:
			switch(LOWORD(wParam)) {
				case SB_LINEUP:
					nXinc = -lptw->CharSize.x;
					break;
				case SB_LINEDOWN:
					nXinc = lptw->CharSize.x;
					break;
				case SB_PAGEUP:
					nXinc = min(-1,-lptw->ClientSize.x);
					break;
				case SB_PAGEDOWN:
					nXinc = max(1,lptw->ClientSize.x);
					break;
				case SB_THUMBPOSITION:
					nXinc = LOWORD(lParam) - lptw->ScrollPos.x;
					break;
				default:
					nXinc = 0;
				}
			if ( (nXinc = max(-lptw->ScrollPos.x, 
				min(nXinc, lptw->ScrollMax.x - lptw->ScrollPos.x)))
				!= 0 ) {
				lptw->ScrollPos.x += nXinc;
				ScrollWindow(hwnd,-nXinc,0,NULL,NULL);
				SetScrollPos(hwnd,SB_HORZ,lptw->ScrollPos.x,TRUE);
				UpdateWindow(hwnd);
			}
			return(0);
		case WM_KEYDOWN:
			if (GetKeyState(VK_SHIFT) < 0) {
			  switch(wParam) {
				case VK_HOME:
					SendMessage(hwnd, WM_VSCROLL, SB_TOP, (LPARAM)0);
					break;
				case VK_END:
					SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, (LPARAM)0);
					break;
				case VK_PRIOR:
					SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, (LPARAM)0);
					break;
				case VK_NEXT:
					SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, (LPARAM)0);
					break;
				case VK_UP:
					SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, (LPARAM)0);
					break;
				case VK_DOWN:
					SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, (LPARAM)0);
					break;
				case VK_LEFT:
					SendMessage(hwnd, WM_HSCROLL, SB_LINEUP, (LPARAM)0);
					break;
				case VK_RIGHT:
					SendMessage(hwnd, WM_HSCROLL, SB_LINEDOWN, (LPARAM)0);
					break;
			  }
			}
			else {
			  switch(wParam) {
				case VK_HOME:
				case VK_END:
				case VK_PRIOR:
				case VK_NEXT:
				case VK_UP:
				case VK_DOWN:
				case VK_LEFT:
				case VK_RIGHT:
				case VK_DELETE:
				{ /* store key in circular buffer */
				long count;
					count = lptw->KeyBufIn - lptw->KeyBufOut;
					if (count < 0) count += lptw->KeyBufSize;
					if (count < lptw->KeyBufSize-2) {
						*lptw->KeyBufIn++ = 0;
						if (lptw->KeyBufIn - lptw->KeyBuf >= lptw->KeyBufSize)
							lptw->KeyBufIn = lptw->KeyBuf;	/* wrap around */
						*lptw->KeyBufIn++ = HIWORD(lParam) & 0xff;
						if (lptw->KeyBufIn - lptw->KeyBuf >= lptw->KeyBufSize)
							lptw->KeyBufIn = lptw->KeyBuf;	/* wrap around */
					}
				}
			  }
			}
			break;
		case WM_RBUTTONDOWN:
			{
			POINT pt;
			pt.x = LOWORD(lParam);
			pt.y = HIWORD(lParam);
			ClientToScreen(hwnd,&pt);
			TrackPopupMenu(lptw->hPopMenu, TPM_LEFTALIGN, 
				pt.x, pt.y, 0, hwnd, NULL);
			}
			return(0);
		case WM_LBUTTONDOWN:
			{ /* start marking text */
			POINT pt;
			pt.x = LOWORD(lParam);
			pt.y = HIWORD(lParam);
			pt.x = (pt.x + lptw->ScrollPos.x)/lptw->CharSize.x;
			pt.y = (pt.y + lptw->ScrollPos.y)/lptw->CharSize.y;
			ClearMark(lptw, pt);
			}
			SetCapture(hwnd);	/* track the mouse */
			lptw->Marking = TRUE;
			break;
		case WM_LBUTTONUP:
			{ /* finish marking text */
			/* ensure begin mark is before end mark */
			ReleaseCapture();
			lptw->Marking = FALSE;
			if ((lptw->ScreenSize.x*lptw->MarkBegin.y + lptw->MarkBegin.x) >
			    (lptw->ScreenSize.x*lptw->MarkEnd.y   + lptw->MarkEnd.x)) {
				POINT tmp;
				tmp.x = lptw->MarkBegin.x;
				tmp.y = lptw->MarkBegin.y;
				lptw->MarkBegin.x = lptw->MarkEnd.x;
				lptw->MarkBegin.y = lptw->MarkEnd.y;
				lptw->MarkEnd.x   = tmp.x;
				lptw->MarkEnd.y   = tmp.y;
			}
			}
			break;
		case WM_MOUSEMOVE:
			if ( (wParam & MK_LBUTTON) && lptw->Marking ) {
			RECT rect;
			POINT pt;
			pt.x = LOWORD(lParam);
			pt.y = HIWORD(lParam);
			GetClientRect(hwnd, &rect);
			if (PtInRect(&rect, pt)) {
				pt.x = (pt.x + lptw->ScrollPos.x)/lptw->CharSize.x;
				pt.y = (pt.y + lptw->ScrollPos.y)/lptw->CharSize.y;
				UpdateMark(lptw, pt);
			}
			else {
			  int nXinc;
			  int nYinc;
			  do {
				nXinc = 0;
				nYinc = 0;
				if (pt.x > rect.right) {
					nXinc = lptw->CharSize.x * 4;
					pt.x = (rect.right + lptw->ScrollPos.x)/lptw->CharSize.x + 2;
				}
				else if (pt.x < rect.left) {
					nXinc = -lptw->CharSize.x * 4;
					pt.x = (rect.left + lptw->ScrollPos.x)/lptw->CharSize.x - 2;
				}
				else
					pt.x = (pt.x + lptw->ScrollPos.x)/lptw->CharSize.x;
				if (pt.y > rect.bottom) {
					nYinc = lptw->CharSize.y;
					pt.y = (rect.bottom + lptw->ScrollPos.y)/lptw->CharSize.y + 1;
				}
				else if (pt.y < rect.top) {
					nYinc = -lptw->CharSize.y;
					pt.y = (rect.top + lptw->ScrollPos.y)/lptw->CharSize.y - 1;
				}
				else
					pt.y = (pt.y + lptw->ScrollPos.y)/lptw->CharSize.y;
				LimitMark(lptw, &pt);
				nXinc = max(nXinc, -lptw->ScrollPos.x);
				nYinc = max(nYinc, -lptw->ScrollPos.y);
				nYinc = min(nYinc, lptw->ScrollMax.y - lptw->ScrollPos.y);
				nXinc = min(nXinc, lptw->ScrollMax.x - lptw->ScrollPos.x);
				if (nYinc || nXinc) {
					lptw->ScrollPos.y += nYinc;
					lptw->ScrollPos.x += nXinc;
					ScrollWindow(lptw->hWndText,-nXinc,-nYinc,NULL,NULL);
					SetScrollPos(lptw->hWndText,SB_VERT,lptw->ScrollPos.y,TRUE);
					SetScrollPos(lptw->hWndText,SB_HORZ,lptw->ScrollPos.x,TRUE);
					UpdateWindow(lptw->hWndText);
				}
				UpdateMark(lptw, pt);
				GetCursorPos(&pt);
				ScreenToClient(hwnd, &pt);
			  }
			  while( (nYinc || nXinc) && !PtInRect(&rect, pt) &&
				(GetAsyncKeyState(VK_LBUTTON) < 0) );
			}
			}
			break;
		case WM_CHAR:
			{ /* store key in circular buffer */
			long count;
				count = lptw->KeyBufIn - lptw->KeyBufOut;
				if (count < 0) count += lptw->KeyBufSize;
				if (count < lptw->KeyBufSize-1) {
					*lptw->KeyBufIn++ = wParam;
					if (lptw->KeyBufIn - lptw->KeyBuf >= lptw->KeyBufSize)
						lptw->KeyBufIn = lptw->KeyBuf;	/* wrap around */
				}
			}
			return(0);
		case WM_COMMAND:
			if (LOWORD(wParam) < NUMMENU)
				SendMacro(lptw, LOWORD(wParam));
			else
			switch(LOWORD(wParam))
			{
				case M_COPY_CLIP:
					TextCopyClip(lptw);
					return 0;
				case M_PASTE:
					{
					HGLOBAL hGMem;
					BYTE FAR *cbuf;
					TEXTMETRIC tm;
					UINT type;
					/* find out what type to get from clipboard */
					hdc = GetDC(hwnd);
					SelectFont(hdc, lptw->hfont);
					GetTextMetrics(hdc,(TEXTMETRIC FAR *)&tm);
					if (tm.tmCharSet == OEM_CHARSET)
						type = CF_OEMTEXT;
					else
						type = CF_TEXT;
					ReleaseDC(lptw->hWndText, hdc);
					/* now get it from clipboard */
					OpenClipboard(hwnd);
					hGMem = GetClipboardData(type);
					if (hGMem) {
						cbuf = (BYTE FAR *) GlobalLock(hGMem);
						while (*cbuf) {
							if (*cbuf != '\n')
								SendMessage(lptw->hWndText,WM_CHAR,*cbuf,1L);
							cbuf++;
						}
						GlobalUnlock(hGMem);
					}
					CloseClipboard();
					return 0;
					}
				case M_CHOOSE_FONT:
					TextSelectFont(lptw);
					return 0;
				case M_WRITEINI:
					WriteTextIni(lptw);
					return 0;
				case M_ABOUT:
					AboutBox(hwnd,lptw->AboutText);
					return 0;
			}
			return(0);
		case WM_PAINT:
			{
			POINT source, width, dest;
			POINT MarkBegin, MarkEnd;
			hdc = BeginPaint(hwnd, &ps);
			SelectFont(hdc, lptw->hfont);
			SetMapMode(hdc, MM_TEXT);
			SetBkMode(hdc,OPAQUE);
			GetClientRect(hwnd, &rect);
			source.x = (rect.left + lptw->ScrollPos.x) / lptw->CharSize.x;		/* source */
			source.y = (rect.top + lptw->ScrollPos.y) / lptw->CharSize.y;
			dest.x = source.x * lptw->CharSize.x - lptw->ScrollPos.x; 				/* destination */
			dest.y = source.y * lptw->CharSize.y - lptw->ScrollPos.y;
			width.x = ((rect.right  + lptw->ScrollPos.x + lptw->CharSize.x - 1) / lptw->CharSize.x) - source.x; /* width */
			width.y = ((rect.bottom + lptw->ScrollPos.y + lptw->CharSize.y - 1) / lptw->CharSize.y) - source.y;
			if (source.x < 0)
				source.x = 0;
			if (source.y < 0)
				source.y = 0;
			if (source.x+width.x > lptw->ScreenSize.x)
				width.x = lptw->ScreenSize.x - source.x;
			if (source.y+width.y > lptw->ScreenSize.y)
				width.y = lptw->ScreenSize.y - source.y;
			/* ensure begin mark is before end mark */
			if ((lptw->ScreenSize.x*lptw->MarkBegin.y + lptw->MarkBegin.x) >
			    (lptw->ScreenSize.x*lptw->MarkEnd.y   + lptw->MarkEnd.x)) {
				MarkBegin.x = lptw->MarkEnd.x;
				MarkBegin.y = lptw->MarkEnd.y;
				MarkEnd.x   = lptw->MarkBegin.x;
				MarkEnd.y   = lptw->MarkBegin.y;
			}
			else {
				MarkBegin.x = lptw->MarkBegin.x;
				MarkBegin.y = lptw->MarkBegin.y;
				MarkEnd.x   = lptw->MarkEnd.x;
				MarkEnd.y   = lptw->MarkEnd.y;
			}
			/* for each line */
			while (width.y>0) {
				if ( (source.y >= MarkBegin.y) && (source.y <= MarkEnd.y) ) {
					int start, end;
					int count, offset;
					if (source.y == MarkBegin.y)
						start = MarkBegin.x;
					else
						start = 0;
					if (source.y == MarkEnd.y)
						end = MarkEnd.x;
					else
						end = lptw->ScreenSize.x;
					/* do stuff before marked text */
					offset = 0;
					count = start - source.x;
					if (count > 0)
					  DoLine(lptw, hdc, dest.x, dest.y, 
						source.y*lptw->ScreenSize.x + source.x, count);
					/* then the marked text */
					offset += count;
					count = end - start;
					if ((count > 0) && (offset < width.x)){
					  SetTextColor(hdc, MARKFORE);
					  SetBkColor(hdc, MARKBACK);
					  TextOut(hdc, dest.x + lptw->CharSize.x*offset, dest.y, 
						(LPSTR)(lptw->ScreenBuffer + source.y*lptw->ScreenSize.x 
						+ source.x + offset), count);
					}
					/* then stuff after marked text */
					offset += count;
					count = width.x + source.x - end;
					if ((count > 0) && (offset < width.x))
					  DoLine(lptw, hdc, dest.x + lptw->CharSize.x*offset, dest.y, 
						source.y*lptw->ScreenSize.x + source.x + offset, count);
				}
				else {
					DoLine(lptw, hdc, dest.x, dest.y, 
						source.y*lptw->ScreenSize.x + source.x, width.x);
				}
				dest.y += lptw->CharSize.y;
				source.y++;
				width.y--;
			}
			EndPaint(hwnd, &ps);
			return 0;
			}
		case WM_CREATE:
			lptw = ((CREATESTRUCT FAR *)lParam)->lpCreateParams;
			SetWindowLong(hwnd, 0, (LONG)lptw);
			lptw->hWndText = hwnd;
			break;
		case WM_DESTROY:
			break;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}


/* ================================== */
/* replacement stdio routines */

/* TRUE if key hit, FALSE if no key */
int WDPROC
TextKBHit(LPTW lptw)
{
	return (lptw->KeyBufIn != lptw->KeyBufOut);
}

/* get character from keyboard, no echo */
/* need to add extended codes */
int WDPROC
TextGetCh(LPTW lptw)
{
	int ch;
	TextToCursor(lptw);
	lptw->bGetCh = TRUE;
	if (lptw->bFocus) {
		SetCaretPos(lptw->CursorPos.x*lptw->CharSize.x - lptw->ScrollPos.x,
			lptw->CursorPos.y*lptw->CharSize.y + lptw->CharAscent 
			- lptw->CaretHeight - lptw->ScrollPos.y);
		ShowCaret(lptw->hWndText);
	}
	do {
		TextMessage();
	} while (!TextKBHit(lptw));
	ch = *lptw->KeyBufOut++;
	if (ch=='\r')
		ch = '\n';
	if (lptw->KeyBufOut - lptw->KeyBuf >= lptw->KeyBufSize)
		lptw->KeyBufOut = lptw->KeyBuf;	/* wrap around */
	if (lptw->bFocus)
		HideCaret(lptw->hWndText);
	lptw->bGetCh = FALSE;
	return ch;
}

/* get character from keyboard, with echo */
int WDPROC
TextGetChE(LPTW lptw)
{
int ch;
	ch = TextGetCh(lptw);
	TextPutCh(lptw, (BYTE)ch);
	return ch;
}

LPSTR WDPROC
TextGetS(LPTW lptw, LPSTR str, unsigned int size)
{
	LPSTR next = str;
	while (--size>0) {
		switch(*next = TextGetChE(lptw)) {
			case EOF:
				*next = NULL;
				if (next == str) return (char *)NULL;
				return str;
			case '\n':
				*(next+1) = NULL;
				return str;
			case 0x08:
			case 0x7f:
				if (next > str)
					--next;
				break;
			default:
				++next;
		}
	}
	*next = NULL;
	return str;
}

int WDPROC
TextPutS(LPTW lptw, LPSTR str)
{
	TextPutStr(lptw, str);
	return str[_fstrlen(str)-1];
}

/* ================================== */
/* routines added for elvis */

void WDPROC
TextGotoXY(LPTW lptw, int x, int y)
{
	lptw->CursorPos.x = x;
	lptw->CursorPos.y = y;
}

int  WDPROC
TextWhereX(LPTW lptw)
{
	return lptw->CursorPos.x;
}

int  WDPROC
TextWhereY(LPTW lptw)
{
	return lptw->CursorPos.y;
}

void WDPROC
TextCursorHeight(LPTW lptw, int height)
{
	lptw->CaretHeight = height;
	if (lptw->bFocus)
		CreateCaret(lptw->hWndText, 0, lptw->CharSize.x, 2+lptw->CaretHeight);
}

void WDPROC
TextClearEOL(LPTW lptw)
{
HDC hdc;
int xpos, ypos;
int from, len;
POINT pt;
	pt.x = pt.y = 0;
	ClearMark(lptw, pt);
	from = lptw->CursorPos.y*lptw->ScreenSize.x + lptw->CursorPos.x;
	len = lptw->ScreenSize.x-lptw->CursorPos.x;
	_fmemset(lptw->ScreenBuffer + from, ' ', len);
	_fmemset(lptw->AttrBuffer + from, NOTEXT, len);
	xpos = lptw->CursorPos.x*lptw->CharSize.x - lptw->ScrollPos.x;
	ypos = lptw->CursorPos.y*lptw->CharSize.y - lptw->ScrollPos.y;
	hdc = GetDC(lptw->hWndText);
	SetTextColor(hdc, TextFore(lptw->Attr));
	SetBkColor(hdc, TextBack(lptw->Attr));
	SelectFont(hdc, (lptw->hfont));
	TextOut(hdc,xpos,ypos,
		(LPSTR)(lptw->ScreenBuffer + lptw->CursorPos.y*lptw->ScreenSize.x + 
		lptw->CursorPos.x), lptw->ScreenSize.x-lptw->CursorPos.x);
	(void)ReleaseDC(lptw->hWndText,hdc);
}

void WDPROC
TextClearEOS(LPTW lptw)
{
RECT rect;
int from, len;
POINT pt;
	pt.x = pt.y = 0;
	ClearMark(lptw, pt);
	from = lptw->CursorPos.y*lptw->ScreenSize.x + lptw->CursorPos.x;
	len = lptw->ScreenSize.x-lptw->CursorPos.x + 
			(lptw->ScreenSize.y-lptw->CursorPos.y-1)*lptw->ScreenSize.x;
	_fmemset(lptw->ScreenBuffer + from, ' ', len);
	_fmemset(lptw->AttrBuffer + from, NOTEXT, len);
	GetClientRect(lptw->hWndText, (LPRECT) &rect);
	InvalidateRect(lptw->hWndText, (LPRECT) &rect, 1);
	UpdateWindow(lptw->hWndText);
}

void WDPROC
TextInsertLine(LPTW lptw)
{
RECT rect;
int from, to, len;
POINT pt;
	pt.x = pt.y = 0;
	ClearMark(lptw, pt);
	from = lptw->CursorPos.y*lptw->ScreenSize.x,
	to = (lptw->CursorPos.y+1)*lptw->ScreenSize.x;
	len = (lptw->ScreenSize.y-lptw->CursorPos.y-1)*lptw->ScreenSize.x;
	_fmemmove(lptw->ScreenBuffer + to, lptw->ScreenBuffer + from, len);
	_fmemmove(lptw->AttrBuffer + to, lptw->AttrBuffer + from, len);
	_fmemset(lptw->ScreenBuffer + from, ' ', lptw->ScreenSize.x);
	_fmemset(lptw->AttrBuffer + from, NOTEXT, lptw->ScreenSize.x);
	GetClientRect(lptw->hWndText, (LPRECT) &rect);
	InvalidateRect(lptw->hWndText, (LPRECT) &rect, 1);
	UpdateWindow(lptw->hWndText);
	if (lptw->CursorFlag)
		TextToCursor(lptw);
}

void WDPROC
TextDeleteLine(LPTW lptw)
{
RECT rect;
int from, to, len;
POINT pt;
	pt.x = pt.y = 0;
	ClearMark(lptw, pt);
	to = lptw->CursorPos.y*lptw->ScreenSize.x,
	from = (lptw->CursorPos.y+1)*lptw->ScreenSize.x;
	len = (lptw->ScreenSize.y-lptw->CursorPos.y-1)*lptw->ScreenSize.x;
	_fmemmove(lptw->ScreenBuffer + to, lptw->ScreenBuffer + from, len);
	_fmemmove(lptw->AttrBuffer + to, lptw->AttrBuffer + from, len);
	from = lptw->ScreenSize.x*(lptw->ScreenSize.y -1);
	_fmemset(lptw->ScreenBuffer + from, ' ', lptw->ScreenSize.x);
	_fmemset(lptw->AttrBuffer + from, NOTEXT, lptw->ScreenSize.x);
	GetClientRect(lptw->hWndText, (LPRECT) &rect);
	InvalidateRect(lptw->hWndText, (LPRECT) &rect, 1);
	UpdateWindow(lptw->hWndText);
	if (lptw->CursorFlag)
		TextToCursor(lptw);
}

void WDPROC
TextScrollReverse(LPTW lptw)
{
RECT rect;
int len = lptw->ScreenSize.x * (lptw->ScreenSize.y - 1); 
	_fmemmove(lptw->ScreenBuffer+lptw->ScreenSize.x, lptw->ScreenBuffer, len);
	_fmemset(lptw->ScreenBuffer, ' ', lptw->ScreenSize.x);
	_fmemmove(lptw->AttrBuffer+lptw->ScreenSize.x, lptw->AttrBuffer, len);
	_fmemset(lptw->AttrBuffer, NOTEXT, lptw->ScreenSize.x);
	if (lptw->CursorPos.y)
		lptw->CursorPos.y--;
	ScrollWindow(lptw->hWndText,0,+lptw->CharSize.y,NULL,NULL);
	GetClientRect(lptw->hWndText, (LPRECT) &rect);
	rect.top = lptw->ScreenSize.y*lptw->CharSize.y;
	if (rect.top < rect.bottom)
		InvalidateRect(lptw->hWndText, (LPRECT) &rect, 1);
	lptw->MarkBegin.y++;
	lptw->MarkEnd.y++;
	LimitMark(lptw, &lptw->MarkBegin);
	LimitMark(lptw, &lptw->MarkEnd);
	UpdateWindow(lptw->hWndText);
}

void WDPROC 
TextAttr(LPTW lptw, BYTE attr)
{
	lptw->Attr = attr;
}

/* About Box */
BOOL CALLBACK _export
AboutDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
	switch (wMsg) {
		case WM_INITDIALOG:
			{
			char buf[80];
			GetWindowText(GetParent(hDlg),buf,80);
			SetDlgItemText(hDlg, AB_TEXT1, buf);
			SetDlgItemText(hDlg, AB_TEXT2, (LPSTR)lParam);
#ifdef __DLL__
			wsprintf(buf,"WGNUPLOT.DLL Version %s",(LPSTR)WGNUPLOTVERSION);
			SetDlgItemText(hDlg, AB_TEXT3, buf);
#endif
			}
			return TRUE;
		case WM_DRAWITEM:
			{
			LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
#ifdef WIN32
			DrawIcon(lpdis->hDC, 0, 0, (HICON)GetClassLong(GetParent(hDlg), GCL_HICON));
#else
			DrawIcon(lpdis->hDC, 0, 0, (HICON)GetClassWord(GetParent(hDlg), GCW_HICON));
#endif
			}
			return FALSE;
		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDCANCEL:
				case IDOK:
					EndDialog(hDlg, LOWORD(wParam));
					return TRUE;
			}
			break;
	}
	return FALSE;
}


void WDPROC
AboutBox(HWND hwnd, LPSTR str)
{
DLGPROC lpfnAboutDlgProc;
#ifdef __DLL__
	lpfnAboutDlgProc = (DLGPROC)GetProcAddress(hdllInstance, "AboutDlgProc");
#else
	lpfnAboutDlgProc = (DLGPROC)MakeProcInstance((FARPROC)AboutDlgProc, hdllInstance);
#endif
	DialogBoxParam(hdllInstance,"AboutDlgBox",hwnd,lpfnAboutDlgProc,(LPARAM)str);
	EnableWindow(hwnd,TRUE);
#ifndef __DLL__
	FreeProcInstance((FARPROC)lpfnAboutDlgProc);
#endif
}
