WARNING: V2.10 has reorganized and renamed some of the definition files for the PDP-10, PDP-11, and VAX. Be sure to delete all previous source files before you unpack the Zip archive, or unpack it into a new directory structure. WARNING: V2.10 has a new, more comprehensive save file format. Restoring save files from previous releases will cause 'invalid register' errors and loss of CPU option flags, device enable/ disable flags, unit online/offline flags, and unit writelock flags. WARNING: If you are using Visual Studio .NET through the IDE, be sure to turn off the /Wp64 flag in the project settings, or dozens of spurious errors will be generated. WARNING: Compiling Ethernet support under Windows requires extra steps; see the Ethernet readme file. Ethernet support is currently available only for Windows, Linux, NetBSD, and OpenBSD. 1. New Features 1.1 SCP and Libraries - The VT emulation package has been replaced by the capability to remote the console to a Telnet session. Telnet clients typically have more complete and robust VT100 emulation. - Simulated devices may now have statically allocated buffers, in addition to dynamically allocated buffers or disk-based data stores. - The DO command now takes substitutable arguments (max 9). In command files, %n represents substitutable argument n. - The initial command line is now interpreted as the command name and substitutable arguments for a DO command. This is backward compatible to prior versions. - The initial command line parses switches. -Q is interpreted as quiet mode; informational messages are suppressed. - The HELP command now takes an optional argument. HELP <cmd> types help on the specified command. - Hooks have been added for implementing GUI-based consoles, as well as simulator-specific command extensions. A few internal data structures and definitions have changed. - Two new routines (tmxr_open_master, tmxr_close_master) have been added to sim_tmxr.c. The calling sequence for sim_accept_conn has been changed in sim_sock.c. - The calling sequence for the VM boot routine has been modified to add an additional parameter. - SAVE now saves, and GET now restores, controller and unit flags. - Library sim_ether.c has been added for Ethernet support. 1.2 VAX - Non-volatile RAM (NVR) can behave either like a memory or like a disk-based peripheral. If unattached, it behaves like memory and is saved and restored by SAVE and RESTORE, respectively. If attached, its contents are loaded from disk by ATTACH and written back to disk at DETACH and EXIT. - SHOW <device> VECTOR displays the device's interrupt vector. A few devices allow the vector to be changed with SET <device> VECTOR=nnn. - SHOW CPU IOSPACE displays the I/O space address map. - The TK50 (TMSCP tape) has been added. - The DEQNA/DELQA (Qbus Ethernet controllers) have been added. - Autoconfiguration support has been added. - The paper tape reader has been removed from vax_stddev.c and now references a common implementation file, dec_pt.h. - Examine and deposit switches now work on all devices, not just the CPU. - Device address conflicts are not detected until simulation starts. 1.3 PDP-11 - SHOW <device> VECTOR displays the device's interrupt vector. Most devices allow the vector to be changed with SET <device> VECTOR=nnn. - SHOW CPU IOSPACE displays the I/O space address map. - The TK50 (TMSCP tape), RK611/RK06/RK07 (cartridge disk), RX211 (double density floppy), and KW11P programmable clock have been added. - The DEQNA/DELQA (Qbus Ethernet controllers) have been added. - Autoconfiguration support has been added. - The paper tape reader has been removed from pdp11_stddev.c and now references a common implementation file, dec_pt.h. - Device bootstraps now use the actual CSR specified by the SET ADDRESS command, rather than just the default CSR. Note that PDP-11 operating systems may NOT support booting with non-standard addresses. - Specifying more than 256KB of memory, or changing the bus configuration, causes all peripherals that are not compatible with the current bus configuration to be disabled. - Device address conflicts are not detected until simulation starts. 1.4 PDP-10 - SHOW <device> VECTOR displays the device's interrupt vector. A few devices allow the vector to be changed with SET <device> VECTOR=nnn. - SHOW CPU IOSPACE displays the I/O space address map. - The RX211 (double density floppy) has been added; it is off by default. - The paper tape now references a common implementation file, dec_pt.h. - Device address conflicts are not detected until simulation starts. 1.5 PDP-1 - DECtape (then known as MicroTape) support has been added. - The line printer and DECtape can be disabled and enabled. 1.6 PDP-8 - The RX28 (double density floppy) has been added as an option to the existing RX8E controller. - SHOW <device> DEVNO displays the device's device number. Most devices allow the device number to be changed with SET <device> DEVNO=nnn. - Device number conflicts are not detected until simulation starts. 1.7 IBM 1620 - The IBM 1620 simulator has been released. 1.8 AltairZ80 - A hard drive has been added for increased storage. - Several bugs have been fixed. 1.9 HP 2100 - The 12845A has been added and made the default line printer (LPT). The 12653A has been renamed LPS and is off by default. It also supports the diagnostic functions needed to run the DCPC and DMS diagnostics. - The 12557A/13210A disk defaults to the 13210A (7900/7901). - The 12559A magtape is off by default. - New CPU options (EAU/NOEAU) enable/disable the extended arithmetic instructions for the 2116. These instructions are standard on the 2100 and 21MX. - New CPU options (MPR/NOMPR) enable/disable memory protect for the 2100 and 21MX. - New CPU options (DMS/NODMS) enable/disable the dynamic mapping instructions for the 21MX. - The 12539 timebase generator autocalibrates. 1.10 Simulated Magtapes - Simulated magtapes recognize end of file and the marker 0xFFFFFFFF as end of medium. Only the TMSCP tape simulator can generate an end of medium marker. - The error handling in simulated magtapes was overhauled to be consistent through all simulators. 1.11 Simulated DECtapes - Added support for RT11 image file format (256 x 16b) to DECtapes. 2. Release Notes 2.1 Bugs Fixed - TS11/TSV05 was not simulating the XS0_MOT bit, causing failures under VMS. In addition, two of the CTL options were coded interchanged. - IBM 1401 tape was not setting a word mark under group mark for load mode reads. This caused the diagnostics to crash. - SCP bugs in ssh_break and set_logon were fixed (found by Dave Hittner). - Numerous bugs in the HP 2100 extended arithmetic, floating point, 21MX, DMS, and IOP instructions were fixed. Bugs were also fixed in the memory protect and DMS functions. The moving head disks (DP, DQ) were revised to simulate the hardware more accurately. Missing functions in DQ (address skip, read address) were added. 2.2 HP 2100 Debugging - The HP 2100 CPU nows runs all of the CPU diagnostics. - The peripherals run most of the peripheral diagnostics. There is still a problem in overlapped seek operation on the disks. See the file hp2100_diag.txt for details. 3. In Progress These simulators are not finished and are available in a separate Zip archive distribution. - Interdata 16b/32b: coded, partially tested. See the file id_diag.txt for details. - SDS 940: coded, partially tested.
1118 lines
30 KiB
C
1118 lines
30 KiB
C
#include "ibm1130_defs.h"
|
|
|
|
/* ibm1130_gdu.c: IBM 1130 2250 Graphical Display Unit
|
|
|
|
(Under construction)
|
|
// stuff to fix:
|
|
// "store revert" might be backwards?
|
|
// alpha keyboard is not implemented
|
|
// pushbuttons are not implemented
|
|
// there is something about interrupts being deferred during a subroutine transition?
|
|
|
|
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 sim@ibm1130.org
|
|
*/
|
|
|
|
#define BLIT_MODE // normally defined, undefine when debugging generate_image()
|
|
//#define DEBUG_LIGHTPEN // normally undefined, define to visualize light-pen sensing
|
|
|
|
#define DEFAULT_GDU_RATE 20 // default frame rate
|
|
#define DEFAULT_PEN_THRESHOLD 3 // default looseness of light-pen hit
|
|
#define INDWIDTH 32 // width of an indicator (there are two columns of these)
|
|
#define INITSIZE 512 // initial window size
|
|
|
|
#define GDU_DSW_ORDER_CONTROLLED_INTERRUPT 0x8000
|
|
#define GDU_DSW_KEYBOARD_INTERUPT 0x4000
|
|
#define GDU_DSW_DETECT_INTERRUPT 0x2000
|
|
#define GDU_DSW_CYCLE_STEAL_CHECK 0x1000
|
|
#define GDU_DSW_DETECT_STATUS 0x0800
|
|
#define GDU_DSW_LIGHT_PEN_SWITCH 0x0100
|
|
#define GDU_DSW_BUSY 0x0080
|
|
#define GDU_DSW_CHARACTER_MODE 0x0040
|
|
#define GDU_DSW_POINT_MODE 0x0020
|
|
#define GDU_DSW_ADDR_DISP 0x0003
|
|
|
|
#define GDU_FKEY_DATA_AVAILABLE 0x8000
|
|
#define GDU_FKEY_KEY_CODE 0x1F00
|
|
#define GDU_FKEY_OVERLAY_CODE 0x00FF
|
|
|
|
#define GDU_AKEY_DATA_AVAILABLE 0x8000
|
|
#define GDU_AKEY_END 0x1000
|
|
#define GDU_AKEY_CANCEL 0x0800
|
|
#define GDU_AKEY_ADVANCE 0x0400
|
|
#define GDU_AKEY_BACKSPACE 0x0200
|
|
#define GDU_AKEY_JUMP 0x0100
|
|
#define GDU_AKEY_KEY_CODE 0x00FF
|
|
|
|
/* -------------------------------------------------------------------------------------- */
|
|
|
|
#define UNIT_V_DISPLAYED (UNIT_V_UF + 0)
|
|
#define UNIT_V_DETECTS_ENABLED (UNIT_V_UF + 1)
|
|
#define UNIT_V_INTERRUPTS_DEFERRED (UNIT_V_UF + 2)
|
|
#define UNIT_V_LARGE_CHARS (UNIT_V_UF + 3)
|
|
|
|
#define UNIT_DISPLAYED (1u << UNIT_V_DISPLAYED) /* display windows is up */
|
|
#define UNIT_DETECTS_ENABLED (1u << UNIT_V_DETECTS_ENABLED) /* light pen detects are enabled */
|
|
#define UNIT_INTERRUPTS_DEFERRED (1u << UNIT_V_INTERRUPTS_DEFERRED) /* light pen interrupts are deferred */
|
|
#define UNIT_LARGE_CHARS (1u << UNIT_V_LARGE_CHARS) /* large character mode */
|
|
|
|
static t_stat gdu_reset (DEVICE *dptr);
|
|
|
|
static int16 gdu_dsw = 1; /* device status word */
|
|
static int16 gdu_ar = 0; /* address register */
|
|
static int16 gdu_x = 0; /* X deflection */
|
|
static int16 gdu_y = 0; /* Y deflection */
|
|
static int16 gdu_fkey = 0; /* function keyboard register */
|
|
static int16 gdu_akey = 0; /* alphanumeric keyboard register */
|
|
static int16 gdu_revert = 0; /* revert address register */
|
|
static int32 gdu_indicators = 0; /* programmed indicator lamps */
|
|
static int32 gdu_threshold = DEFAULT_PEN_THRESHOLD; /* mouse must be within 3/1024 of line to be a hit */
|
|
static int32 gdu_rate = DEFAULT_GDU_RATE; /* refresh rate. 0 = default */
|
|
|
|
UNIT gdu_unit = { UDATA (NULL, 0, 0) };
|
|
|
|
REG gdu_reg[] = {
|
|
{ HRDATA (GDUDSW, gdu_dsw, 16) }, /* device status word */
|
|
{ HRDATA (GDUAR, gdu_ar, 16) }, /* address register */
|
|
{ HRDATA (GDUXREG, gdu_x, 16) }, /* X deflection register */
|
|
{ HRDATA (GDUYREG, gdu_y, 16) }, /* Y deflection register */
|
|
{ HRDATA (GDUFKEY, gdu_fkey, 16) }, /* function keyboard register */
|
|
{ HRDATA (GDUAKEY, gdu_akey, 16) }, /* alphanumeric keyboard register */
|
|
{ HRDATA (GDUREVERT,gdu_revert, 16) }, /* revert address register */
|
|
{ HRDATA (GDUAKEY, gdu_indicators, 32) }, /* programmed indicators */
|
|
{ DRDATA (GDUTHRESH,gdu_threshold, 32) }, /* mouse closeness threshhold */
|
|
{ DRDATA (GDURATE, gdu_rate, 32) }, /* refresh rate in frames/sec */
|
|
{ NULL } };
|
|
|
|
DEVICE gdu_dev = {
|
|
"GDU", &gdu_unit, gdu_reg, NULL,
|
|
1, 16, 16, 1, 16, 16,
|
|
NULL, NULL, gdu_reset,
|
|
NULL, NULL, NULL};
|
|
|
|
/* -------------------------------------------------------------------------------------- */
|
|
|
|
#ifndef GUI_SUPPORT
|
|
|
|
static t_stat gdu_reset (DEVICE *dptr)
|
|
{
|
|
return SCPE_OK;
|
|
}
|
|
|
|
void xio_2250_display (int32 addr, int32 func, int32 modify)
|
|
{
|
|
// ignore commands to nonexistent device
|
|
}
|
|
|
|
t_bool gdu_active (void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------------------- */
|
|
#else // GUI_SUPPORT defined
|
|
|
|
/******* PLATFORM INDEPENDENT CODE ********************************************************/
|
|
|
|
static int32 gdu_instaddr; // address of first word of instruction
|
|
static int xmouse, ymouse, lpen_dist, lpen_dist2; // current mouse pointer, scaled closeness threshhold, same squared
|
|
static double sfactor; // current scaling factor
|
|
static t_bool last_abs = TRUE; // last positioning instruction was absolute
|
|
static t_bool mouse_present = FALSE; // mouse is/is not in the window
|
|
|
|
static void clear_interrupts (void);
|
|
static void set_indicators (int32 new_inds);
|
|
static void start_regeneration (void);
|
|
static void halt_regeneration (void);
|
|
static void draw_characters (void);
|
|
static void notify_window_closed (void);
|
|
|
|
// routines that must be implemented per-platform
|
|
|
|
static void DrawLine(int x0, int y0, int x1, int y1);
|
|
static void DrawPoint(int x, int y);
|
|
static void CheckGDUKeyboard(void);
|
|
static t_bool CreateGDUWindow(void);
|
|
static void StartGDUUpdates(void);
|
|
static void StopGDUUpdates(void);
|
|
static void GetMouseCoordinates(void);
|
|
static void UpdateGDUIndicators(void);
|
|
static void ShowPenHit (int x, int y);
|
|
static void EraseGDUScreen (void);
|
|
|
|
/* -------------------------------------------------------------------------------------- */
|
|
|
|
void xio_2250_display (int32 addr, int32 func, int32 modify)
|
|
{
|
|
switch (func) {
|
|
case XIO_SENSE_DEV:
|
|
ACC = (gdu_dsw & GDU_DSW_BUSY) ? GDU_DSW_BUSY : gdu_dsw;
|
|
if (modify & 1)
|
|
clear_interrupts();
|
|
break;
|
|
|
|
case XIO_READ: /* store status data into word pointed to by IOCC packet */
|
|
if (gdu_dsw & GDU_DSW_BUSY) /* not permitted while device is busy */
|
|
break;
|
|
|
|
WriteW(addr, gdu_ar); /* save status information */
|
|
WriteW(addr+1, gdu_dsw);
|
|
WriteW(addr+2, gdu_x & 0x7FF);
|
|
WriteW(addr+3, gdu_y & 0x7FF);
|
|
WriteW(addr+4, gdu_fkey);
|
|
WriteW(addr+5, gdu_akey);
|
|
gdu_ar = addr+6; /* this alters the channel address register? */
|
|
|
|
clear_interrupts(); /* read status clears the interrupts */
|
|
break;
|
|
|
|
case XIO_WRITE:
|
|
if (gdu_dsw & GDU_DSW_BUSY) /* treated as no-op if display is busy */
|
|
break;
|
|
|
|
if (modify & 0x80) { /* bit 8 on means set indicators, 0 means start regeneration */
|
|
set_indicators((ReadW(addr) << 16) | ReadW(addr+1));
|
|
}
|
|
else {
|
|
gdu_ar = addr;
|
|
gdu_fkey = 0;
|
|
gdu_akey = 0;
|
|
clear_interrupts();
|
|
start_regeneration();
|
|
}
|
|
break;
|
|
|
|
case XIO_CONTROL:
|
|
if (modify & 0x80) { /* bit 8 on means reset, off is no-op */
|
|
gdu_reset(&gdu_dev);
|
|
set_indicators((addr << 16) | addr);
|
|
}
|
|
break;
|
|
|
|
default: /* all other commands are no-ops */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static t_stat gdu_reset (DEVICE *dptr)
|
|
{
|
|
halt_regeneration();
|
|
clear_interrupts();
|
|
set_indicators(0);
|
|
gdu_x = gdu_y = 512;
|
|
CLRBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED | UNIT_DETECTS_ENABLED | UNIT_LARGE_CHARS);
|
|
gdu_dsw = 1;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static void clear_interrupts (void)
|
|
{
|
|
CLRBIT(gdu_dsw, GDU_DSW_ORDER_CONTROLLED_INTERRUPT | GDU_DSW_KEYBOARD_INTERUPT | GDU_DSW_DETECT_INTERRUPT);
|
|
CLRBIT(ILSW[3], ILSW_3_2250_DISPLAY);
|
|
calc_ints();
|
|
}
|
|
|
|
static void gdu_interrupt (int32 dswbit)
|
|
{
|
|
SETBIT(gdu_dsw, dswbit);
|
|
SETBIT(ILSW[3], ILSW_3_2250_DISPLAY);
|
|
calc_ints();
|
|
halt_regeneration();
|
|
}
|
|
|
|
static void set_indicators (int32 new_inds)
|
|
{
|
|
gdu_indicators = new_inds;
|
|
if (gdu_unit.flags & UNIT_DISPLAYED)
|
|
UpdateGDUIndicators();
|
|
}
|
|
|
|
static void start_regeneration (void)
|
|
{
|
|
SETBIT(gdu_dsw, GDU_DSW_BUSY);
|
|
|
|
if (gdu_unit.flags & UNIT_DISPLAYED) {
|
|
StartGDUUpdates();
|
|
}
|
|
else {
|
|
if (! CreateGDUWindow())
|
|
return;
|
|
SETBIT(gdu_unit.flags, UNIT_DISPLAYED);
|
|
}
|
|
}
|
|
|
|
static void halt_regeneration (void)
|
|
{
|
|
if (gdu_dsw & GDU_DSW_BUSY) {
|
|
StopGDUUpdates();
|
|
CLRBIT(gdu_dsw, GDU_DSW_BUSY);
|
|
}
|
|
EraseGDUScreen();
|
|
}
|
|
|
|
static void notify_window_closed (void)
|
|
{
|
|
if (gdu_dsw & GDU_DSW_BUSY) {
|
|
StopGDUUpdates();
|
|
CLRBIT(gdu_dsw, GDU_DSW_BUSY);
|
|
}
|
|
CLRBIT(gdu_unit.flags, UNIT_DISPLAYED);
|
|
gdu_reset(&gdu_dev);
|
|
}
|
|
|
|
static int32 read_gduword (void)
|
|
{
|
|
int32 w;
|
|
|
|
w = M[gdu_ar++ & mem_mask];
|
|
gdu_dsw = (gdu_dsw & ~GDU_DSW_ADDR_DISP) | ((gdu_ar - gdu_instaddr) & GDU_DSW_ADDR_DISP);
|
|
|
|
return w;
|
|
}
|
|
|
|
#define DIST2(x0,y0,x1,y1) (((x1)-(x0))*((x1)-(x0))+((y1)-(y0))*((y1)-(y0)))
|
|
|
|
static void draw (int32 newx, int32 newy, t_bool beam)
|
|
{
|
|
int xmin, xmax, ymin, ymax, xd, yd;
|
|
double s;
|
|
int hit = FALSE;
|
|
|
|
if (beam) {
|
|
if (gdu_dsw & GDU_DSW_POINT_MODE) {
|
|
DrawPoint(newx, newy);
|
|
|
|
#ifdef DEBUG_LIGHTPEN
|
|
if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
|
|
hit = TRUE;
|
|
#else
|
|
if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)
|
|
if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
|
|
hit = TRUE;
|
|
#endif
|
|
}
|
|
else {
|
|
DrawLine(gdu_x, gdu_y, newx, newy);
|
|
|
|
// calculate proximity of light pen to the line
|
|
#ifndef DEBUG_LIGHTPEN
|
|
if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present) {
|
|
#endif
|
|
if (gdu_x <= newx)
|
|
xmin = gdu_x, xmax = newx;
|
|
else
|
|
xmin = newx, xmax = gdu_x;
|
|
|
|
if (gdu_y <= newy)
|
|
ymin = gdu_y, ymax = newy;
|
|
else
|
|
ymin = newy, ymax = gdu_y;
|
|
|
|
if (newx == gdu_x) {
|
|
// line is vertical. Nearest point is an endpoint if the mouse is above or
|
|
// below the line segment, otherwise the segment point at the same y as the mouse
|
|
xd = gdu_x;
|
|
yd = (ymouse <= ymin) ? ymin : (ymouse >= ymax) ? ymax : ymouse;
|
|
|
|
if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
|
|
hit = TRUE;
|
|
}
|
|
else if (newy == gdu_y) {
|
|
// line is horizontal. Nearest point is an endpoint if the mouse is to the left or
|
|
// the right of the line segment, otherwise the segment point at the same x as the mouse
|
|
xd = (xmouse <= xmin) ? xmin : (xmouse >= xmax) ? xmax : xmouse;
|
|
yd = gdu_y;
|
|
|
|
if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
|
|
hit = TRUE;
|
|
}
|
|
else {
|
|
// line is diagonal. See if the mouse is inside the box lpen_dist wider than the line segment's bounding rectangle
|
|
if (xmouse >= (xmin-lpen_dist) && xmouse <= (xmax+lpen_dist) && ymouse >= (ymin-lpen_dist) || ymouse <= (ymax+lpen_dist)) {
|
|
// compute the point at the intersection of the line through the line segment and the normal
|
|
// to that line through the mouse. This is the point on the line through the line segment
|
|
// nearest the mouse
|
|
|
|
s = (double)(newy - gdu_y) / (double)(newx - gdu_x); // slope of line segment
|
|
xd = (int) ((ymouse + xmouse/s - gdu_y + s*gdu_x) / (s + 1./s) + 0.5);
|
|
|
|
// if intersection is beyond either end of the line segment, the nearest point to the
|
|
// mouse is nearest segment end, otherwise it's the computed intersection point
|
|
if (xd < xmin || xd > xmax) {
|
|
#ifdef DEBUG_LIGHTPEN
|
|
// if it's a hit, set xd and yd so we can display the hit
|
|
if (DIST2(gdu_x, gdu_y, xmouse, ymouse) <= lpen_dist2) {
|
|
hit = TRUE;
|
|
xd = gdu_x;
|
|
yd = gdu_y;
|
|
}
|
|
else if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2) {
|
|
hit = TRUE;
|
|
xd = newx;
|
|
yd = newy;
|
|
}
|
|
#else
|
|
if (DIST2(gdu_x, gdu_y, xmouse, ymouse) <= lpen_dist2 || DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
|
|
hit = TRUE;
|
|
#endif
|
|
}
|
|
else {
|
|
yd = (int) (gdu_y + s*(xd - gdu_x) + 0.5);
|
|
if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
|
|
hit = TRUE;
|
|
}
|
|
}
|
|
}
|
|
#ifndef DEBUG_LIGHTPEN
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (hit) {
|
|
#ifdef DEBUG_LIGHTPEN
|
|
ShowPenHit(xd, yd);
|
|
if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)
|
|
SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
|
|
#else
|
|
SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
|
|
#endif
|
|
}
|
|
|
|
gdu_x = newx;
|
|
gdu_y = newy;
|
|
}
|
|
|
|
static void generate_image (void)
|
|
{
|
|
int32 instr, new_addr, newx, newy;
|
|
t_bool run = TRUE, accept;
|
|
|
|
if (! (gdu_dsw & GDU_DSW_BUSY))
|
|
return;
|
|
|
|
GetMouseCoordinates();
|
|
|
|
lpen_dist = (int) (gdu_threshold/sfactor + 0.5); // mouse-to-line threshhold at current scaling factor
|
|
lpen_dist2 = lpen_dist * lpen_dist;
|
|
|
|
while (run) {
|
|
if ((gdu_dsw & GDU_DSW_DETECT_STATUS) && ! (gdu_unit.flags & UNIT_INTERRUPTS_DEFERRED)) {
|
|
CLRBIT(gdu_dsw, GDU_DSW_DETECT_STATUS); // clear when interrupt is activated
|
|
gdu_interrupt(GDU_DSW_DETECT_INTERRUPT);
|
|
run = FALSE;
|
|
break;
|
|
}
|
|
|
|
gdu_instaddr = gdu_ar; // remember address of GDU instruction
|
|
instr = read_gduword(); // fetch instruction (and we really are cycle stealing here!)
|
|
|
|
switch ((instr >> 12) & 0xF) { // decode instruction
|
|
case 0: // short branch
|
|
case 1:
|
|
gdu_revert = gdu_ar; // save revert address & get new address
|
|
gdu_ar = read_gduword() & 0x1FFF;
|
|
if (gdu_dsw & GDU_DSW_CHARACTER_MODE) {
|
|
draw_characters(); // in character mode this means we are at character data
|
|
gdu_ar = gdu_revert;
|
|
}
|
|
break;
|
|
|
|
case 2: // long branch/interrupt
|
|
new_addr = read_gduword(); // get next word
|
|
accept = ((instr & 1) ? (gdu_dsw & GDU_DSW_LIGHT_PEN_SWITCH) : TRUE) && ((instr & 2) ? (gdu_dsw & GDU_DSW_DETECT_STATUS) : TRUE);
|
|
|
|
if (instr & 2) // clear after testing
|
|
CLRBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
|
|
|
|
if (instr & 0x0400) // NOP
|
|
accept = FALSE;
|
|
|
|
if (accept) {
|
|
if (instr & 0x0800) { // branch
|
|
gdu_revert = gdu_ar;
|
|
|
|
if (instr & 0x0080) // indirect
|
|
new_addr = M[new_addr & mem_mask];
|
|
|
|
gdu_ar = new_addr;
|
|
|
|
if (gdu_dsw & GDU_DSW_CHARACTER_MODE) {
|
|
draw_characters();
|
|
gdu_ar = gdu_revert;
|
|
}
|
|
}
|
|
else { // interrupt
|
|
gdu_interrupt(GDU_DSW_ORDER_CONTROLLED_INTERRUPT);
|
|
run = FALSE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 3: // control instructions
|
|
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
|
|
|
|
switch ((instr >> 8) & 0xF) {
|
|
case 1: // set pen mode
|
|
if ((instr & 0xC) == 8)
|
|
SETBIT(gdu_unit.flags, UNIT_DETECTS_ENABLED);
|
|
else if ((instr & 0xC) == 4)
|
|
CLRBIT(gdu_unit.flags, UNIT_DETECTS_ENABLED);
|
|
|
|
if ((instr & 0x3) == 2)
|
|
SETBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED);
|
|
else if ((instr & 0x3) == 1)
|
|
CLRBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED);
|
|
break;
|
|
|
|
case 2: // set graphic mode
|
|
if (instr & 1)
|
|
SETBIT(gdu_dsw, GDU_DSW_POINT_MODE);
|
|
else
|
|
CLRBIT(gdu_dsw, GDU_DSW_POINT_MODE);
|
|
break;
|
|
|
|
case 3: // set character mode
|
|
SETBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
|
|
if (instr & 1)
|
|
SETBIT(gdu_unit.flags, UNIT_LARGE_CHARS);
|
|
else
|
|
CLRBIT(gdu_unit.flags, UNIT_LARGE_CHARS);
|
|
break;
|
|
|
|
case 4: // start timer
|
|
run = FALSE; // (which, for us, means stop processing until next timer message)
|
|
CheckGDUKeyboard();
|
|
break;
|
|
|
|
case 5: // store revert
|
|
M[gdu_ar & mem_mask] = gdu_revert;
|
|
read_gduword(); // skip to next address
|
|
break;
|
|
|
|
case 6: // revert
|
|
gdu_ar = gdu_revert;
|
|
break;
|
|
|
|
default: // all others treated as no-ops
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 4: // long absolute
|
|
case 5:
|
|
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
|
|
newx = instr & 0x3FF;
|
|
newy = read_gduword() & 0x3FF;
|
|
draw(newx, newy, instr & 0x1000);
|
|
last_abs = TRUE;
|
|
break;
|
|
|
|
case 6: // short absolute
|
|
case 7:
|
|
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
|
|
newx = gdu_x;
|
|
newy = gdu_y;
|
|
if (instr & 0x0800)
|
|
newy = instr & 0x3FF;
|
|
else
|
|
newx = instr & 0x3FF;
|
|
draw(newx, newy, instr & 0x1000);
|
|
last_abs = TRUE;
|
|
break;
|
|
|
|
default: // high bit set - it's a relative instruction
|
|
CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
|
|
newx = (instr >> 8) & 0x3F;
|
|
newy = instr & 0x3F;
|
|
|
|
if (instr & 0x4000) // sign extend x - values are in 2's complement
|
|
newx |= -1 & ~0x3F; // although documentation doesn't make that clear
|
|
|
|
if (instr & 0x0040) // sign extend y
|
|
newy |= -1 & ~0x3F;
|
|
|
|
newx = gdu_x + newx;
|
|
newy = gdu_y + newy;
|
|
draw(newx, newy, instr & 0x0080);
|
|
last_abs = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct charinfo { // character mode scaling info:
|
|
int dx, dy; // character and line spacing
|
|
double sx, sy; // scaling factors: character units to screen units
|
|
int xoff, yoff; // x and y offset to lower left corner of character
|
|
int suby; // subscript/superscript offset
|
|
} cx[2] = {
|
|
{14, 20, 1.7, 2.0, -6, -7, 6}, // regular
|
|
{21, 30, 2.5, 3.0, -9, -11, 9} // large
|
|
};
|
|
|
|
static void draw_characters (void)
|
|
{
|
|
int32 w, x0, y0, x1, y1, yoff = 0, ninstr = 0;
|
|
t_bool dospace, didstroke = FALSE;
|
|
struct charinfo *ci;
|
|
|
|
ci = &cx[(gdu_unit.flags & UNIT_LARGE_CHARS) ? 1 : 0];
|
|
x0 = gdu_x + ci->xoff; // starting position
|
|
y0 = gdu_y + ci->yoff;
|
|
|
|
do {
|
|
if (++ninstr > 29) { // too many control words
|
|
gdu_interrupt(GDU_DSW_CYCLE_STEAL_CHECK);
|
|
return;
|
|
}
|
|
|
|
dospace = TRUE;
|
|
w = M[gdu_ar++ & mem_mask]; // get next stroke or control word
|
|
|
|
x1 = (w >> 12) & 7;
|
|
y1 = (w >> 8) & 7;
|
|
|
|
if (x1 == 7) { // this is a character control word
|
|
dospace = FALSE; // inhibit character spacing
|
|
|
|
switch (y1) {
|
|
case 1: // subscript
|
|
if (yoff == 0) // (ignored if superscript is in effect)
|
|
yoff = -ci->suby;
|
|
break;
|
|
|
|
// case 2: // no-op or null (nothing to do)
|
|
// default: // all unknowns are no-ops
|
|
// break;
|
|
|
|
case 4: // superscript
|
|
yoff = ci->suby;
|
|
break;
|
|
|
|
case 7: // new line
|
|
gdu_x = 0;
|
|
gdu_y -= ci->dy;
|
|
if (gdu_y < 0 && last_abs)
|
|
gdu_y = 1024 - ci->dy; // this is a guess
|
|
break;
|
|
}
|
|
}
|
|
else { // this is stroke data -- extract two strokes
|
|
x1 = gdu_x + (int) (x1*ci->sx + 0.5);
|
|
y1 = gdu_y + (int) ((y1+yoff)*ci->sy + 0.5);
|
|
|
|
if (w & 0x0800) {
|
|
didstroke = TRUE;
|
|
DrawLine(x0, y0, x1, y1);
|
|
}
|
|
|
|
x0 = (w >> 4) & 7;
|
|
y0 = w & 7;
|
|
|
|
x0 = gdu_x + (int) (x0*ci->sx + 0.5);
|
|
y0 = gdu_y + (int) ((y0+yoff)*ci->sy + 0.5);
|
|
|
|
if (w & 0x0008) {
|
|
didstroke = TRUE;
|
|
DrawLine(x1, y1, x0, y0);
|
|
}
|
|
}
|
|
|
|
if (dospace) {
|
|
gdu_x += ci->dx;
|
|
if (gdu_x > 1023 && last_abs) { // line wrap
|
|
gdu_x = 0;
|
|
gdu_y -= ci->dy;
|
|
}
|
|
}
|
|
} while ((w & 0x0080) == 0); // repeat until we hit revert bit
|
|
|
|
if (didstroke && mouse_present && (gdu_unit.flags & UNIT_DETECTS_ENABLED)) {
|
|
if (xmouse >= (gdu_x - ci->xoff/2) && xmouse <= (gdu_x + ci->xoff/2) &&
|
|
ymouse >= (gdu_y - ci->yoff/2) && ymouse <= (gdu_y + ci->yoff/2))
|
|
SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
|
|
}
|
|
}
|
|
|
|
/******* PLATFORM SPECIFIC CODE ***********************************************************/
|
|
|
|
#ifdef WIN32
|
|
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
|
|
#define APPCLASS "IBM2250GDU" // window class name
|
|
|
|
#define RGB_GREEN RGB(0,255,0) // handy colors
|
|
#define RGB_RED RGB(255,0,0)
|
|
|
|
static HINSTANCE hInstance;
|
|
static HWND hwGDU = NULL;
|
|
static HDC hdcGDU = NULL;
|
|
static HBITMAP hBmp = NULL;
|
|
static int curwid = 0;
|
|
static int curht = 0;
|
|
static BOOL wcInited = FALSE;
|
|
static int GDUPumpID = 0;
|
|
static HANDLE hGDUPump = INVALID_HANDLE_VALUE;
|
|
static HPEN hGreenPen = NULL;
|
|
static HBRUSH hRedBrush = NULL;
|
|
#ifdef DEBUG_LIGHTPEN
|
|
static HPEN hRedPen = NULL;
|
|
#endif
|
|
static HBRUSH hGrayBrush, hDarkBrush;
|
|
static HPEN hBlackPen;
|
|
|
|
static LRESULT APIENTRY GDUWndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
|
|
static DWORD WINAPI GDUPump (LPVOID arg);
|
|
|
|
static void destroy_GDU_window (void)
|
|
{
|
|
if (hwGDU != NULL)
|
|
SendMessage(hwGDU, WM_CLOSE, 0, 0); // cross thread call is OK
|
|
|
|
if (hGDUPump != INVALID_HANDLE_VALUE) { // this is not the most graceful way to do it
|
|
TerminateThread(hGDUPump, 0);
|
|
hGDUPump = INVALID_HANDLE_VALUE;
|
|
GDUPumpID = 0;
|
|
hwGDU = NULL;
|
|
}
|
|
|
|
if (hdcGDU != NULL) {
|
|
DeleteDC(hdcGDU);
|
|
hdcGDU = NULL;
|
|
}
|
|
|
|
if (hBmp != NULL) {
|
|
DeleteObject(hBmp);
|
|
hBmp = NULL;
|
|
}
|
|
|
|
if (hGreenPen != NULL) {
|
|
DeleteObject(hGreenPen);
|
|
hGreenPen = NULL;
|
|
}
|
|
|
|
if (hRedBrush != NULL) {
|
|
DeleteObject(hRedBrush);
|
|
hRedBrush = NULL;
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHTPEN
|
|
if (hRedPen != NULL) {
|
|
DeleteObject(hRedPen);
|
|
hRedPen = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static t_bool CreateGDUWindow (void)
|
|
{
|
|
static BOOL did_atexit = FALSE;
|
|
|
|
if (hwGDU != NULL) { // window already exists
|
|
StartGDUUpdates();
|
|
return TRUE;
|
|
}
|
|
|
|
hInstance = GetModuleHandle(NULL);
|
|
|
|
if (hGDUPump == INVALID_HANDLE_VALUE)
|
|
hGDUPump = CreateThread(NULL, 0, GDUPump, 0, 0, &GDUPumpID);
|
|
|
|
if (! did_atexit) {
|
|
atexit(destroy_GDU_window);
|
|
did_atexit = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// windows message handlers ----------------------------------------------------
|
|
|
|
// close the window
|
|
|
|
static void gdu_WM_CLOSE (HWND hWnd)
|
|
{
|
|
DestroyWindow(hWnd);
|
|
}
|
|
|
|
// the window is being destroyed
|
|
|
|
static void gdu_WM_DESTROY (HWND hWnd)
|
|
{
|
|
notify_window_closed();
|
|
hwGDU = NULL;
|
|
}
|
|
|
|
// adjust the min and max resizing boundaries
|
|
|
|
static void gdu_WM_GETMINMAXINFO (HWND hWnd, LPMINMAXINFO mm)
|
|
{
|
|
mm->ptMinTrackSize.x = 100 + 2*INDWIDTH;
|
|
mm->ptMinTrackSize.y = 100;
|
|
}
|
|
|
|
static void PaintImage (HDC hDC, BOOL draw_indicators)
|
|
{
|
|
HPEN hOldPen;
|
|
RECT r;
|
|
int wid, ht, x, y, dy, i, j, ycirc;
|
|
unsigned long bit;
|
|
|
|
GetClientRect(hwGDU, &r);
|
|
wid = r.right+1 - 2*INDWIDTH;
|
|
ht = r.bottom+1;
|
|
sfactor = (double) MIN(wid,ht) / 1024.;
|
|
|
|
if (gdu_dsw & GDU_DSW_BUSY) {
|
|
#ifdef BLIT_MODE
|
|
// if compiled for BLIT_MODE, draw the image into a memory display context, then
|
|
// blit the new image over window. This eliminates the flicker that a normal erase-and-
|
|
// repaint would cause.
|
|
|
|
if (wid != curwid || ht != curht) { // dimensions have changed, discard old memory display context
|
|
if (hdcGDU != NULL) {
|
|
DeleteDC(hdcGDU);
|
|
hdcGDU = NULL;
|
|
}
|
|
curwid = wid;
|
|
curht = ht;
|
|
}
|
|
|
|
if (hdcGDU == NULL) { // allocate memory display context & select a bitmap into it
|
|
hdcGDU = CreateCompatibleDC(hDC);
|
|
if (hBmp != NULL)
|
|
DeleteObject(hBmp);
|
|
hBmp = CreateCompatibleBitmap(hDC, wid, ht);
|
|
SelectObject(hdcGDU, hBmp);
|
|
}
|
|
|
|
PatBlt(hdcGDU, 0, 0, wid, ht, BLACKNESS); // start with a black screen
|
|
|
|
hOldPen = SelectObject(hdcGDU, hGreenPen);
|
|
|
|
SetMapMode(hdcGDU, MM_ANISOTROPIC);
|
|
SetWindowExtEx(hdcGDU, 1024, -1024, NULL);
|
|
SetViewportExtEx(hdcGDU, wid, ht, NULL);
|
|
SetWindowOrgEx(hdcGDU, 0, 1023, NULL);
|
|
|
|
generate_image(); // run the display program to paint the image into the memory context
|
|
|
|
SetWindowExtEx(hdcGDU, wid, ht, NULL); // undo the scaling so the blit isn't distorted
|
|
SetViewportExtEx(hdcGDU, wid, ht, NULL);
|
|
SetWindowOrgEx(hdcGDU, 0, 0, NULL);
|
|
BitBlt(hDC, 0, 0, wid, ht, hdcGDU, 0, 0, SRCCOPY); // blit the new image over the old
|
|
|
|
SelectObject(hdcGDU, hOldPen);
|
|
#else
|
|
// for testing purposes -- draw the image directly onto the screen.
|
|
// Compile this way when you want to single-step through the image drawing routine,
|
|
// so you can see the draws occur.
|
|
hdcGDU = hDC;
|
|
hOldPen = SelectObject(hdcGDU, hGreenPen);
|
|
|
|
SetMapMode(hdcGDU, MM_ANISOTROPIC);
|
|
SetWindowExtEx(hdcGDU, 1024, -1024, NULL);
|
|
SetViewportExtEx(hdcGDU, wid, ht, NULL);
|
|
SetWindowOrgEx(hdcGDU, 0, 1023, NULL);
|
|
|
|
generate_image();
|
|
|
|
SelectObject(hdcGDU, hOldPen);
|
|
hdcGDU = NULL;
|
|
#endif
|
|
}
|
|
|
|
if (draw_indicators) {
|
|
x = r.right-2*INDWIDTH+1;
|
|
dy = ht / 16;
|
|
ycirc = MIN(dy-2, 8);
|
|
|
|
r.left = x;
|
|
FillRect(hDC, &r, hGrayBrush);
|
|
SelectObject(hDC, hBlackPen);
|
|
|
|
bit = 0x80000000L;
|
|
for (i = 0; i < 2; i++) {
|
|
MoveToEx(hDC, x, 0, NULL);
|
|
LineTo(hDC, x, r.bottom);
|
|
y = 0;
|
|
for (j = 0; j < 16; j++) {
|
|
MoveToEx(hDC, x, y, NULL);
|
|
LineTo(hDC, x+INDWIDTH, y);
|
|
|
|
SelectObject(hDC, (gdu_indicators & bit) ? hRedBrush : hDarkBrush);
|
|
Pie(hDC, x+1, y+1, x+1+ycirc, y+1+ycirc, x+1, y+1, x+1, y+1);
|
|
|
|
y += dy;
|
|
bit >>= 1;
|
|
}
|
|
x += INDWIDTH;
|
|
}
|
|
}
|
|
}
|
|
|
|
// repaint the window
|
|
|
|
static void gdu_WM_PAINT (HWND hWnd)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hDC;
|
|
// code for display
|
|
hDC = BeginPaint(hWnd, &ps);
|
|
PaintImage(hDC, TRUE);
|
|
EndPaint(hWnd, &ps);
|
|
}
|
|
|
|
// the window has been resized
|
|
|
|
static void gdu_WM_SIZE (HWND hWnd, UINT state, int cx, int cy)
|
|
{
|
|
InvalidateRect(hWnd, NULL, FALSE);
|
|
}
|
|
|
|
// tweak the sizing rectangle during a resize to guarantee a square window
|
|
|
|
static void gdu_WM_SIZING (HWND hWnd, WPARAM fwSide, LPRECT r)
|
|
{
|
|
switch (fwSide) {
|
|
case WMSZ_LEFT:
|
|
case WMSZ_RIGHT:
|
|
case WMSZ_BOTTOMLEFT:
|
|
case WMSZ_BOTTOMRIGHT:
|
|
r->bottom = r->right - r->left - 2*INDWIDTH + r->top;
|
|
break;
|
|
|
|
case WMSZ_TOP:
|
|
case WMSZ_BOTTOM:
|
|
case WMSZ_TOPRIGHT:
|
|
r->right = r->bottom - r->top + r->left + 2*INDWIDTH;
|
|
break;
|
|
|
|
case WMSZ_TOPLEFT:
|
|
r->left = r->top - r->bottom + r->right - 2*INDWIDTH;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// the refresh timer has gone off
|
|
|
|
static void gdu_WM_TIMER (HWND hWnd, UINT id)
|
|
{
|
|
HDC hDC;
|
|
|
|
if (running) { // if CPU is running, update picture
|
|
#ifdef BLIT_MODE
|
|
hDC = GetDC(hWnd); // blit the new image right over the old
|
|
PaintImage(hDC, FALSE);
|
|
ReleaseDC(hWnd, hDC);
|
|
#else
|
|
InvalidateRect(hWnd, NULL, TRUE); // repaint
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// window procedure ------------------------------------------------------------
|
|
|
|
#define HANDLE(msg) case msg: return HANDLE_##msg(hWnd, wParam, lParam, gdu_##msg);
|
|
|
|
#ifndef HANDLE_WM_SIZING
|
|
// void Cls_OnSizing(HWND hwnd, UINT fwSide, LPRECT r)
|
|
# define HANDLE_WM_SIZING(hwnd, wParam, lParam, fn) \
|
|
((fn)((hwnd), (UINT)(wParam), (LPRECT)(lParam)), 0L)
|
|
#endif
|
|
|
|
static LRESULT APIENTRY GDUWndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (iMessage) {
|
|
HANDLE(WM_CLOSE);
|
|
HANDLE(WM_GETMINMAXINFO);
|
|
HANDLE(WM_DESTROY);
|
|
HANDLE(WM_PAINT);
|
|
HANDLE(WM_SIZE);
|
|
HANDLE(WM_SIZING);
|
|
HANDLE(WM_TIMER);
|
|
default: // any message we don't process
|
|
return DefWindowProc(hWnd, iMessage, wParam, lParam);
|
|
}
|
|
return 0L;
|
|
}
|
|
|
|
// graphic calls ----------------------------------------------------------------
|
|
|
|
static void DrawLine (int x0, int y0, int x1, int y1)
|
|
{
|
|
MoveToEx(hdcGDU, x0, y0, NULL);
|
|
LineTo(hdcGDU, x1, y1);
|
|
}
|
|
|
|
static void DrawPoint (int x, int y)
|
|
{
|
|
SetPixel(hdcGDU, x, y, RGB_GREEN);
|
|
}
|
|
|
|
static void UpdateGDUIndicators(void)
|
|
{
|
|
if (hwGDU != NULL)
|
|
InvalidateRect(hwGDU, NULL, TRUE);
|
|
}
|
|
|
|
static void CheckGDUKeyboard (void)
|
|
{
|
|
}
|
|
|
|
static UINT idTimer = 0;
|
|
|
|
static void StartGDUUpdates (void)
|
|
{
|
|
int msec;
|
|
|
|
if (idTimer == 0) {
|
|
msec = (gdu_rate == 0) ? (1000 / DEFAULT_GDU_RATE) : 1000/gdu_rate;
|
|
idTimer = SetTimer(hwGDU, 1, msec, NULL);
|
|
}
|
|
}
|
|
|
|
static void StopGDUUpdates (void)
|
|
{
|
|
if (idTimer != 0) {
|
|
KillTimer(hwGDU, 1);
|
|
idTimer = 0;
|
|
}
|
|
}
|
|
|
|
static void GetMouseCoordinates()
|
|
{
|
|
POINT p;
|
|
RECT r;
|
|
|
|
GetCursorPos(&p);
|
|
GetClientRect(hwGDU, &r);
|
|
if (! ScreenToClient(hwGDU, &p)) {
|
|
xmouse = ymouse = -2000;
|
|
mouse_present = FALSE;
|
|
return;
|
|
}
|
|
|
|
if (p.x < r.left || p.x >= r.right || p.y < r.top || p.y > r.bottom) {
|
|
mouse_present = FALSE;
|
|
return;
|
|
}
|
|
|
|
// convert mouse coordinates to scaled coordinates
|
|
|
|
xmouse = (int) (1024./(r.right+1.-2*INDWIDTH)*p.x + 0.5);
|
|
ymouse = 1023 - (int) (1024./(r.bottom+1.)*p.y + 0.5);
|
|
mouse_present = TRUE;
|
|
}
|
|
|
|
t_bool gdu_active (void)
|
|
{
|
|
return gdu_dsw & GDU_DSW_BUSY;
|
|
}
|
|
|
|
static void EraseGDUScreen (void)
|
|
{
|
|
if (hwGDU != NULL) /* redraw screen. it will be blank if GDU is not running */
|
|
InvalidateRect(hwGDU, NULL, TRUE);
|
|
}
|
|
|
|
/* GDUPump - thread responsible for creating and displaying the graphics window */
|
|
|
|
static DWORD WINAPI GDUPump (LPVOID arg)
|
|
{
|
|
MSG msg;
|
|
WNDCLASS wc;
|
|
|
|
if (! wcInited) { /* register Window class */
|
|
memset(&wc, 0, sizeof(wc));
|
|
wc.style = CS_NOCLOSE;
|
|
wc.lpfnWndProc = GDUWndProc;
|
|
wc.hInstance = hInstance;
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hbrBackground = GetStockObject(BLACK_BRUSH);
|
|
wc.lpszClassName = APPCLASS;
|
|
|
|
if (! RegisterClass(&wc)) {
|
|
GDUPumpID = 0;
|
|
return 0;
|
|
}
|
|
|
|
wcInited = TRUE;
|
|
}
|
|
|
|
if (hGreenPen == NULL)
|
|
hGreenPen = CreatePen(PS_SOLID, 1, RGB_GREEN);
|
|
|
|
#ifdef DEBUG_LIGHTPEN
|
|
if (hRedPen == NULL)
|
|
hRedPen = CreatePen(PS_SOLID, 1, RGB_RED);
|
|
#endif
|
|
|
|
if (hRedBrush == NULL)
|
|
hRedBrush = CreateSolidBrush(RGB_RED);
|
|
|
|
hGrayBrush = GetStockObject(GRAY_BRUSH);
|
|
hDarkBrush = GetStockObject(DKGRAY_BRUSH);
|
|
hBlackPen = GetStockObject(BLACK_PEN);
|
|
|
|
if (hwGDU == NULL) { /* create window */
|
|
hwGDU = CreateWindow(APPCLASS,
|
|
"2250 Display",
|
|
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
|
|
CW_USEDEFAULT, CW_USEDEFAULT, // initial x, y position
|
|
INITSIZE+2*INDWIDTH, INITSIZE, // initial width and height
|
|
NULL, // parent window handle
|
|
NULL, // use class menu
|
|
hInstance, // program instance handle
|
|
NULL); // create parameters
|
|
|
|
if (hwGDU == NULL) {
|
|
GDUPumpID = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ShowWindow(hwGDU, SW_SHOWNOACTIVATE); /* display it */
|
|
UpdateWindow(hwGDU);
|
|
|
|
StartGDUUpdates();
|
|
|
|
while (GetMessage(&msg, hwGDU, 0, 0)) { /* message pump - this basically loops forevermore */
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
if (hwGDU != NULL) {
|
|
DestroyWindow(hwGDU); /* but if a quit message got posted, clean up */
|
|
hwGDU = NULL;
|
|
}
|
|
|
|
GDUPumpID = 0;
|
|
return 0;
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHTPEN
|
|
static void ShowPenHit (int x, int y)
|
|
{
|
|
HPEN hOldPen;
|
|
|
|
hOldPen = SelectObject(hdcGDU, hRedPen);
|
|
DrawLine(x-10, y-10, x+10, y+10);
|
|
DrawLine(x-10, y+10, x+10, y-10);
|
|
SelectObject(hdcGDU, hOldPen);
|
|
}
|
|
#endif
|
|
|
|
#endif // WIN32 defined
|
|
#endif // GUI_SUPPORT defined
|