/* ibm1130_gui.c: IBM 1130 CPU simulator Console Display
 *
 *  Based on the SIMH package written by Robert M Supnik
 *
 * (C) Copyright 2002, Brian Knittel.
 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
 * usual yada-yada. Please keep this notice and the copyright in any distributions
 * or modifications.
 *
 * This is not a supported product, but I welcome bug reports and fixes.
 * Mail to simh@ibm1130.org
 *
 * 09-Apr-04 BLK	Changed code to use stock windows cursor IDC_HAND if available
 *
 * 02-Dec-02 BLK	Changed display, added printer and card reader icons
 *					Added drag and drop support for scripts and card decks
 *					Added support for physical card reader and printer (hides icons)
 *
 * 17-May-02 BLK	Pulled out of ibm1130_cpu.c
 */

/* ------------------------------------------------------------------------
 * Definitions
 * ------------------------------------------------------------------------ */

#include <stdarg.h>
#include <math.h>

#include "ibm1130_defs.h"
#include "ibm1130res.h"

#define UPDATE_BY_TIMER

#ifdef UPDATE_BY_TIMER
#  define UPDATE_INTERVAL	20				// set to desired number of updates/second
#else
#  define UPDATE_INTERVAL	5000 			// GUI: set to 100000/f where f = desired updates/second of 1130 time
#endif

#define UNIT_V_CR_EMPTY	   (UNIT_V_UF + 5)			/* NOTE: THESE MUST MATCH THE DEFINITION IN ibm1130_cr.c */
#define UNIT_CR_EMPTY	   (1u << UNIT_V_CR_EMPTY)
#define UNIT_V_PHYSICAL	   (UNIT_V_UF + 9)
#define UNIT_PHYSICAL	   (1u << UNIT_V_PHYSICAL)

#define UNIT_V_PHYSICAL_PTR	(UNIT_V_UF + 10)		/* NOTE: THESE MUST MATCH THE DEFINITION IN ibm1130_prt.c */
#define UNIT_PHYSICAL_PTR (1u << UNIT_V_PHYSICAL_PTR)

// I think I had it wrong; Program Load actually does start the processor after
// reading in the card?

#define PROGRAM_LOAD_STARTS_CPU

/* ------------------------------------------------------------------------
 * Function declarations
 * ------------------------------------------------------------------------ */

t_stat console_reset (DEVICE *dptr);

/* ------------------------------------------------------------------------ 
 * Console display - on Windows builds (only) this code displays the 1130 console
 * and toggle switches. It really enhances the experience.
 *
 * Currently, when the IPS throttle is nonzero, I update the display after every
 * UPDATE_INTERVAL instructions, plus or minus a random amount to avoid aliased
 * sampling in loops.  When UPDATE_INTERVAL is defined as zero, we update every
 * instruction no matter what the throttle. This makes the simulator too slow
 * but it's cool and helpful during development.
 * ------------------------------------------------------------------------ */

#define UNIT_V_DISPLAY   (UNIT_V_UF + 0)
#define UNIT_DISPLAY	 (1u << UNIT_V_DISPLAY)

MTAB console_mod[] = {
	{ UNIT_DISPLAY, 0,            "off", "OFF", NULL },
	{ UNIT_DISPLAY, UNIT_DISPLAY, "on",  "ON",  NULL },
	{ 0 }
};

UNIT console_unit = {UDATA (NULL, UNIT_DISABLE|UNIT_DISPLAY, 0) };

DEVICE console_dev = {
	"GUI", &console_unit, NULL, console_mod,
	1, 16, 16, 1, 16, 16,
	NULL, NULL, console_reset,
	NULL, NULL, NULL
};

// reset for the "console" display device 

extern char *read_line (char *cptr, int size, FILE *stream);
extern FILE *sim_log;
extern DEVICE *find_unit (char *cptr, UNIT **uptr);

extern UNIT cr_unit;									/* pointers to 1442 and 1132 (1403) printers */
extern UNIT prt_unit;

#ifndef GUI_SUPPORT
	void update_gui (int force)				  {}		/* stubs for non-GUI builds */
	void forms_check (int set)				  {}
	void print_check (int set)				  {}
	void keyboard_select (int select)		  {}
	void keyboard_selected (int select) 	  {}
	void disk_ready (int ready)         	  {}
	void disk_unlocked (int unlocked)   	  {}
	void gui_run (int running)          	  {} 
	static void init_console_window (void) 	  {}
	static void destroy_console_window (void) {}

	t_stat console_reset (DEVICE *dptr)		  					{return SCPE_OK;}
	void   stuff_cmd (char *cmd) 				  				{}
	t_bool stuff_and_wait (char *cmd, int timeout, int delay)	{return FALSE;}
	char  *read_cmdline (char *ptr, int size, FILE *stream)		{return read_line(ptr, size, stream);}
	void   remark_cmd (char *remark)			  				{printf("%s\n", remark); if (sim_log) fprintf(sim_log, "%s\n", remark);}
#else

t_stat console_reset (DEVICE *dptr)
{
	if (! sim_gui) {
		SETBIT(console_unit.flags, UNIT_DIS);			// disable the GUI
		CLRBIT(console_unit.flags, UNIT_DISPLAY);		// turn the GUI off
	}

	update_gui(FALSE);
	return SCPE_OK;
}

/* scp_panic - report fatal internal programming error */

void scp_panic (char *msg)
{
	fprintf(stderr, "%s\n", msg);
	exit(1);
}

#ifdef _WIN32
	/* only _WIN32 is defined right now */

#include <windows.h>

#define BUTTON_WIDTH  90
#define BUTTON_HEIGHT 50

#define IDC_KEYBOARD_SELECT		0
#define IDC_DISK_UNLOCK			1
#define IDC_RUN					2
#define IDC_PARITY_CHECK		3
#define IDC_UNUSED				4
#define IDC_FILE_READY			5
#define IDC_FORMS_CHECK			6
#define IDC_POWER_ON			7
#define IDC_POWER				8
#define IDC_PROGRAM_START		9
#define IDC_PROGRAM_STOP		10
#define IDC_LOAD_IAR			11
#define IDC_KEYBOARD			12
#define IDC_IMM_STOP			13
#define IDC_RESET				14
#define IDC_PROGRAM_LOAD		15

#define IDC_TEAR				16	// standard button
#define IDC_1442				17	// device images
#define IDC_1132				18

#define LAMPTIME        500			// 500 msec delay on updating
#define FLASH_TIMER_ID    1
#define UPDATE_TIMER_ID   2

#define RUNSWITCH_X 689				// center of the run mode switch dial
#define RUNSWITCH_Y 107
#define TOGGLES_X	122				// left edge of series of toggle switches

#define TXTBOX_X 	200				// text labels showing attached devices
#define TXTBOX_Y	300
#define TXTBOX_WIDTH   	195
#define TXTBOX_HEIGHT	 12

static BOOL   class_defined = FALSE;
static HWND   hConsoleWnd = NULL;
static HBITMAP hBitmap = NULL;
static HFONT  hFont = NULL;
static HFONT  hBtnFont = NULL;
static HFONT  hTinyFont = NULL;
static HBRUSH hbLampOut = NULL;
static HBRUSH hbWhite = NULL;
static HBRUSH hbBlack = NULL;
static HBRUSH hbGray  = NULL;
static HPEN   hSwitchPen = NULL;
static HPEN   hWhitePen  = NULL;
static HPEN   hBlackPen  = NULL;
static HPEN   hLtGreyPen = NULL;
static HPEN   hGreyPen   = NULL;
static HPEN   hDkGreyPen = NULL;
static int    hUpdateTimer = 0;
static int    hFlashTimer  = 0;

static HCURSOR hcArrow = NULL;
static HCURSOR hcHand  = NULL;
static HINSTANCE hInstance;
static HDC hCDC = NULL;
static char szConsoleClassName[] = "1130CONSOLE";
static DWORD PumpID = 0;
static HANDLE hPump = INVALID_HANDLE_VALUE;
static int bmwid, bmht;
static HANDLE hbm1442_full, hbm1442_empty, hbm1442_eof, hbm1442_middle;
static HANDLE hbm1132_full, hbm1132_empty;

static struct tag_btn {
	int x, y, wx, wy;
	char *txt;
	BOOL pushable, state;
	COLORREF clr;
	HBRUSH hbrLit, hbrDark;
	HWND   hBtn;
	BOOL   subclassed;

} btn[] = {
	0, 0, BUTTON_WIDTH, BUTTON_HEIGHT,	"KEYBOARD\nSELECT",		FALSE,	FALSE,	RGB(255,255,180),	NULL, NULL, NULL,	TRUE,
	0, 1, BUTTON_WIDTH, BUTTON_HEIGHT,	"DISK\nUNLOCK",			FALSE, 	TRUE,	RGB(255,255,180),	NULL, NULL, NULL,	TRUE,
	0, 2, BUTTON_WIDTH, BUTTON_HEIGHT,	"RUN",					FALSE,	FALSE,	RGB(0,255,0),		NULL, NULL, NULL,	TRUE,
	0, 3, BUTTON_WIDTH, BUTTON_HEIGHT,	"PARITY\nCHECK",		FALSE,	FALSE,	RGB(255,0,0),		NULL, NULL, NULL,	TRUE,

	1, 0, BUTTON_WIDTH, BUTTON_HEIGHT,	"",						FALSE, 	FALSE,	RGB(255,255,180),	NULL, NULL, NULL,	TRUE,
	1, 1, BUTTON_WIDTH, BUTTON_HEIGHT,	"FILE\nREADY",			FALSE, 	FALSE,	RGB(0,255,0),		NULL, NULL, NULL,	TRUE,
	1, 2, BUTTON_WIDTH, BUTTON_HEIGHT,	"FORMS\nCHECK",			FALSE, 	FALSE,	RGB(255,255,0),		NULL, NULL, NULL,	TRUE,
	1, 3, BUTTON_WIDTH, BUTTON_HEIGHT,	"POWER\nON",			FALSE, 	TRUE,	RGB(255,255,180),	NULL, NULL, NULL,	TRUE,

	2, 0, BUTTON_WIDTH, BUTTON_HEIGHT,	"POWER",				TRUE,	FALSE,	RGB(255,255,180), 	NULL, NULL, NULL,	TRUE,
	2, 1, BUTTON_WIDTH, BUTTON_HEIGHT,	"PROGRAM\nSTART",		TRUE,	FALSE,	RGB(0,255,0),		NULL, NULL, NULL,	TRUE,
	2, 2, BUTTON_WIDTH, BUTTON_HEIGHT,	"PROGRAM\nSTOP",		TRUE,	FALSE,	RGB(255,0,0),		NULL, NULL, NULL,	TRUE,
	2, 3, BUTTON_WIDTH, BUTTON_HEIGHT,	"LOAD\nIAR",			TRUE,	FALSE,	RGB(0,0,255),		NULL, NULL, NULL,	TRUE,

	3, 0, BUTTON_WIDTH, BUTTON_HEIGHT,	"KEYBOARD",				TRUE, 	FALSE,	RGB(255,255,180),	NULL, NULL, NULL,	TRUE,
	3, 1, BUTTON_WIDTH, BUTTON_HEIGHT,	"IMM\nSTOP",			TRUE, 	FALSE,	RGB(255,0,0),		NULL, NULL, NULL,	TRUE,
	3, 2, BUTTON_WIDTH, BUTTON_HEIGHT,	"CHECK\nRESET",			TRUE, 	FALSE,	RGB(0,0,255),		NULL, NULL, NULL,	TRUE,
	3, 3, BUTTON_WIDTH, BUTTON_HEIGHT,	"PROGRAM\nLOAD",		TRUE, 	FALSE,	RGB(0,0,255),		NULL, NULL, NULL,	TRUE,
							
	TXTBOX_X+40, TXTBOX_Y+25, 35, 12, 	"Tear",					TRUE,	FALSE,	0,					NULL, NULL, NULL,	FALSE,
	635, 238, 110, 110,                 "EMPTY_1442",			TRUE,   FALSE,  0,					NULL, NULL, NULL,	FALSE,
	635, 366, 110, 110,                 "EMPTY_1132",		    TRUE,   FALSE,  0,					NULL, NULL, NULL,	FALSE,
//	635, 366, 110, 110,                 "EMPTY_1132",		    TRUE,   FALSE,  0,					NULL, NULL, NULL,	FALSE,
};
#define NBUTTONS (sizeof(btn) / sizeof(btn[0]))

#define STATE_1442_EMPTY	0		// no cards (no file attached)
#define STATE_1442_FULL		1		// cards in hopper (file attached at BOF)
#define STATE_1442_MIDDLE	2		// cards in hopper and stacker (file attached, neither EOF nor BOF)
#define STATE_1442_EOF		3		// cards in stacker (file attached, at EOF)
#define STATE_1442_HIDDEN	4		// simulator is attached to physical card reader

#define STATE_1132_EMPTY	0		// no paper hanging out of printer
#define STATE_1132_FULL		1		// paper hanging out of printer
#define STATE_1132_HIDDEN	2		// printer is attached to physical printer

static struct tag_txtbox {
	int x, y;
	char *txt;
	char *unitname;
	int idctrl;
} txtbox[] = {
	TXTBOX_X, TXTBOX_Y, 	"Card Reader",	"CR",		-1,
	TXTBOX_X, TXTBOX_Y+ 25, "Printer",		"PRT",		IDC_1132,
	TXTBOX_X, TXTBOX_Y+ 50, "Disk 1",		"DSK0",		-1,
	TXTBOX_X, TXTBOX_Y+ 75, "Disk 2",		"DSK1",		-1,
	TXTBOX_X, TXTBOX_Y+100, "Disk 3",		"DSK2",		-1,
	TXTBOX_X, TXTBOX_Y+125, "Disk 4",		"DSK3",		-1,
	TXTBOX_X, TXTBOX_Y+150, "Disk 5",		"DSK4",		-1,
};
#define NTXTBOXES (sizeof(txtbox) / sizeof(txtbox[0]))

#define TXTBOX_BOTTOM	(TXTBOX_Y+150)

static void init_console_window (void);
static void destroy_console_window (void);
LRESULT CALLBACK ConsoleWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
static DWORD WINAPI Pump (LPVOID arg);
static void accept_dropped_file (HANDLE hDrop);
static void tear_printer (void);

#define NIXOBJECT(hObj) if (hObj != NULL) {DeleteObject(hObj); hObj = NULL;}

/* ------------------------------------------------------------------------ 
 * init_console_window - display the 1130 console. Actually just creates a thread 
 * to run the Pump routine which does the actual work.
 * ------------------------------------------------------------------------ */

static void init_console_window (void)
{
	static BOOL did_atexit = FALSE;

	if (hConsoleWnd != NULL)
		return;

	if (PumpID == 0)
		hPump = CreateThread(NULL, 0, Pump, 0, 0, &PumpID);

	if (! did_atexit) {
		atexit(destroy_console_window);
		did_atexit = TRUE;
	}
}

/* ------------------------------------------------------------------------ 
 * destroy_console_window - delete GDI objects.
 * ------------------------------------------------------------------------ */

static void destroy_console_window (void)
{
	int i;

	if (hConsoleWnd != NULL)
		SendMessage(hConsoleWnd, WM_CLOSE, 0, 0);	// cross thread call is OK

	if (hPump != INVALID_HANDLE_VALUE) {			// this is not the most graceful way to do it
		TerminateThread(hPump, 0);
		hPump  = INVALID_HANDLE_VALUE;
		PumpID = 0;
		hConsoleWnd = NULL;
	}
	if (hCDC != NULL) {
		DeleteDC(hCDC);
		hCDC = NULL;
	}

	NIXOBJECT(hBitmap)
	NIXOBJECT(hbLampOut)
	NIXOBJECT(hFont)
	NIXOBJECT(hBtnFont);
	NIXOBJECT(hTinyFont);
	NIXOBJECT(hcHand)
	NIXOBJECT(hSwitchPen)
	NIXOBJECT(hLtGreyPen)
	NIXOBJECT(hGreyPen)
	NIXOBJECT(hDkGreyPen)

	for (i = 0; i < NBUTTONS; i++) {
		NIXOBJECT(btn[i].hbrLit);
		NIXOBJECT(btn[i].hbrDark);
	}

//	if (class_defined) {
//		UnregisterClass(hInstance, szConsoleClassName);
//		class_defined = FALSE;
//	}
}

/* ------------------------------------------------------------------------ 
 * these variables hold the displayed versions of the system registers 
 * ------------------------------------------------------------------------ */

static int shown_iar = 0, shown_sar = 0, shown_sbr = 0, shown_afr = 0, shown_acc = 0, shown_ext  = 0;
static int shown_op  = 0, shown_tag = 0, shown_irq = 0, shown_ccc = 0, shown_cnd = 0, shown_wait = 0;
static int shown_ces = 0, shown_runmode = MODE_RUN;
static int CND;

/* ------------------------------------------------------------------------ 
 * RedrawRegion - mark a region for redrawing without background erase
 * ------------------------------------------------------------------------ */

static void RedrawRegion (HWND hWnd, int left, int top, int right, int bottom)
{
	RECT r;

	r.left   = left;
	r.top    = top;
	r.right  = right;
	r.bottom = bottom;

	InvalidateRect(hWnd, &r, FALSE);
}

/* ------------------------------------------------------------------------ 
 * RepaintRegion - mark a region for redrawing with background erase
 * ------------------------------------------------------------------------ */

static void RepaintRegion (HWND hWnd, int left, int top, int right, int bottom)
{
	RECT r;

	r.left   = left;
	r.top    = top;
	r.right  = right;
	r.bottom = bottom;

	InvalidateRect(hWnd, &r, TRUE);
}

/* ------------------------------------------------------------------------ 
 * update_gui - sees if anything on the console display has changed, and invalidates 
 * the changed regions. Then it calls UpdateWindow to force an immediate repaint. This
 * function (update_gui) should probably not be called every time through the main
 * instruction loop but it should be called at least whenever wait_state or int_req change, and then
 * every so many instructions.  It's also called after every simh command so manual changes are
 * reflected instantly.
 * ------------------------------------------------------------------------ */

void update_gui (BOOL force)
{	
	int i;
	BOOL state;
	static int in_here = FALSE;
	static int32 displayed = 0;
	RECT xin;

	if ((int32)(console_unit.flags & UNIT_DISPLAY) != displayed) {		// setting has changed
		displayed = console_unit.flags & UNIT_DISPLAY;
		if (displayed)
			init_console_window();
		else
			destroy_console_window();
	}

	if (hConsoleWnd == NULL)
		return;

	GUI_BEGIN_CRITICAL_SECTION				/* only one thread at a time, please */
	if (in_here) {
		GUI_END_CRITICAL_SECTION
		return;
	}
	in_here = TRUE;
	GUI_END_CRITICAL_SECTION

	CND = 0;	/* combine carry and V as two bits */
	if (C)
		CND |= 2;
	if (V)
		CND |= 1;

	int_lamps |= int_req;
	if (ipl >= 0)
		int_lamps |= (0x20 >> ipl);

	if (RUNMODE == MODE_LOAD)
		SBR = CES;			/* in load mode, SBR follows the console switches */

	if (IAR != shown_iar)
			{shown_iar = IAR; 		 RedrawRegion(hConsoleWnd, 75,    8, 364,  32);}	/* lamps: don't bother erasing bkgnd */
	if (SAR != shown_sar)
			{shown_sar = SAR; 		 RedrawRegion(hConsoleWnd, 75,   42, 364,  65);}
	if (ACC != shown_acc)
			{shown_acc = ACC; 		 RedrawRegion(hConsoleWnd, 75,  141, 364, 164);}
	if (EXT != shown_ext)
			{shown_ext = EXT; 		 RedrawRegion(hConsoleWnd, 75,  174, 364, 197);}
	if (SBR != shown_sbr)
			{shown_sbr = SBR; 		 RedrawRegion(hConsoleWnd, 75,   77, 364,  97);}
	if (OP  != shown_op)		  			 
			{shown_op  = OP;  		 RedrawRegion(hConsoleWnd, 501,   8, 595,  32);}
	if (TAG != shown_tag)
			{shown_tag = TAG; 		 RedrawRegion(hConsoleWnd, 501,  77, 595,  97);}

	if (int_lamps != shown_irq)
			{shown_irq = int_lamps;  RedrawRegion(hConsoleWnd, 501, 108, 595, 130);}

	if (CCC != shown_ccc)
			{shown_ccc = CCC;		 RedrawRegion(hConsoleWnd, 501, 141, 595, 164);}
	if (CND != shown_cnd)
			{shown_cnd = CND;        RedrawRegion(hConsoleWnd, 501, 174, 595, 197);}
	if ((wait_state|wait_lamp) != shown_wait)
			{shown_wait= (wait_state|wait_lamp); RedrawRegion(hConsoleWnd, 380,  77, 414,  97);}
	if (CES != shown_ces)
			{shown_ces = CES; 		 RepaintRegion(hConsoleWnd, TOGGLES_X-7, 230, TOGGLES_X+360, 275);}	/* console entry sw: do erase bkgnd */
	if (RUNMODE != shown_runmode)
			{shown_runmode = RUNMODE;RepaintRegion(hConsoleWnd, RUNSWITCH_X-50, RUNSWITCH_Y-50, RUNSWITCH_X+50, RUNSWITCH_Y+50);}

	int_lamps = 0;

	// this loop works with lamp buttons that are calculated on-the-fly only
	for (i = 0; i < NBUTTONS; i++) {
		if (btn[i].pushable)
			continue;

		switch (i) {
			case IDC_RUN:
				state = hFlashTimer || (running && ! wait_state);
				break;

			// this button is always off
//			case IDC_PARITY_CHECK:

			// these buttons are enabled/disabled directly
//			case IDC_POWER_ON:
//			case IDC_FILE_READY:
//			case IDC_FORMS_CHECK:
//			case IDC_KEYBOARD_SELECT:
//			case IDC_DISK_UNLOCK:
			default:
				continue;
		}

		if (state != btn[i].state) {				// state has changed
			EnableWindow(btn[i].hBtn, state);
			btn[i].state = state;
		}
	}

	if (force) {									// if force flag is set, update text region
		SetRect(&xin, TXTBOX_X, TXTBOX_Y, TXTBOX_X+TXTBOX_WIDTH, TXTBOX_BOTTOM+2*TXTBOX_HEIGHT);
		InvalidateRect(hConsoleWnd, &xin, TRUE);
	}

	state = ((cr_unit.flags & UNIT_ATT) == 0) ? STATE_1442_EMPTY  :
			 (cr_unit.flags & UNIT_PHYSICAL)  ? STATE_1442_HIDDEN :
			 (cr_unit.flags & UNIT_CR_EMPTY)  ? STATE_1442_EOF    : 
			  cr_unit.pos 					  ? STATE_1442_MIDDLE :
			  								    STATE_1442_FULL;

	if (state != btn[IDC_1442].state) {
		if (state == STATE_1442_HIDDEN)
			ShowWindow(btn[IDC_1442].hBtn, SW_HIDE);
		else {
			if (btn[IDC_1442].state == STATE_1442_HIDDEN)
				ShowWindow(btn[IDC_1442].hBtn, SW_SHOWNA);

			SendMessage(btn[IDC_1442].hBtn, STM_SETIMAGE, IMAGE_BITMAP,
				(LPARAM) (
					(state == STATE_1442_FULL)   ? hbm1442_full   :
					(state == STATE_1442_MIDDLE) ? hbm1442_middle : 
					(state == STATE_1442_EOF)    ? hbm1442_eof    :
					hbm1442_empty));
		}

		btn[IDC_1442].state = state;
	}

	state = ((prt_unit.flags & UNIT_ATT) == 0)    ? STATE_1132_EMPTY  :
			 (prt_unit.flags & UNIT_PHYSICAL_PTR) ? STATE_1132_HIDDEN :
			  prt_unit.pos						  ? STATE_1132_FULL :
			  								        STATE_1132_EMPTY;

	if (state != btn[IDC_1132].state) {
		if (state == STATE_1132_HIDDEN)
			ShowWindow(btn[IDC_1132].hBtn, SW_HIDE);
		else {
			if (btn[IDC_1132].state == STATE_1132_HIDDEN)
				ShowWindow(btn[IDC_1132].hBtn, SW_SHOWNA);

			SendMessage(btn[IDC_1132].hBtn, STM_SETIMAGE, IMAGE_BITMAP,
				(LPARAM) (
					(state == STATE_1132_FULL) ? hbm1132_full : hbm1132_empty));
		}

		btn[IDC_1132].state = state;
	}

	in_here = FALSE;
}

WNDPROC oldButtonProc = NULL;

/* ------------------------------------------------------------------------ 
 * ------------------------------------------------------------------------ */

LRESULT CALLBACK ButtonProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	int i;

	i = GetWindowLong(hWnd, GWL_ID);

	if (! btn[i].pushable) {
		if (uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK)
			return 0;

		if (uMsg == WM_CHAR)
			if ((TCHAR) wParam == ' ')
				return 0;
	}

   	return CallWindowProc(oldButtonProc, hWnd, uMsg, wParam, lParam);
}

/* ------------------------------------------------------------------------ 
 * ------------------------------------------------------------------------ */

static int occurs (char *txt, char ch)
{
	int count = 0;

	while (*txt)
		if (*txt++ == ch)
			count++;

	return count;
}

/* ------------------------------------------------------------------------ 
// turns out to get properly colored buttons you have to paint them yourself. Sheesh.
// On the plus side, this lets do a better job of aligning the button text than
// the button would by itself.
 * ------------------------------------------------------------------------ */

void PaintButton (LPDRAWITEMSTRUCT dis)
{
	int i = dis->CtlID, nc, nlines, x, y, dy;
 	BOOL down = dis->itemState & ODS_SELECTED;
	HPEN hOldPen;
	HFONT hOldFont;
	UINT oldAlign;
	COLORREF oldBk;
	char *txt, *tstart;

	if (! BETWEEN(i, 0, NBUTTONS-1))
		return;

	if (! btn[i].subclassed)
		return;

	FillRect(dis->hDC, &dis->rcItem, ((btn[i].pushable || power) && IsWindowEnabled(btn[i].hBtn)) ? btn[i].hbrLit : btn[i].hbrDark);

	if (! btn[i].pushable) {
		hOldPen = SelectObject(dis->hDC, hBlackPen);
		MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.top, NULL);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.bottom-1);
		LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.bottom-1);
		LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.top);
	}
	else if (down) {
		// do the three-D thing
		hOldPen = SelectObject(dis->hDC, hDkGreyPen);
		MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-2, NULL);
		LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.top);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

		SelectObject(dis->hDC, hWhitePen);
		MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-1, NULL);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.bottom-1);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

		SelectObject(dis->hDC, hGreyPen);
		MoveToEx(dis->hDC, dis->rcItem.left+1,  dis->rcItem.bottom-3, NULL);
		LineTo(dis->hDC,   dis->rcItem.left+1,  dis->rcItem.top+1);
		LineTo(dis->hDC,   dis->rcItem.right-3, dis->rcItem.top+1);
	}
	else {
		hOldPen = SelectObject(dis->hDC, hWhitePen);
		MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-2, NULL);
		LineTo(dis->hDC,   dis->rcItem.left,    dis->rcItem.top);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

		SelectObject(dis->hDC, hDkGreyPen);
		MoveToEx(dis->hDC, dis->rcItem.left,    dis->rcItem.bottom-1, NULL);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.bottom-1);
		LineTo(dis->hDC,   dis->rcItem.right-1, dis->rcItem.top);

		SelectObject(dis->hDC, hGreyPen);
		MoveToEx(dis->hDC, dis->rcItem.left+1,  dis->rcItem.bottom-2, NULL);
		LineTo(dis->hDC,   dis->rcItem.right-2, dis->rcItem.bottom-2);
		LineTo(dis->hDC,   dis->rcItem.right-2, dis->rcItem.top+1);
	}

	SelectObject(dis->hDC, hOldPen);

	hOldFont = SelectObject(dis->hDC, hBtnFont);
	oldAlign = SetTextAlign(dis->hDC, TA_CENTER|TA_TOP);
	oldBk    = SetBkMode(dis->hDC, TRANSPARENT);

	txt = btn[i].txt;
	nlines = occurs(txt, '\n')+1;
	x  = (dis->rcItem.left + dis->rcItem.right)  / 2;
	y  = (dis->rcItem.top  + dis->rcItem.bottom) / 2;

	dy = 14;
	y  = y - (nlines*dy)/2;

	if (down) {
		x += 1;
		y += 1;
	}

	for (;;) {
		for (nc = 0, tstart = txt; *txt && *txt != '\n'; txt++, nc++)
			;

		TextOut(dis->hDC, x, y, tstart, nc);

		if (*txt == '\0')
			break;

		txt++;
		y += dy;
	}

	SetTextAlign(dis->hDC, oldAlign);
	SetBkMode(dis->hDC,    oldBk);
	SelectObject(dis->hDC, hOldFont);
}
	
/* ------------------------------------------------------------------------ 
 * ------------------------------------------------------------------------ */

HWND CreateSubclassedButton (HWND hwParent, int i)
{
	HWND hBtn;
	int x, y;
	int r, g, b;

	y = bmht - (4*BUTTON_HEIGHT) + BUTTON_HEIGHT * btn[i].y;
	x = (btn[i].x < 2) ? (btn[i].x*BUTTON_WIDTH) : (598 - (4-btn[i].x)*BUTTON_WIDTH);

	if ((hBtn = CreateWindow("BUTTON", btn[i].txt, WS_CHILD|WS_VISIBLE|BS_CENTER|BS_MULTILINE|BS_OWNERDRAW,
			x, y, BUTTON_WIDTH, BUTTON_HEIGHT, hwParent, (HMENU) i, hInstance, NULL)) == NULL)
		return NULL;

	btn[i].hBtn = hBtn;

	if (oldButtonProc == NULL)
		oldButtonProc = (WNDPROC) GetWindowLong(hBtn, GWL_WNDPROC);

	btn[i].hbrLit = CreateSolidBrush(btn[i].clr);

	if (! btn[i].pushable) {
		r = GetRValue(btn[i].clr) / 4;
		g = GetGValue(btn[i].clr) / 4;
		b = GetBValue(btn[i].clr) / 4;

		btn[i].hbrDark = CreateSolidBrush(RGB(r,g,b));
		EnableWindow(hBtn, FALSE);
	}

	SetWindowLong(hBtn, GWL_WNDPROC, (LONG) ButtonProc);
	return hBtn;
}

/* ------------------------------------------------------------------------ 
 * Pump - thread that takes care of the console window. It has to be a separate thread so that it gets
 * execution time even when the simulator is compute-bound or IO-blocked. This routine creates the window
 * and runs a standard Windows message pump. The window function does the actual display work.
 * ------------------------------------------------------------------------ */

static DWORD WINAPI Pump (LPVOID arg)
{
	MSG msg;
	int wx, wy, i;
	RECT r, ra;
	BITMAP bm;
	WNDCLASS cd;
	HDC hDC;
	HWND hActWnd;

	hActWnd = GetForegroundWindow();

	if (! class_defined) {							/* register Window class */
		hInstance = GetModuleHandle(NULL);

		memset(&cd, 0, sizeof(cd));
		cd.style         = CS_NOCLOSE;
		cd.lpfnWndProc   = ConsoleWndProc;
		cd.cbClsExtra    = 0;
		cd.cbWndExtra    = 0;
		cd.hInstance     = hInstance;
		cd.hIcon         = NULL;
		cd.hCursor       = hcArrow;
		cd.hbrBackground = NULL;
		cd.lpszMenuName  = NULL;
		cd.lpszClassName = szConsoleClassName;

		if (! RegisterClass(&cd)) {
			PumpID = 0;
			return 0;
		}

		class_defined = TRUE;
	}

	hbWhite    = GetStockObject(WHITE_BRUSH);			/* create or fetch useful GDI objects */
	hbBlack    = GetStockObject(BLACK_BRUSH);			/* create or fetch useful GDI objects */
	hbGray     = GetStockObject(GRAY_BRUSH);
	hSwitchPen = CreatePen(PS_SOLID, 5, RGB(255,255,255));

	hWhitePen  = GetStockObject(WHITE_PEN);
	hBlackPen  = GetStockObject(BLACK_PEN);
	hLtGreyPen = CreatePen(PS_SOLID, 1, RGB(190,190,190));
	hGreyPen   = CreatePen(PS_SOLID, 1, RGB(128,128,128));
	hDkGreyPen = CreatePen(PS_SOLID, 1, RGB(64,64,64));

	hcArrow    = LoadCursor(NULL,      IDC_ARROW);
#ifdef IDC_HAND
	hcHand     = LoadCursor(NULL, IDC_HAND);							// use stock object provided by Windows
	if (hcHand == NULL)
		hcHand = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYHAND));
#else
	hcHand     = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYHAND));
#endif

	if (hBitmap   == NULL)
		hBitmap   = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_CONSOLE));
	if (hbLampOut == NULL)
		hbLampOut = CreateSolidBrush(RGB(50,50,50));
	if (hFont     == NULL)
		hFont     = CreateFont(-10, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FIXED_PITCH, FF_SWISS, "Arial");
	if (hBtnFont  == NULL)
		hBtnFont  = CreateFont(-12, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FIXED_PITCH, FF_SWISS, "Arial");
	if (hTinyFont == NULL)
		hTinyFont = CreateFont(-10, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, FIXED_PITCH, FF_SWISS, "Arial");

	if (hConsoleWnd == NULL) {						/* create window */
		if ((hConsoleWnd = CreateWindow(szConsoleClassName, "IBM 1130", WS_OVERLAPPED|WS_CLIPCHILDREN, 0, 0, 200, 200, NULL, NULL, hInstance, NULL)) == NULL) {
			PumpID = 0;
			return 0;
		}

		DragAcceptFiles(hConsoleWnd, TRUE);			/* let it accept dragged files (scripts) */
	}

	GetObject(hBitmap, sizeof(bm), &bm);			/* get bitmap size */
	bmwid = bm.bmWidth;
	bmht  = bm.bmHeight;

	for (i = 0; i < NBUTTONS; i++) {
		if (! btn[i].subclassed)
			continue;

		CreateSubclassedButton(hConsoleWnd, i);
		if (! btn[i].pushable)
			EnableWindow(btn[i].hBtn, btn[i].state);
	}

// This isn't needed anymore, now that we have the big printer icon -- it acts like a button now
//	i = IDC_TEAR;
//	btn[i].hBtn = CreateWindow("BUTTON", btn[i].txt, WS_CHILD|WS_VISIBLE|BS_CENTER,
//			btn[i].x, btn[i].y, btn[i].wx, btn[i].wy, hConsoleWnd, (HMENU) i, hInstance, NULL);
//
//	SendMessage(btn[i].hBtn, WM_SETFONT, (WPARAM) hTinyFont, TRUE);

	hbm1442_full   = LoadBitmap(hInstance, "FULL_1442");
	hbm1442_empty  = LoadBitmap(hInstance, "EMPTY_1442");
	hbm1442_eof    = LoadBitmap(hInstance, "EOF_1442");
	hbm1442_middle = LoadBitmap(hInstance, "MIDDLE_1442");
	hbm1132_full   = LoadBitmap(hInstance, "FULL_1132");
	hbm1132_empty  = LoadBitmap(hInstance, "EMPTY_1132");

	i = IDC_1442;

	btn[i].hBtn = CreateWindow("STATIC", btn[i].txt, WS_CHILD|WS_VISIBLE|SS_BITMAP|SS_SUNKEN|WS_BORDER|SS_REALSIZEIMAGE|SS_NOTIFY,
			btn[i].x, btn[i].y, btn[i].wx, btn[i].wy, hConsoleWnd, (HMENU) i, hInstance, NULL);
	btn[i].state = STATE_1442_EMPTY;

//	wx = SendMessage(btn[i].hBtn, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbm1442_full);
	wx = SendMessage(btn[i].hBtn, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbm1442_empty);

	i = IDC_1132;

	btn[i].hBtn = CreateWindow("STATIC", btn[i].txt, WS_CHILD|WS_VISIBLE|SS_BITMAP|SS_SUNKEN|WS_BORDER|SS_REALSIZEIMAGE|SS_NOTIFY,
			btn[i].x, btn[i].y, btn[i].wx, btn[i].wy, hConsoleWnd, (HMENU) i, hInstance, NULL);
	btn[i].state = FALSE;

	wx = SendMessage(btn[i].hBtn, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hbm1132_empty);

//	for (i = 0; i < NTXTBOXES; i++) {
//		txtbox[i].hBox = CreateWindow("EDIT", txtbox[i].txt, 
//				WS_CHILD|WS_VISIBLE|ES_LEFT|ES_READONLY,
//				txtbox[i].x, txtbox[i].y, TXTBOX_WIDTH, TXTBOX_HEIGHT, hConsoleWnd, (HMENU) (i+100), hInstance, NULL);
//		SendMessage(txtbox[i].hBox, WM_SETFONT, (WPARAM) hTinyFont, TRUE);
//	}

	GetWindowRect(hConsoleWnd, &r);					/* get window size as created */
	wx = r.right  - r.left + 1;
	wy = r.bottom - r.top  + 1;

	if (hCDC == NULL) {								/* get a memory DC and select the bitmap into ti */
		hDC = GetDC(hConsoleWnd);
		hCDC = CreateCompatibleDC(hDC);
		SelectObject(hCDC, hBitmap);
		ReleaseDC(hConsoleWnd, hDC);
	}

	GetClientRect(hConsoleWnd, &r);
	wx = (wx - r.right  - 1) + bmwid;				/* compute new desired size based on how client area came out */
	wy = (wy - r.bottom - 1) + bmht;
	MoveWindow(hConsoleWnd, 0, 0, wx, wy, FALSE);	/* resize window */

	ShowWindow(hConsoleWnd, SW_SHOWNOACTIVATE);		/* display it */
	UpdateWindow(hConsoleWnd);

	if (hActWnd != NULL) {							/* bring console (sim) window back to top */
		GetWindowRect(hConsoleWnd, &r);
		ShowWindow(hActWnd, SW_NORMAL);				/* and move it just below the display window */
		SetWindowPos(hActWnd, HWND_TOP, 0, r.bottom, 0, 0, SWP_NOSIZE);
		GetWindowRect(hActWnd, &ra);
		if (ra.bottom >= GetSystemMetrics(SM_CYSCREEN)) {	/* resize if it goes of bottom of screen */
			ra.bottom = GetSystemMetrics(SM_CYSCREEN) - 1;
			SetWindowPos(hActWnd, 0, 0, 0, ra.right-ra.left+1, ra.bottom-ra.top+1, SWP_NOZORDER|SWP_NOMOVE);
		}
	}

	if (running)									/* if simulator is already running, start update timer */
		gui_run(TRUE);

	while (GetMessage(&msg, hConsoleWnd, 0, 0)) {	/* message pump - this basically loops forevermore */
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	if (hConsoleWnd != NULL) { 
		DragAcceptFiles(hConsoleWnd, FALSE);		/* unregister as drag/drop target */
		DestroyWindow(hConsoleWnd);					/* but if a quit message got posted, clean up */
		hConsoleWnd = NULL;
	}

	PumpID = 0;
	return 0;
}

/* ------------------------------------------------------------------------ 
 * DrawBits - starting at position (x,y), draw lamps for nbits bits of word 'bits',	looking only at masked bits
 * ------------------------------------------------------------------------ */

static void DrawBits (HDC hDC, int x, int y, int bits, int nbits, int mask, char *syms)
{
	int i, b = 0x0001 << (nbits-1);

	for (i = 0; i < nbits; i++, b >>= 1) {
		if (mask & b) {								/* select white or black lettering then write 2 chars */
			SetTextColor(hDC, (b & bits && power) ? RGB(255,255,255) : RGB(0,0,0));
			TextOut(hDC, x, y, syms, 2);
		}
		syms += 2;									/* go to next symbol pair */

		if (i < 10)
			x += 15;								/* step between lamps */
		else
			x += 19;

		if (x < 500) {
			if (b & 0x1110)
				x += 10;							/* step over nibble divisions on left side */
			else if (b & 0x0001)
				x += 9;
		}
	}
}

/* ------------------------------------------------------------------------ 
 * DrawToggles - display the console sense switches
 * ------------------------------------------------------------------------ */


static void DrawToggles (HDC hDC, int bits)
{
	int b, x;

	for (b = 0x8000, x = TOGGLES_X; b != 0; b >>= 1) {
		if (shown_ces & b) {			/* up */
			SelectObject(hDC, hbWhite);
			Rectangle(hDC, x, 232, x+9, 240);
			SelectObject(hDC, hbGray);
			Rectangle(hDC, x, 239, x+9, 255);
 		}
		else {							/* down */
			SelectObject(hDC, hbWhite);
			Rectangle(hDC, x, 263, x+9, 271);
			SelectObject(hDC, hbGray);
			Rectangle(hDC, x, 248, x+9, 264);
		}

		x += (b & 0x1111) ? 31 : 21;
	}
}

/* ------------------------------------------------------------------------ 
 * DrawRunmode - draw the run mode rotary switch's little tip
 * ------------------------------------------------------------------------ */

void DrawRunmode (HDC hDC, int mode)
{
	double angle = (mode*45. + 90.) * 3.1415926 / 180.;		/* convert mode position to angle in radians */
	double ca, sa;											/* sine and cosine */
	int x0, y0, x1, y1;
	HPEN hOldPen;

	ca = cos(angle);
	sa = sin(angle);

	x0 = RUNSWITCH_X + (int) (20.*ca + 0.5);		/* inner radius */
	y0 = RUNSWITCH_Y - (int) (20.*sa + 0.5);
	x1 = RUNSWITCH_X + (int) (25.*ca + 0.5);		/* outer radius */
	y1 = RUNSWITCH_Y - (int) (25.*sa + 0.5);

	hOldPen = SelectObject(hDC, hSwitchPen);

	MoveToEx(hDC, x0, y0, NULL);
	LineTo(hDC, x1, y1);

	SelectObject(hDC, hOldPen);
}

/* ------------------------------------------------------------------------ 
 * HandleClick - handle mouse clicks on the console window. Now we just 
 * look at the console sense switches.  Actual says this is a real click, rather
 * than a mouse-region test.  Return value TRUE means the cursor is over a hotspot.
 * ------------------------------------------------------------------------ */

static BOOL HandleClick (HWND hWnd, int xh, int yh, BOOL actual)
{
	int b, x, r, ang, i;

	for (b = 0x8000, x = TOGGLES_X; b != 0; b >>= 1) {
		if (BETWEEN(xh, x-3, x+8+3) && BETWEEN(yh, 230, 275)) {
			if (actual) {
				CES ^= b;						/* a hit. Invert the bit and redisplay */
				update_gui(TRUE);
			}
			return TRUE;
		}
		x += (b & 0x1111) ? 31 : 21;
	}

	if (BETWEEN(xh, RUNSWITCH_X-50, RUNSWITCH_X+50) && BETWEEN(yh, RUNSWITCH_Y-50, RUNSWITCH_Y+50)) {		/* hit near rotary switch */
		ang = (int) (atan2(RUNSWITCH_X-xh, RUNSWITCH_Y-yh)*180./3.1415926);	/* this does implicit 90 deg rotation by the way */
		r = (int) sqrt((xh-RUNSWITCH_X)*(xh-RUNSWITCH_X)+(yh-RUNSWITCH_Y)*(yh-RUNSWITCH_Y));
		if (r > 12) {
			for (i = MODE_LOAD; i <= MODE_INT_RUN; i++) {
				if (BETWEEN(ang, i*45-12, i*45+12)) {
					if (actual) {
						RUNMODE = i;
						update_gui(TRUE);
					}
					return TRUE;
				}
			}
			
		}
	}

	return FALSE;
}

/* ------------------------------------------------------------------------ 
 * DrawConsole - refresh the console display. (This routine could be sped up by intersecting
 * the various components' bounding rectangles with the repaint rectangle.  The bounding rects
 * could be put into an array and used both here and in the refresh routine).
 *
 * RedrawRegion -> force repaint w/o background redraw. used for lamps which are drawn in the same place in either state
 * RepaintRegion-> repaint with background redraw. Used for toggles which change position.
 * ------------------------------------------------------------------------ */

static void DrawConsole (HDC hDC, PAINTSTRUCT *ps)
{
	static char digits[] = " 0 1 2 3 4 5 6 7 8 9101112131415";
	static char cccs[]   = "3216 8 4 2 1";
	static char cnds[]   = " C V";
	static char waits[]  = " W";
	HFONT hOldFont, hOldBrush;
	RECT xout, xin;
	int i, n;
	DEVICE *dptr;
	UNIT *uptr;
	t_bool enab;
	char nametemp[50], *dispname;

	hOldFont  = SelectObject(hDC, hFont);			/* use that tiny font */
	hOldBrush = SelectObject(hDC, hbWhite);

	SetBkMode(hDC, TRANSPARENT);					/* overlay letters w/o changing background */

	DrawBits(hDC,  76,  15, shown_iar,    16, 0x3FFF, digits);
	DrawBits(hDC,  76, 	48, shown_sar,    16, 0x3FFF, digits);
	DrawBits(hDC,  76,  81, shown_sbr,    16, 0xFFFF, digits);
	DrawBits(hDC,  76, 147, shown_acc,    16, 0xFFFF, digits);
	DrawBits(hDC,  76, 180, shown_ext,    16, 0xFFFF, digits);

	DrawBits(hDC, 506,  15, shown_op,      5, 0x001F, digits);
	DrawBits(hDC, 506,  81, shown_tag,     4, 0x0007, digits);
	DrawBits(hDC, 506, 114, shown_irq,     6, 0x003F, digits);
	DrawBits(hDC, 506, 147, shown_ccc,     6, 0x003F, cccs);
	DrawBits(hDC, 506, 180, shown_cnd,     2, 0x0003, cnds);

	DrawBits(hDC, 390,  81, shown_wait?1:0,1, 0x0001, waits);

	DrawToggles(hDC, shown_ces);

	DrawRunmode(hDC, shown_runmode);

	SelectObject(hDC, hOldFont);
	SelectObject(hDC, hOldBrush);

	SetBkColor(hDC, RGB(0,0,0));

	SetRect(&xin, TXTBOX_X, TXTBOX_Y, TXTBOX_X+TXTBOX_WIDTH, TXTBOX_BOTTOM+TXTBOX_HEIGHT);
	if (IntersectRect(&xout, &xin, &ps->rcPaint)) {
		hOldFont = SelectObject(hDC, hTinyFont);

		for (i = 0; i < NTXTBOXES; i++) {
			enab = FALSE;

			dptr = find_unit(txtbox[i].unitname, &uptr);
			if (dptr != NULL && uptr != NULL) {
				if (uptr->flags & UNIT_DIS) {
					SetTextColor(hDC, RGB(128,0,0));
				}
				else if (uptr->flags & UNIT_ATT) {
					SetTextColor(hDC, RGB(0,0,255));
					if ((n = strlen(uptr->filename)) > 30) {
						strcpy(nametemp, "...");
						strcpy(nametemp+3, uptr->filename+n-30);
						dispname = nametemp;						
					}
					else
						dispname = uptr->filename;

					TextOut(hDC, txtbox[i].x+25, txtbox[i].y+TXTBOX_HEIGHT, dispname, strlen(dispname));
					SetTextColor(hDC, RGB(255,255,255));
					enab = TRUE;
				}
				else {
					SetTextColor(hDC, RGB(128,128,128));
				}
				TextOut(hDC, txtbox[i].x, txtbox[i].y, txtbox[i].txt, strlen(txtbox[i].txt));
			}

			if (txtbox[i].idctrl >= 0)
				EnableWindow(btn[txtbox[i].idctrl].hBtn, enab);
		}

		SelectObject(hDC, hOldFont);
	}
}

/* ------------------------------------------------------------------------ 
 * Handles button presses. Remember that this occurs in the context of 
 * the Pump thread, not the simulator thread.
 * ------------------------------------------------------------------------ */

void flash_run (void)              
{
	EnableWindow(btn[IDC_RUN].hBtn, TRUE);		// enable the run lamp

	if (hFlashTimer != 0)
		KillTimer(hConsoleWnd, FLASH_TIMER_ID);	// (re)schedule lamp update

	hFlashTimer = SetTimer(hConsoleWnd, FLASH_TIMER_ID, LAMPTIME, NULL);
}

void gui_run (int running)
{
	if (running && hUpdateTimer == 0 && hConsoleWnd != NULL) {
		hUpdateTimer = SetTimer(hConsoleWnd, UPDATE_TIMER_ID, 1000/UPDATE_INTERVAL, NULL);
	}
	else if (hUpdateTimer != 0 && ! running) {
		KillTimer(hConsoleWnd, UPDATE_TIMER_ID);
		hUpdateTimer = 0;
	}
	flash_run();								/* keep run lamp active for a while after we stop running */
} 

void HandleCommand (HWND hWnd, WORD wNotify, WORD idCtl, HWND hwCtl)
{
	int i;

	switch (idCtl) {
		case IDC_POWER:						/* toggle system power */
			power = ! power;
			reset_all(0);
			if (running && ! power) {		/* turning off */
				reason = STOP_POWER_OFF;
// this prevents message pump from running, which unfortunately locks up
// the emulator thread when it calls gui_run(FALSE) which calls EnableWindow on the Run lamp
//				while (running)
//					Sleep(10);				/* wait for execution thread to exit */
			}

			btn[IDC_POWER_ON].state = power;
			EnableWindow(btn[IDC_POWER_ON].hBtn, power);

			for (i = 0; i < NBUTTONS; i++)	/* repaint all of the lamps */
				if (! btn[i].pushable)
					InvalidateRect(btn[i].hBtn, NULL, TRUE);

			break;

		case IDC_PROGRAM_START:				/* begin execution */
			if (! running) {
				switch (RUNMODE) {
					case MODE_INT_RUN:
					case MODE_RUN:
					case MODE_SI:
						stuff_cmd("go");
						break;

					case MODE_DISP:			/* display core and advance IAR */
						ReadW(IAR);
						IAR = IAR+1;
						flash_run();		/* illuminate run lamp for .5 sec */
						break;

					case MODE_LOAD:			/* store to core and advance IAR */
						WriteW(IAR, CES);
						IAR = IAR+1;
						flash_run();
						break;
				}
			}
			break;

		case IDC_PROGRAM_STOP:
			if (running) {					/* potential race condition here */
				GUI_BEGIN_CRITICAL_SECTION
				SETBIT(cpu_dsw, CPU_DSW_PROGRAM_STOP);
				SETBIT(ILSW[5], ILSW_5_PROGRAM_STOP);
				int_req |= INT_REQ_5;
				GUI_END_CRITICAL_SECTION
			}
			break;

		case IDC_LOAD_IAR:
			if (! running) {
				IAR = CES & 0x3FFF;			/* set IAR from console entry switches */
			}
			break;

		case IDC_KEYBOARD:					/* toggle between console/keyboard mode */
			break;

		case IDC_IMM_STOP:
			if (running) {
				reason = STOP_WAIT;			/* terminate execution without setting wait_mode */

// this prevents message pump from running, which unfortunately locks up
// the emulator thread when it calls gui_run(FALSE) which calls EnableWindow on the Run lamp
//				while (running)
//					Sleep(10);				/* wait for execution thread to exit */
			}
			break;

		case IDC_RESET:
			if (! running) {				/* check-reset is disabled while running */
				reset_all(0);
				forms_check(0);				/* clear forms-check status */
				print_check(0);
			}
			break;

		case IDC_PROGRAM_LOAD:
			if (! running) {				/* if card reader is attached to a file, do cold start read of one card */
				IAR = 0;					/* reset IAR */
#ifdef PROGRAM_LOAD_STARTS_CPU
				stuff_cmd("boot cr");
#else
				if (cr_boot(0, NULL) != SCPE_OK)	/* load boot card */
					remark_cmd("IPL failed");
#endif
			}
			break;

		case IDC_TEAR:						/* "tear off printer output" */
		case IDC_1132:						/* do same if they click on the printer icon */
			if (btn[IDC_1132].state && (wNotify == STN_CLICKED || wNotify == STN_DBLCLK))
				tear_printer();
			break;

		case IDC_1442:
			if (btn[IDC_1442].state == STATE_1442_FULL || wNotify == STN_DBLCLK)
				stuff_cmd("detach cr");
			else if (btn[IDC_1442].state != STATE_1442_EMPTY && wNotify == STN_CLICKED) {
				cr_rewind();
				update_gui(TRUE);
			}
			break;
	}
	
	update_gui(FALSE);
}

/* ------------------------------------------------------------------------ 
 * ConsoleWndProc - window process for the console display
 * ------------------------------------------------------------------------ */

LRESULT CALLBACK ConsoleWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	HDC hDC;
	PAINTSTRUCT ps;
	POINT p;
	RECT clip, xsect, rbmp;
	int i;

	switch (uMsg) {
		case WM_CLOSE:
			DestroyWindow(hWnd);
			break;

		case WM_DESTROY:
			gui_run(FALSE);
			hConsoleWnd = NULL;
			break;

		case WM_ERASEBKGND:
			hDC = (HDC) wParam;
			GetClipBox(hDC, &clip);
			SetRect(&rbmp, 0, 0, bmwid, bmht);
			if (IntersectRect(&xsect, &clip, &rbmp))
				BitBlt(hDC, xsect.left, xsect.top, xsect.right-xsect.left+1, xsect.bottom-xsect.top+1, hCDC, xsect.left, xsect.top, SRCCOPY);
//			rbmp.top = rbmp.bottom;
//			rbmp.bottom += 200;
//			if (IntersectRect(&xsect, &clip, &rbmp))
//				FillRect(hDC, &xsect, hbBlack);
			return TRUE;			/* let Paint do this so we know what the update region is (ps.rcPaint) */

		case WM_PAINT:
			hDC = BeginPaint(hWnd, &ps);
			DrawConsole(hDC, &ps);
			EndPaint(hWnd, &ps);
			break;

		case WM_COMMAND:							/* button click */
			HandleCommand(hWnd, HIWORD(wParam), LOWORD(wParam), (HWND) lParam);
			break;

		case WM_CTLCOLOREDIT:						/* text color for edit controls */
			SetBkColor((HDC) wParam, RGB(0,0,0));
			SetTextColor((HDC) wParam, RGB(255,255,255));
			break;

		case WM_DRAWITEM:
			PaintButton((LPDRAWITEMSTRUCT) lParam);
			break;

		case WM_SETCURSOR:
			GetCursorPos(&p);
			ScreenToClient(hWnd, &p);
			SetCursor(HandleClick(hWnd, p.x, p.y, FALSE) ? hcHand : hcArrow);
			return TRUE;

		case WM_LBUTTONDOWN:
			HandleClick(hWnd, LOWORD(lParam), HIWORD(lParam), TRUE);
			break;

		case WM_CTLCOLORBTN:
			i = GetWindowLong((HWND) lParam, GWL_ID);
			if (BETWEEN(i, 0, NBUTTONS-1))
				return (LRESULT) (power && IsWindowEnabled((HWND) lParam) ? btn[i].hbrLit : btn[i].hbrDark);

		case WM_TIMER:
			if (wParam == FLASH_TIMER_ID && hFlashTimer != 0) {
				KillTimer(hWnd, FLASH_TIMER_ID);
				hFlashTimer = 0;
			}
			update_gui(FALSE);
			break;

		case WM_DROPFILES:
			accept_dropped_file((HANDLE) wParam);		// console window - dragged file is a script or card deck
			break;

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

	return 0;
}

enum {PRINTER_OK = 0, FORMS_CHECK = 1, PRINT_CHECK = 2, BOTH_CHECK = 3} printerstatus = PRINTER_OK;

void forms_check (int set)
{
	COLORREF oldcolor = btn[IDC_FORMS_CHECK].clr;

	if (set)
		SETBIT(printerstatus, FORMS_CHECK);
	else
		CLRBIT(printerstatus, FORMS_CHECK);

	btn[IDC_FORMS_CHECK].clr = (printerstatus & PRINT_CHECK) ? RGB(255,0,0) : RGB(255,255,0);

	btn[IDC_FORMS_CHECK].state = printerstatus;

	if (btn[IDC_FORMS_CHECK].hBtn != NULL) {
		EnableWindow(btn[IDC_FORMS_CHECK].hBtn, printerstatus);

		if (btn[IDC_FORMS_CHECK].clr != oldcolor)
			InvalidateRect(btn[IDC_FORMS_CHECK].hBtn, NULL, TRUE);		// change color in any case
	}
}

void print_check (int set)
{
	COLORREF oldcolor = btn[IDC_FORMS_CHECK].clr;

	if (set)
		SETBIT(printerstatus, PRINT_CHECK);
	else
		CLRBIT(printerstatus, PRINT_CHECK);

	btn[IDC_FORMS_CHECK].clr = (printerstatus & PRINT_CHECK) ? RGB(255,0,0) : RGB(255,255,0);

	btn[IDC_FORMS_CHECK].state = printerstatus;

	if (btn[IDC_FORMS_CHECK].hBtn != NULL) {
		EnableWindow(btn[IDC_FORMS_CHECK].hBtn, printerstatus);

		if (btn[IDC_FORMS_CHECK].clr != oldcolor)
			InvalidateRect(btn[IDC_FORMS_CHECK].hBtn, NULL, TRUE);		// change color in any case
	}
}

void keyboard_selected (int select)
{
	btn[IDC_KEYBOARD_SELECT].state = select;

	if (btn[IDC_KEYBOARD_SELECT].hBtn != NULL)
		EnableWindow(btn[IDC_KEYBOARD_SELECT].hBtn, select);
}

void disk_ready (int ready)
{
	btn[IDC_FILE_READY].state = ready;

	if (btn[IDC_FILE_READY].hBtn != NULL)
		EnableWindow(btn[IDC_FILE_READY].hBtn, ready);
}

void disk_unlocked (int unlocked)
{
	btn[IDC_DISK_UNLOCK].state = unlocked;

	if (btn[IDC_DISK_UNLOCK].hBtn != NULL)
		EnableWindow(btn[IDC_DISK_UNLOCK].hBtn, unlocked);
}

static void accept_dropped_file (HANDLE hDrop)
{
	int nfiles;
	char fname[MAX_PATH], cmd[MAX_PATH+50], *deckfile;
	BOOL cardreader;
	POINT pt;
	HWND hWndDrop;

	nfiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);		// get file count,
	DragQueryFile(hDrop, 0, fname, sizeof(fname));			// get first filename
	DragQueryPoint(hDrop, &pt);								// get location of drop
	DragFinish(hDrop);

	if (nfiles <= 0)							// hmm, this seems unlikely to occur, but better check
		return;

	if (running) {								// can only accept a drop while processor is stopped
		MessageBeep(0);
		return;
	}

	if ((hWndDrop = ChildWindowFromPoint(hConsoleWnd, pt)) == btn[IDC_1442].hBtn)
		cardreader = TRUE;									// file was dropped onto 1442 card reader
	else if (hWndDrop == NULL || hWndDrop == hConsoleWnd)
		cardreader = FALSE;									// file was dropped onto console window, not a button
	else {
		MessageBeep(0);										// file was dropped onto another button
		return;
	}

	if (nfiles > 1) {							// oops, we wouldn't know what order to read them in
		MessageBox(hConsoleWnd, "You may only drop one file at a time", "", MB_OK);
		return;
	}

												// if shift key is down, prepend @ to name (make it a deck file)
	deckfile = ((GetKeyState(VK_SHIFT) & 0x8000) && cardreader) ? "@" : "";

	sprintf(cmd, "%s \"%s%s\"", cardreader ? "attach cr" : "do", deckfile, fname);
	stuff_cmd(cmd);
}

static void tear_printer (void)
{
	char cmd[MAX_PATH+100], filename[MAX_PATH];

	if ((prt_unit.flags & UNIT_ATT) == 0)
		return;

	strcpy(filename, prt_unit.filename);				// save current attached filename

	if (! stuff_and_wait("detach prt", 1000, 0))		// detach it
		return;

	sprintf(cmd, "view \"%s\"", filename);				// spawn notepad to view it
	if (! stuff_and_wait(cmd, 3000, 500))
		return;

// no, now we have them click the card reader icon twice to unload the deck. more flexible that way
//	if (! stuff_and_wait("detach cr", 1000, 0))			// detach the card reader so they can edit the deck file
//		return;

	remove(filename);									// delete the file

	sprintf(cmd, "attach prt \"%s\"", filename);		// reattach
	stuff_cmd(cmd);
}

#ifdef XXX
	if ((hBtn = CreateWindow("BUTTON", btn[i].txt, WS_CHILD|WS_VISIBLE|BS_CENTER|BS_MULTILINE|BS_OWNERDRAW,
			x, y, BUTTON_WIDTH, BUTTON_HEIGHT, hwParent, (HMENU) i, hInstance, NULL)) == NULL)
		return NULL;

#endif

CRITICAL_SECTION critsect;

void begin_critical_section (void)
{
	static BOOL mustinit = TRUE;

	if (mustinit) {
		InitializeCriticalSection(&critsect);
		mustinit = FALSE;
	}

	EnterCriticalSection(&critsect);
}

void end_critical_section (void)
{
	LeaveCriticalSection(&critsect);
}

#ifndef MIN
#  define MIN(a,b) (((a) <= (b)) ? (a) : (b))
#endif

/* win32 - use a separate thread to read command lines so the GUI
 * can insert commands as well */

static HANDLE hCmdThread     = NULL;
static DWORD  iCmdThreadID   = 0;
static HANDLE hCmdReadEvent  = NULL;
static HANDLE hCmdReadyEvent = NULL;
static BOOL   scp_stuffed = FALSE, scp_reading = FALSE;
static char   cmdbuffer[256];

/* CmdThread - separate thread to read commands from stdin upon request */

static DWORD WINAPI CmdThread (LPVOID arg)
{
	for (;;) {
		WaitForSingleObject(hCmdReadEvent, INFINITE);		/* wait for request */
		read_line(cmdbuffer, sizeof(cmdbuffer), stdin);		/* read one line */
		scp_stuffed = FALSE;								/* say how we got it */
		scp_reading = FALSE;
		SetEvent(hCmdReadyEvent);							/* notify main thread a line is ready */
	}
	return 0;
}									

char *read_cmdline (char *ptr, int size, FILE *stream)
{
	char *cptr;

	if (hCmdThread == NULL)	{								/* set up command-reading thread */
		if ((hCmdReadEvent  = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
			scp_panic("Can't create command line read event");

		if ((hCmdReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
			scp_panic("Can't create command line ready event");
																/* start up the command thread */
		if ((hCmdThread = CreateThread(NULL, 0, CmdThread, NULL, 0, &iCmdThreadID)) == NULL)
			scp_panic("Unable to create command line reading thread");
	}

	scp_reading = TRUE;

	SetEvent(hCmdReadEvent);								/* let read thread get one line */
	WaitForSingleObject(hCmdReadyEvent, INFINITE);			/* wait for read thread or GUI to respond */
	strncpy(ptr, cmdbuffer, MIN(size, sizeof(cmdbuffer)));	/* copy line to caller's buffer */

	for (cptr = ptr; isspace(*cptr); cptr++)				/* absorb spaces */
		;

	if (scp_stuffed) {										/* stuffed command needs to be echoed */
		printf("%s\n", cptr);
		if (sim_log) fprintf(sim_log, "%s\n", cptr);
	}

	return cptr;
}

/* stuff_cmd - force a command into the read_cmdline output buffer. Called asynchronously by GUI */

void stuff_cmd (char *cmd)
{
	strcpy(cmdbuffer, cmd);									/* save the string */
	scp_stuffed = TRUE;										/* note where it came from */
	scp_reading = FALSE;
	ResetEvent(hCmdReadEvent);								/* clear read request event */
	SetEvent(hCmdReadyEvent);								/* notify main thread a line is ready */
}

// my_yield - process GUI messages. It's not apparent why stuff_and_wait would block,
// since it sleeps in the GUI thread while scp runs in the main thread. However,
// at the end of every command scp calls update_gui, which can result in messages
// being sent to the GUI thread. So, the GUI thread has to process messages while
// stuff_and_wait is waiting.

static void my_yield (void)
{
	MSG msg;
					// multitask
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
    }
}

// stuff_and_wait -- stuff a command and wait for the emulator to process the command
// and come back to prompt for another

t_bool stuff_and_wait (char *cmd, int timeout, int delay)
{
	scp_reading = FALSE;

	stuff_cmd(cmd);

	while (! scp_reading) {
		if (timeout < 0)
			return FALSE;

		my_yield();
		if (scp_reading)
			break;

		Sleep(50);
		if (timeout)
			if ((timeout -= 50) <= 0)
				timeout = -1;

		my_yield();
	}

	if (delay)
		Sleep(delay);

	return TRUE;
}

/* remark_cmd - print a remark from inside a command processor. This routine takes
 * into account the possiblity that the command might have been stuffed, in which
 * case the sim> prompt needs to be reprinted.
 */

void remark_cmd (char *remark)
{
	if (scp_reading) {
		putchar('\n');
		if (sim_log) putc('\n', sim_log);
	}

	printf("%s\n", remark);
	if (sim_log) fprintf(sim_log, "%s\n", remark);

	if (scp_reading) {
		printf("sim> ");
		if (sim_log) fprintf(sim_log, "sim> ");
	}
}

#endif 		// _WIN32 defined
#endif		// GUI_SUPPORT defined
