blob: 86d32c45ba35be4d4b5bf1b57727d844d005ed26 [file] [log] [blame] [raw]
#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 /* define for better performance, undefine when debugging generate_image() */
/* #define DEBUG_LIGHTPEN */ /* define to debug 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 if device is nonexistent */
}
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 = (int16) (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 = (int16) 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) == 0) {
if (! CreateGDUWindow())
return;
SETBIT(gdu_unit.flags, UNIT_DISPLAYED);
}
StartGDUUpdates();
}
static void halt_regeneration (void)
{
// halt_regeneration gets called at end of every refresh interation, so it should NOT black out the
// screen -- this is why it was flickering so badly. The lower level code (called on a timer)
// should check to see if GDU_DSW_BUSY is clear, and if it it still zero after several msec,
// only then should it black out the screen and call StopGDUUpdates.
if (gdu_dsw & GDU_DSW_BUSY) {
// StopGDUUpdates(); // let lower level code discover this during next refresh
CLRBIT(gdu_dsw, GDU_DSW_BUSY);
}
// EraseGDUScreen(); // let cessation of regeneration erase it (eventually)
}
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 = (int16) ((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 = (int16) newx;
gdu_y = (int16) 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 = (int16) (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 = (int16) 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 -= (int16) ci->dy;
if (gdu_y < 0 && last_abs)
gdu_y = (int16) (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 -= (int16) 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 DWORD 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 int halted = 0; // number of time intervals that GDU has been halted w/o a regeneration
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;
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, TRUE);
}
// 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
if ((gdu_dsw & GDU_DSW_BUSY) == 0) { // regeneration is not to occur
if (++halted >= 4) { // stop the timer if four timer intervals go by with the display halted
EraseGDUScreen(); // screen goes black due to cessation of refreshing
StopGDUUpdates(); // might as well kill the timer
return;
}
}
else
halted = 0;
#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, FALSE); // no need to erase the background -- the draw routine fully paints the indicator
}
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);
}
halted = 0;
}
static void StopGDUUpdates (void)
{
if (idTimer != 0) {
KillTimer(hwGDU, 1);
idTimer = 0;
halted = 10000;
}
}
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);
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