#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 simh@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 |