#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) { if (cgi) return; /* ignore this device in CGI mode */ 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) { if (cgi) return SCPE_OK; /* ignore this device in CGI mode */ 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) { CLRBIT(gdu_dsw, GDU_DSW_BUSY); } } 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 #include #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 UINT idTimer = 0; static t_bool painting = FALSE; 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 #ifdef XXXX // let window closure do this 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; } #endif #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) { PostMessage(hWnd, WM_QUIT, 0, 0); if (idTimer != 0) { KillTimer(hwGDU, 1); idTimer = 0; halted = 10000; painting = FALSE; } 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; int msec; // code for display hDC = BeginPaint(hWnd, &ps); PaintImage(hDC, TRUE); EndPaint(hWnd, &ps); // set a timer so we keep doing it! if (idTimer == 0) { msec = (gdu_rate == 0) ? (1000 / DEFAULT_GDU_RATE) : 1000/gdu_rate; idTimer = SetTimer(hwGDU, 1, msec, NULL); } } // the window has been resized static void gdu_WM_SIZE (HWND hWnd, UINT state, int cx, int cy) { #ifdef BLIT_MODE InvalidateRect(hWnd, NULL, FALSE); // in blt mode, we'll paint a full black bitmap over the new screen size #else InvalidateRect(hWnd, NULL, TRUE); #endif } // 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 (painting) { // if GDU 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 void StartGDUUpdates (void) { halted = 0; painting = TRUE; } static void StopGDUUpdates (void) { painting = FALSE; } 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 cgi ? 0 : (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); } painting = FALSE; 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