simh-testsetgenerator/Ibm1130/ibm1130_gui.c
B. Scott Michel c37d26a370 IBM 1130: GUI resource file, RegSanityCheck fix
- Add the missing ibm1130.rc GUI resource file to the Windows build so
  that the GUI renders correctly.

- Set the add_simulator TEST_ARGS  to "-g" to fix an edge case Heisenbug
  encountered during Github CI/CD tests where ibm1130 appears to hang
  indefinitely on the Windows runners.

  The cause is the GUI's Pump() thread function being prematurely
  terminated before all GUI resources are acquired. The net result is an
  infinite loop in the MS C runtime trying to exit the process with
  unstable internal state. (Separate patch: synchronization across main
  and Pump() threads to ensure resource acquisition completes.)

  This issue never shows (cannot show) up on non-Windows platforms or
  the SIMH makefile.

- Ibm1130/ibm1130_cr.c

  - Fix printf() warnings (format should be long, not int)
  - Signed/unsigned mismatch, size_t for array indexing
  - Comment out the unused trim() function.

- Ibm1130/ibm1130_cpu.c, ibm1130_gui.c: Remove undefined static functions.
2024-06-08 13:51:23 -04:00

1834 lines
67 KiB
C

/* 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
*
* 30-Dec-05 BLK Fixed mask for IAR and SAR register display and added display
* of Arithmetic Factor, per Carl Claunch.
*
* 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 <ctype.h>
#include <math.h>
#include "ibm1130_defs.h"
#include "ibm1130res.h"
#include "sim_tmxr.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 UNIT cr_unit; /* pointers to 1442 and 1132 (1403) printers */
extern UNIT prt_unit;
extern UNIT dsk_unit[];
extern int boot_drive;
extern t_bool program_is_loaded;
#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) {}
t_stat console_reset (DEVICE *dptr) {return SCPE_OK;}
long stuff_cmd (char *cmd) {return 0;}
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) {sim_printf("%s\n", remark);}
#else
static HWND hConsoleWindow = NULL;
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 */
} else {
if (!hConsoleWindow)
hConsoleWindow = GetConsoleWindow();
}
update_gui(FALSE);
return SCPE_OK;
}
/* scp_panic - report fatal internal programming error */
void scp_panic (const 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_UNUSED 0
#define IDC_DISK_UNLOCK 1
#define IDC_RUN 2
#define IDC_KEYBOARD_SELECT 3
#define IDC_POWER_ON 4
#define IDC_FILE_READY 5
#define IDC_PARITY_CHECK 6
#define IDC_FORMS_CHECK 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, "", 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, "K B\nSELECT", FALSE, FALSE, RGB(255,255,180), NULL, NULL, NULL, TRUE,
1, 0, BUTTON_WIDTH, BUTTON_HEIGHT, "POWER\nON", FALSE, TRUE, 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, "PARITY\nCHECK", FALSE, FALSE, RGB(255,0,0), NULL, NULL, NULL, TRUE,
1, 3, BUTTON_WIDTH, BUTTON_HEIGHT, "FORMS\nCHECK", FALSE, FALSE, RGB(255,255,0), 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, "RESET", 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,
};
#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 0", "DSK0", -1,
TXTBOX_X, TXTBOX_Y+ 75, "Disk 1", "DSK1", -1,
TXTBOX_X, TXTBOX_Y+100, "Disk 2", "DSK2", -1,
TXTBOX_X, TXTBOX_Y+125, "Disk 3", "DSK3", -1,
TXTBOX_X, TXTBOX_Y+150, "Disk 4", "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_arf = 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;
if ((boot_drive<0) || (!program_is_loaded)) {
boot_drive = CES & 7;
if (boot_drive > 4)
boot_drive = -1;
}
if ((boot_drive>=0) && (dsk_unit[boot_drive].flags&UNIT_ATT)) {
disk_ready(TRUE);
disk_unlocked(FALSE);
}
else {
disk_ready(FALSE);
disk_unlocked(TRUE);
}
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 (ARF != shown_arf)
{shown_arf = ARF; RedrawRegion(hConsoleWnd, 75, 114, 364, 136);}
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 = GetWindowLongPtr(hWnd, GWLP_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, UINT_PTR 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) GetWindowLongPtr(hBtn, GWLP_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);
}
SetWindowLongPtr(hBtn, GWLP_WNDPROC, (UINT_PTR) 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;
UINT_PTR 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_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);
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, BOOL rightclick)
{
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, mem_mask, digits); /* register holds only 15 bits */
DrawBits(hDC, 76, 48, shown_sar, 16, mem_mask, digits); /* but let's display only used bits */
DrawBits(hDC, 76, 81, shown_sbr, 16, 0xFFFF, digits);
DrawBits(hDC, 76, 114, shown_arf, 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;
if (running && ! power) { /* turning off */
reason = STOP_POWER_OFF;
/* wait for execution thread to exit */
/* 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);
*/
}
stuff_and_wait("reset", 0, 500);
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);
if ((cr_unit.flags & UNIT_ATT) &&
(btn[IDC_1442].state!=STATE_1442_FULL)) {
stuff_and_wait("detach cr", 0, 500);
update_gui(TRUE);
}
program_is_loaded = FALSE;
break;
case IDC_PROGRAM_START: /* begin execution */
if (! running) {
switch (RUNMODE) {
case MODE_INT_RUN:
case MODE_RUN:
case MODE_SI:
stuff_cmd("cont");
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(con_dsw, CPU_DSW_PROGRAM_STOP);
SETBIT(ILSW[5], ILSW_5_INT_RUN_PROGRAM_STOP);
int_req |= INT_REQ_5; /* note: calc_ints() is not needed in this case */
int_lamps |= INT_REQ_5;
GUI_END_CRITICAL_SECTION
}
break;
case IDC_LOAD_IAR:
if (! running) {
IAR = CES & mem_mask; /* set IAR from console entry switches */
}
break;
case IDC_KEYBOARD: /* toggle between console/keyboard mode */
break;
case IDC_IMM_STOP:
if (running) {
reason = STOP_IMMEDIATE; /* terminate execution without setting wait_mode */
/* wait for execution thread to exit */
/* 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);
*/
}
break;
case IDC_RESET:
if (! running) { /* check-reset is disabled while running */
stuff_and_wait("reset", 0, 500);
forms_check(0); /* clear forms-check status */
print_check(0);
}
if ((cr_unit.flags & UNIT_ATT) &&
(btn[IDC_1442].state!=STATE_1442_FULL)) {
stuff_and_wait("detach cr", 0, 500);
update_gui(TRUE);
}
program_is_loaded = FALSE;
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
if (cr_unit.flags & UNIT_ATT)
stuff_cmd("boot cr");
else {
if (((CES & 7) <= 4) &&
(dsk_unit[(CES & 7)].flags&UNIT_ATT))
boot_drive = CES & 7;
else
boot_drive = -1;
if (boot_drive >= 0) {
char cmd[50];
sprintf(cmd, "boot dsk%d", boot_drive);
stuff_cmd(cmd);
}
}
#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) {
if (running)
MessageBeep(0);
else
stuff_cmd("detach cr");
} else if (btn[IDC_1442].state != STATE_1442_EMPTY && wNotify == STN_CLICKED) {
cr_rewind();
update_gui(TRUE);
}
break;
}
SetForegroundWindow(hConsoleWindow);
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);
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, FALSE) ? hcHand : hcArrow);
return TRUE;
case WM_LBUTTONDOWN:
HandleClick(hWnd, LOWORD(lParam), HIWORD(lParam), TRUE, FALSE);
break;
case WM_RBUTTONDOWN:
HandleClick(hWnd, LOWORD(lParam), HIWORD(lParam), TRUE, TRUE);
break;
case WM_CTLCOLORBTN:
i = GetWindowLongPtr((HWND) lParam, GWLP_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)
{
extern TMLN sim_con_ldsc;
extern TMXR sim_con_tmxr;
btn[IDC_KEYBOARD_SELECT].state = select;
if (select && /* selected */
(sim_con_tmxr.master != 0) && /* not Telnet? */
(sim_con_ldsc.serport != 0)) /* and not serial? */
SetForegroundWindow(hConsoleWindow);
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 BOOL is_scp_file(const char *filename)
{
char cbuf[4*CBUFSIZE], gbuf[CBUFSIZE];
char *argv[1] = {NULL};
FILE *f = fopen(filename, "r");
int lines = 0, comment_lines = 0;
BOOL result = TRUE;
size_t i;
if (!f)
return FALSE;
while (result) {
CONST char *cptr;
cptr = fgets(cbuf, sizeof(cbuf), f);
if (cptr == NULL)
break;
if (strlen(cptr) == sizeof(cbuf)-1) /* VERY long lines are not SCP commands */
result = FALSE;
if (!strchr(cptr, '\n')) /* lines without newlines are not SCP commands */
result = FALSE;
if (!memcmp(cptr,"!// ", 4)) /* indirect deck file literals are not SCP commands */
result = FALSE;
cptr = sim_trim_endspc(cbuf);
while (sim_isspace (*cptr)) /* trim leading space */
cptr++;
++lines;
for (i = 0; i < strlen(cptr); i++)
if ((cptr[i] & 0x80) ||
((!isprint(cptr[i])) && (!isspace(cptr[i]))))
result = FALSE; /* SCP files only have printable ASCII */
if ((*cptr == ';') || (*cptr == '#')) { /* ignore comments */
++comment_lines;
continue;
}
if (*cptr == 0) /* ignore blank */
continue;
sim_sub_args (cbuf, sizeof(cbuf), argv);
cptr = get_glyph_cmd (cptr, gbuf); /* get command glyph */
if (!find_cmd (gbuf)) { /* lookup command */
result = FALSE;
break;
}
}
fclose(f);
if (lines == 0) /* Empty file isn't SCP */
result = FALSE;
return result;
}
static void accept_dropped_file (HANDLE hDrop)
{
int nfiles;
char fname[MAX_PATH], cmd[MAX_PATH+50], *deckfile;
BOOL cardreader;
BOOL scp_file;
POINT pt;
HWND hWndDrop;
char msg[512];
msg[sizeof(msg)-1] = '\0';
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;
}
scp_file = is_scp_file(fname);
if (cardreader) {
if (scp_file) {
snprintf(msg, sizeof(msg)-1, "\"%s\"\r\n\r\nContains SCP commands (not card reader input).\r\n\r\nProcess as SCP commands?", fname);
if (IDYES != MessageBox(hConsoleWnd, msg, "", MB_YESNO))
return;
cardreader = FALSE; /* Process as SCP commands */
}
} else {
if (!scp_file) {
snprintf(msg, sizeof(msg)-1, "Invalid SCP command file:\r\n\r\n\"%s\"\r\n\r\nProcess as Card Reader Input?", fname);
if (IDYES != MessageBox(hConsoleWnd, msg, "", MB_YESNO))
return;
cardreader = TRUE; /* Process as Card Input */
}
}
/* 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;
if (running) { /* can only accept a drop while processor is stopped */
MessageBeep(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, 2000))
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_reading = FALSE;
static long scp_command = 0;
static char cmdbuffer[256];
static BOOL read_exiting = FALSE;
#define SCP_COMMAND InterlockedExchangeAdd(&scp_command, 0L)
#define NEXT_SCP_COMMAND InterlockedIncrement(&scp_command)
/* CmdThread - separate thread to read commands from stdin upon request */
static DWORD WINAPI CmdThread (LPVOID arg)
{
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD dwBytesRead;
for (;;) {
if (WAIT_TIMEOUT == WaitForSingleObject(hCmdReadEvent, 2000)) /* wait for request */
continue; /* put breakpoint here to debug */
if (read_exiting)
break;
scp_reading = FALSE;
if (ReadFile(hStdIn, cmdbuffer, sizeof(cmdbuffer)-1, &dwBytesRead, NULL)) {
cmdbuffer[dwBytesRead] = '\0';
scp_reading = FALSE;
NEXT_SCP_COMMAND;
SetEvent(hCmdReadyEvent); /* notify main thread a line is ready */
} else {
DWORD dwError = GetLastError();
scp_reading = FALSE;
NEXT_SCP_COMMAND;
}
}
return 0;
}
static void read_atexit (void)
{
typedef BOOL (WINAPI *_func)(HANDLE, LPOVERLAPPED);
_func pCancelIoEx;
read_exiting = TRUE;
pCancelIoEx = (_func)GetProcAddress(GetModuleHandleA("kernel32.dll"), "CancelIoEx");
if (pCancelIoEx) {
pCancelIoEx(GetStdHandle(STD_INPUT_HANDLE), NULL);
SetEvent(hCmdReadEvent); /* wake read thread */
WaitForSingleObject(hCmdThread, INFINITE);
}
CloseHandle(hCmdReadyEvent);
hCmdReadyEvent = NULL;
CloseHandle(hCmdReadEvent);
hCmdReadEvent = NULL;
CloseHandle(hCmdThread);
hCmdThread = NULL;
}
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");
atexit(read_atexit);
Sleep(500); /* Let GUI threads startup and start to process messages */
}
update_gui(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 */
;
return cptr;
}
/* stuff_cmd - force a command into the read_cmdline output buffer. Called asynchronously by GUI */
long stuff_cmd (char *cmd)
{
INPUT_RECORD *ip;
size_t i, j, cmdsize = strlen(cmd);
DWORD dwEventsWritten;
long scp_cmd = SCP_COMMAND;
ip = (INPUT_RECORD *)calloc(2+2*cmdsize, sizeof(*ip));
for (i=j=0; i<cmdsize; i++, j++) {
ip[j].EventType = KEY_EVENT;
ip[j].Event.KeyEvent.bKeyDown = TRUE;
ip[j].Event.KeyEvent.wRepeatCount = 1;
ip[j].Event.KeyEvent.uChar.AsciiChar = cmd[i];
j++;
ip[j] = ip[j-1];
ip[j].Event.KeyEvent.bKeyDown = FALSE;
}
ip[j].EventType = KEY_EVENT;
ip[j].Event.KeyEvent.bKeyDown = TRUE;
ip[j].Event.KeyEvent.wRepeatCount = 1;
ip[j].Event.KeyEvent.uChar.AsciiChar = '\r';
j++;
ip[j] = ip[j-1];
ip[j].Event.KeyEvent.bKeyDown = FALSE;
WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ip, 2+j, &dwEventsWritten);
free(ip);
return scp_cmd;
}
/* 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)
{
long scp_cmd = stuff_cmd(cmd);
while (scp_cmd == SCP_COMMAND) {
if (timeout < 0)
return FALSE;
my_yield();
if (scp_cmd != SCP_COMMAND)
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)
sim_printf("\n");
sim_printf("%s\n", remark);
if (scp_reading)
sim_printf("%s", sim_prompt);
}
#endif /* _WIN32 defined */
#endif /* GUI_SUPPORT defined */