#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <aygshell.h>
#include <sipapi.h>
//DB?#include <newmenu.h>
#include "ceglktads.h"

#include "resource.h"
#include "glk.h"
#include "gi_dispa.h"
#include "gi_blorb.h"

#define HANDLE_WM_SETTINGCHANGE(hwnd,wParam,lParam,fn) \
	((fn)((hwnd),(UINT)(wParam)), 0L)

#define MENU_HEIGHT	26
#define CLEARTYPE_QUALITY 5

static HINSTANCE hInst;
static HWND mainWnd,cmdBar;
static SHACTIVATEINFO sainfo;
static TCHAR moveMenu[1024],actionMenu[1024];
static TCHAR *menuStr;
static void SaveRegistrySettings(void);
static void TextBufferDraw(HDC dc,winid_t wnd,int w,int h);
static void	_glk_close_all_streams(void);
static void	_glk_close_all_filerefs(void);
static void TextBufferOnPaint(HWND hwnd);
static int firstTime;
static int suppressBlit;

/* glk stuff */

#define	reqev_Line		1
#define	reqev_Char		2
#define reqev_Mouse		4

struct glk_window_struct
{
	HWND hwnd;				// physical window handle, NULL for pair windows
	glui32 type;			// Glk wintype
	glui32 rock;			// user specified rock, 0 for pair windows
	gidispatch_rock_t drock;// dispatch rock
	strid_t str;			// window stream
	strid_t echo;			// echo stream
	winid_t parent;			// parent window
	winid_t	child;			// first child
	winid_t constr;			// second child
	glui32 constrtype;		// constraint window type - original type of new window
	glui32 method,size;		// constraints for 2nd child
	glui32 x,y,w,h;			// dimensions, relative to mainWnd
	HFONT styleFont[style_NUMSTYLES];
	COLORREF styleFG[style_NUMSTYLES];
	COLORREF styleBG[style_NUMSTYLES];
	HFONT currFont;
	void *data;
	glui32 dataSize,dataCount;
	glui32 reqEvent;	// requested event
	char *line;			// input line event buffer
	glui32 maxLine;		// size of input line event buffer
	glui32 lastSeg;		// cursor position at last char/line request
	glui32 lastChar;
	glui32 printSeg;	// last printed line
	glui32 printChar;
	int caretx,carety;	// caret position
	BOOL needsUpdate;
	BOOL needsMore;
};

typedef struct
{
	glui32 style;
	char *text;
	glui32 textSize,textCount;
} TextBuffData;

typedef struct
{
	glui32 style;
	int x,y;
	char *text;
	glui32 textSize,textCount;
} TextGridData;

#define	GRTYPE_RECT		0x1000
#define	GRTYPE_PICT		0x2000
typedef struct
{
	glui32 grtype;
	glui32 x,y,w,h,origw,origh;
	glui32 data;
} GraphicsData;

#define MAX_WINDOWS			256
static winid_t windowList[MAX_WINDOWS], rootWnd;
static int windowCount;

static void (*_interrupt_handler)(void)=NULL;
static gidispatch_rock_t (*_vm_reg_object)(void *obj, glui32 objclass)=NULL;
static void (*_vm_unreg_object)(void *obj, glui32 objclass, gidispatch_rock_t objrock)=NULL;
static strid_t blorbfile = NULL;
static giblorb_map_t *blorbmap = NULL;

#define MAX_HISTORY			100
static char *inputHistory[MAX_HISTORY];
static int inputCurr,inputSize;
static BOOL imagesEnabled;
static int graphicScale;

//DB
char *xcegetenv(const char*name)
{
	return(NULL);
}

/* Startup & exit */

void glk_exit(void)
{
	MSG msg;

	if(_interrupt_handler)
		_interrupt_handler();
	DestroyWindow(mainWnd);
	while(GetMessage(&msg,NULL,0,0))		// wait for WM_QUIT to come back
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	if(rootWnd)
		glk_window_close(rootWnd,NULL);
	_glk_close_all_streams();
	_glk_close_all_filerefs();
	SaveRegistrySettings();
	exit(0);
}

void glk_set_interrupt_handler(void (*func)(void))	// TODO
{
	_interrupt_handler=func;
}

/* Ticks & Events */

static event_t *eventQueue;
static int eventQueueSize,eventQueueCount;
static UINT timer;

static void _post_glk_event(glui32 type,winid_t win,glui32 val1,glui32 val2)
{
	while(eventQueueCount>=eventQueueSize)
	{
		event_t *temp;
		temp=(event_t *)realloc(eventQueue,sizeof(event_t)*(eventQueueSize+1));
		if(!temp)
			return;
		eventQueue=temp;
		eventQueueSize++;
	}
	eventQueue[eventQueueCount].type=type;
	eventQueue[eventQueueCount].win=win;
	eventQueue[eventQueueCount].val1=val1;
	eventQueue[eventQueueCount++].val2=val2;
	if(type==evtype_CharInput)
		glk_cancel_char_event(win);
}

void glk_tick()
{
	MSG msg;
	register int i;

	if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	
}

void glk_select(event_t *event)
{
	MSG msg;
	register int i;

	for(i=0;i<windowCount;++i)
		if(windowList[i]->needsUpdate)
		{
			windowList[i]->needsUpdate=FALSE;
			InvalidateRect(windowList[i]->hwnd,NULL,FALSE);
			UpdateWindow(windowList[i]->hwnd);
		}
	while(!eventQueueCount)
	{
		if(GetMessage(&msg,NULL,0,0))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			if(rootWnd)
				glk_window_close(rootWnd,NULL);
			_glk_close_all_streams();
			_glk_close_all_filerefs();
			SaveRegistrySettings();
			exit(0);
		}
	}
	*event=eventQueue[0];
	for(i=1;i<eventQueueCount;++i)
		eventQueue[i-1]=eventQueue[i];
	eventQueueCount--;
}

void glk_select_poll(event_t *event)
{
	register int i,j;

	glk_tick();							// do the tick thing
	for(i=0;i<eventQueueCount;++i)
		if(eventQueue[i].type==evtype_Timer || eventQueue[i].type==evtype_Arrange
			|| eventQueue[i].type==evtype_SoundNotify)
		{
			*event=eventQueue[i];
			for(j=i+1;j<eventQueueCount;++j)
				eventQueue[j-1]=eventQueue[j];
			eventQueueCount--;
			return;
		}
	event->type=evtype_None;
}

void glk_request_char_event(winid_t win)
{
	if((win->type==wintype_TextBuffer || win->type==wintype_TextGrid)
		&& !(win->reqEvent & reqev_Line))
	{
		win->reqEvent|=reqev_Char;
		SetFocus(win->hwnd);
		if(win->type==wintype_TextBuffer)
		{
			TextBuffData *tbd;
			if(win->dataCount)
			{
				win->lastSeg=win->dataCount-1;
				tbd=&((TextBuffData *)win->data)[win->dataCount-1];
				win->lastChar=tbd->textCount;
			}
			else
				win->lastSeg=win->lastChar=0;
		}
	}
}

void glk_cancel_char_event(winid_t win)
{
	win->reqEvent&=~reqev_Char;
}

void glk_request_line_event(winid_t win, char *buf, glui32 maxlen, glui32 initlen)
{
	TextBuffData *tbd;
    SIPINFO si;

	if(win->type!=wintype_TextBuffer || (win->reqEvent&reqev_Line)
		|| (win->reqEvent&reqev_Char))
		return;
	/* 
	 * unless the programmer just printed something with style_Input
	 * the following call should create a new data entry. If (s)he did, then
	 * the text printed with style_Input would be part of the input.
	 */
	glk_set_style_stream(win->str,style_Input);
	win->reqEvent|=reqev_Line;
	win->line=buf;
	win->maxLine=maxlen;
	inputCurr=inputSize;
	win->lastSeg=win->dataCount-1;			// at least 0 since we have a style_Input entry
	tbd=&((TextBuffData *)win->data)[win->dataCount-1];
	win->lastChar=tbd->textCount;
	if(initlen)
	{
		if(tbd->textCount+initlen>=tbd->textSize)
		{
			char *temp;
			temp=(char *)realloc(tbd->text,sizeof(char)*(tbd->textCount+initlen+1));
			if(!temp)
				return;
			tbd->text=temp;
			tbd->textSize=tbd->textCount+initlen+1;
		}
		memcpy(&tbd->text[tbd->textCount],buf,initlen);
		tbd->textCount+=initlen;
	}
	if(GetFocus()!=win->hwnd)
		SetFocus(win->hwnd);
	else
	{
		CreateCaret(win->hwnd,NULL,0,8);
		ShowCaret(win->hwnd);
		if(win->needsMore)
		{
			SetCaretPos(-10,-10);
			InvalidateRect(win->hwnd,NULL,FALSE);
			//DB UpdateWindow(win->hwnd);
		}
		else
			SetCaretPos(win->caretx,win->carety);
	}

	//DB
	memset( &si, 0, sizeof( si ) );
	si.cbSize = sizeof( si );
	if( SHSipInfo( SPI_GETSIPINFO, 0, &si, 0 ) )
	{
		si.fdwFlags &= ~SIPF_ON;
		SHSipInfo( SPI_SETSIPINFO, 0, &si, 0 );
	}
	//DB

}

void glk_cancel_line_event(winid_t win, event_t *event)
{
	TextBuffData *tbd;
	glui32 nochars;

	if(!(win->reqEvent & reqev_Line))
	{
		if(event)
			event->type=evtype_None;
		return;
	}
	win->reqEvent&=~reqev_Line;
	tbd=&((TextBuffData *)win->data)[win->dataCount-1];
	nochars=tbd->textCount<=win->maxLine?tbd->textCount:win->maxLine;
	if(tbd->textCount)
	{
		memcpy(win->line,tbd->text,nochars);
		// add line to history
		inputHistory[inputSize]=malloc(nochars+1);
		if(inputHistory[inputSize])
		{
			memcpy(inputHistory[inputSize],tbd->text,nochars);
			inputHistory[inputSize++][nochars]='\0';
		}
		if(inputSize>=MAX_HISTORY)
		{
			register int i;
			if(inputHistory[0])
				free(inputHistory[0]);
			for(i=1;i<inputSize;++i)
				inputHistory[i-1]=inputHistory[i];
			inputSize--;
		}
	}
	if(GetFocus()==win->hwnd)
	{
		// HideCaret(win->hwnd);
		DestroyCaret();
	}
	if(event)
	{
		event->type=evtype_LineInput;
		event->win=win;
		event->val1=nochars;
		event->val2=0;
	}
}

void glk_request_mouse_event(winid_t win)
{
	if(win->type==wintype_TextGrid || win->type==wintype_Graphics)
		win->reqEvent|=reqev_Mouse;
}

void glk_cancel_mouse_event(winid_t win)
{
	win->reqEvent&=~reqev_Mouse;
}

static void CALLBACK _timer_timeout(HWND hwnd,UINT msg,UINT idEvent,DWORD dwTime)
{
	_post_glk_event(evtype_Timer,NULL,0,0);

}

void glk_request_timer_events(glui32 millisecs)
{
	if(timer)
		KillTimer(NULL,timer);
	if(millisecs)
		timer=SetTimer(NULL,0,millisecs,(TIMERPROC)_timer_timeout);
}

/* Character conversion */

unsigned char glk_char_to_lower(unsigned char ch)
{
	if((ch>=0x41 && ch<=0x5a) || (ch>=0xc0 && ch<=0xde))
		return(ch+0x20);
	return(ch);
}

unsigned char glk_char_to_upper(unsigned char ch)
{
	if((ch>=0x61 && ch<=0x7a) || (ch>=0xe0 && ch<=0xfe))
		return(ch-0x20);
	return(ch);
}


/* Gestalt */

glui32 glk_gestalt(glui32 sel, glui32 val)
{
	return(glk_gestalt_ext(sel,val,NULL,0));
}

glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, glui32 arrlen)
{
	switch(sel)
	{
	case gestalt_Version:
		return(0x00000601);				// Glk spec 0.6.1
	case gestalt_CharOutput:
		if(((val&0x7f)<0x20 && val!=0x0a) || val>0xff)
		{
			if(arrlen)
				arr[0]=0;
			return(gestalt_CharOutput_CannotPrint);
		}
		if(arrlen)
			arr[0]=1;
		return(gestalt_CharOutput_ExactPrint);
	case gestalt_LineInput:
		if((val&0x7f)<0x20 || val>0xff)
			return(FALSE);
		return(TRUE);
	case gestalt_CharInput:
		if(val==keycode_Tab || val>=keycode_Delete)
			return(TRUE);
		if((val==0x0a || (val&0x7f)>=0x20) && val<=0xff)
			return(TRUE);
		return(FALSE);
	case gestalt_MouseInput:
		return(val==wintype_TextGrid || val==wintype_Graphics);
	case gestalt_Timer:
	case gestalt_Graphics:
		return(TRUE);
	case gestalt_DrawImage:
		if(imagesEnabled)
			return(val==wintype_Graphics);
		return(FALSE);
	case gestalt_GraphicsTransparency:
	case gestalt_Sound:
	case gestalt_SoundMusic:
	case gestalt_SoundVolume:
	case gestalt_SoundNotify:
	case gestalt_Hyperlinks:
	case gestalt_HyperlinkInput:
		return(FALSE);					// for now
	}
	return(FALSE);						// unknown selector
}


/* File references */

struct glk_fileref_struct {
	TCHAR name[256];
	glui32 mode;
	glui32 usage;
	glui32 rock;
	gidispatch_rock_t drock;
};

#define MAX_FILEREFS	256
frefid_t fileRef[MAX_FILEREFS];
int fileRefCount;

static void	_glk_close_all_filerefs(void)
{
	register int i;
	for(i=0;i<fileRefCount;++i)
		free(fileRef[i]);
	fileRefCount=0;
}

static frefid_t _new_fref(void)
{
	frefid_t newFref;

	if(fileRefCount>=MAX_FILEREFS)
		return(NULL);
	newFref=calloc(1,sizeof(struct glk_fileref_struct));
	if(!newFref)
		return(NULL);
	fileRef[fileRefCount++]=newFref;
	return(newFref);
}

frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock)
{
	// TODO
	// There is no API call for creating temp files on WinCE
	// Makes sense considering "files" are stored in memory 
	return(NULL);
}

frefid_t glk_fileref_create_by_prompt(glui32 usage,glui32 fmode,glui32 rock)
{
	OPENFILENAME ofn;
	frefid_t newFref;
	BOOL result;

	newFref=_new_fref();
	if(!newFref)
		return(NULL);
	memset(&ofn,0,sizeof(ofn));
	ofn.lStructSize=sizeof(ofn);
	switch(usage & fileusage_TypeMask)
	{
	case fileusage_SavedGame:
		ofn.lpstrFilter=L"Saved Games\0*.sav\0All Files\0*.*\0";
		ofn.lpstrDefExt=L"sav";
		break;
	case fileusage_Transcript:
		ofn.lpstrFilter=L"Transcripts\0*.str\0All Files\0*.*\0";
		ofn.lpstrDefExt=L"scr";
		break;
	case fileusage_InputRecord:
		ofn.lpstrFilter=L"Records\0*.rec\0All Files\0*.*\0";
		ofn.lpstrDefExt=L"rec";
		break;
	default:
		ofn.lpstrFilter=L"All Files\0*.*\0";
	}
	ofn.lpstrFile=newFref->name; 
	ofn.nMaxFile=256;
	if(fmode==filemode_Read)
		ofn.Flags|=OFN_FILEMUSTEXIST;
	else
		ofn.Flags|=OFN_OVERWRITEPROMPT;
	ofn.Flags|=OFN_PATHMUSTEXIST;
	if(fmode==filemode_Read)
		result=GetOpenFileName(&ofn);
	else
		result=GetSaveFileName(&ofn);
	if(result)
	{
		newFref->rock=rock;
		newFref->usage=usage;
		newFref->mode=fmode;
		if(_vm_reg_object)
			newFref->drock=_vm_reg_object(newFref,gidisp_Class_Fileref);
		return(newFref);
	}
	fileRefCount--;
	free(newFref);
	return(NULL);
}

frefid_t glk_fileref_create_by_name(glui32 usage, char *name, glui32 rock)
{
	frefid_t newFref;
	int len;

	newFref=_new_fref();
	if(!newFref)
		return(NULL);
	len=mbstowcs(newFref->name,name,strlen(name));
	if(len>0)
		newFref->name[len]=L'\0';
	else
		newFref->name[0]=L'\0';
	newFref->usage=usage;
	newFref->rock=rock;
	if(_vm_reg_object)
		newFref->drock=_vm_reg_object(newFref,gidisp_Class_Fileref);
	return(newFref);
}

frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, glui32 rock)
{
	frefid_t newFref;

	newFref=_new_fref();
	if(!newFref)
		return(NULL);
	*newFref=*fref;
	newFref->rock=rock;
	newFref->usage=usage;
	if(_vm_reg_object)
		newFref->drock=_vm_reg_object(newFref,gidisp_Class_Fileref);
	return(newFref);
}

void glk_fileref_destroy(frefid_t fref)
{
	register int i,j;
	for(i=0;i<fileRefCount;++i)
		if(fileRef[i]==fref)
		{
			for(j=i+1;j<fileRefCount;++j)
				fileRef[j-1]=fileRef[j];
			fileRefCount--;
		}
	if(_vm_unreg_object)
		_vm_unreg_object(fref,gidisp_Class_Fileref,fref->drock);
	free(fref);
}

frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr)
{
	register int i;
	if(!fref)
	{
		if(fileRefCount)
		{
			if(rockptr)
				*rockptr=fileRef[0]->rock;
			return(fileRef[0]);
		}
		return(NULL);
	}
	for(i=0;i<fileRefCount;++i)
		if(fileRef[i]==fref && i<fileRefCount-1)
		{
			if(rockptr)
				*rockptr=fileRef[i+1]->rock;
			return(fileRef[i+1]);
		}
	return(NULL);
}

glui32 glk_fileref_get_rock(frefid_t fref)
{
	return(fref->rock);
}

void glk_fileref_delete_file(frefid_t fref)
{
	DeleteFile(fref->name);
}


glui32 glk_fileref_does_file_exist(frefid_t fref)
{
	FILE *f;
	f=_tfopen(fref->name,L"r");
	if(!f)
		return(FALSE);
	fclose(f);
	return(TRUE);
}

/* Styles */

struct glk_style_struct
{
	LOGFONT lf;
	COLORREF fg,bg;
};

struct glk_style_struct globalStyle[style_NUMSTYLES][2];

void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, glsi32 val)
{
	// no style hints for now TODO
}

void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint)
{
	// no style hints for now TODO
}

glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2)
{
	return(0);	// TODO
}

glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint, glui32 *result)
{
	return(0);  // TODO
}

/* Streams */

#define	STR_WINDOW		1
#define STR_MEMORY		2
#define STR_FILE		3

struct glk_stream_struct
{
	glui32 type;
	glui32 mode;
	FILE *file;
	char *buffer;
	glui32 bufsize;
	glui32 bufpos;
	winid_t win;
	glui32 rock;
	gidispatch_rock_t drock;
	glui32 rcount,wcount;
};

#define MAX_STREAMS		256
static strid_t currStr, strList[MAX_STREAMS];
int strCount;

static void	_glk_close_all_streams(void)
{
	register int i;
	for(i=0;i<strCount;++i)
	{
		if(strList[i]->type==STR_FILE)
			fclose(strList[i]->file);
		free(strList[i]);
	}
	strCount=0;
}

static strid_t _new_stream(void)
{
	strid_t newStr;

	if(strCount>=MAX_STREAMS)
		return(NULL);
	newStr=(strid_t)calloc(1,sizeof(struct glk_stream_struct));
	if(!newStr)
		return(NULL);
	strList[strCount++]=newStr;
	return(newStr);
}

void glk_stream_set_current(strid_t str)
{
	currStr=str;
}

strid_t glk_stream_get_current(void)
{
	return(currStr);
}

void glk_put_char(unsigned char ch)
{
	glk_put_char_stream(currStr,ch);
}

void glk_put_string(char *s)
{
	glk_put_string_stream(currStr,s);
}

void glk_put_buffer(char *buf,glui32 len)
{
	glk_put_buffer_stream(currStr,buf,len);
}

void glk_put_char_stream(strid_t str, unsigned char ch)
{
	if(!str)
		return;
	if(str->mode==filemode_Read)
		return;
	switch(str->type)
	{
	case STR_WINDOW:
		if(str->win->reqEvent & reqev_Line)				// line input pending?
			return;										
		if(str->win->echo)								// echo stream
			glk_put_char_stream(str->win->echo,ch);		// echo to stream
		if(str->win->type==wintype_TextBuffer)
		{
			TextBuffData *data;
			if(!str->win->dataSize)
			{
				str->win->data=calloc(1,sizeof(TextBuffData));
				if(!str->win->data)
					return;
				str->win->dataSize=str->win->dataCount=1;
			}
			data=&((TextBuffData *)str->win->data)[str->win->dataCount-1];
			if(data->textCount>=data->textSize)
			{
				char *temp;
				temp=realloc(data->text,(16+data->textSize)*sizeof(char));
				if(!temp)
					return;
				data->text=temp;
				data->textSize+=16;
			}
			data->text[data->textCount++]=ch;
			str->win->needsUpdate=TRUE;
		}
		else if(str->win->type==wintype_TextGrid)
		{
			TextGridData *data;
			if(!str->win->dataSize)
			{
				str->win->data=calloc(1,sizeof(TextGridData));
				if(!str->win->data)
					return;
				str->win->dataSize=str->win->dataCount=1;
			}
			data=&((TextGridData *)str->win->data)[str->win->dataCount-1];
			while(data->textCount>=data->textSize)
			{
				char *temp;
				temp=realloc(data->text,(16+data->textSize)*sizeof(char));
				if(!temp)
					return;
				data->text=temp;
				data->textSize+=16;
			}
			data->text[data->textCount++]=ch;
			str->win->needsUpdate=TRUE;
		}
		break;
	case STR_MEMORY:
		if(str->bufpos<str->bufsize)
			str->buffer[str->bufpos++]=ch;
		break;
	case STR_FILE:
		fputc(ch,str->file);
		break;
	}
	str->wcount++;
}

void glk_put_string_stream(strid_t str, char *s)
{
	glk_put_buffer_stream(str,s,strlen(s));
}

void glk_put_buffer_stream(strid_t str, char *buf, glui32 len)
{
	int nch;

	if(!str)
		return;
	if(str->mode==filemode_Read)
		return;
	switch(str->type)
	{
	case STR_WINDOW:
		if(str->win->reqEvent & reqev_Line)				// line input pending?
			return;										
		if(str->win->echo)									// echo stream
			glk_put_buffer_stream(str->win->echo,buf,len);	// echo to stream
		if(str->win->type==wintype_TextBuffer)
		{
			TextBuffData *data;
			if(!str->win->dataSize)
			{
				str->win->data=calloc(1,sizeof(TextBuffData));
				if(!str->win->data)
					return;
				str->win->dataSize=str->win->dataCount=1;
			}
			data=&((TextBuffData *)str->win->data)[str->win->dataCount-1];
			if(data->textCount+len>=data->textSize)
			{
				char *temp;
				temp=realloc(data->text,(data->textCount+len)*sizeof(char));
				if(!temp)
					return;
				data->text=temp;
				data->textSize=data->textCount+len;
			}
			memcpy(&data->text[data->textCount],buf,len);
			data->textCount+=len;
			str->win->needsUpdate=TRUE;
		}
		else if(str->win->type==wintype_TextGrid)
		{
			TextGridData *data;
			if(!str->win->dataSize)
			{
				str->win->data=calloc(1,sizeof(TextGridData));
				if(!str->win->data)
					return;
				str->win->dataSize=str->win->dataCount=1;
			}
			data=&((TextGridData *)str->win->data)[str->win->dataCount-1];
			if(data->textCount+len>=data->textSize)
			{
				char *temp;
				temp=realloc(data->text,(data->textCount+len)*sizeof(char));
				if(!temp)
					return;
				data->text=temp;
				data->textSize=data->textCount+len;
			}
			memcpy(&data->text[data->textCount],buf,len);
			data->textCount+=len;
			str->win->needsUpdate=TRUE;
		}
		break;
	case STR_MEMORY:
		nch=len;
		if(str->bufpos+nch>=str->bufsize)
			nch=str->bufsize-str->bufpos;
		memcpy(&str->buffer[str->bufpos],buf,nch);
		str->bufpos+=nch;
		break;
	case STR_FILE:
		fwrite(buf,1,len,str->file);
		break;
	}
	str->wcount+=len;
}

glsi32 glk_get_char_stream(strid_t str)
{
/*	TCHAR wch[2];
	char ch[2]; */
	int c;

	if(str->mode!=filemode_Read && str->mode!=filemode_ReadWrite)
		return(-1);
	switch(str->type)
	{
	case STR_MEMORY:
		if(str->bufpos<=str->bufsize)
		{
			str->rcount++;
			return((glsi32)((glui32)(unsigned char)str->buffer[str->bufpos++]));
		}
		break;
	case STR_FILE:
		c=fgetc(str->file);
		if(c==EOF)
			return(-1);
		str->rcount++;
		return((glsi32)(glui32)(unsigned char)c);
	}
	return(-1);
}

glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len)
{
	glui32 count;
	glsi32 ch;

	for(count=0;count<len;++count)
	{
		ch=glk_get_char_stream(str);
		if(ch==-1)
			break;
		buf[count]=(char)(unsigned char)(glui32)ch;
	}
	return(count);
}

glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len)
{
	glui32 count;
	glsi32 ch;

	for(count=0;count<len-1;++count)
	{
		ch=glk_get_char_stream(str);
		if(ch==-1 || ch==0)
			break;
		buf[count]=(char)(unsigned char)(glui32)ch;
		if(buf[count]=='\n')
		{
			count++;
			break;
		}
	}
	buf[count]='\0';
	return(count);
}

void glk_stream_close(strid_t str, stream_result_t *result)
{
	register int i,j;

	if(str->type!=STR_MEMORY && str->type!=STR_FILE)
		return;
	if(str->type==STR_FILE)
		fclose(str->file);
	for(i=0;i<strCount;++i)
		if(strList[i]==str)
		{
			for(j=i+1;j<strCount;++j)
				strList[j-1]=strList[j];
			break;
		}
	if(result)
	{
		result->readcount=str->rcount;
		result->writecount=str->wcount;
	}
	if(_vm_unreg_object)
		_vm_unreg_object(str,gidisp_Class_Stream,str->drock);
	free(str);
}

glui32 glk_stream_get_position(strid_t str)
{
	long pos;

	switch(str->type)
	{
	case STR_MEMORY:
		return(str->bufpos);
	case STR_FILE:
		pos=ftell(str->file);
		return((glui32)pos);
	}
	return(0);
}

void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode)
{
	long newpos;

	switch(str->type)
	{
	case STR_MEMORY:
		switch(seekmode)
		{
		case seekmode_End:
			newpos=(long)str->bufsize+pos;
			break;
		case seekmode_Current:
			newpos=(long)str->bufpos+pos;
			break;
		default:
			newpos=pos;
		}
		if(newpos<0)
			pos=0;
		if((glui32)newpos>str->bufsize)
			newpos=(long)str->bufsize;
		str->bufpos=(glui32)newpos;
		break;
	case STR_FILE:
		switch(seekmode)
		{
		case seekmode_End:
			fseek(str->file,pos,SEEK_END);
			break;
		case seekmode_Current:
			fseek(str->file,pos,SEEK_CUR);
			break;
		default:
			fseek(str->file,pos,SEEK_SET);
		}
	}
}

void glk_set_style(glui32 val)
{
	glk_set_style_stream(currStr,val);
}

void glk_set_style_stream(strid_t str, glui32 val)
{
	if(str->type!=STR_WINDOW)					// ignore style for non-window streams
		return;
	if(val>=style_NUMSTYLES)					// invalid style
		return;
	if(str->win->type==wintype_TextBuffer)
	{
		TextBuffData *tbd;

		if(str->win->dataSize)
		{
			tbd=(TextBuffData *)str->win->data;
			if(tbd[str->win->dataCount-1].style==val)
				return;							// same style, nothing to set
			if(!tbd[str->win->dataCount-1].textCount)
			{									// no text for old style, replace
				tbd[str->win->dataCount-1].style=val;
				return;
			}
		}
		// create new entry
		while(str->win->dataSize<=str->win->dataCount)
		{
			void *temp;
			temp=realloc(str->win->data,sizeof(TextBuffData)*(str->win->dataSize+1));
			if(!temp)							// not enough memory
				return;
			tbd=(TextBuffData *)str->win->data=temp;
			memset(&tbd[str->win->dataSize],0,sizeof(TextBuffData));
			str->win->dataSize++;
		}
		tbd=(TextBuffData *)str->win->data;
		tbd[str->win->dataCount++].style=val;
	}
	else if(str->win->type==wintype_TextGrid)
	{
		TextGridData *tgd;

		if(str->win->dataSize)
		{
			tgd=(TextGridData *)str->win->data;
			if(tgd[str->win->dataCount-1].style==val)
				return;							// same style, nothing to set
			if(!tgd[str->win->dataCount-1].textCount)
			{									// no text for old style, replace
				tgd[str->win->dataCount-1].style=val;
				return;
			}
		}
		// create new entry
		while(str->win->dataSize<=str->win->dataCount)
		{
			void *temp;
			temp=realloc(str->win->data,sizeof(TextGridData)*(str->win->dataSize+1));
			if(!temp)							// not enough memory
				return;
			tgd=(TextGridData *)str->win->data=temp;
			memset(&tgd[str->win->dataSize],0,sizeof(TextGridData));
			str->win->dataSize++;
		}
		tgd=(TextGridData *)str->win->data;
		tgd[str->win->dataCount].x=-1;
		tgd[str->win->dataCount].y=-1;
		tgd[str->win->dataCount++].style=val;
	}
}

strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, glui32 rock)
{
	strid_t newStr;

	newStr=_new_stream();
	if(!newStr)
		return(NULL);
	newStr->buffer=buf;
	newStr->type=STR_MEMORY;
	if(buf)
		newStr->bufsize=buflen;
	newStr->mode=fmode;
	newStr->rock=rock;
	if(_vm_reg_object)
		newStr->drock=_vm_reg_object(newStr,gidisp_Class_Stream);
	return(newStr);
}

strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode, glui32 rock)
{
	strid_t newStr;
	
	if(fmode && fileref->mode && fmode != fileref->mode)
		fmode = fileref->mode;
		//return(NULL); // this breaks save and restore
	newStr=_new_stream();
	if(!newStr)
		return(NULL);
	switch(fmode?fmode:fileref->mode)
	{
	case filemode_Write:
		//if(fileref->usage & fileusage_TextMode)
		//	newStr->file=_tfopen(fileref->name,L"wt");
		//else
			newStr->file=_tfopen(fileref->name,L"wb");
		break;
	case filemode_Read:
		//if(fileref->usage & fileusage_TextMode)
		//	newStr->file=_tfopen(fileref->name,L"rt");
		//else
			newStr->file=_tfopen(fileref->name,L"rb");
		break;
	case filemode_ReadWrite:
		//if(fileref->usage & fileusage_TextMode)
		//	newStr->file=_tfopen(fileref->name,L"r+t");
		//else
			newStr->file=_tfopen(fileref->name,L"r+b");
		if(!newStr->file)
			//if(fileref->usage & fileusage_TextMode)
			//	newStr->file=_tfopen(fileref->name,L"w+t");
			//else
				newStr->file=_tfopen(fileref->name,L"w+b");
		break;
	case filemode_WriteAppend:
		if(fileref->usage & fileusage_TextMode)
			newStr->file=_tfopen(fileref->name,L"a+t");
		else
			newStr->file=_tfopen(fileref->name,L"a+b");
		break;
	}
	if(!newStr->file)
	{
		free(newStr);
		strCount--;
		return(NULL);
	}
	newStr->type=STR_FILE;
	newStr->rock=rock;
	newStr->mode=fmode;
	if(_vm_reg_object)
		newStr->drock=_vm_reg_object(newStr,gidisp_Class_Stream);
	return(newStr);
}

strid_t glk_stream_iterate(strid_t str, glui32 *rockptr)
{
	register int i;
	if(!str)
		if(!strCount)
			return(NULL);
		else
		{
			if(rockptr)
				*rockptr=strList[0]->rock;
			return(strList[0]);
		}
	for(i=0;i<strCount;++i)
		if(strList[i]==str && i<strCount-1)
		{
			if(rockptr)
				*rockptr=strList[i+1]->rock;
			return(strList[i+1]);
		}
	return(NULL);
}

glui32 glk_stream_get_rock(strid_t str)
{
	return(str->rock);
}

/* Windows */

static void _glk_init_data(void)
{
	windowCount=0;
	fileRefCount=0;
	rootWnd=NULL;
	eventQueue=NULL;
	eventQueueSize=eventQueueSize=0;
	timer=0;
	menuStr=NULL;
	inputSize=0;
	inputCurr=0;
}

static LPTSTR _glk_window_class[]={
	L"ALL",
	L"PAIR",
	L"BLANK",
	L"TEXTBUFFER",
	L"TEXTGRID",
	L"GRAPHICS",
};

static winid_t _new_window_struct(glui32 wintype)
{
	register int i;
	winid_t newWin;

	if(windowCount>=MAX_WINDOWS)
		return(NULL);
	newWin=(winid_t)calloc(1,sizeof(struct glk_window_struct));
	newWin->type=wintype;
	if(wintype==wintype_TextBuffer || wintype==wintype_TextGrid)
	{
		for(i=0;i<style_NUMSTYLES;++i)
		{
			newWin->styleFont[i]=
				CreateFontIndirect(&globalStyle[i][wintype-wintype_TextBuffer].lf);
			newWin->styleFG[i]=globalStyle[i][wintype-wintype_TextBuffer].fg;
			newWin->styleBG[i]=globalStyle[i][wintype-wintype_TextBuffer].bg;
			newWin->currFont=newWin->styleFont[0];
		}
	}
	windowList[windowCount++]=newWin;
	return(newWin);
}

static HDC sizeHDC;

static void _resize_sub_windows(winid_t wnd)
{
	HFONT sizeFont;
	HGDIOBJ oldFont;
	SIZE size;

	if(!wnd)
		return;
	if(!wnd->child)
	{
		if(wnd->hwnd)
			MoveWindow(wnd->hwnd,wnd->x,wnd->y,wnd->w,wnd->h,TRUE);
		return;
	}
	switch(wnd->method & winmethod_DirMask)
	{
		case winmethod_Above:
			if((wnd->method & winmethod_DivisionMask) == winmethod_Proportional)
				wnd->constr->h=wnd->size*wnd->h/100;
			else
			{
				switch(wnd->constrtype)
				{
				case wintype_TextBuffer:
					sizeFont=CreateFontIndirect(&globalStyle[0][0].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->h=size.cy*wnd->size+2*GetSystemMetrics(SM_CYEDGE);
					break;
				case wintype_TextGrid:
					sizeFont=CreateFontIndirect(&globalStyle[0][1].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->h=size.cy*wnd->size+2*GetSystemMetrics(SM_CYEDGE);
					break;
				default:									// Graphics window
					wnd->constr->h=wnd->size/graphicScale+2*GetSystemMetrics(SM_CYEDGE);
				}
				if(wnd->constr->h > wnd->h)
					wnd->constr->h=wnd->h;
			}
			wnd->constr->w=wnd->w;
			wnd->constr->x=wnd->x;
			wnd->constr->y=wnd->y;
			wnd->child->x=wnd->x;
			wnd->child->w=wnd->w;
			wnd->child->y=wnd->y+wnd->constr->h;
			wnd->child->h=wnd->h-wnd->constr->h;
			break;
		case winmethod_Below:
			if((wnd->method & winmethod_DivisionMask) == winmethod_Proportional)
				wnd->constr->h=wnd->size*wnd->h/100;
			else
			{
				switch(wnd->constrtype)
				{
				case wintype_TextBuffer:
					sizeFont=CreateFontIndirect(&globalStyle[0][0].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->h=size.cy*wnd->size+2*GetSystemMetrics(SM_CYEDGE);
					break;
				case wintype_TextGrid:
					sizeFont=CreateFontIndirect(&globalStyle[0][1].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->h=size.cy*wnd->size+2*GetSystemMetrics(SM_CYEDGE);
					break;
				default:								// wintype_Graphics
					wnd->constr->h=wnd->size/graphicScale+2*GetSystemMetrics(SM_CYEDGE);
				}
				if(wnd->constr->h > wnd->h)
					wnd->constr->h=wnd->h;
			}
			wnd->constr->w=wnd->w;
			wnd->constr->x=wnd->x;
			wnd->constr->y=wnd->y+wnd->h-wnd->constr->h;
			wnd->child->x=wnd->x;
			wnd->child->w=wnd->w;
			wnd->child->y=wnd->y;
			wnd->child->h=wnd->h-wnd->constr->h;
			break;
		case winmethod_Left:
			if((wnd->method & winmethod_DivisionMask) == winmethod_Proportional)
				wnd->constr->w=wnd->size*wnd->w/100;
			else
			{
				switch(wnd->constrtype)
				{
				case wintype_TextBuffer:
					sizeFont=CreateFontIndirect(&globalStyle[0][0].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->w=size.cx*wnd->size+2*GetSystemMetrics(SM_CXEDGE);
					break;
				case wintype_TextGrid:
					sizeFont=CreateFontIndirect(&globalStyle[0][1].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->w=size.cx*wnd->size+2*GetSystemMetrics(SM_CXEDGE);
					break;
				default:									// wintype_Graphics
					wnd->constr->w=wnd->size/graphicScale+2*GetSystemMetrics(SM_CXEDGE);
				}
				if(wnd->constr->w > wnd->w)
					wnd->constr->w=wnd->w;
			}
			wnd->constr->h=wnd->h;
			wnd->constr->y=wnd->y;
			wnd->constr->x=wnd->x;
			wnd->child->x=wnd->x+wnd->constr->w;
			wnd->child->w=wnd->w-wnd->constr->w;
			wnd->child->y=wnd->y;
			wnd->child->h=wnd->h;
			break;
		// case winmethod_Right:
		default:
			if((wnd->method & winmethod_DivisionMask) == winmethod_Proportional)
				wnd->constr->w=wnd->size*wnd->w/100;
			else
			{
				switch(wnd->constrtype)
				{
				case wintype_TextBuffer:
					sizeFont=CreateFontIndirect(&globalStyle[0][0].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->w=size.cx*wnd->size+2*GetSystemMetrics(SM_CXEDGE);
					break;
				case wintype_TextGrid:
					sizeFont=CreateFontIndirect(&globalStyle[0][1].lf);
					oldFont=SelectObject(sizeHDC,sizeFont);
					GetTextExtentPoint32(sizeHDC,L"0",1,&size);
					SelectObject(sizeHDC,oldFont);
					DeleteObject(sizeFont);
					wnd->constr->w=size.cx*wnd->size+2*GetSystemMetrics(SM_CXEDGE);
					break;
				default:									// wintype_graphics
					wnd->constr->w=wnd->size/graphicScale+2*GetSystemMetrics(SM_CXEDGE);
				}
				if(wnd->constr->w > wnd->w)
					wnd->constr->w=wnd->w;
			}
			wnd->constr->h=wnd->h;
			wnd->constr->y=wnd->y;
			wnd->constr->x=wnd->x+wnd->w-wnd->constr->w;
			wnd->child->x=wnd->x;
			wnd->child->w=wnd->w-wnd->constr->w;
			wnd->child->y=wnd->y;
			wnd->child->h=wnd->h;
	}
	_resize_sub_windows(wnd->constr);
	_resize_sub_windows(wnd->child);
}

static void _recompute_win_sizes()
{
	sizeHDC=GetDC(mainWnd);
	_resize_sub_windows(rootWnd);
	ReleaseDC(mainWnd,sizeHDC);
}

winid_t glk_window_open(winid_t split, glui32 method, glui32 size, glui32 wintype, glui32 rock)
{
	HWND hwnd;
	RECT spRect,menuRect;
	winid_t pair,newWnd;
	strid_t newStr;

	if(!windowCount)
	{

		if(split)
			return(NULL);
		if(wintype<wintype_Blank || wintype>wintype_Graphics)
			return(NULL);
		newWnd=_new_window_struct(wintype);
		if(!newWnd)
			return(NULL);
		if(wintype==wintype_TextBuffer || wintype==wintype_TextGrid)
		{
			newStr=_new_stream();
			if(!newStr)
			{
				free(newWnd);
				windowCount--;
				return(NULL);
			}
		}
		else
			newStr=NULL;
		GetClientRect(mainWnd,&spRect);
		GetClientRect(cmdBar,&menuRect);
		spRect.bottom-=menuRect.bottom;
		hwnd=CreateWindowEx(0,_glk_window_class[wintype],L"Glk",
			WS_VISIBLE|WS_CHILD, spRect.left,spRect.top,spRect.right-spRect.left,
			spRect.bottom-spRect.top,mainWnd,(HMENU)-1,hInst,NULL);
		if(!hwnd)
			return(NULL);
		SetWindowLong(hwnd,GWL_USERDATA,(LONG)newWnd);
		newWnd->hwnd=hwnd;
		newWnd->type=wintype;
		newWnd->rock=rock;
		newWnd->str=newStr;
		if(newStr)
		{
			newStr->type=STR_WINDOW;
			newStr->win=newWnd;
			newStr->mode=filemode_Write;
			if(_vm_reg_object)
				newStr->drock=_vm_reg_object(newStr,gidisp_Class_Stream);
		}
		newWnd->parent=NULL;
		newWnd->child=NULL;
		newWnd->constr=NULL;
		newWnd->x=spRect.left;
		newWnd->y=spRect.top;
		newWnd->w=spRect.right-spRect.left;
		newWnd->h=spRect.bottom-spRect.top;
		rootWnd=newWnd;				// first window is the root
		if(_vm_reg_object)
			newWnd->drock=_vm_reg_object(newWnd,gidisp_Class_Window);
		//UpdateWindow(hwnd);
		return(newWnd);
	}
	if(!split)
		return(NULL);
	if(wintype<wintype_Blank || wintype>wintype_Graphics)
		return(NULL);
	if(split->child)
		return(NULL);				// can't split a pair window
	if(wintype==wintype_Blank && (method & winmethod_Fixed))
		return(NULL);				// can't have a fixed size blank window
	pair=_new_window_struct(wintype_Pair);
	newWnd=_new_window_struct(wintype);
	if(!pair || !newWnd)
	{
		if(pair || newWnd)
		{
			windowCount--;			// one was allocated... delete
			if(pair)
				free(pair);
			else
				free(newWnd);
		}
		return(NULL);				// can't create new window
	}
	if(wintype==wintype_TextBuffer || wintype==wintype_TextGrid)
	{
		newStr=_new_stream();
		if(!newStr)
		{
			free(pair);
			free(newWnd);
			windowCount-=2;
			return(NULL);
		}
	}
	else
		newStr=NULL;
	newWnd->str=newStr;
	if(newStr)
	{
		newStr->win=newWnd;
		newStr->type=STR_WINDOW;
		newStr->mode=filemode_Write;
		if(_vm_reg_object)
			newStr->drock=_vm_reg_object(newStr,gidisp_Class_Stream);
	}
	if(split->parent)
	{
		if(split->parent->child==split)
			split->parent->child=pair;
		else
			split->parent->constr=pair;
	}
	if(rootWnd==split)
		rootWnd=pair;
	pair->parent=split->parent;
	pair->x=split->x;
	pair->y=split->y;
	pair->w=split->w;
	pair->h=split->h;
	newWnd->parent=pair;
	split->parent=pair;
	pair->child=split;
	pair->constr=newWnd;
	pair->constrtype=wintype;
	pair->method=method;
	pair->size=size;
	newWnd->child=newWnd->constr=NULL;
	_recompute_win_sizes();
	hwnd=CreateWindowEx(WS_EX_CLIENTEDGE,_glk_window_class[wintype],L"glk",
		WS_BORDER|WS_VISIBLE|WS_CHILD, newWnd->x,newWnd->y,newWnd->w,newWnd->h,
		mainWnd,(HMENU)-1,hInst,NULL);
	if(!hwnd)
	{
		// UNDO
		split->parent=pair->parent;
		if(pair->parent)
		{
			if(pair->parent->child==pair)
				pair->parent->child=split;
			else
				pair->parent->constr=split;
		}
		free(pair);
		free(newWnd);
		// remove pair and newWnd from list
		windowCount-=2;
		free(newStr);
			strCount--;
		return(NULL);
	}
	SetWindowLong(hwnd,GWL_USERDATA,(LONG)newWnd);
	newWnd->hwnd=hwnd;
	newWnd->rock=rock;
	SetWindowPos(split->hwnd,HWND_TOP,split->x,split->y,split->w,split->h,0);
	//UpdateWindow(hwnd);
	if(_vm_reg_object)
	{
		newWnd->drock=_vm_reg_object(newWnd,gidisp_Class_Window);
		pair->drock=_vm_reg_object(pair,gidisp_Class_Window);
	}
	return(newWnd);
}

void glk_window_close(winid_t win,stream_result_t *result)
{
	register int i,j;

	if(win->child)
	{
		winid_t constr;
		constr=win->constr;
		glk_window_close(win->child,NULL);
		glk_window_close(constr,NULL);
		if(result)
		{
			result->readcount=0;
			result->writecount=0;
		}
		return;
	}
	DestroyWindow(win->hwnd);
	if(win->str)
	{
		if(result)
		{
			result->readcount=win->str->rcount;
			result->writecount=win->str->wcount;
		}
		for(i=0;i<strCount;++i)
			if(strList[i]==win->str)
			{
				for(j=i+1;j<strCount;++j)
					strList[j-1]=strList[j];
				strCount--;
			}
		if(_vm_unreg_object)
			_vm_unreg_object(win->str,gidisp_Class_Stream,win->str->drock);
		free(win->str);
	}
	if(win->type==wintype_TextBuffer)
	{
		TextBuffData *tbd;
		tbd=(TextBuffData *)win->data;
		for(i=0;(unsigned)i<win->dataSize;++i)
			if(tbd[i].text)
				free(tbd[i].text);
		free(win->data);
	}
	else if(win->type==wintype_TextGrid)
	{
		TextGridData *tgd;
		tgd=(TextGridData *)win->data;
		for(i=0;(unsigned)i<win->dataSize;++i)
			if(tgd[i].text)
				free(tgd[i].text);
		free(win->data);
	}
	else if(win->type==wintype_Graphics)
	{
		GraphicsData *gd;
		gd=(GraphicsData *)win->data;
		for(i=0;(unsigned)i<win->dataSize;++i)
			if(gd[i].grtype=GRTYPE_PICT)
				DeleteObject((HBITMAP)gd[i].data);
		free(win->data);
	}
	for(i=0;i<windowCount;++i)
		if(windowList[i]==win)
		{
			for(j=i+1;j<windowCount;++j)
				windowList[j-1]=windowList[j];
			windowCount--;
			break;
		}
	if(win->parent)
	{
		winid_t parent,kept;

		parent=win->parent;
		if(parent->child==win)
			kept=parent->constr;
		else
			kept=parent->child;
	
		for(i=0;i<windowCount;++i)
			if(windowList[i]==parent)
			{
				for(j=i+1;j<windowCount;++j)
					windowList[j-1]=windowList[j];
				windowCount--;
				break;
			}
		kept->parent=parent->parent;
		if(parent->parent)
			if(parent->parent->child==parent)
				parent->parent->child=kept;
			else
				parent->parent->constr=kept;
		else
			rootWnd=kept;
		if(_vm_unreg_object)
			_vm_unreg_object(parent,gidisp_Class_Window,parent->drock);
		free(parent);
	}
	else
	{
		rootWnd=NULL;
		windowCount=0;
	}
	if(_vm_unreg_object)
		_vm_unreg_object(win,gidisp_Class_Window,win->drock);
	free(win);
	_recompute_win_sizes();
}

glui32 glk_window_get_rock(winid_t win)
{
	return(win->rock);
}

glui32 glk_window_get_type(winid_t win)
{
	return(win->type);
}

winid_t glk_window_get_root(void)
{
	return(rootWnd);
}

winid_t glk_window_get_parent(winid_t win)
{
	return(win->parent);
}

strid_t glk_window_get_stream(winid_t win)
{
	return(win->str);
}

void glk_window_get_size(winid_t win,glui32 *widthptr,glui32 *heightptr)
{
	glui32 w,h;
	HDC dc;
	HGDIOBJ oldFont;
	SIZE size;

	switch(win->type)
	{
	case wintype_TextBuffer:
	case wintype_TextGrid:
		dc=GetDC(win->hwnd);
		oldFont=SelectObject(dc,win->styleFont[0]);
		GetTextExtentPoint32(dc,L"0",1,&size);
		w=(win->w-2*GetSystemMetrics(SM_CXEDGE))/(size.cx?size.cx:1);
		h=(win->h-2*GetSystemMetrics(SM_CYEDGE))/(size.cy?size.cy:1);
		SelectObject(dc,oldFont);
		ReleaseDC(win->hwnd,dc);
		break;
	case wintype_Graphics:
		if(win->w>(unsigned)2*GetSystemMetrics(SM_CXEDGE))
			w=(win->w-2*GetSystemMetrics(SM_CXEDGE))*graphicScale;
		else
			w=0;
		if(win->h>(unsigned)2*GetSystemMetrics(SM_CYEDGE))
			h=(win->h-2*GetSystemMetrics(SM_CYEDGE))*graphicScale;
		else
			h=0;
		break;
	default:
		w=h=0;
	}
	if(widthptr)
		*widthptr=w;
	if(heightptr)
		*heightptr=h;
}

void glk_window_set_arrangement(winid_t win,glui32 method,glui32 size,winid_t keywin)
{
	if(win->child!=keywin && win->constr!=keywin)
		return;
	if(win->child==keywin)
	{
		win->child=win->constr;
		win->constr=keywin;
	}
	win->method=method;
	win->size=size;
	_recompute_win_sizes();
}

void glk_window_get_arrangement(winid_t win,glui32 *methodptr,glui32 *sizeptr, winid_t *keywinptr)
{
	if(methodptr)
		*methodptr=win->method;
	if(sizeptr)
		*sizeptr=win->size;
	if(keywinptr)
		*keywinptr=win->constr;
}

winid_t glk_window_iterate(winid_t win,glui32 *rockptr)
{
	register int i;

	if(!win)
	{
		if(!windowCount)
			return(NULL);
		if(rockptr)
			*rockptr=windowList[0]->rock;
		return(windowList[0]);
	}
	for(i=0;i<windowCount;++i)
		if(windowList[i]==win && i<windowCount-1)
		{
			if(rockptr)
				*rockptr=windowList[i+1]->rock;
			return(windowList[i+1]);
		}
	return(NULL);
}

winid_t glk_window_get_sibling(winid_t win)
{
	if(!win->parent)
		return(NULL);
	if(win->parent->child==win)
		return(win->parent->constr);
	return(win->parent->child);
}

void glk_window_clear(winid_t win)
{
	register unsigned i;
	if(win->type==wintype_TextBuffer)
	{
		TextBuffData *data;
		data=(TextBuffData *)win->data;
		for(i=0;i<win->dataCount;++i)
			if(data[i].textSize)
				free(data[i].text);
		if(win->dataSize)
			free(win->data);
		win->dataCount=win->dataSize=0;
		win->data=NULL;
		win->lastSeg=win->lastChar=0;
		win->printSeg=win->printChar=0;
		win->needsMore=FALSE;
		SHSetNavBarText(mainWnd,NULL);
	}
	else if(win->type==wintype_TextGrid)
	{
		TextGridData *data;
		data=(TextGridData *)win->data;
		for(i=0;i<win->dataCount;++i)
			if(data[i].textSize)
				free(data[i].text);
		if(win->dataSize)
			free(win->data);
		win->dataCount=win->dataSize=0;
		win->data=NULL;
	}
	else if(win->type==wintype_Graphics)
	{
		GraphicsData *data;
		data=(GraphicsData *)win->data;
		for(i=0;i<win->dataCount;++i)
			if(data[i].grtype==GRTYPE_PICT)
				DeleteObject((HBITMAP)data[i].data);
		if(win->dataSize)
			free(win->data);
		win->dataCount=win->dataSize=0;
		win->data=NULL;
	}
	InvalidateRect(win->hwnd,NULL,FALSE);
	//DB UpdateWindow(win->hwnd);
	return;
}

void glk_window_move_cursor(winid_t win,glui32 xpos,glui32 ypos)
{
	TextGridData *tgd;

	if(win->type!=wintype_TextGrid)
		return;						// can position cursor for Text Grid windows only

	if(win->dataSize)
	{
		tgd=&((TextGridData *)win->data)[win->dataCount-1];
		if(!tgd->textCount)
		{
			tgd->x=(int)xpos;
			tgd->y=(int)ypos;
			return;
		}
	}
	while(win->dataSize<=win->dataCount)
	{
		void *temp;
		temp=realloc(win->data,sizeof(TextGridData)*(win->dataSize+1));
		if(!temp)
			return;
		tgd=(TextGridData *)win->data=temp;
		memset(&tgd[win->dataSize],0,sizeof(TextGridData));
		win->dataSize++;
	}
	tgd=(TextGridData *)win->data;
	if(win->dataCount)
		tgd[win->dataCount].style=tgd[win->dataCount-1].style;
	else
		tgd[win->dataCount].style=style_Normal;
	tgd[win->dataCount].x=(int)xpos;
	tgd[win->dataCount++].y=(int)ypos;
}

void glk_set_window(winid_t win)
{
	// That's according to the spec. What happens if win->str is NULL?
	currStr=win->str;
}

void glk_window_set_echo_stream(winid_t win, strid_t str)
{
	win->echo=str;
}

strid_t glk_window_get_echo_stream(winid_t win)
{
	return(win->echo);
}

/* Graphics */

glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height)
{
	return(FALSE);
}

glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2)
{
	return(FALSE);
}

glui32 glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2, glui32 width, glui32 height)
{
	return(FALSE);
}

void glk_window_set_background_color(winid_t win, glui32 color)
{
	if(win->type!=wintype_Graphics)
		return;
	win->styleBG[0]=RGB((color&0xff0000)>>16,(color&0xff00)>>8,color&0xff);

}

void glk_window_fill_rect(winid_t win, glui32 color, glsi32 left, glsi32 top, glui32 width, glui32 height)
{
	HDC dc;
	HBRUSH hBrush;
	HGDIOBJ oldBrush;
	GraphicsData *gd;

	if(win->type!=wintype_Graphics)
		return;
	dc=GetDC(win->hwnd);
	hBrush=CreateSolidBrush(RGB((color&0xff0000)>>16,(color&0xff00)>>8,color&0xff));
	SelectObject(dc,GetStockObject(NULL_PEN));
	oldBrush=SelectObject(dc,hBrush);
	Rectangle(dc,left/graphicScale,top/graphicScale,
		(left+width)/graphicScale,(top+height)/graphicScale);
	SelectObject(dc,oldBrush);
	ReleaseDC(win->hwnd,dc);
	while(win->dataCount>=win->dataSize)
	{
		void *temp;
		temp=realloc(win->data,sizeof(GraphicsData)*(win->dataSize+1));
		if(!temp)
			return;
		win->data=temp;
		win->dataSize++;
	}
	gd=&((GraphicsData *)win->data)[win->dataCount++];
	gd->grtype=GRTYPE_RECT;
	gd->x=left;
	gd->y=top;
	gd->w=width;
	gd->h=height;
	gd->data=(glui32)RGB((color&0xff0000)>>16,(color&0xff00)>>8,color&0xff);
}

void glk_window_erase_rect(winid_t win, glsi32 left, glsi32 top, glui32 width, glui32 height)
{
	HDC dc;
	HBRUSH hBrush;
	HGDIOBJ oldBrush;
	GraphicsData *gd;

	if(win->type!=wintype_Graphics)
		return;
	dc=GetDC(win->hwnd);
	hBrush=CreateSolidBrush(win->styleBG[0]);
	SelectObject(dc,GetStockObject(NULL_PEN));
	oldBrush=SelectObject(dc,hBrush);
	Rectangle(dc,left/graphicScale,top/graphicScale,
		(left+width)/graphicScale,(top+height)/graphicScale);
	SelectObject(dc,oldBrush);
	ReleaseDC(win->hwnd,dc);
	while(win->dataCount>=win->dataSize)
	{
		void *temp;
		temp=realloc(win->data,sizeof(GraphicsData)*(win->dataSize+1));
		if(!temp)
			return;
		win->data=temp;
		win->dataSize++;
	}
	gd=&((GraphicsData *)win->data)[win->dataCount++];
	gd->grtype=GRTYPE_RECT;
	gd->x=left;
	gd->y=top;
	gd->w=width;
	gd->h=height;
	gd->data=(glui32)win->styleBG[0];
}

void glk_window_flow_break(winid_t win)
{
}

/* Sound */

schanid_t glk_schannel_create(glui32 rock)
{
	return(NULL);
}


void glk_schannel_destroy(schanid_t chan)
{
}

glui32 glk_schannel_play(schanid_t chan, glui32 snd)
{
	return(glk_schannel_play_ext(chan,snd,1,0));
}

glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, glui32 notify)
{
	return(0);		// for now - TODO
}

void glk_schannel_stop(schanid_t chan)
{
}

void glk_schannel_set_volume(schanid_t chan, glui32 vol)
{
}

void glk_sound_load_hint(glui32 snd, glui32 flag)
{
}

schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr)
{
	return(NULL);
}

glui32 glk_schannel_get_rock(schanid_t chan)
{
	return(0);
}

/* Hyperlinks */

void glk_set_hyperlink(glui32 linkval)
{
}

void glk_set_hyperlink_stream(strid_t str, glui32 linkval)
{
}

void glk_request_hyperlink_event(winid_t win)
{
}

void glk_cancel_hyperlink_event(winid_t win)
{
}

/* dispatch layer interface */

void gidispatch_set_object_registry(gidispatch_rock_t (*reg)(void *obj, glui32 objclass), void (*unreg)(void *obj, glui32 objclass, gidispatch_rock_t objrock))
{
	register int i;
	if(reg)
	{
		_vm_reg_object=reg;
		for(i=0;i<windowCount;++i)
			windowList[i]->drock=reg(windowList[i],gidisp_Class_Window);
		for(i=0;i<strCount;++i)
			strList[i]->drock=reg(strList[i],gidisp_Class_Stream);
		for(i=0;i<fileRefCount;++i)
			fileRef[i]->drock=reg(fileRef[i],gidisp_Class_Fileref);
	}
	if(unreg)
		_vm_unreg_object=unreg;
}

gidispatch_rock_t gidispatch_get_objrock(void *obj, glui32 objclass)
{
	gidispatch_rock_t ret;
	switch(objclass)
	{
	case gidisp_Class_Window:
		ret=((winid_t)obj)->drock;
		break;
	case gidisp_Class_Stream:
		ret=((strid_t)obj)->drock;
		break;
	case gidisp_Class_Fileref:
		ret=((frefid_t)obj)->drock;
		break;
	default:
		ret.num=0;
	}
	return(ret);
}

/* Blorb layer interface */
/* copied from XGlk */

giblorb_err_t giblorb_set_resource_map(strid_t file)
{
  giblorb_err_t err;

  /* For the moment, we only allow file-streams, because the resource
     loaders expect a FILE*. This could be changed, but I see no
     reason right now. */
  if (file->type != STR_FILE)
    return giblorb_err_NotAMap;
  
  err = giblorb_create_map(file, &blorbmap);
  if (err) {
    blorbmap = NULL;
    return err;
  }
  
  blorbfile = file;
  
  return giblorb_err_None;
}

giblorb_map_t *giblorb_get_resource_map()
{
  return blorbmap;
}

/* start program - adapted from winstart.c in the original glulxe code*/

//DB extern strid_t gamefile;	// defined in glulxe.h
//DB extern int locate_gamefile(int);
strid_t gamefile;

char **tads_argv;
int tads_argc;

static void _load_tads_file(LPTSTR name)
{
	int len;
	char mbname[256];
	HMENU hMenu;
	NMNEWMENU nmMenu;
	
	len=wcstombs(mbname,name,_tcslen(name));
	if(len>0)
		mbname[len]='\0';
	else
		mbname[0]='\0';
	tads_argc = 2;
	tads_argv = malloc(512);
	tads_argv[0] = "tr";
	tads_argv[1] = mbname;

	hMenu=(HMENU)SendMessage(cmdBar,SHCMBM_GETMENU,0,(LPARAM)&nmMenu);
	EnableMenuItem(hMenu,ID_OPENSTORY,MF_BYCOMMAND|MF_GRAYED);
	EnableMenuItem(hMenu,ID_SAVE,MF_BYCOMMAND|MF_ENABLED);
	EnableMenuItem(hMenu,ID_RESTORE,MF_BYCOMMAND|MF_ENABLED);
	EnableMenuItem(hMenu,ID_SCRIPT,MF_BYCOMMAND|MF_ENABLED);

	glk_main();
	glk_exit();
}

/* UI stuff */

static BOOL CALLBACK AboutDlgProc(HWND hdlg,UINT msg,WPARAM wParam,LPARAM lParam)
{
	if(msg==WM_COMMAND && wParam==IDOK)
	{
		EndDialog(hdlg,0);
		return(TRUE);
	}
	return(FALSE);
}

static BOOL MenuOnInitDialog(HWND hdlg,HWND hwndFocus,LPARAM lParam)
{
	SHINITDLGINFO shidi;
	register int i;

	shidi.dwMask = SHIDIM_FLAGS;
	shidi.dwFlags = SHIDIF_DONEBUTTON |  
                          SHIDIF_SIZEDLGFULLSCREEN;
	shidi.hDlg = hdlg;
	SHInitDialog(&shidi);
	menuStr=(TCHAR *)lParam;
	for(i=0;menuStr[i];++i)
	{
		SendDlgItemMessage(hdlg,IDC_MENULIST,LB_ADDSTRING,0,(LPARAM)&menuStr[i]);
		for(;menuStr[i];++i);
	}
	return(FALSE);
}

static void MenuOnCommand(HWND hdlg,int id,HWND hwndCtl,UINT codeNotify)
{
	int pos,ret,idx;
	TCHAR newItem[256];

	switch(id)
	{
	case ID_ADD:
		SendDlgItemMessage(hdlg,IDC_TOADD,WM_GETTEXT,(WPARAM)256,(LPARAM)newItem);
		SendDlgItemMessage(hdlg,IDC_MENULIST,LB_ADDSTRING,0,(LPARAM)newItem);
		SendDlgItemMessage(hdlg,IDC_TOADD,WM_SETTEXT,0,(LPARAM)L"");
		break;
	case ID_DELETE:
		ret=SendDlgItemMessage(hdlg,IDC_MENULIST,LB_GETCURSEL,0,0);
		if(ret==LB_ERR)
			break;
		SendDlgItemMessage(hdlg,IDC_MENULIST,LB_DELETESTRING,(WPARAM)ret,0);
		break;
	case IDOK:
	case IDCANCEL:
		pos=idx=0;
		while(1)
		{
			ret=SendDlgItemMessage(hdlg,IDC_MENULIST,LB_GETTEXTLEN,(WPARAM)idx,0);
			if(ret==LB_ERR || ret+pos>1022)
				break;
			ret=SendDlgItemMessage(hdlg,IDC_MENULIST,LB_GETTEXT,(WPARAM)idx,(LPARAM)&menuStr[pos]);
			pos+=ret+1;
			idx++;
		}
		menuStr=NULL;
		EndDialog(hdlg,0);
	}
}

static BOOL CALLBACK MenuDlgProc(HWND hdlg,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG(hdlg,WM_INITDIALOG,MenuOnInitDialog);
		HANDLE_MSG(hdlg,WM_COMMAND,MenuOnCommand);
	}
	return(FALSE);
}

static UINT red,green,blue;
static COLORREF *ColorlParam;

static BOOL ColorOnInitDialog(HWND hdlg,HWND hwndFocus,LPARAM lParam)
{
	SHINITDLGINFO shidi;
	
	shidi.dwMask = SHIDIM_FLAGS;
	shidi.dwFlags = SHIDIF_DONEBUTTON |  
                          SHIDIF_SIZEDLGFULLSCREEN;
	shidi.hDlg = hdlg;
	SHInitDialog(&shidi);
	ColorlParam=(COLORREF *)lParam;
	red=GetRValue(*ColorlParam);
	green=GetGValue(*ColorlParam);
	blue=GetBValue(*ColorlParam);
	SetDlgItemInt(hdlg,IDC_RED,red,FALSE);
	SetDlgItemInt(hdlg,IDC_GREEN,green,FALSE);
	SetDlgItemInt(hdlg,IDC_BLUE,blue,FALSE);
	SendDlgItemMessage(hdlg,IDC_SPINRED,UDM_SETRANGE,0,(LPARAM)MAKELONG(255,0));
	SendDlgItemMessage(hdlg,IDC_SPINGREEN,UDM_SETRANGE,0,(LPARAM)MAKELONG(255,0));
	SendDlgItemMessage(hdlg,IDC_SPINBLUE,UDM_SETRANGE,0,(LPARAM)MAKELONG(255,0));
	InvalidateRect(GetDlgItem(hdlg,IDC_CHOSEN),NULL,TRUE);
	return(FALSE);
}

static void ColorOnDrawItem(HWND hdlg,const DRAWITEMSTRUCT *lpdi)
{
	HGDIOBJ oldBrush;
	HBRUSH pBrush;

	SelectObject(lpdi->hDC,GetStockObject(NULL_PEN));
	if(lpdi->CtlID != IDC_CHOSEN)
		return;
	pBrush=CreateSolidBrush(RGB(red,green,blue));
	oldBrush=SelectObject(lpdi->hDC,pBrush);
	Rectangle(lpdi->hDC,lpdi->rcItem.left,lpdi->rcItem.top,lpdi->rcItem.right,lpdi->rcItem.bottom);
	SelectObject(lpdi->hDC,oldBrush);
	DeleteObject(pBrush);
}

static void ColorOnCommand(HWND hdlg,int id,HWND hwndCtl,UINT codeNotify)
{
	switch(id)
	{
	case IDOK:
		*ColorlParam=RGB(red,green,blue);
		EndDialog(hdlg,0);
		break;
	case IDCANCEL:
		EndDialog(hdlg,1);
		break;
	case IDC_RED:
		red=GetDlgItemInt(hdlg,IDC_RED,NULL,FALSE);
		InvalidateRect(GetDlgItem(hdlg,IDC_CHOSEN),NULL,TRUE);
		break;
	case IDC_GREEN:
		green=GetDlgItemInt(hdlg,IDC_GREEN,NULL,FALSE);
		InvalidateRect(GetDlgItem(hdlg,IDC_CHOSEN),NULL,TRUE);
		break;
	case IDC_BLUE:
		blue=GetDlgItemInt(hdlg,IDC_BLUE,NULL,FALSE);
		InvalidateRect(GetDlgItem(hdlg,IDC_CHOSEN),NULL,TRUE);
		break;
	}
}

static BOOL CALLBACK ColorDlgProc(HWND hdlg,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG(hdlg,WM_INITDIALOG,ColorOnInitDialog);
		HANDLE_MSG(hdlg,WM_DRAWITEM,ColorOnDrawItem);
		HANDLE_MSG(hdlg,WM_COMMAND,ColorOnCommand);
	}
	return(FALSE);
}

static int CALLBACK StyleEnumFonts(ENUMLOGFONT FAR *lpelf, TEXTMETRIC FAR *lpntm, 
							int FontType, LPARAM lParam)
{
	if(SendMessage((HWND)lParam,CB_FINDSTRING,(WPARAM)-1,(LPARAM)lpelf->elfFullName)==CB_ERR)
		SendMessage((HWND)lParam,CB_ADDSTRING,0,(LPARAM)lpelf->elfFullName);
	return(1);
}

static struct glk_style_struct *currStyle;

static void StylesUpdateStyle(HWND hdlg,glui32 style,glui32 wndtype)
{
	currStyle=&globalStyle[style][wndtype];
	SetDlgItemText(hdlg,IDC_FONT,currStyle->lf.lfFaceName);
	SetDlgItemInt(hdlg,IDC_FONTSIZE,currStyle->lf.lfHeight,FALSE);
	if(style==style_Preformatted || wndtype==1)
	{
		SendDlgItemMessage(hdlg,IDC_FIXED,BM_SETCHECK,BST_CHECKED,0);
		EnableWindow(GetDlgItem(hdlg,IDC_FIXED),FALSE);
	}
	else
	{
		EnableWindow(GetDlgItem(hdlg,IDC_FIXED),TRUE);
		if(currStyle->lf.lfPitchAndFamily & FIXED_PITCH)
			SendDlgItemMessage(hdlg,IDC_FIXED,BM_SETCHECK,BST_CHECKED,0);
		else
			SendDlgItemMessage(hdlg,IDC_FIXED,BM_SETCHECK,BST_UNCHECKED,0);
	}
	SendDlgItemMessage(hdlg,IDC_FONT,CB_SELECTSTRING,(WPARAM)-1,(LPARAM)currStyle->lf.lfFaceName);
	if(currStyle->lf.lfWeight<400)
		SendDlgItemMessage(hdlg,IDC_BOLD,CB_SETCURSEL,0,0);
	else if(currStyle->lf.lfWeight<700)
		SendDlgItemMessage(hdlg,IDC_BOLD,CB_SETCURSEL,1,0);
	else
		SendDlgItemMessage(hdlg,IDC_BOLD,CB_SETCURSEL,2,0);
	if(currStyle->lf.lfItalic)
		SendDlgItemMessage(hdlg,IDC_ITALIC,BM_SETCHECK,BST_CHECKED,0);
	else
		SendDlgItemMessage(hdlg,IDC_ITALIC,BM_SETCHECK,BST_UNCHECKED,0);
	//DB
	if (currStyle->lf.lfQuality == CLEARTYPE_QUALITY)
		SendDlgItemMessage(hdlg,IDC_CLEARTYPE,BM_SETCHECK,BST_CHECKED,0);
	else
		SendDlgItemMessage(hdlg,IDC_CLEARTYPE,BM_SETCHECK,BST_UNCHECKED,0);
	InvalidateRect(GetDlgItem(hdlg,IDC_FORE),NULL,TRUE);
	InvalidateRect(GetDlgItem(hdlg,IDC_BACK),NULL,TRUE);
	InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);

}

static BOOL StylesOnInitDialog(HWND hdlg,HWND hwndFocus,LPARAM lParam)
{
	HWND hwndFonts;
	HDC hdc;
	SHINITDLGINFO shidi;
	
	shidi.dwMask = SHIDIM_FLAGS;
	shidi.dwFlags = SHIDIF_DONEBUTTON |  
                          SHIDIF_SIZEDLGFULLSCREEN;
	shidi.hDlg = hdlg;
	SHInitDialog(&shidi);
	hwndFonts=GetDlgItem(hdlg,IDC_FONT);
	hdc=GetDC(mainWnd);
	EnumFontFamilies(hdc,NULL,(FONTENUMPROC)StyleEnumFonts,(LPARAM)hwndFonts);
	ReleaseDC(mainWnd,hdc);
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Normal");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Emphasized");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Preformatted");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Header");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Subheader");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Alert");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Note");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"BlockQuote");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"Input");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"User1");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_ADDSTRING,0,(LPARAM)L"User2");
	SendDlgItemMessage(hdlg,IDC_STYLE,CB_SETCURSEL,0,0);

	SendDlgItemMessage(hdlg,IDC_WINDOW,CB_ADDSTRING,0,(LPARAM)L"Buffer");
	SendDlgItemMessage(hdlg,IDC_WINDOW,CB_ADDSTRING,0,(LPARAM)L"Grid");
	SendDlgItemMessage(hdlg,IDC_WINDOW,CB_SETCURSEL,0,0);

	SendDlgItemMessage(hdlg,IDC_BOLD,CB_ADDSTRING,0,(LPARAM)L"Light");
	SendDlgItemMessage(hdlg,IDC_BOLD,CB_ADDSTRING,0,(LPARAM)L"Normal");
	SendDlgItemMessage(hdlg,IDC_BOLD,CB_ADDSTRING,0,(LPARAM)L"Bold");

	StylesUpdateStyle(hdlg,0,0);
	return(TRUE);
}

static void StylesOnCommand(HWND hdlg,int id,HWND hwndCtl,UINT codeNotify)
{
	LONG curstyle,curwnd;

	switch(id)
	{
	case IDOK:
	case IDCANCEL:
		EndDialog(hdlg,0);
		break;
	case IDC_STYLE:
	case IDC_WINDOW:
		curstyle=SendDlgItemMessage(hdlg,IDC_STYLE,CB_GETCURSEL,0,0);
		curwnd=SendDlgItemMessage(hdlg,IDC_WINDOW,CB_GETCURSEL,0,0);
		if(curstyle != CB_ERR && curwnd != CB_ERR)
			StylesUpdateStyle(hdlg,(glui32)curstyle,(glui32)curwnd);
		break;
	case IDC_FORE:
		DialogBoxParam(hInst,MAKEINTRESOURCE(IDD_COLOR),NULL,ColorDlgProc,(LPARAM)&currStyle->fg);
		InvalidateRect(GetDlgItem(hdlg,IDC_FORE),NULL,TRUE);
		InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);
		break;
	case IDC_BACK:
		DialogBoxParam(hInst,MAKEINTRESOURCE(IDD_COLOR),NULL,ColorDlgProc,(LPARAM)&currStyle->bg);
		InvalidateRect(GetDlgItem(hdlg,IDC_BACK),NULL,TRUE);
		InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);
		break;
	case IDC_FONT:
		GetDlgItemText(hdlg,IDC_FONT,currStyle->lf.lfFaceName,LF_FACESIZE);
		InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);
		break;
	case IDC_FONTSIZE:
		currStyle->lf.lfHeight=GetDlgItemInt(hdlg,IDC_FONTSIZE,NULL,FALSE);
		InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);
		break;
	case IDC_BOLD:
		switch(SendDlgItemMessage(hdlg,IDC_BOLD,CB_GETCURSEL,0,0))
		{
		case 0:									// Light
			currStyle->lf.lfWeight=300;
			break;
		case 2:									// Bold
			currStyle->lf.lfWeight=700;
			break;
		default:								// Normal
			currStyle->lf.lfWeight=400;
		}
		InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);
		break;
	case IDC_ITALIC:
		if(SendDlgItemMessage(hdlg,IDC_ITALIC,BM_GETCHECK,0,0)==BST_CHECKED)
			currStyle->lf.lfItalic=1;
		else
			currStyle->lf.lfItalic=0;
		InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);
		break;
	case IDC_CLEARTYPE:
		if(SendDlgItemMessage(hdlg,IDC_CLEARTYPE,BM_GETCHECK,0,0)==BST_CHECKED)
			currStyle->lf.lfQuality = CLEARTYPE_QUALITY;
		else
			currStyle->lf.lfQuality=0;
		InvalidateRect(GetDlgItem(hdlg,IDC_SAMPLE),NULL,TRUE);
		break;
	}
}

static void StylesOnDrawItem(HWND hdlg,const DRAWITEMSTRUCT *lpdi)
{
	HGDIOBJ oldBrush;
	HBRUSH pBrush;
	SelectObject(lpdi->hDC,GetStockObject(NULL_PEN));
	switch(lpdi->CtlID)
	{
	case IDC_FORE:
		pBrush=CreateSolidBrush(currStyle->fg);
		break;
	case IDC_BACK:
	case IDC_SAMPLE:
		pBrush=CreateSolidBrush(currStyle->bg);
		break;
	}
	oldBrush=SelectObject(lpdi->hDC,pBrush);
	Rectangle(lpdi->hDC,lpdi->rcItem.left,lpdi->rcItem.top,lpdi->rcItem.right,lpdi->rcItem.bottom);
	if(lpdi->CtlID==IDC_SAMPLE)
	{
		HFONT hFont;
		HGDIOBJ oldFont;

		hFont=CreateFontIndirect(&currStyle->lf);
		oldFont=SelectObject(lpdi->hDC,hFont);
		SetTextColor(lpdi->hDC,currStyle->fg);
		SetBkColor(lpdi->hDC,currStyle->bg);
		DrawText(lpdi->hDC,L"Sample",-1,(LPRECT)&lpdi->rcItem,DT_CENTER|DT_VCENTER);
		SelectObject(lpdi->hDC,oldFont);
		DeleteObject(hFont);
	}
	SelectObject(lpdi->hDC,oldBrush);
	DeleteObject(pBrush);
}

static BOOL CALLBACK StylesDlgProc(HWND hdlg,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG(hdlg,WM_INITDIALOG,StylesOnInitDialog);
		HANDLE_MSG(hdlg,WM_COMMAND,StylesOnCommand);
		HANDLE_MSG(hdlg,WM_DRAWITEM,StylesOnDrawItem);
	}
	return(FALSE);
}

static void MainOnClose(HWND hwnd)
{
	DestroyWindow(hwnd);
}

static void MainOnDestroy(HWND hwnd)
{
	PostQuitMessage(0);
}

static void _glk_fake_input(winid_t win,char *word,int len)
{
	TextBuffData *tbd;
	register int i;

	tbd=&((TextBuffData *)win->data)[win->dataCount-1];
	if(tbd->textCount+len+1>=tbd->textSize)
	{
		char *temp;
		temp=realloc(tbd->text,sizeof(char)*(tbd->textCount+len+1));
		if(!temp)
			return;
		tbd->text=temp;
		tbd->textSize=tbd->textCount+len+1;
	}
	for(i=0;i<len;++i)
		if(word[i]!='.')
			tbd->text[tbd->textCount++]=word[i];
		else
		{
			InvalidateRect(win->hwnd,NULL,FALSE);
			UpdateWindow(win->hwnd);
			SendMessage(win->hwnd,WM_KEYDOWN,VK_RETURN,0);
			return;
		}
	tbd->text[tbd->textCount++]=' ';
	InvalidateRect(win->hwnd,NULL,FALSE);
	UpdateWindow(win->hwnd);
}

static void MainOnCommand(HWND hwnd,int id,HWND hwndCtl,UINT codeNotify)
{
	OPENFILENAME ofn;
	TCHAR fileName[256];
	static winid_t tempWnd=NULL;
	HMENU hMenu,hSubMenu;
	NMNEWMENU nmMenu;
	register int i,j;

	if(id>=20000 && id<20100)
	{
		j=id-20000;
		for(i=0;j;i++)
			if(!moveMenu[i])
				j--;
		for(j=0;j<windowCount;++j)
			if(windowList[j]->reqEvent & reqev_Line)
			{
				char word[256];
				int len;
				len=wcstombs(word,&moveMenu[i],wcslen(&moveMenu[i]));
				_glk_fake_input(windowList[j],word,len);
				break;
			}
		return;
	}
	if(id>=30000 && id<30100)
	{
		j=id-30000;
		for(i=0;j;i++)
			if(!actionMenu[i])
				j--;
		for(j=0;j<windowCount;++j)
			if(windowList[j]->reqEvent & reqev_Line)
			{
				char word[256];
				int len;
				len=wcstombs(word,&actionMenu[i],wcslen(&actionMenu[i]));
				_glk_fake_input(windowList[j],word,len);
				break;
			}
		return;
	}
	switch(id)
	{
	case ID_OPENSTORY:
		ofn.lStructSize=sizeof(OPENFILENAME);
		ofn.hwndOwner=NULL;
		ofn.hInstance=hInst;
		ofn.lpstrFilter=L"TADS Files\0*.gam\0All Files\0*.*\0";
		ofn.lpstrCustomFilter=NULL;
		ofn.nMaxCustFilter=0;
		ofn.nFilterIndex=0;
		ofn.lpstrFile=fileName;
		fileName[0]=0;
		ofn.nMaxFile=256;
		ofn.lpstrFileTitle=NULL;
		ofn.nMaxFileTitle=0;
		ofn.lpstrInitialDir=NULL;
		ofn.lpstrTitle=NULL;
		ofn.Flags=OFN_FILEMUSTEXIST;
		ofn.lpstrDefExt=L"ulx";
		if(GetOpenFileName(&ofn))
			//_load_glulx_file(ofn.lpstrFile);
			_load_tads_file(ofn.lpstrFile);
		break;
	case ID_STYLES:
		DialogBox(hInst,MAKEINTRESOURCE(IDD_STYLES),NULL,StylesDlgProc);
		break;
	case ID_ABOUT:
		DialogBox(hInst,MAKEINTRESOURCE(IDD_ABOUTBOX),mainWnd,AboutDlgProc);
		break;
	case ID_EXIT:
		glk_exit();
	case ID_MOVES:
		if(menuStr)
			break;
		hMenu=(HMENU)SendMessage(cmdBar,SHCMBM_GETMENU,0,(LPARAM)&nmMenu);
		for(i=0;i<100;++i)
			DeleteMenu(hMenu,20000+i,MF_BYCOMMAND);
		DialogBoxParam(hInst,MAKEINTRESOURCE(IDD_MENU),NULL,MenuDlgProc,(LPARAM)moveMenu);
		hSubMenu=GetSubMenu(hMenu,2);		// Move
		for(i=0;moveMenu[i];++i)
		{
			AppendMenu(hSubMenu,MF_STRING,20000+i,&moveMenu[i]);
			for(;moveMenu[i];++i);
		}
		DrawMenuBar(cmdBar);
		break;
	case ID_ACTIONS:
		if(menuStr)
			break;
		hMenu=(HMENU)SendMessage(cmdBar,SHCMBM_GETMENU,0,(LPARAM)&nmMenu);
		for(i=0;i<100;++i)
			DeleteMenu(hMenu,20000+i,MF_BYCOMMAND);
		DialogBoxParam(hInst,MAKEINTRESOURCE(IDD_MENU),NULL,MenuDlgProc,(LPARAM)actionMenu);
		hSubMenu=GetSubMenu(hMenu,3);		// Action
		for(i=0;actionMenu[i];++i)
		{
			AppendMenu(hSubMenu,MF_STRING,30000+i,&actionMenu[i]);
			for(;actionMenu[i];++i);
		}
		DrawMenuBar(cmdBar);
		break;
	case ID_SAVE:
		for(i=0;i<windowCount;++i)
			if(windowList[i]->reqEvent&reqev_Line)
			{
				_glk_fake_input(windowList[i],"save.",5);
				return;
			}
		break;
	case ID_RESTORE:
		for(i=0;i<windowCount;++i)
			if(windowList[i]->reqEvent&reqev_Line)
			{
				_glk_fake_input(windowList[i],"restore.",8);
				return;
			}
		break;
	case ID_SCRIPT:
		for(i=0;i<windowCount;++i)
			if(windowList[i]->reqEvent&reqev_Line)
			{
				_glk_fake_input(windowList[i],"script.",10);
				return;
			}
		break;
	}
}

static HWND MainCreateMenuBar(HWND hwnd)
{
	SHMENUBARINFO mbi;

	memset(&mbi, 0, sizeof(SHMENUBARINFO));
	mbi.cbSize     = sizeof(SHMENUBARINFO);
	mbi.hwndParent = hwnd;
	mbi.nToolBarId = IDM_MENU;
	mbi.hInstRes   = hInst;
	mbi.nBmpId     = IDB_MENU;
	mbi.cBmpImages = 2;

	if (!SHCreateMenuBar(&mbi)) 
		return NULL;
	return mbi.hwndMB;
}

static BOOL MainOnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
	cmdBar=MainCreateMenuBar(hwnd);
	memset(&sainfo,0,sizeof(sainfo));
	sainfo.cbSize=sizeof(sainfo);
	return(TRUE);
}

static void MainOnSettingChange(HWND hwnd,UINT wFlag)
{
	SIPINFO si;
	RECT rc;

	if(wFlag==SPI_SETSIPINFO || wFlag==SPI_SETCURRENTIM)
	{
		si.cbSize=sizeof(SIPINFO);
		si.dwImDataSize=0;
		SipGetInfo(&si);
		rc=si.rcVisibleDesktop;
		if(!(si.fdwFlags & SIPF_ON))
			if(cmdBar)
				rc.bottom-=MENU_HEIGHT;
		MoveWindow(mainWnd,rc.left,rc.top,rc.right-rc.left,rc.bottom-rc.top,TRUE);
		if(rootWnd)
		{
			GetClientRect(mainWnd,&rc);
			rootWnd->x=rc.left;
			rootWnd->y=rc.top;
			rootWnd->w=rc.right;
			rootWnd->h=rc.bottom;
			_recompute_win_sizes();
		}
	}
}

static void MainOnSetFocus(HWND hwnd,HWND hwndOldFocus)
{
	register int i;

	for(i=0;i<windowCount;++i)
		if(windowList[i]->reqEvent & (reqev_Line|reqev_Char))
		{
			SetFocus(windowList[i]->hwnd);
			break;
		}
}

static LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG(hwnd,WM_CLOSE,MainOnClose);
		HANDLE_MSG(hwnd,WM_DESTROY,MainOnDestroy);
		HANDLE_MSG(hwnd,WM_COMMAND,MainOnCommand);
		HANDLE_MSG(hwnd,WM_CREATE,MainOnCreate);
		HANDLE_MSG(hwnd,WM_SETTINGCHANGE,MainOnSettingChange);
		HANDLE_MSG(hwnd,WM_SETFOCUS,MainOnSetFocus);
	}
	return(DefWindowProc(hwnd,msg,wParam,lParam));
}

static LRESULT CALLBACK BlankWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	return(DefWindowProc(hwnd,msg,wParam,lParam));
}

static int TextBufferParaHeight(HDC dc,winid_t wnd,int w,int cseg,int cchar,int ccount,int *llh)
{
	register int i,j,k;
	TextBuffData *tbd;
	char word[50];
	TCHAR wword[50];
	int wcount;
	HGDIOBJ oldFont;
	int cx,cy,lineh;
	SIZE size;

	tbd=(TextBuffData *)wnd->data;
	k=0;
	i=cseg;
	j=cchar;
	cx=cy=0;
	oldFont=SelectObject(dc,wnd->styleFont[tbd[cseg].style]);
	GetTextExtentPoint32(dc,L" ",1,&size);
	lineh=size.cy?size.cy:1;
	wcount=0;
	while(k<ccount)
	{
		if((unsigned)j>=tbd[i].textCount)
		{
			SelectObject(dc,wnd->styleFont[tbd[++i].style]);
			GetTextExtentPoint32(dc,L" ",1,&size);
			lineh=lineh>size.cy?lineh:size.cy;
			j=0;
		}
		word[wcount]=tbd[i].text[j++];
		k++;
		wcount++;
		if(word[wcount]==' ' || wcount>=50 || k>=ccount)
		{
			// we have a word - fake draw it
			mbstowcs(wword,word,wcount);
			GetTextExtentPoint32(dc,wword,wcount,&size);
			if(cx>0 && cx+size.cx>w)
			{
				cy+=lineh;
				cx=size.cx;
				lineh=size.cy;
			}
			else
			{
				lineh=(size.cy>lineh)?size.cy:lineh;
				cx+=size.cx;
			}
			wcount=0;
		}
	}
	SelectObject(dc,oldFont);
	*llh=lineh;
	return(cy+lineh);
}

static void TextBufferDraw(HDC dc,winid_t wnd,int w,int h)
{
	int cseg,cchar,ccount,llh;
	TextBuffData *tbd;
	int cx,cy,lineh;
	char word[50];
	TCHAR wword[50];
	int wcount;
	BOOL lastLineCounted;
	HGDIOBJ oldFont;
	SIZE size;
	BOOL newline;

	tbd=(TextBuffData *)wnd->data;
	if(wnd->needsMore)
	{
		if (firstTime)
		{
			suppressBlit = TRUE;
			firstTime = FALSE;
		}
		cseg=wnd->lastSeg;
		cchar=wnd->lastChar;
		cy=0;
	}
	else
	{
		ccount=0;
		cy=h;
		lastLineCounted=0;
		cseg=wnd->dataCount-1;
		while(cseg>=0)
		{
			cchar=tbd[cseg].textCount-1;
			while(cchar>=0)
			{
				if(tbd[cseg].text[cchar]=='\n')
				{
					// we have the beginning of a paragraph
					cy-=TextBufferParaHeight(dc,wnd,w,cseg,cchar+1,ccount,&llh);
					if(!lastLineCounted)
					{
						lastLineCounted=TRUE;
						cy-=llh;
					}
					if(cy<0)
						break;
					ccount=0;
				}
				else
					ccount++;
				cchar--;
			}
			if(cy<0)
				break;
			cseg--;
		}
		if(cy>0 && wnd->dataCount)
		{
			cy-=TextBufferParaHeight(dc,wnd,w,0,0,ccount-1,&llh);
			if(!lastLineCounted)
				cy-=llh;
		}
		if(cseg>(int)wnd->printSeg || (cseg==(int)wnd->printSeg && cchar>(int)wnd->printChar))
		{
			cseg=wnd->lastSeg=wnd->printSeg;
			cchar=wnd->lastChar=wnd->printChar-1;
			cy=0;
			wnd->needsMore=TRUE;
			SHSetNavBarText(mainWnd,L"[More]");
			//BringWindowToTop(wnd->hwnd);
		}
	}
	if(cseg<0)
		cseg=cchar=0;
	else if(cchar<0)
		cchar=0;
	else
	{
		cchar++;
		if((unsigned)cchar>=tbd[cseg].textCount)
		{
			cseg++;
			cchar=0;
		}
	}
	cx=0;
	wcount=0;
	lineh=0;
	while((unsigned)cseg<wnd->dataCount)
	{
		oldFont=SelectObject(dc,wnd->styleFont[tbd[cseg].style]);
		GetTextExtentPoint32(dc,L" ",1,&size);
		lineh=lineh>size.cy?lineh:size.cy;
		SetTextColor(dc,wnd->styleFG[tbd[cseg].style]);
		SetBkColor(dc,wnd->styleBG[tbd[cseg].style]);
		while((unsigned)cchar<tbd[cseg].textCount)
		{
			newline=FALSE;
			word[wcount]=tbd[cseg].text[cchar++];
			if(word[wcount]==' ' || word[wcount]=='\n' || 
				wcount>=50 || (unsigned)cchar>=tbd[cseg].textCount)
			{
				// we have a word - draw it
				if(word[wcount]=='\n')
					newline=TRUE;
				else
				{
					newline=FALSE;
					wcount++;
				}
				mbstowcs(wword,word,wcount);
				GetTextExtentPoint32(dc,wword,wcount,&size);
				if(cx>0 && cx+size.cx>w)
				{
					cy+=lineh;
					if(cy<h)
					{
						wnd->printSeg=cseg;
						wnd->printChar=cchar-wcount;
					}
					ExtTextOut(dc,0,cy,ETO_OPAQUE,NULL,wword,wcount,NULL);
					cx=size.cx;
					lineh=size.cy;
				}
				else
				{
					lineh=(size.cy>lineh)?size.cy:lineh;
					ExtTextOut(dc,cx,cy,ETO_OPAQUE,NULL,wword,wcount,NULL);
					cx+=size.cx;
				}
				if(newline)
				{
					cx=0;
					cy+=lineh;
					lineh=0;
					if(cy<h)
					{
						wnd->printSeg=cseg;
						wnd->printChar=cchar;
					}
				}
				wcount=0;
			}
			else
				wcount++;
		}
		SelectObject(dc,oldFont);
		cseg++;
		cchar=0;
	}
	if(!wnd->needsMore)
	{
		wnd->lastSeg=wnd->printSeg;
		wnd->lastChar=wnd->printChar;
	}
	wnd->caretx=cx;
	wnd->carety=cy+lineh-8;
	if(wnd->reqEvent&reqev_Line)
		if(wnd->needsMore)
			SetCaretPos(-10,-10);
		else
			SetCaretPos(cx,cy+lineh-8);
}

static void TextBufferOnPaint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc,memdc;
	winid_t wnd;
	HBRUSH hBrush;
	HGDIOBJ oldBitmap,oldBrush;
	HBITMAP hBitmap;
	RECT rc;

	hdc=BeginPaint(hwnd,&ps);
	wnd=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	// get window size
	GetClientRect(hwnd,&rc);
	// create memory DC and bitmap for image buffering
	memdc=CreateCompatibleDC(hdc);
	hBitmap=CreateCompatibleBitmap(hdc,rc.right,rc.bottom);
	oldBitmap=SelectObject(memdc,hBitmap);
	// clear area
	hBrush=CreateSolidBrush(wnd->styleBG[0]);
	oldBrush=SelectObject(memdc,hBrush);
	SelectObject(memdc,GetStockObject(NULL_PEN));

	Rectangle(memdc,ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);

	SelectObject(memdc,oldBrush);
	DeleteObject(hBrush);
	// draw the text
	TextBufferDraw(memdc,wnd,rc.right,rc.bottom);
	if (!suppressBlit)
		BitBlt(hdc,0,0,rc.right,rc.bottom,memdc,0,0,SRCCOPY);
	if (suppressBlit)
		suppressBlit = FALSE;
	SelectObject(memdc,oldBitmap);
	DeleteObject(hBitmap);
	DeleteDC(memdc);
	EndPaint(hwnd,&ps);
}

#if 0
static void CludgeDraw()
{
	register int i;
	for(i=0;i<windowCount;++i)
		if(windowList[i]->type == wintype_TextBuffer)
		{
			TextBufferDraw(windowList[i]->hwnd, windowList[i], windowList[i]->width, windowList[i]->height);
		}
}
#endif

static void TextBufferOnKey(HWND hwnd,UINT vk,BOOL fDown,int crepeat,UINT flags)
{
	winid_t win;

	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(win->needsMore)
	{
		win->lastSeg=win->printSeg;
		win->lastChar=win->printChar;
		win->needsMore=FALSE;
		SHSetNavBarText(mainWnd,NULL);
		InvalidateRect(hwnd,NULL,FALSE);
		UpdateWindow(hwnd);
		return;
	}
	if(win->reqEvent & reqev_Char)
	{
		switch(vk)
		{
		case VK_LEFT:
			_post_glk_event(evtype_CharInput,win,keycode_Left,0);
			break;
		case VK_RIGHT:
			_post_glk_event(evtype_CharInput,win,keycode_Right,0);
			break;
		case VK_UP:
			_post_glk_event(evtype_CharInput,win,keycode_Up,0);
			break;
		case VK_DOWN:
			_post_glk_event(evtype_CharInput,win,keycode_Down,0);
			break;
		case VK_RETURN:
			_post_glk_event(evtype_CharInput,win,keycode_Return,0);
			break;
		case VK_TAB:
			_post_glk_event(evtype_CharInput,win,keycode_Tab,0);
			break;
		}
		return;
	}
	if(win->reqEvent & reqev_Line)
	{
		TextBuffData *tbd;
		BOOL needRepaint;
		event_t event;

		tbd=&((TextBuffData *)win->data)[win->dataCount-1];
		needRepaint=FALSE;
		switch(vk)
		{
		case VK_LEFT:							// will act as backspace
		case VK_BACK:
		case VK_DELETE:
			if(tbd->textCount)
			{
				tbd->textCount--;
				needRepaint=TRUE;
			}
			break;
		case VK_RETURN:
		case VK_RIGHT:
			glk_cancel_line_event(win,&event);
			glk_set_style_stream(win->str,style_Normal);
			glk_put_char_stream(win->str,'\n');
			_post_glk_event(event.type,event.win,event.val1,event.val2);
			break;
		case VK_DOWN:
			if(inputCurr<inputSize-1)
			{
				inputCurr++;
				if(tbd->textSize<strlen(inputHistory[inputCurr]))
				{
					char *temp;
					temp=realloc(tbd->text,sizeof(char)*strlen(inputHistory[inputCurr]));
					if(!temp)
					{
						inputCurr--;
						break;
					}
					tbd->text=temp;
					tbd->textSize=strlen(inputHistory[inputCurr]);
				}
				memcpy(tbd->text,inputHistory[inputCurr],strlen(inputHistory[inputCurr]));
				tbd->textCount=strlen(inputHistory[inputCurr]);
				needRepaint=TRUE;
			}
			break;
		case VK_UP:
			if(inputCurr>0)
			{
				inputCurr--;
				if(tbd->textSize<strlen(inputHistory[inputCurr]))
				{
					char *temp;
					temp=realloc(tbd->text,sizeof(char)*strlen(inputHistory[inputCurr]));
					if(!temp)
					{
						inputCurr++;
						break;
					}
					tbd->text=temp;
					tbd->textSize=strlen(inputHistory[inputCurr]);
				}
				memcpy(tbd->text,inputHistory[inputCurr],strlen(inputHistory[inputCurr]));
				tbd->textCount=strlen(inputHistory[inputCurr]);
				needRepaint=TRUE;
			}
		}
		if(needRepaint)
		{
			InvalidateRect(hwnd,NULL,FALSE);
			UpdateWindow(hwnd);
		}
	}
}

static void TextBufferOnChar(HWND hwnd,TCHAR ch,int cRepeat)
{
	winid_t win;
	TCHAR wcs[2];
	unsigned char mbs[2];

	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(win->needsMore)
	{
		win->lastSeg=win->printSeg;
		win->lastChar=win->printChar;
		win->needsMore=FALSE;
		SHSetNavBarText(mainWnd,NULL);
		InvalidateRect(hwnd,NULL,FALSE);
		//DB UpdateWindow(hwnd);
		return;
	}
	if(win->reqEvent & reqev_Char)
	{
		wcs[0]=ch;
		wcs[1]=L'\0';
		wcstombs(mbs,wcs,1);
		if((mbs[0]>=0x20 && mbs[0]<=0x7f) || (mbs[0]>=0xa0 && mbs[0]<=0xff))
			_post_glk_event(evtype_CharInput,win,(glui32)mbs[0],0);
		else if(mbs[0]==0x09)
			_post_glk_event(evtype_CharInput,win,keycode_Tab,0);
		else if(mbs[0]==0x08 || mbs[0]==0x7f)			// BS or DEL
			_post_glk_event(evtype_CharInput,win,keycode_Delete,0);
		return;
	}
	if(win->reqEvent & reqev_Line)
	{
		TextBuffData *tbd;
		tbd=&((TextBuffData *)win->data)[win->dataCount-1];
		if(tbd->textCount<win->maxLine)
		{
			wcs[0]=ch;
			wcs[1]=L'\0';
			wcstombs(mbs,wcs,1);
			if((mbs[0]>=0x20 && mbs[0]<=0x7f) || (mbs[0]>=0xa0 && mbs[0]<=0xff))
			{
				while(tbd->textCount>=tbd->textSize)
				{
					char *temp;
					temp=(char *)realloc(tbd->text,sizeof(char)*(tbd->textSize+16));
					if(!temp)
						return;
					tbd->text=temp;
					tbd->textSize+=16;
				}
				tbd->text[tbd->textCount++]=glk_char_to_lower(mbs[0]);
				InvalidateRect(hwnd,NULL,FALSE);
				//DB UpdateWindow(hwnd);
			}
		}	
	}
}

static void TextBufferOnSetFocus(HWND hwnd,HWND hwndOldFocus)
{
	winid_t win;
	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(win->reqEvent&reqev_Line)
	{
		CreateCaret(hwnd,NULL,0,8);
		ShowCaret(hwnd);
		if(win->needsMore)
		{
			SetCaretPos(-10,-10);
			InvalidateRect(win->hwnd, NULL, FALSE);
			//DB UpdateWindow(win->hwnd);
		}
		else
			SetCaretPos(win->caretx,win->carety);
	}
}

static void TextBufferOnKillFocus(HWND hwnd,HWND hwndNewFocus)
{
	winid_t win;
	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(win->reqEvent&reqev_Line)
	{
		// HideCaret(hwnd);
		DestroyCaret();
	}
}

static BOOL TextBufferWordFromPoint(HDC dc,winid_t wnd,int w,int h,
									int x,int y,char **foundWord,int *foundLen)
{
	int cseg,cchar,ccount,llh;
	TextBuffData *tbd;
	int cx,cy,lineh;
	static char word[50];
	TCHAR wword[50];
	int wcount;
	BOOL lastLineCounted;
	HGDIOBJ oldFont;
	SIZE size;
	BOOL newline;

	/*
	 * Fake drawing the text in the window to get the word from point
	 */

	tbd=(TextBuffData *)wnd->data;
	ccount=0;
	cy=h;
	lastLineCounted=0;
	cseg=wnd->dataCount-1;
	while(cseg>=0)
	{
		cchar=tbd[cseg].textCount-1;
		while(cchar>=0)
		{
			if(tbd[cseg].text[cchar]=='\n')
			{
				// we have the beginning of a paragraph
				cy-=TextBufferParaHeight(dc,wnd,w,cseg,cchar+1,ccount,&llh);
				if(!lastLineCounted)
				{
					lastLineCounted=TRUE;
					cy-=llh;
				}
				if(cy<0)
					break;
				ccount=0;
			}
			else
				ccount++;
			cchar--;
		}
		if(cy<0)
			break;
		cseg--;
	}
	if(cy>0 && wnd->dataCount)
	{
		cy-=TextBufferParaHeight(dc,wnd,w,0,0,ccount-1,&llh);
		if(!lastLineCounted)
			cy-=llh;
	}
	if(cseg<0)
		cseg=cchar=0;
	else if(cchar<0)
		cchar=0;
	else
	{
		cchar++;
		if((unsigned)cchar>=tbd[cseg].textCount)
		{
			cseg++;
			cchar=0;
		}
	}
	cx=0;
	wcount=0;
	lineh=0;
	while((unsigned)cseg<wnd->dataCount)
	{
		oldFont=SelectObject(dc,wnd->styleFont[tbd[cseg].style]);
		GetTextExtentPoint32(dc,L" ",1,&size);
		lineh=lineh>size.cy?lineh:size.cy;
		while((unsigned)cchar<tbd[cseg].textCount)
		{
			newline=FALSE;
			word[wcount]=tbd[cseg].text[cchar++];
			if(word[wcount]==' ' || word[wcount]=='\n' || 
				wcount>=50 || (unsigned)cchar>=tbd[cseg].textCount)
			{
				// we have a word - check it
				if(word[wcount]=='\n')
					newline=TRUE;
				else
				{
					newline=FALSE;
					wcount++;
				}
				mbstowcs(wword,word,wcount);
				GetTextExtentPoint32(dc,wword,wcount,&size);
				if(cx>0 && cx+size.cx>w)
				{
					cy+=lineh;
					cx=size.cx;
					lineh=size.cy;
					if(x>=0 && x<=size.cx && y>=cy && y<=cy+size.cy)
					{
						*foundWord=word;
						*foundLen=wcount;
						SelectObject(dc,oldFont);
						return(TRUE);
					}
				}
				else
				{
					lineh=(size.cy>lineh)?size.cy:lineh;
					if(x>=cx && x<=cx+size.cx && y>=cy && y<=cy+size.cy)
					{
						*foundWord=word;
						*foundLen=wcount;
						SelectObject(dc,oldFont);
						return(TRUE);
					}
					cx+=size.cx;
				}
				if(newline)
				{
					cx=0;
					cy+=lineh;
					lineh=0;
				}
				wcount=0;
			}
			else
				wcount++;
		}
		SelectObject(dc,oldFont);
		cseg++;
		cchar=0;
	}
	return(FALSE);
}

static void TextBufferOnLButtonDown(HWND hwnd,BOOL fDblClick,int x,int y,UINT keyFlags)
{
	winid_t win;
	char *word;
	int len;
	char input[9];
	HDC dc;
	BOOL found;
	RECT rc;
	register int i,j;
	TextBuffData *tbd;

	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(win->needsMore)
	{
		win->lastSeg=win->printSeg;
		win->lastChar=win->printChar;
		win->needsMore=FALSE;
		SHSetNavBarText(mainWnd,NULL);
		InvalidateRect(hwnd,NULL,FALSE);
		UpdateWindow(hwnd);
		return;
	}
	if(win->reqEvent & reqev_Char)
	{
		// char input event requested. Send an space
		_post_glk_event(evtype_CharInput,win,0x20,0);
		return;
	}
	if(!(win->reqEvent & reqev_Line))
		return;
	// we have a line input event pending. Find the word
	GetClientRect(hwnd,&rc);
	dc=GetDC(hwnd);
	found=TextBufferWordFromPoint(dc,win,rc.right,rc.bottom,x,y,&word,&len);
	ReleaseDC(hwnd,dc);
	if(!found || !len)
		return;
	for(i=j=0;i<len && j<9;++i)
	{
		if(!isalnum(word[i]))
			continue;
		input[j++]=glk_char_to_lower(word[i]);
	}
	tbd=&((TextBuffData *)win->data)[win->dataCount-1];
	if(tbd->textCount+j+1>=tbd->textSize)
	{
		char *temp;
		temp=(char *)realloc(tbd->text,sizeof(char)*(tbd->textCount+j+1));
		if(!temp)
			return;
		tbd->text=temp;
		tbd->textSize=tbd->textCount+j+1;
	}
	memcpy(&tbd->text[tbd->textCount],input,j);
	tbd->textCount+=j;
	tbd->text[tbd->textCount++]=' ';
	InvalidateRect(hwnd,NULL,FALSE);
	UpdateWindow(hwnd);
}

static LRESULT CALLBACK TextBufferWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG(hwnd,WM_PAINT,TextBufferOnPaint);
		HANDLE_MSG(hwnd,WM_KEYDOWN,TextBufferOnKey);
		HANDLE_MSG(hwnd,WM_CHAR,TextBufferOnChar);
		HANDLE_MSG(hwnd,WM_SETFOCUS,TextBufferOnSetFocus);
		HANDLE_MSG(hwnd,WM_KILLFOCUS,TextBufferOnKillFocus);
		HANDLE_MSG(hwnd,WM_LBUTTONDOWN,TextBufferOnLButtonDown);
	}
	return(DefWindowProc(hwnd,msg,wParam,lParam));
}

static void TextGridDraw(HDC dc,winid_t win,int w,int h)
{
	TextGridData *data;
	int cellx,celly,cx,cy;
	HGDIOBJ oldFont;
	SIZE size;
	register int i,j;
	TCHAR wcs[2];
	char mbs[2];

	data=(TextGridData *)win->data;
	oldFont=SelectObject(dc,win->styleFont[0]);
	GetTextExtentPoint32(dc,L"0",1,&size);
	cellx=size.cx?size.cx:1;
	celly=size.cy?size.cy:1;
	SelectObject(dc,oldFont);
	cx=cy=0;
	for(i=0;(unsigned)i<win->dataCount;++i)
	{
		oldFont=SelectObject(dc,win->styleFont[data[i].style]);
		SetTextColor(dc,win->styleFG[data[i].style]);
		SetBkColor(dc,win->styleBG[data[i].style]);
		if(data[i].x!=-1)
			cx=data[i].x*cellx;
		if(data[i].y!=-1)
			cy=data[i].y*celly;
		for(j=0;(unsigned)j<data[i].textCount;++j)
		{
			if(data[i].text[j]=='\n')
			{
				cx=0;
				cy+=celly;
				continue;
			}
			if(cx+cellx>w)
			{
				cx=0;
				cy+=celly;
			}
			if(cy>=h)
				break;
			mbs[0]=data[i].text[j];
			mbs[1]='\0';
			mbstowcs(wcs,mbs,1);
			ExtTextOut(dc,cx,cy,ETO_OPAQUE,NULL,wcs,1,NULL);
			cx+=cellx;
		}
		SelectObject(dc,oldFont);
	}
}
	
static void TextGridOnPaint(HWND hwnd)
{
	PAINTSTRUCT ps;
	HDC hdc,memdc;
	winid_t wnd;
	HBRUSH hBrush;
	HGDIOBJ oldBitmap,oldBrush;
	HBITMAP hBitmap;
	RECT rc;

	hdc=BeginPaint(hwnd,&ps);
	wnd=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	// get window size
	GetClientRect(hwnd,&rc);
	// create memory DC and bitmap for image buffering
	memdc=CreateCompatibleDC(hdc);
	hBitmap=CreateCompatibleBitmap(hdc,rc.right,rc.bottom);
	oldBitmap=SelectObject(memdc,hBitmap);
	// clear area
	hBrush=CreateSolidBrush(wnd->styleBG[0]);
	oldBrush=SelectObject(memdc,hBrush);
	SelectObject(memdc,GetStockObject(NULL_PEN));
	Rectangle(memdc,ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
	SelectObject(memdc,oldBrush);
	DeleteObject(hBrush);
	// draw the text
	TextGridDraw(memdc,wnd,rc.right,rc.bottom);
	BitBlt(hdc,0,0,rc.right,rc.bottom,memdc,0,0,SRCCOPY);
	SelectObject(memdc,oldBitmap);
	DeleteObject(hBitmap);
	DeleteDC(memdc);
	EndPaint(hwnd,&ps);
}

static void TextGridOnKey(HWND hwnd,UINT vk,BOOL fDown,int crepeat,UINT flags)
{
	winid_t win;

	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(!(win->reqEvent & reqev_Char))
		return;
	switch(vk)
	{
	case VK_LEFT:
		_post_glk_event(evtype_CharInput,win,keycode_Left,0);
		break;
	case VK_RIGHT:
		_post_glk_event(evtype_CharInput,win,keycode_Right,0);
		break;
	case VK_UP:
		_post_glk_event(evtype_CharInput,win,keycode_Up,0);
		break;
	case VK_DOWN:
		_post_glk_event(evtype_CharInput,win,keycode_Down,0);
		break;
	case VK_RETURN:
		_post_glk_event(evtype_CharInput,win,keycode_Return,0);
		break;
	case VK_TAB:
		_post_glk_event(evtype_CharInput,win,keycode_Tab,0);
		break;
	}
}

static void TextGridOnChar(HWND hwnd,TCHAR ch,int cRepeat)
{
	winid_t win;
	TCHAR wcs[2];
	unsigned char mbs[2];

	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(!(win->reqEvent & reqev_Char))
		return;
	wcs[0]=ch;
	wcs[1]=L'\0';
	wcstombs(mbs,wcs,1);
	_post_glk_event(evtype_CharInput,win,(glui32)mbs[0],0);
}

static void TextGridOnLButtonDown(HWND hwnd,BOOL fDblClick,int x,int y,UINT keyFlags)
{
	winid_t win;
	HDC dc;
	HGDIOBJ oldFont;
	SIZE size;

	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(!(win->reqEvent & reqev_Mouse))
		return;
	dc=GetDC(hwnd);
	oldFont=SelectObject(dc,win->styleFont[0]);
	size.cx=size.cy=0;
	GetTextExtentPoint32(dc,L"0",1,&size);
	SelectObject(dc,oldFont);
	ReleaseDC(hwnd,dc);
	if(!size.cx)
		size.cx=1;
	if(!size.cy)
		size.cy=0;
	_post_glk_event(evtype_MouseInput,win,x/size.cx,y/size.cy);
}

static LRESULT CALLBACK TextGridWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG(hwnd,WM_PAINT,TextGridOnPaint);
		HANDLE_MSG(hwnd,WM_KEYDOWN,TextGridOnKey);
		HANDLE_MSG(hwnd,WM_CHAR,TextGridOnChar);
		HANDLE_MSG(hwnd,WM_LBUTTONDOWN,TextGridOnLButtonDown);
	}
	return(DefWindowProc(hwnd,msg,wParam,lParam));
}

static void GraphicsOnPaint(HWND hwnd)
{
	PAINTSTRUCT ps;
	winid_t win;
	HDC hdc,memdc;
	HBRUSH hBrush;
	HGDIOBJ oldBrush,oldBitmap;
	RECT rc;
	register int i;
	GraphicsData *gd;

	hdc=BeginPaint(hwnd,&ps);
	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	// get window size
	GetClientRect(hwnd,&rc);
	// clear area
	hBrush=CreateSolidBrush(win->styleBG[0]);
	oldBrush=SelectObject(hdc,hBrush);
	SelectObject(hdc,GetStockObject(NULL_PEN));
	Rectangle(hdc,ps.rcPaint.left,ps.rcPaint.top,ps.rcPaint.right,ps.rcPaint.bottom);
	SelectObject(hdc,oldBrush);
	DeleteObject(hBrush);
	gd=(GraphicsData *)win->data;
	for(i=0;(unsigned)i<win->dataCount;++i)
		if(gd[i].grtype==GRTYPE_PICT)
		{
			memdc=CreateCompatibleDC(hdc);
			oldBitmap=SelectObject(memdc,(HBITMAP)gd[i].data);
			StretchBlt(hdc,gd[i].x/graphicScale,gd[i].y/graphicScale,
				gd[i].w/graphicScale,gd[i].h/graphicScale,memdc,0,0,
				gd[i].origw,gd[i].origh,SRCCOPY);
			SelectObject(memdc,oldBitmap);
			DeleteDC(memdc);
		}
		else
		{
			hBrush=CreateSolidBrush((COLORREF)gd[i].data);
			oldBrush=SelectObject(hdc,hBrush);
			Rectangle(hdc,gd[i].x/graphicScale,gd[i].y/graphicScale,
				(gd[i].w+gd[i].x)/graphicScale,(gd[i].h+gd[i].y)/graphicScale);
			SelectObject(hdc,oldBrush);
		}
	EndPaint(hwnd,&ps);
}

static void GraphicsOnLButtonDown(HWND hwnd,BOOL fDblClick,int x,int y,UINT keyFlags)
{
	winid_t win;

	win=(winid_t)GetWindowLong(hwnd,GWL_USERDATA);
	if(!(win->reqEvent & reqev_Mouse))
		return;
	_post_glk_event(evtype_MouseInput,win,x,y);
}

static LRESULT CALLBACK GraphicsWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
	{
		HANDLE_MSG(hwnd,WM_PAINT,GraphicsOnPaint);
		HANDLE_MSG(hwnd,WM_LBUTTONDOWN,GraphicsOnLButtonDown);
	}
	return(DefWindowProc(hwnd,msg,wParam,lParam));
}

static LPTSTR regWinTypes[] = 
{
	L"TextBuffer",
	L"TextGrid"
};

static LPTSTR regStyles[] = 
{
	L"Normal",
	L"Emphasized",
	L"Preformatted",
	L"Header",
	L"Subheader",
	L"Alert",
	L"Note",
	L"BlockQuote",
	L"Input",
	L"User1",
	L"User2"
};

static void LoadStyleDefaults(void)
{
	struct glk_style_struct *gptr;
	register int i,j;

	for(i=0;i<2;++i)
		for(j=0;j<style_NUMSTYLES;++j)
		{
			gptr=&globalStyle[j][i];
			gptr->bg=RGB(255,255,255);
			gptr->fg=RGB(0,0,0);
			memset(&gptr->lf,0,sizeof(LOGFONT));
			gptr->lf.lfQuality = CLEARTYPE_QUALITY;
			if(i==1 || j==2)
				_tcscpy(gptr->lf.lfFaceName,L"Courier New");
			else
				_tcscpy(gptr->lf.lfFaceName,L"Tahoma");
			if(j==1 || j==3 || j==4 || j==5 || j==8)
				gptr->lf.lfWeight=700;
			else
				gptr->lf.lfWeight=400;
			if(j==6)
				gptr->lf.lfItalic=1;
			if(i==0 && j==3)
				gptr->lf.lfHeight=16;
			else if(i==0 && j==4)
				gptr->lf.lfHeight=14;
			else
				gptr->lf.lfHeight=12;
		}
	memcpy(moveMenu,L"n.\0e.\0s.\0w.\0u.\0d.\0ne.\0se.\0sw.\0nw.\0in.\0out.\0",44*sizeof(TCHAR));
	memcpy(actionMenu,L"get\0take\0inventory.\0examine\0look.\0",35*sizeof(TCHAR));
	imagesEnabled=TRUE;
	graphicScale=2;
}


static void LoadRegistrySettings(void)
{
	HKEY hAppKey;
	HKEY hKey;
	TCHAR keyName[256];
	DWORD valsize;
	register int i,j;
	HMENU hMenu,hSubMenu;
	NMNEWMENU nmMenu;

	if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SOFTWARE\\Apps\\pGlk",0,0,&hAppKey)==ERROR_SUCCESS)
	{	
		for(i=0;i<2;++i)
			for(j=0;j<style_NUMSTYLES;++j)
			{
				wsprintf(keyName,L"%s\\%s",regWinTypes[i],regStyles[j]);
				if(RegOpenKeyEx(hAppKey,keyName,0,0,&hKey)!=ERROR_SUCCESS)
					continue;
				valsize=sizeof(DWORD);
				RegQueryValueEx(hKey,L"BGColor",0,NULL,(LPBYTE)&globalStyle[j][i].bg,&valsize);
				valsize=sizeof(DWORD);
				RegQueryValueEx(hKey,L"FGColor",0,NULL,(LPBYTE)&globalStyle[j][i].fg,&valsize);
				valsize=LF_FACESIZE;
				RegQueryValueEx(hKey,L"Font",0,NULL,(LPBYTE)globalStyle[j][i].lf.lfFaceName,&valsize);
				valsize=sizeof(DWORD);
				RegQueryValueEx(hKey,L"FontSize",0,NULL,(LPBYTE)&globalStyle[j][i].lf.lfHeight,&valsize);
				valsize=sizeof(DWORD);
				RegQueryValueEx(hKey,L"FontWeight",0,NULL,(LPBYTE)&globalStyle[j][i].lf.lfWeight,&valsize);
				valsize=sizeof(DWORD);
				RegQueryValueEx(hKey,L"FontItalic",0,NULL,(LPBYTE)&globalStyle[j][i].lf.lfItalic,&valsize);
				valsize=sizeof(DWORD);
				RegQueryValueEx(hKey,L"FontQuality", 0, NULL, (LPBYTE)&globalStyle[j][i].lf.lfQuality,&valsize);
				RegCloseKey(hKey);
				//globalStyle[j][i].lf.lfQuality = 6;

			}
		valsize=1024*sizeof(TCHAR);
		RegQueryValueEx(hAppKey,L"MoveMenu",0,NULL,(LPBYTE)moveMenu,&valsize);
		valsize=1024*sizeof(TCHAR);
		RegQueryValueEx(hAppKey,L"ActionMenu",0,NULL,(LPBYTE)actionMenu,&valsize);
		RegCloseKey(hAppKey);
	}
	hMenu=(HMENU)SendMessage(cmdBar,SHCMBM_GETMENU,0,(LPARAM)&nmMenu);
	hSubMenu=GetSubMenu(hMenu,2);		// Move
	j=0;
	for(i=0;moveMenu[i];++i)
	{
		AppendMenu(hSubMenu,MF_STRING,20000+(j++),&moveMenu[i]);
		for(;moveMenu[i];++i);
	}
	hSubMenu=GetSubMenu(hMenu,3);		// Action
	j=0;
	for(i=0;actionMenu[i];++i)
	{
		AppendMenu(hSubMenu,MF_STRING,30000+(j++),&actionMenu[i]);
		for(;actionMenu[i];++i);
	}
	DrawMenuBar(cmdBar);
}

static void SaveRegistrySettings(void)
{
	HKEY hAppKey;
	HKEY hKey;
	TCHAR keyName[256];
	DWORD valsize;
	register int i,j;

	if(RegCreateKeyEx(HKEY_LOCAL_MACHINE,L"SOFTWARE\\Apps\\pGlk",0,
		L"",0,0,NULL,&hAppKey,&valsize)!=ERROR_SUCCESS)
		return;
	
	for(i=0;i<2;++i)
		for(j=0;j<style_NUMSTYLES;++j)
		{
			wsprintf(keyName,L"%s\\%s",regWinTypes[i],regStyles[j]);
			if(RegCreateKeyEx(hAppKey,keyName,0,L"",0,0,NULL,&hKey,&valsize)!=ERROR_SUCCESS)
				continue;
			RegSetValueEx(hKey,L"BGColor",0,REG_DWORD,(LPBYTE)&globalStyle[j][i].bg,sizeof(COLORREF));
			RegSetValueEx(hKey,L"FGColor",0,REG_DWORD,(LPBYTE)&globalStyle[j][i].fg,sizeof(COLORREF));
			valsize=_tcslen(globalStyle[j][i].lf.lfFaceName)+1;
			RegSetValueEx(hKey,L"Font",0,REG_SZ,(LPBYTE)globalStyle[j][i].lf.lfFaceName,valsize*sizeof(TCHAR));
			RegSetValueEx(hKey,L"FontSize",0,REG_DWORD,(LPBYTE)&globalStyle[j][i].lf.lfHeight,sizeof(DWORD));
			RegSetValueEx(hKey,L"FontWeight",0,REG_DWORD,(LPBYTE)&globalStyle[j][i].lf.lfWeight,sizeof(DWORD));
			RegSetValueEx(hKey,L"FontItalic",0,REG_DWORD,(LPBYTE)&globalStyle[j][i].lf.lfItalic,sizeof(DWORD));
			RegSetValueEx(hKey,L"FontQuality",0,REG_DWORD,(LPBYTE)&globalStyle[j][i].lf.lfQuality,sizeof(DWORD));
			RegCloseKey(hKey);
		}
	for(valsize=1;valsize<1024;++valsize)
		if(moveMenu[valsize]==L'\0' && moveMenu[valsize-1]==L'\0')
			break;
	valsize=(valsize+1)*sizeof(TCHAR);
	RegSetValueEx(hAppKey,L"MoveMenu",0,REG_MULTI_SZ,(LPBYTE)moveMenu,valsize);
	for(valsize=1;valsize<1024;++valsize)
		if(actionMenu[valsize]==L'\0' && actionMenu[valsize-1]==L'\0')
			break;
	valsize=(valsize+1)*sizeof(TCHAR);
	RegSetValueEx(hAppKey,L"ActionMenu",0,REG_MULTI_SZ,(LPBYTE)actionMenu,valsize);
	RegCloseKey(hAppKey);
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nShowCmd)
{
	WNDCLASS wc;
	ATOM mclass;
	RECT mainRect;
	MSG msg;

	firstTime = TRUE;
	hInst=hInstance;
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=GetStockObject(LTGRAY_BRUSH);
	wc.hCursor=NULL;
	wc.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(IDI_PGLULXE));
	wc.hInstance=hInstance;
	wc.lpfnWndProc=(WNDPROC)MainWndProc;
	wc.lpszClassName=L"MAIN";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	mclass=RegisterClass(&wc);
	if(!mclass)
	{
		MessageBox(NULL,L"Failed to register MAIN class",L"PocketTADS",MB_ICONSTOP|MB_OK);
		return 1;
	}
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=GetStockObject(WHITE_BRUSH);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hInstance;
	wc.lpfnWndProc=(WNDPROC)BlankWndProc;
	wc.lpszClassName=L"BLANK";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	mclass=RegisterClass(&wc);
	if(!mclass)
	{
		MessageBox(NULL,L"Failed to register BLANK class",L"PocketTADS",MB_ICONSTOP|MB_OK);
		return 1;
	}
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=GetStockObject(NULL_BRUSH);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hInstance;
	wc.lpfnWndProc=(WNDPROC)TextBufferWndProc;
	wc.lpszClassName=L"TEXTBUFFER";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	mclass=RegisterClass(&wc);
	if(!mclass)
	{
		MessageBox(NULL,L"Failed to register TEXTBUFFER class",L"PocketTADS",MB_ICONSTOP|MB_OK);
		return 1;
	}
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=GetStockObject(NULL_BRUSH);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hInstance;
	wc.lpfnWndProc=(WNDPROC)TextGridWndProc;
	wc.lpszClassName=L"TEXTGRID";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	mclass=RegisterClass(&wc);
	if(!mclass)
	{
		MessageBox(NULL,L"Failed to register TEXTGRID class",L"PocketTADS",MB_ICONSTOP|MB_OK);
		return 1;
	}
	wc.cbClsExtra=0;
	wc.cbWndExtra=0;
	wc.hbrBackground=GetStockObject(NULL_BRUSH);
	wc.hCursor=NULL;
	wc.hIcon=NULL;
	wc.hInstance=hInstance;
	wc.lpfnWndProc=(WNDPROC)GraphicsWndProc;
	wc.lpszClassName=L"GRAPHICS";
	wc.lpszMenuName=NULL;
	wc.style=CS_HREDRAW|CS_VREDRAW;
	mclass=RegisterClass(&wc);
	if(!mclass)
	{
		MessageBox(NULL,L"Failed to register GRAPHICS class",L"PocketTADS",MB_ICONSTOP|MB_OK);
		return 1;
	}
	mainWnd=CreateWindow(L"MAIN",L"PocketTADS",WS_VISIBLE,
		CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
		NULL,NULL,hInstance,NULL);
	if(!mainWnd)
	{
		MessageBox(NULL,L"Can't create main window",L"PocketTADS",MB_ICONSTOP|MB_OK);
		return 1;
	}
	GetWindowRect(mainWnd,&mainRect);
	mainRect.bottom-=MENU_HEIGHT;
	if(cmdBar)
		MoveWindow(mainWnd, mainRect.left, mainRect.top, mainRect.right, mainRect.bottom, FALSE);
	GetClientRect(mainWnd,&mainRect);
	ShowWindow(mainWnd,nShowCmd);
	UpdateWindow(mainWnd);
	LoadStyleDefaults();
	LoadRegistrySettings();
	if(lpCmdLine && lpCmdLine[0])
		_load_tads_file(lpCmdLine);
		//_load_glulx_file(lpCmdLine);
	while(GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return(0);					// never executed
}
