1 #include "ibm1130_defs.h"
3 /* ibm1130_gdu.c: IBM 1130 2250 Graphical Display Unit
7 "store revert" might be backwards?
8 alpha keyboard is not implemented
9 pushbuttons are not implemented
10 there is something about interrupts being deferred during a subroutine transition?
12 Based on the SIMH package written by Robert M Supnik
14 * (C) Copyright 2002, Brian Knittel.
15 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN
16 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the
17 * usual yada-yada. Please keep this notice and the copyright in any distributions
20 * This is not a supported product, but I welcome bug reports and fixes.
21 * Mail to simh@ibm1130.org
24 #define BLIT_MODE /* define for better performance, undefine when debugging generate_image() */
25 /* #define DEBUG_LIGHTPEN */ /* define to debug light-pen sensing */
27 #define DEFAULT_GDU_RATE 20 /* default frame rate */
28 #define DEFAULT_PEN_THRESHOLD 3 /* default looseness of light-pen hit */
29 #define INDWIDTH 32 /* width of an indicator (there are two columns of these) */
30 #define INITSIZE 512 /* initial window size */
32 #define GDU_DSW_ORDER_CONTROLLED_INTERRUPT 0x8000
33 #define GDU_DSW_KEYBOARD_INTERUPT 0x4000
34 #define GDU_DSW_DETECT_INTERRUPT 0x2000
35 #define GDU_DSW_CYCLE_STEAL_CHECK 0x1000
36 #define GDU_DSW_DETECT_STATUS 0x0800
37 #define GDU_DSW_LIGHT_PEN_SWITCH 0x0100
38 #define GDU_DSW_BUSY 0x0080
39 #define GDU_DSW_CHARACTER_MODE 0x0040
40 #define GDU_DSW_POINT_MODE 0x0020
41 #define GDU_DSW_ADDR_DISP 0x0003
43 #define GDU_FKEY_DATA_AVAILABLE 0x8000
44 #define GDU_FKEY_KEY_CODE 0x1F00
45 #define GDU_FKEY_OVERLAY_CODE 0x00FF
47 #define GDU_AKEY_DATA_AVAILABLE 0x8000
48 #define GDU_AKEY_END 0x1000
49 #define GDU_AKEY_CANCEL 0x0800
50 #define GDU_AKEY_ADVANCE 0x0400
51 #define GDU_AKEY_BACKSPACE 0x0200
52 #define GDU_AKEY_JUMP 0x0100
53 #define GDU_AKEY_KEY_CODE 0x00FF
55 /* -------------------------------------------------------------------------------------- */
57 #define UNIT_V_DISPLAYED (UNIT_V_UF + 0)
58 #define UNIT_V_DETECTS_ENABLED (UNIT_V_UF + 1)
59 #define UNIT_V_INTERRUPTS_DEFERRED (UNIT_V_UF + 2)
60 #define UNIT_V_LARGE_CHARS (UNIT_V_UF + 3)
62 #define UNIT_DISPLAYED (1u << UNIT_V_DISPLAYED) /* display windows is up */
63 #define UNIT_DETECTS_ENABLED (1u << UNIT_V_DETECTS_ENABLED) /* light pen detects are enabled */
64 #define UNIT_INTERRUPTS_DEFERRED (1u << UNIT_V_INTERRUPTS_DEFERRED) /* light pen interrupts are deferred */
65 #define UNIT_LARGE_CHARS (1u << UNIT_V_LARGE_CHARS) /* large character mode */
67 static t_stat
gdu_reset (DEVICE
*dptr
);
69 static int16 gdu_dsw
= 1; /* device status word */
70 static int16 gdu_ar
= 0; /* address register */
71 static int16 gdu_x
= 0; /* X deflection */
72 static int16 gdu_y
= 0; /* Y deflection */
73 static int16 gdu_fkey
= 0; /* function keyboard register */
74 static int16 gdu_akey
= 0; /* alphanumeric keyboard register */
75 static int16 gdu_revert
= 0; /* revert address register */
76 static int32 gdu_indicators
= 0; /* programmed indicator lamps */
77 static int32 gdu_threshold
= DEFAULT_PEN_THRESHOLD
; /* mouse must be within 3/1024 of line to be a hit */
78 static int32 gdu_rate
= DEFAULT_GDU_RATE
; /* refresh rate. 0 = default */
80 UNIT gdu_unit
= { UDATA (NULL
, 0, 0) };
83 { HRDATA (GDUDSW
, gdu_dsw
, 16) }, /* device status word */
84 { HRDATA (GDUAR
, gdu_ar
, 16) }, /* address register */
85 { HRDATA (GDUXREG
, gdu_x
, 16) }, /* X deflection register */
86 { HRDATA (GDUYREG
, gdu_y
, 16) }, /* Y deflection register */
87 { HRDATA (GDUFKEY
, gdu_fkey
, 16) }, /* function keyboard register */
88 { HRDATA (GDUAKEY
, gdu_akey
, 16) }, /* alphanumeric keyboard register */
89 { HRDATA (GDUREVERT
,gdu_revert
, 16) }, /* revert address register */
90 { HRDATA (GDUAKEY
, gdu_indicators
, 32) }, /* programmed indicators */
91 { DRDATA (GDUTHRESH
,gdu_threshold
, 32) }, /* mouse closeness threshhold */
92 { DRDATA (GDURATE
, gdu_rate
, 32) }, /* refresh rate in frames/sec */
96 "GDU", &gdu_unit
, gdu_reg
, NULL
,
98 NULL
, NULL
, gdu_reset
,
101 /* -------------------------------------------------------------------------------------- */
105 static t_stat
gdu_reset (DEVICE
*dptr
)
110 void xio_2250_display (int32 addr
, int32 func
, int32 modify
)
112 /* ignore commands if device is nonexistent */
115 t_bool
gdu_active (void)
120 /* -------------------------------------------------------------------------------------- */
121 #else /* GUI_SUPPORT defined */
123 /******* PLATFORM INDEPENDENT CODE ********************************************************/
125 static int32 gdu_instaddr
; // address of first word of instruction
126 static int xmouse
, ymouse
, lpen_dist
, lpen_dist2
; // current mouse pointer, scaled closeness threshhold, same squared
127 static double sfactor
; // current scaling factor
128 static t_bool last_abs
= TRUE
; // last positioning instruction was absolute
129 static t_bool mouse_present
= FALSE
; // mouse is/is not in the window
130 static void clear_interrupts (void);
131 static void set_indicators (int32 new_inds
);
132 static void start_regeneration (void);
133 static void halt_regeneration (void);
134 static void draw_characters (void);
135 static void notify_window_closed (void);
137 // routines that must be implemented per-platform
139 static void DrawLine(int x0
, int y0
, int x1
, int y1
);
140 static void DrawPoint(int x
, int y
);
141 static void CheckGDUKeyboard(void);
142 static t_bool
CreateGDUWindow(void);
143 static void StartGDUUpdates(void);
144 static void StopGDUUpdates(void);
145 static void GetMouseCoordinates(void);
146 static void UpdateGDUIndicators(void);
147 static void ShowPenHit (int x
, int y
);
148 static void EraseGDUScreen (void);
150 /* -------------------------------------------------------------------------------------- */
152 void xio_2250_display (int32 addr
, int32 func
, int32 modify
)
156 ACC
= (gdu_dsw
& GDU_DSW_BUSY
) ? GDU_DSW_BUSY
: gdu_dsw
;
161 case XIO_READ
: /* store status data into word pointed to by IOCC packet */
162 if (gdu_dsw
& GDU_DSW_BUSY
) /* not permitted while device is busy */
165 WriteW(addr
, gdu_ar
); /* save status information */
166 WriteW(addr
+1, gdu_dsw
);
167 WriteW(addr
+2, gdu_x
& 0x7FF);
168 WriteW(addr
+3, gdu_y
& 0x7FF);
169 WriteW(addr
+4, gdu_fkey
);
170 WriteW(addr
+5, gdu_akey
);
171 gdu_ar
= (int16
) (addr
+6); /* this alters the channel address register? */
173 clear_interrupts(); /* read status clears the interrupts */
177 if (gdu_dsw
& GDU_DSW_BUSY
) /* treated as no-op if display is busy */
180 if (modify
& 0x80) { /* bit 8 on means set indicators, 0 means start regeneration */
181 set_indicators((ReadW(addr
) << 16) | ReadW(addr
+1));
184 gdu_ar
= (int16
) addr
;
188 start_regeneration();
193 if (modify
& 0x80) { /* bit 8 on means reset, off is no-op */
195 set_indicators((addr
<< 16) | addr
);
199 default: /* all other commands are no-ops */
204 static t_stat
gdu_reset (DEVICE
*dptr
)
210 CLRBIT(gdu_unit
.flags
, UNIT_INTERRUPTS_DEFERRED
| UNIT_DETECTS_ENABLED
| UNIT_LARGE_CHARS
);
215 static void clear_interrupts (void)
217 CLRBIT(gdu_dsw
, GDU_DSW_ORDER_CONTROLLED_INTERRUPT
| GDU_DSW_KEYBOARD_INTERUPT
| GDU_DSW_DETECT_INTERRUPT
);
218 CLRBIT(ILSW
[3], ILSW_3_2250_DISPLAY
);
222 static void gdu_interrupt (int32 dswbit
)
224 SETBIT(gdu_dsw
, dswbit
);
225 SETBIT(ILSW
[3], ILSW_3_2250_DISPLAY
);
230 static void set_indicators (int32 new_inds
)
232 gdu_indicators
= new_inds
;
233 if (gdu_unit
.flags
& UNIT_DISPLAYED
)
234 UpdateGDUIndicators();
237 static void start_regeneration (void)
239 SETBIT(gdu_dsw
, GDU_DSW_BUSY
);
241 if ((gdu_unit
.flags
& UNIT_DISPLAYED
) == 0) {
242 if (! CreateGDUWindow())
245 SETBIT(gdu_unit
.flags
, UNIT_DISPLAYED
);
251 static void halt_regeneration (void)
253 // halt_regeneration gets called at end of every refresh interation, so it should NOT black out the
254 // screen -- this is why it was flickering so badly. The lower level code (called on a timer)
255 // should check to see if GDU_DSW_BUSY is clear, and if it it still zero after several msec,
256 // only then should it black out the screen and call StopGDUUpdates.
257 if (gdu_dsw
& GDU_DSW_BUSY
) {
258 // StopGDUUpdates(); // let lower level code discover this during next refresh
259 CLRBIT(gdu_dsw
, GDU_DSW_BUSY
);
261 // EraseGDUScreen(); // let cessation of regeneration erase it (eventually)
264 static void notify_window_closed (void)
266 if (gdu_dsw
& GDU_DSW_BUSY
) {
268 CLRBIT(gdu_dsw
, GDU_DSW_BUSY
);
271 CLRBIT(gdu_unit
.flags
, UNIT_DISPLAYED
);
276 static int32
read_gduword (void)
280 w
= M
[gdu_ar
++ & mem_mask
];
281 gdu_dsw
= (int16
) ((gdu_dsw
& ~GDU_DSW_ADDR_DISP
) | ((gdu_ar
- gdu_instaddr
) & GDU_DSW_ADDR_DISP
));
286 #define DIST2(x0,y0,x1,y1) (((x1)-(x0))*((x1)-(x0))+((y1)-(y0))*((y1)-(y0)))
288 static void draw (int32 newx
, int32 newy
, t_bool beam
)
290 int xmin
, xmax
, ymin
, ymax
, xd
, yd
;
295 if (gdu_dsw
& GDU_DSW_POINT_MODE
) {
296 DrawPoint(newx
, newy
);
298 #ifdef DEBUG_LIGHTPEN
299 if (DIST2(newx
, newy
, xmouse
, ymouse
) <= lpen_dist2
)
302 if (gdu_unit
.flags
& UNIT_DETECTS_ENABLED
&& mouse_present
)
303 if (DIST2(newx
, newy
, xmouse
, ymouse
) <= lpen_dist2
)
308 DrawLine(gdu_x
, gdu_y
, newx
, newy
);
310 // calculate proximity of light pen to the line
311 #ifndef DEBUG_LIGHTPEN
312 if (gdu_unit
.flags
& UNIT_DETECTS_ENABLED
&& mouse_present
) {
315 xmin
= gdu_x
, xmax
= newx
;
317 xmin
= newx
, xmax
= gdu_x
;
320 ymin
= gdu_y
, ymax
= newy
;
322 ymin
= newy
, ymax
= gdu_y
;
325 // line is vertical. Nearest point is an endpoint if the mouse is above or
326 // below the line segment, otherwise the segment point at the same y as the mouse
328 yd
= (ymouse
<= ymin
) ? ymin
: (ymouse
>= ymax
) ? ymax
: ymouse
;
330 if (DIST2(xd
, yd
, xmouse
, ymouse
) <= lpen_dist2
)
333 else if (newy
== gdu_y
) {
334 // line is horizontal. Nearest point is an endpoint if the mouse is to the left or
335 // the right of the line segment, otherwise the segment point at the same x as the mouse
336 xd
= (xmouse
<= xmin
) ? xmin
: (xmouse
>= xmax
) ? xmax
: xmouse
;
339 if (DIST2(xd
, yd
, xmouse
, ymouse
) <= lpen_dist2
)
343 // line is diagonal. See if the mouse is inside the box lpen_dist wider than the line segment's bounding rectangle
344 if (xmouse
>= (xmin
-lpen_dist
) && xmouse
<= (xmax
+lpen_dist
) && ymouse
>= (ymin
-lpen_dist
) || ymouse
<= (ymax
+lpen_dist
)) {
345 // compute the point at the intersection of the line through the line segment and the normal
346 // to that line through the mouse. This is the point on the line through the line segment
349 s
= (double)(newy
- gdu_y
) / (double)(newx
- gdu_x
); // slope of line segment
350 xd
= (int) ((ymouse
+ xmouse
/s
- gdu_y
+ s
*gdu_x
) / (s
+ 1./s
) + 0.5);
352 // if intersection is beyond either end of the line segment, the nearest point to the
353 // mouse is nearest segment end, otherwise it's the computed intersection point
354 if (xd
< xmin
|| xd
> xmax
) {
355 #ifdef DEBUG_LIGHTPEN
356 // if it's a hit, set xd and yd so we can display the hit
357 if (DIST2(gdu_x
, gdu_y
, xmouse
, ymouse
) <= lpen_dist2
) {
362 else if (DIST2(newx
, newy
, xmouse
, ymouse
) <= lpen_dist2
) {
368 if (DIST2(gdu_x
, gdu_y
, xmouse
, ymouse
) <= lpen_dist2
|| DIST2(newx
, newy
, xmouse
, ymouse
) <= lpen_dist2
)
373 yd
= (int) (gdu_y
+ s
*(xd
- gdu_x
) + 0.5);
374 if (DIST2(xd
, yd
, xmouse
, ymouse
) <= lpen_dist2
)
379 #ifndef DEBUG_LIGHTPEN
386 #ifdef DEBUG_LIGHTPEN
388 if (gdu_unit
.flags
& UNIT_DETECTS_ENABLED
&& mouse_present
)
389 SETBIT(gdu_dsw
, GDU_DSW_DETECT_STATUS
);
391 SETBIT(gdu_dsw
, GDU_DSW_DETECT_STATUS
);
395 gdu_x
= (int16
) newx
;
396 gdu_y
= (int16
) newy
;
399 static void generate_image (void)
401 int32 instr
, new_addr
, newx
, newy
;
402 t_bool run
= TRUE
, accept
;
404 if (! (gdu_dsw
& GDU_DSW_BUSY
))
407 GetMouseCoordinates();
409 lpen_dist
= (int) (gdu_threshold
/sfactor
+ 0.5); // mouse-to-line threshhold at current scaling factor
410 lpen_dist2
= lpen_dist
* lpen_dist
;
413 if ((gdu_dsw
& GDU_DSW_DETECT_STATUS
) && ! (gdu_unit
.flags
& UNIT_INTERRUPTS_DEFERRED
)) {
414 CLRBIT(gdu_dsw
, GDU_DSW_DETECT_STATUS
); // clear when interrupt is activated
415 gdu_interrupt(GDU_DSW_DETECT_INTERRUPT
);
420 gdu_instaddr
= gdu_ar
; // remember address of GDU instruction
421 instr
= read_gduword(); // fetch instruction (and we really are cycle stealing here!)
423 switch ((instr
>> 12) & 0xF) { // decode instruction
424 case 0: // short branch
426 gdu_revert
= gdu_ar
; // save revert address & get new address
427 gdu_ar
= (int16
) (read_gduword() & 0x1FFF);
428 if (gdu_dsw
& GDU_DSW_CHARACTER_MODE
) {
429 draw_characters(); // in character mode this means we are at character data
434 case 2: // long branch/interrupt
435 new_addr
= read_gduword(); // get next word
436 accept
= ((instr
& 1) ? (gdu_dsw
& GDU_DSW_LIGHT_PEN_SWITCH
) : TRUE
) && ((instr
& 2) ? (gdu_dsw
& GDU_DSW_DETECT_STATUS
) : TRUE
);
438 if (instr
& 2) // clear after testing
439 CLRBIT(gdu_dsw
, GDU_DSW_DETECT_STATUS
);
441 if (instr
& 0x0400) // NOP
445 if (instr
& 0x0800) { // branch
448 if (instr
& 0x0080) // indirect
449 new_addr
= M
[new_addr
& mem_mask
];
451 gdu_ar
= (int16
) new_addr
;
453 if (gdu_dsw
& GDU_DSW_CHARACTER_MODE
) {
459 gdu_interrupt(GDU_DSW_ORDER_CONTROLLED_INTERRUPT
);
465 case 3: // control instructions
466 CLRBIT(gdu_dsw
, GDU_DSW_CHARACTER_MODE
);
468 switch ((instr
>> 8) & 0xF) {
469 case 1: // set pen mode
470 if ((instr
& 0xC) == 8)
471 SETBIT(gdu_unit
.flags
, UNIT_DETECTS_ENABLED
);
472 else if ((instr
& 0xC) == 4)
473 CLRBIT(gdu_unit
.flags
, UNIT_DETECTS_ENABLED
);
475 if ((instr
& 0x3) == 2)
476 SETBIT(gdu_unit
.flags
, UNIT_INTERRUPTS_DEFERRED
);
477 else if ((instr
& 0x3) == 1)
478 CLRBIT(gdu_unit
.flags
, UNIT_INTERRUPTS_DEFERRED
);
481 case 2: // set graphic mode
483 SETBIT(gdu_dsw
, GDU_DSW_POINT_MODE
);
485 CLRBIT(gdu_dsw
, GDU_DSW_POINT_MODE
);
488 case 3: // set character mode
489 SETBIT(gdu_dsw
, GDU_DSW_CHARACTER_MODE
);
491 SETBIT(gdu_unit
.flags
, UNIT_LARGE_CHARS
);
493 CLRBIT(gdu_unit
.flags
, UNIT_LARGE_CHARS
);
496 case 4: // start timer
497 run
= FALSE
; // (which, for us, means stop processing until next timer message)
501 case 5: // store revert
502 M
[gdu_ar
& mem_mask
] = gdu_revert
;
503 read_gduword(); // skip to next address
510 default: // all others treated as no-ops
515 case 4: // long absolute
517 CLRBIT(gdu_dsw
, GDU_DSW_CHARACTER_MODE
);
518 newx
= instr
& 0x3FF;
519 newy
= read_gduword() & 0x3FF;
520 draw(newx
, newy
, instr
& 0x1000);
524 case 6: // short absolute
526 CLRBIT(gdu_dsw
, GDU_DSW_CHARACTER_MODE
);
530 newy
= instr
& 0x3FF;
532 newx
= instr
& 0x3FF;
533 draw(newx
, newy
, instr
& 0x1000);
537 default: // high bit set - it's a relative instruction
538 CLRBIT(gdu_dsw
, GDU_DSW_CHARACTER_MODE
);
539 newx
= (instr
>> 8) & 0x3F;
542 if (instr
& 0x4000) // sign extend x - values are in 2's complement
543 newx
|= -1 & ~0x3F; // although documentation doesn't make that clear
545 if (instr
& 0x0040) // sign extend y
550 draw(newx
, newy
, instr
& 0x0080);
557 static struct charinfo
{ // character mode scaling info:
558 int dx
, dy
; // character and line spacing
559 double sx
, sy
; // scaling factors: character units to screen units
560 int xoff
, yoff
; // x and y offset to lower left corner of character
561 int suby
; // subscript/superscript offset
563 {14, 20, 1.7, 2.0, -6, -7, 6}, // regular
564 {21, 30, 2.5, 3.0, -9, -11, 9} // large
567 static void draw_characters (void)
569 int32 w
, x0
, y0
, x1
, y1
, yoff
= 0, ninstr
= 0;
570 t_bool dospace
, didstroke
= FALSE
;
573 ci
= &cx
[(gdu_unit
.flags
& UNIT_LARGE_CHARS
) ? 1 : 0];
574 x0
= gdu_x
+ ci
->xoff
; // starting position
575 y0
= gdu_y
+ ci
->yoff
;
578 if (++ninstr
> 29) { // too many control words
579 gdu_interrupt(GDU_DSW_CYCLE_STEAL_CHECK
);
584 w
= M
[gdu_ar
++ & mem_mask
]; // get next stroke or control word
589 if (x1
== 7) { // this is a character control word
590 dospace
= FALSE
; // inhibit character spacing
594 if (yoff
== 0) // (ignored if superscript is in effect)
598 // case 2: // no-op or null (nothing to do)
599 // default: // all unknowns are no-ops
602 case 4: // superscript
608 gdu_y
-= (int16
) ci
->dy
;
609 if (gdu_y
< 0 && last_abs
)
610 gdu_y
= (int16
) (1024 - ci
->dy
); // this is a guess
614 else { // this is stroke data -- extract two strokes
615 x1
= gdu_x
+ (int) (x1
*ci
->sx
+ 0.5);
616 y1
= gdu_y
+ (int) ((y1
+yoff
)*ci
->sy
+ 0.5);
620 DrawLine(x0
, y0
, x1
, y1
);
626 x0
= gdu_x
+ (int) (x0
*ci
->sx
+ 0.5);
627 y0
= gdu_y
+ (int) ((y0
+yoff
)*ci
->sy
+ 0.5);
631 DrawLine(x1
, y1
, x0
, y0
);
637 if (gdu_x
> 1023 && last_abs
) { // line wrap
639 gdu_y
-= (int16
) ci
->dy
;
642 } while ((w
& 0x0080) == 0); // repeat until we hit revert bit
644 if (didstroke
&& mouse_present
&& (gdu_unit
.flags
& UNIT_DETECTS_ENABLED
)) {
645 if (xmouse
>= (gdu_x
- ci
->xoff
/2) && xmouse
<= (gdu_x
+ ci
->xoff
/2) &&
646 ymouse
>= (gdu_y
- ci
->yoff
/2) && ymouse
<= (gdu_y
+ ci
->yoff
/2))
647 SETBIT(gdu_dsw
, GDU_DSW_DETECT_STATUS
);
651 /******* PLATFORM SPECIFIC CODE ***********************************************************/
656 #include <windowsx.h>
658 #define APPCLASS "IBM2250GDU" // window class name
660 #define RGB_GREEN RGB(0,255,0) // handy colors
661 #define RGB_RED RGB(255,0,0)
663 static HINSTANCE hInstance
;
664 static HWND hwGDU
= NULL
;
665 static HDC hdcGDU
= NULL
;
666 static HBITMAP hBmp
= NULL
;
667 static int curwid
= 0;
668 static int curht
= 0;
669 static BOOL wcInited
= FALSE
;
670 static DWORD GDUPumpID
= 0;
671 static HANDLE hGDUPump
= INVALID_HANDLE_VALUE
;
672 static HPEN hGreenPen
= NULL
;
673 static HBRUSH hRedBrush
= NULL
;
674 #ifdef DEBUG_LIGHTPEN
675 static HPEN hRedPen
= NULL
;
677 static HBRUSH hGrayBrush
, hDarkBrush
;
678 static HPEN hBlackPen
;
679 static int halted
= 0; // number of time intervals that GDU has been halted w/o a regeneration
680 static LRESULT APIENTRY
GDUWndProc (HWND hWnd
, UINT iMessage
, WPARAM wParam
, LPARAM lParam
);
681 static DWORD WINAPI
GDUPump (LPVOID arg
);
683 static void destroy_GDU_window (void)
686 SendMessage(hwGDU
, WM_CLOSE
, 0, 0); // cross thread call is OK
688 if (hGDUPump
!= INVALID_HANDLE_VALUE
) { // this is not the most graceful way to do it
689 TerminateThread(hGDUPump
, 0);
690 hGDUPump
= INVALID_HANDLE_VALUE
;
695 if (hdcGDU
!= NULL
) {
705 if (hGreenPen
!= NULL
) {
706 DeleteObject(hGreenPen
);
710 if (hRedBrush
!= NULL
) {
711 DeleteObject(hRedBrush
);
715 #ifdef DEBUG_LIGHTPEN
716 if (hRedPen
!= NULL
) {
717 DeleteObject(hRedPen
);
723 static t_bool
CreateGDUWindow (void)
725 static BOOL did_atexit
= FALSE
;
727 hInstance
= GetModuleHandle(NULL
);
729 if (hGDUPump
== INVALID_HANDLE_VALUE
)
730 hGDUPump
= CreateThread(NULL
, 0, GDUPump
, 0, 0, &GDUPumpID
);
733 atexit(destroy_GDU_window
);
740 // windows message handlers ----------------------------------------------------
744 static void gdu_WM_CLOSE (HWND hWnd
)
749 // the window is being destroyed
751 static void gdu_WM_DESTROY (HWND hWnd
)
753 notify_window_closed();
757 // adjust the min and max resizing boundaries
759 static void gdu_WM_GETMINMAXINFO (HWND hWnd
, LPMINMAXINFO mm
)
761 mm
->ptMinTrackSize
.x
= 100 + 2*INDWIDTH
;
762 mm
->ptMinTrackSize
.y
= 100;
765 static void PaintImage (HDC hDC
, BOOL draw_indicators
)
769 int wid
, ht
, x
, y
, dy
, i
, j
, ycirc
;
772 GetClientRect(hwGDU
, &r
);
773 wid
= r
.right
+1 - 2*INDWIDTH
;
775 sfactor
= (double) MIN(wid
,ht
) / 1024.;
777 if (gdu_dsw
& GDU_DSW_BUSY
) {
779 // if compiled for BLIT_MODE, draw the image into a memory display context, then
780 // blit the new image over window. This eliminates the flicker that a normal erase-and-
781 // repaint would cause.
783 if (wid
!= curwid
|| ht
!= curht
) { // dimensions have changed, discard old memory display context
784 if (hdcGDU
!= NULL
) {
792 if (hdcGDU
== NULL
) { // allocate memory display context & select a bitmap into it
793 hdcGDU
= CreateCompatibleDC(hDC
);
796 hBmp
= CreateCompatibleBitmap(hDC
, wid
, ht
);
797 SelectObject(hdcGDU
, hBmp
);
800 PatBlt(hdcGDU
, 0, 0, wid
, ht
, BLACKNESS
); // start with a black screen
802 hOldPen
= SelectObject(hdcGDU
, hGreenPen
);
804 SetMapMode(hdcGDU
, MM_ANISOTROPIC
);
805 SetWindowExtEx(hdcGDU
, 1024, -1024, NULL
);
806 SetViewportExtEx(hdcGDU
, wid
, ht
, NULL
);
807 SetWindowOrgEx(hdcGDU
, 0, 1023, NULL
);
809 generate_image(); // run the display program to paint the image into the memory context
811 SetWindowExtEx(hdcGDU
, wid
, ht
, NULL
); // undo the scaling so the blit isn't distorted
812 SetViewportExtEx(hdcGDU
, wid
, ht
, NULL
);
813 SetWindowOrgEx(hdcGDU
, 0, 0, NULL
);
814 BitBlt(hDC
, 0, 0, wid
, ht
, hdcGDU
, 0, 0, SRCCOPY
); // blit the new image over the old
816 SelectObject(hdcGDU
, hOldPen
);
818 // for testing purposes -- draw the image directly onto the screen.
819 // Compile this way when you want to single-step through the image drawing routine,
820 // so you can see the draws occur.
822 hOldPen
= SelectObject(hdcGDU
, hGreenPen
);
824 SetMapMode(hdcGDU
, MM_ANISOTROPIC
);
825 SetWindowExtEx(hdcGDU
, 1024, -1024, NULL
);
826 SetViewportExtEx(hdcGDU
, wid
, ht
, NULL
);
827 SetWindowOrgEx(hdcGDU
, 0, 1023, NULL
);
831 SelectObject(hdcGDU
, hOldPen
);
836 if (draw_indicators
) {
837 x
= r
.right
-2*INDWIDTH
+1;
839 ycirc
= MIN(dy
-2, 8);
842 FillRect(hDC
, &r
, hGrayBrush
);
843 SelectObject(hDC
, hBlackPen
);
846 for (i
= 0; i
< 2; i
++) {
847 MoveToEx(hDC
, x
, 0, NULL
);
848 LineTo(hDC
, x
, r
.bottom
);
850 for (j
= 0; j
< 16; j
++) {
851 MoveToEx(hDC
, x
, y
, NULL
);
852 LineTo(hDC
, x
+INDWIDTH
, y
);
854 SelectObject(hDC
, (gdu_indicators
& bit
) ? hRedBrush
: hDarkBrush
);
855 Pie(hDC
, x
+1, y
+1, x
+1+ycirc
, y
+1+ycirc
, x
+1, y
+1, x
+1, y
+1);
865 // repaint the window
867 static void gdu_WM_PAINT (HWND hWnd
)
872 hDC
= BeginPaint(hWnd
, &ps
);
873 PaintImage(hDC
, TRUE
);
877 // the window has been resized
879 static void gdu_WM_SIZE (HWND hWnd
, UINT state
, int cx
, int cy
)
881 InvalidateRect(hWnd
, NULL
, TRUE
);
884 // tweak the sizing rectangle during a resize to guarantee a square window
886 static void gdu_WM_SIZING (HWND hWnd
, WPARAM fwSide
, LPRECT r
)
891 case WMSZ_BOTTOMLEFT
:
892 case WMSZ_BOTTOMRIGHT
:
893 r
->bottom
= r
->right
- r
->left
- 2*INDWIDTH
+ r
->top
;
899 r
->right
= r
->bottom
- r
->top
+ r
->left
+ 2*INDWIDTH
;
903 r
->left
= r
->top
- r
->bottom
+ r
->right
- 2*INDWIDTH
;
908 // the refresh timer has gone off
910 static void gdu_WM_TIMER (HWND hWnd
, UINT id
)
914 if (running
) { // if CPU is running, update picture
915 if ((gdu_dsw
& GDU_DSW_BUSY
) == 0) { // regeneration is not to occur
916 if (++halted
>= 4) { // stop the timer if four timer intervals go by with the display halted
917 EraseGDUScreen(); // screen goes black due to cessation of refreshing
918 StopGDUUpdates(); // might as well kill the timer
926 hDC
= GetDC(hWnd
); // blit the new image right over the old
927 PaintImage(hDC
, FALSE
);
928 ReleaseDC(hWnd
, hDC
);
930 InvalidateRect(hWnd
, NULL
, TRUE
); // repaint
935 // window procedure ------------------------------------------------------------
937 #define HANDLE(msg) case msg: return HANDLE_##msg(hWnd, wParam, lParam, gdu_##msg);
939 #ifndef HANDLE_WM_SIZING
940 // void Cls_OnSizing(HWND hwnd, UINT fwSide, LPRECT r)
941 # define HANDLE_WM_SIZING(hwnd, wParam, lParam, fn) \
942 ((fn)((hwnd), (UINT)(wParam), (LPRECT)(lParam)), 0L)
945 static LRESULT APIENTRY
GDUWndProc (HWND hWnd
, UINT iMessage
, WPARAM wParam
, LPARAM lParam
)
949 HANDLE(WM_GETMINMAXINFO
);
955 default: // any message we don't process
956 return DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
961 // graphic calls ----------------------------------------------------------------
963 static void DrawLine (int x0
, int y0
, int x1
, int y1
)
965 MoveToEx(hdcGDU
, x0
, y0
, NULL
);
966 LineTo(hdcGDU
, x1
, y1
);
969 static void DrawPoint (int x
, int y
)
971 SetPixel(hdcGDU
, x
, y
, RGB_GREEN
);
974 static void UpdateGDUIndicators(void)
977 InvalidateRect(hwGDU
, NULL
, FALSE
); // no need to erase the background -- the draw routine fully paints the indicator
980 static void CheckGDUKeyboard (void)
984 static UINT idTimer
= 0;
986 static void StartGDUUpdates (void)
991 msec
= (gdu_rate
== 0) ? (1000 / DEFAULT_GDU_RATE
) : 1000/gdu_rate
;
992 idTimer
= SetTimer(hwGDU
, 1, msec
, NULL
);
997 static void StopGDUUpdates (void)
1000 KillTimer(hwGDU
, 1);
1006 static void GetMouseCoordinates()
1012 GetClientRect(hwGDU
, &r
);
1013 if (! ScreenToClient(hwGDU
, &p
)) {
1014 xmouse
= ymouse
= -2000;
1015 mouse_present
= FALSE
;
1019 if (p
.x
< r
.left
|| p
.x
>= r
.right
|| p
.y
< r
.top
|| p
.y
> r
.bottom
) {
1020 mouse_present
= FALSE
;
1024 // convert mouse coordinates to scaled coordinates
1026 xmouse
= (int) (1024./(r
.right
+1.-2*INDWIDTH
)*p
.x
+ 0.5);
1027 ymouse
= 1023 - (int) (1024./(r
.bottom
+1.)*p
.y
+ 0.5);
1028 mouse_present
= TRUE
;
1031 t_bool
gdu_active (void)
1033 return gdu_dsw
& GDU_DSW_BUSY
;
1036 static void EraseGDUScreen (void)
1038 if (hwGDU
!= NULL
) /* redraw screen. it will be blank if GDU is not running */
1039 InvalidateRect(hwGDU
, NULL
, TRUE
);
1042 /* GDUPump - thread responsible for creating and displaying the graphics window */
1044 static DWORD WINAPI
GDUPump (LPVOID arg
)
1049 if (! wcInited
) { /* register Window class */
1050 memset(&wc
, 0, sizeof(wc
));
1051 wc
.style
= CS_NOCLOSE
;
1052 wc
.lpfnWndProc
= GDUWndProc
;
1053 wc
.hInstance
= hInstance
;
1054 wc
.hCursor
= LoadCursor(NULL
, IDC_ARROW
);
1055 wc
.hbrBackground
= GetStockObject(BLACK_BRUSH
);
1056 wc
.lpszClassName
= APPCLASS
;
1058 if (! RegisterClass(&wc
)) {
1066 if (hGreenPen
== NULL
)
1067 hGreenPen
= CreatePen(PS_SOLID
, 1, RGB_GREEN
);
1069 #ifdef DEBUG_LIGHTPEN
1070 if (hRedPen
== NULL
)
1071 hRedPen
= CreatePen(PS_SOLID
, 1, RGB_RED
);
1074 if (hRedBrush
== NULL
)
1075 hRedBrush
= CreateSolidBrush(RGB_RED
);
1077 hGrayBrush
= GetStockObject(GRAY_BRUSH
);
1078 hDarkBrush
= GetStockObject(DKGRAY_BRUSH
);
1079 hBlackPen
= GetStockObject(BLACK_PEN
);
1081 if (hwGDU
== NULL
) { /* create window */
1082 hwGDU
= CreateWindow(APPCLASS
,
1084 WS_OVERLAPPEDWINDOW
| WS_CLIPCHILDREN
,
1085 CW_USEDEFAULT
, CW_USEDEFAULT
, // initial x, y position
1086 INITSIZE
+2*INDWIDTH
, INITSIZE
, // initial width and height
1087 NULL
, // parent window handle
1088 NULL
, // use class menu
1089 hInstance
, // program instance handle
1090 NULL
); // create parameters
1092 if (hwGDU
== NULL
) {
1098 ShowWindow(hwGDU
, SW_SHOWNOACTIVATE
); /* display it */
1099 UpdateWindow(hwGDU
);
1101 while (GetMessage(&msg
, hwGDU
, 0, 0)) { /* message pump - this basically loops forevermore */
1102 TranslateMessage(&msg
);
1103 DispatchMessage(&msg
);
1106 if (hwGDU
!= NULL
) {
1107 DestroyWindow(hwGDU
); /* but if a quit message got posted, clean up */
1115 #ifdef DEBUG_LIGHTPEN
1116 static void ShowPenHit (int x
, int y
)
1120 hOldPen
= SelectObject(hdcGDU
, hRedPen
);
1121 DrawLine(x
-10, y
-10, x
+10, y
+10);
1122 DrawLine(x
-10, y
+10, x
+10, y
-10);
1123 SelectObject(hdcGDU
, hOldPen
);
1127 #endif // _WIN32 defined
1128 #endif // GUI_SUPPORT defined