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