First Commit of my working state
[simh.git] / Ibm1130 / ibm1130_gdu.c
1 #include "ibm1130_defs.h"
2
3 /* ibm1130_gdu.c: IBM 1130 2250 Graphical Display Unit
4
5 (Under construction)
6 stuff to fix:
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?
11
12 Based on the SIMH package written by Robert M Supnik
13
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
18 * or modifications.
19 *
20 * This is not a supported product, but I welcome bug reports and fixes.
21 * Mail to simh@ibm1130.org
22 */
23
24 #define BLIT_MODE /* define for better performance, undefine when debugging generate_image() */
25 /* #define DEBUG_LIGHTPEN */ /* define to debug light-pen sensing */
26
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 */
31
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
42
43 #define GDU_FKEY_DATA_AVAILABLE 0x8000
44 #define GDU_FKEY_KEY_CODE 0x1F00
45 #define GDU_FKEY_OVERLAY_CODE 0x00FF
46
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
54
55 /* -------------------------------------------------------------------------------------- */
56
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)
61
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 */
66
67 static t_stat gdu_reset (DEVICE *dptr);
68
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 */
79
80 UNIT gdu_unit = { UDATA (NULL, 0, 0) };
81
82 REG gdu_reg[] = {
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 */
93 { NULL } };
94
95 DEVICE gdu_dev = {
96 "GDU", &gdu_unit, gdu_reg, NULL,
97 1, 16, 16, 1, 16, 16,
98 NULL, NULL, gdu_reset,
99 NULL, NULL, NULL};
100
101 /* -------------------------------------------------------------------------------------- */
102
103 #ifndef GUI_SUPPORT
104
105 static t_stat gdu_reset (DEVICE *dptr)
106 {
107 return SCPE_OK;
108 }
109
110 void xio_2250_display (int32 addr, int32 func, int32 modify)
111 {
112 /* ignore commands if device is nonexistent */
113 }
114
115 t_bool gdu_active (void)
116 {
117 return 0;
118 }
119
120 /* -------------------------------------------------------------------------------------- */
121 #else /* GUI_SUPPORT defined */
122
123 /******* PLATFORM INDEPENDENT CODE ********************************************************/
124
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);
136
137 // routines that must be implemented per-platform
138
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);
149
150 /* -------------------------------------------------------------------------------------- */
151
152 void xio_2250_display (int32 addr, int32 func, int32 modify)
153 {
154 switch (func) {
155 case XIO_SENSE_DEV:
156 ACC = (gdu_dsw & GDU_DSW_BUSY) ? GDU_DSW_BUSY : gdu_dsw;
157 if (modify & 1)
158 clear_interrupts();
159 break;
160
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 */
163 break;
164
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? */
172
173 clear_interrupts(); /* read status clears the interrupts */
174 break;
175
176 case XIO_WRITE:
177 if (gdu_dsw & GDU_DSW_BUSY) /* treated as no-op if display is busy */
178 break;
179
180 if (modify & 0x80) { /* bit 8 on means set indicators, 0 means start regeneration */
181 set_indicators((ReadW(addr) << 16) | ReadW(addr+1));
182 }
183 else {
184 gdu_ar = (int16) addr;
185 gdu_fkey = 0;
186 gdu_akey = 0;
187 clear_interrupts();
188 start_regeneration();
189 }
190 break;
191
192 case XIO_CONTROL:
193 if (modify & 0x80) { /* bit 8 on means reset, off is no-op */
194 gdu_reset(&gdu_dev);
195 set_indicators((addr << 16) | addr);
196 }
197 break;
198
199 default: /* all other commands are no-ops */
200 break;
201 }
202 }
203
204 static t_stat gdu_reset (DEVICE *dptr)
205 {
206 halt_regeneration();
207 clear_interrupts();
208 set_indicators(0);
209 gdu_x = gdu_y = 512;
210 CLRBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED | UNIT_DETECTS_ENABLED | UNIT_LARGE_CHARS);
211 gdu_dsw = 1;
212 return SCPE_OK;
213 }
214
215 static void clear_interrupts (void)
216 {
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);
219 calc_ints();
220 }
221
222 static void gdu_interrupt (int32 dswbit)
223 {
224 SETBIT(gdu_dsw, dswbit);
225 SETBIT(ILSW[3], ILSW_3_2250_DISPLAY);
226 calc_ints();
227 halt_regeneration();
228 }
229
230 static void set_indicators (int32 new_inds)
231 {
232 gdu_indicators = new_inds;
233 if (gdu_unit.flags & UNIT_DISPLAYED)
234 UpdateGDUIndicators();
235 }
236
237 static void start_regeneration (void)
238 {
239 SETBIT(gdu_dsw, GDU_DSW_BUSY);
240
241 if ((gdu_unit.flags & UNIT_DISPLAYED) == 0) {
242 if (! CreateGDUWindow())
243 return;
244
245 SETBIT(gdu_unit.flags, UNIT_DISPLAYED);
246 }
247
248 StartGDUUpdates();
249 }
250
251 static void halt_regeneration (void)
252 {
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);
260 }
261 // EraseGDUScreen(); // let cessation of regeneration erase it (eventually)
262 }
263
264 static void notify_window_closed (void)
265 {
266 if (gdu_dsw & GDU_DSW_BUSY) {
267 StopGDUUpdates();
268 CLRBIT(gdu_dsw, GDU_DSW_BUSY);
269 }
270
271 CLRBIT(gdu_unit.flags, UNIT_DISPLAYED);
272
273 gdu_reset(&gdu_dev);
274 }
275
276 static int32 read_gduword (void)
277 {
278 int32 w;
279
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));
282
283 return w;
284 }
285
286 #define DIST2(x0,y0,x1,y1) (((x1)-(x0))*((x1)-(x0))+((y1)-(y0))*((y1)-(y0)))
287
288 static void draw (int32 newx, int32 newy, t_bool beam)
289 {
290 int xmin, xmax, ymin, ymax, xd, yd;
291 double s;
292 int hit = FALSE;
293
294 if (beam) {
295 if (gdu_dsw & GDU_DSW_POINT_MODE) {
296 DrawPoint(newx, newy);
297
298 #ifdef DEBUG_LIGHTPEN
299 if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
300 hit = TRUE;
301 #else
302 if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)
303 if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
304 hit = TRUE;
305 #endif
306 }
307 else {
308 DrawLine(gdu_x, gdu_y, newx, newy);
309
310 // calculate proximity of light pen to the line
311 #ifndef DEBUG_LIGHTPEN
312 if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present) {
313 #endif
314 if (gdu_x <= newx)
315 xmin = gdu_x, xmax = newx;
316 else
317 xmin = newx, xmax = gdu_x;
318
319 if (gdu_y <= newy)
320 ymin = gdu_y, ymax = newy;
321 else
322 ymin = newy, ymax = gdu_y;
323
324 if (newx == gdu_x) {
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
327 xd = gdu_x;
328 yd = (ymouse <= ymin) ? ymin : (ymouse >= ymax) ? ymax : ymouse;
329
330 if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
331 hit = TRUE;
332 }
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;
337 yd = gdu_y;
338
339 if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
340 hit = TRUE;
341 }
342 else {
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
347 // nearest the mouse
348
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);
351
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) {
358 hit = TRUE;
359 xd = gdu_x;
360 yd = gdu_y;
361 }
362 else if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2) {
363 hit = TRUE;
364 xd = newx;
365 yd = newy;
366 }
367 #else
368 if (DIST2(gdu_x, gdu_y, xmouse, ymouse) <= lpen_dist2 || DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)
369 hit = TRUE;
370 #endif
371 }
372 else {
373 yd = (int) (gdu_y + s*(xd - gdu_x) + 0.5);
374 if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)
375 hit = TRUE;
376 }
377 }
378 }
379 #ifndef DEBUG_LIGHTPEN
380 }
381 #endif
382 }
383 }
384
385 if (hit) {
386 #ifdef DEBUG_LIGHTPEN
387 ShowPenHit(xd, yd);
388 if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)
389 SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
390 #else
391 SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
392 #endif
393 }
394
395 gdu_x = (int16) newx;
396 gdu_y = (int16) newy;
397 }
398
399 static void generate_image (void)
400 {
401 int32 instr, new_addr, newx, newy;
402 t_bool run = TRUE, accept;
403
404 if (! (gdu_dsw & GDU_DSW_BUSY))
405 return;
406
407 GetMouseCoordinates();
408
409 lpen_dist = (int) (gdu_threshold/sfactor + 0.5); // mouse-to-line threshhold at current scaling factor
410 lpen_dist2 = lpen_dist * lpen_dist;
411
412 while (run) {
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);
416 run = FALSE;
417 break;
418 }
419
420 gdu_instaddr = gdu_ar; // remember address of GDU instruction
421 instr = read_gduword(); // fetch instruction (and we really are cycle stealing here!)
422
423 switch ((instr >> 12) & 0xF) { // decode instruction
424 case 0: // short branch
425 case 1:
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
430 gdu_ar = gdu_revert;
431 }
432 break;
433
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);
437
438 if (instr & 2) // clear after testing
439 CLRBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);
440
441 if (instr & 0x0400) // NOP
442 accept = FALSE;
443
444 if (accept) {
445 if (instr & 0x0800) { // branch
446 gdu_revert = gdu_ar;
447
448 if (instr & 0x0080) // indirect
449 new_addr = M[new_addr & mem_mask];
450
451 gdu_ar = (int16) new_addr;
452
453 if (gdu_dsw & GDU_DSW_CHARACTER_MODE) {
454 draw_characters();
455 gdu_ar = gdu_revert;
456 }
457 }
458 else { // interrupt
459 gdu_interrupt(GDU_DSW_ORDER_CONTROLLED_INTERRUPT);
460 run = FALSE;
461 }
462 }
463 break;
464
465 case 3: // control instructions
466 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
467
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);
474
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);
479 break;
480
481 case 2: // set graphic mode
482 if (instr & 1)
483 SETBIT(gdu_dsw, GDU_DSW_POINT_MODE);
484 else
485 CLRBIT(gdu_dsw, GDU_DSW_POINT_MODE);
486 break;
487
488 case 3: // set character mode
489 SETBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
490 if (instr & 1)
491 SETBIT(gdu_unit.flags, UNIT_LARGE_CHARS);
492 else
493 CLRBIT(gdu_unit.flags, UNIT_LARGE_CHARS);
494 break;
495
496 case 4: // start timer
497 run = FALSE; // (which, for us, means stop processing until next timer message)
498 CheckGDUKeyboard();
499 break;
500
501 case 5: // store revert
502 M[gdu_ar & mem_mask] = gdu_revert;
503 read_gduword(); // skip to next address
504 break;
505
506 case 6: // revert
507 gdu_ar = gdu_revert;
508 break;
509
510 default: // all others treated as no-ops
511 break;
512 }
513 break;
514
515 case 4: // long absolute
516 case 5:
517 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
518 newx = instr & 0x3FF;
519 newy = read_gduword() & 0x3FF;
520 draw(newx, newy, instr & 0x1000);
521 last_abs = TRUE;
522 break;
523
524 case 6: // short absolute
525 case 7:
526 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
527 newx = gdu_x;
528 newy = gdu_y;
529 if (instr & 0x0800)
530 newy = instr & 0x3FF;
531 else
532 newx = instr & 0x3FF;
533 draw(newx, newy, instr & 0x1000);
534 last_abs = TRUE;
535 break;
536
537 default: // high bit set - it's a relative instruction
538 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);
539 newx = (instr >> 8) & 0x3F;
540 newy = instr & 0x3F;
541
542 if (instr & 0x4000) // sign extend x - values are in 2's complement
543 newx |= -1 & ~0x3F; // although documentation doesn't make that clear
544
545 if (instr & 0x0040) // sign extend y
546 newy |= -1 & ~0x3F;
547
548 newx = gdu_x + newx;
549 newy = gdu_y + newy;
550 draw(newx, newy, instr & 0x0080);
551 last_abs = FALSE;
552 break;
553 }
554 }
555 }
556
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
562 } cx[2] = {
563 {14, 20, 1.7, 2.0, -6, -7, 6}, // regular
564 {21, 30, 2.5, 3.0, -9, -11, 9} // large
565 };
566
567 static void draw_characters (void)
568 {
569 int32 w, x0, y0, x1, y1, yoff = 0, ninstr = 0;
570 t_bool dospace, didstroke = FALSE;
571 struct charinfo *ci;
572
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;
576
577 do {
578 if (++ninstr > 29) { // too many control words
579 gdu_interrupt(GDU_DSW_CYCLE_STEAL_CHECK);
580 return;
581 }
582
583 dospace = TRUE;
584 w = M[gdu_ar++ & mem_mask]; // get next stroke or control word
585
586 x1 = (w >> 12) & 7;
587 y1 = (w >> 8) & 7;
588
589 if (x1 == 7) { // this is a character control word
590 dospace = FALSE; // inhibit character spacing
591
592 switch (y1) {
593 case 1: // subscript
594 if (yoff == 0) // (ignored if superscript is in effect)
595 yoff = -ci->suby;
596 break;
597
598 // case 2: // no-op or null (nothing to do)
599 // default: // all unknowns are no-ops
600 // break;
601
602 case 4: // superscript
603 yoff = ci->suby;
604 break;
605
606 case 7: // new line
607 gdu_x = 0;
608 gdu_y -= (int16) ci->dy;
609 if (gdu_y < 0 && last_abs)
610 gdu_y = (int16) (1024 - ci->dy); // this is a guess
611 break;
612 }
613 }
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);
617
618 if (w & 0x0800) {
619 didstroke = TRUE;
620 DrawLine(x0, y0, x1, y1);
621 }
622
623 x0 = (w >> 4) & 7;
624 y0 = w & 7;
625
626 x0 = gdu_x + (int) (x0*ci->sx + 0.5);
627 y0 = gdu_y + (int) ((y0+yoff)*ci->sy + 0.5);
628
629 if (w & 0x0008) {
630 didstroke = TRUE;
631 DrawLine(x1, y1, x0, y0);
632 }
633 }
634
635 if (dospace) {
636 gdu_x += ci->dx;
637 if (gdu_x > 1023 && last_abs) { // line wrap
638 gdu_x = 0;
639 gdu_y -= (int16) ci->dy;
640 }
641 }
642 } while ((w & 0x0080) == 0); // repeat until we hit revert bit
643
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);
648 }
649 }
650
651 /******* PLATFORM SPECIFIC CODE ***********************************************************/
652
653 #ifdef _WIN32
654
655 #include <windows.h>
656 #include <windowsx.h>
657
658 #define APPCLASS "IBM2250GDU" // window class name
659
660 #define RGB_GREEN RGB(0,255,0) // handy colors
661 #define RGB_RED RGB(255,0,0)
662
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;
676 #endif
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);
682
683 static void destroy_GDU_window (void)
684 {
685 if (hwGDU != NULL)
686 SendMessage(hwGDU, WM_CLOSE, 0, 0); // cross thread call is OK
687
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;
691 GDUPumpID = 0;
692 hwGDU = NULL;
693 }
694
695 if (hdcGDU != NULL) {
696 DeleteDC(hdcGDU);
697 hdcGDU = NULL;
698 }
699
700 if (hBmp != NULL) {
701 DeleteObject(hBmp);
702 hBmp = NULL;
703 }
704
705 if (hGreenPen != NULL) {
706 DeleteObject(hGreenPen);
707 hGreenPen = NULL;
708 }
709
710 if (hRedBrush != NULL) {
711 DeleteObject(hRedBrush);
712 hRedBrush = NULL;
713 }
714
715 #ifdef DEBUG_LIGHTPEN
716 if (hRedPen != NULL) {
717 DeleteObject(hRedPen);
718 hRedPen = NULL;
719 }
720 #endif
721 }
722
723 static t_bool CreateGDUWindow (void)
724 {
725 static BOOL did_atexit = FALSE;
726
727 hInstance = GetModuleHandle(NULL);
728
729 if (hGDUPump == INVALID_HANDLE_VALUE)
730 hGDUPump = CreateThread(NULL, 0, GDUPump, 0, 0, &GDUPumpID);
731
732 if (! did_atexit) {
733 atexit(destroy_GDU_window);
734 did_atexit = TRUE;
735 }
736
737 return TRUE;
738 }
739
740 // windows message handlers ----------------------------------------------------
741
742 // close the window
743
744 static void gdu_WM_CLOSE (HWND hWnd)
745 {
746 DestroyWindow(hWnd);
747 }
748
749 // the window is being destroyed
750
751 static void gdu_WM_DESTROY (HWND hWnd)
752 {
753 notify_window_closed();
754 hwGDU = NULL;
755 }
756
757 // adjust the min and max resizing boundaries
758
759 static void gdu_WM_GETMINMAXINFO (HWND hWnd, LPMINMAXINFO mm)
760 {
761 mm->ptMinTrackSize.x = 100 + 2*INDWIDTH;
762 mm->ptMinTrackSize.y = 100;
763 }
764
765 static void PaintImage (HDC hDC, BOOL draw_indicators)
766 {
767 HPEN hOldPen;
768 RECT r;
769 int wid, ht, x, y, dy, i, j, ycirc;
770 unsigned long bit;
771
772 GetClientRect(hwGDU, &r);
773 wid = r.right+1 - 2*INDWIDTH;
774 ht = r.bottom+1;
775 sfactor = (double) MIN(wid,ht) / 1024.;
776
777 if (gdu_dsw & GDU_DSW_BUSY) {
778 #ifdef BLIT_MODE
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.
782
783 if (wid != curwid || ht != curht) { // dimensions have changed, discard old memory display context
784 if (hdcGDU != NULL) {
785 DeleteDC(hdcGDU);
786 hdcGDU = NULL;
787 }
788 curwid = wid;
789 curht = ht;
790 }
791
792 if (hdcGDU == NULL) { // allocate memory display context & select a bitmap into it
793 hdcGDU = CreateCompatibleDC(hDC);
794 if (hBmp != NULL)
795 DeleteObject(hBmp);
796 hBmp = CreateCompatibleBitmap(hDC, wid, ht);
797 SelectObject(hdcGDU, hBmp);
798 }
799
800 PatBlt(hdcGDU, 0, 0, wid, ht, BLACKNESS); // start with a black screen
801
802 hOldPen = SelectObject(hdcGDU, hGreenPen);
803
804 SetMapMode(hdcGDU, MM_ANISOTROPIC);
805 SetWindowExtEx(hdcGDU, 1024, -1024, NULL);
806 SetViewportExtEx(hdcGDU, wid, ht, NULL);
807 SetWindowOrgEx(hdcGDU, 0, 1023, NULL);
808
809 generate_image(); // run the display program to paint the image into the memory context
810
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
815
816 SelectObject(hdcGDU, hOldPen);
817 #else
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.
821 hdcGDU = hDC;
822 hOldPen = SelectObject(hdcGDU, hGreenPen);
823
824 SetMapMode(hdcGDU, MM_ANISOTROPIC);
825 SetWindowExtEx(hdcGDU, 1024, -1024, NULL);
826 SetViewportExtEx(hdcGDU, wid, ht, NULL);
827 SetWindowOrgEx(hdcGDU, 0, 1023, NULL);
828
829 generate_image();
830
831 SelectObject(hdcGDU, hOldPen);
832 hdcGDU = NULL;
833 #endif
834 }
835
836 if (draw_indicators) {
837 x = r.right-2*INDWIDTH+1;
838 dy = ht / 16;
839 ycirc = MIN(dy-2, 8);
840
841 r.left = x;
842 FillRect(hDC, &r, hGrayBrush);
843 SelectObject(hDC, hBlackPen);
844
845 bit = 0x80000000L;
846 for (i = 0; i < 2; i++) {
847 MoveToEx(hDC, x, 0, NULL);
848 LineTo(hDC, x, r.bottom);
849 y = 0;
850 for (j = 0; j < 16; j++) {
851 MoveToEx(hDC, x, y, NULL);
852 LineTo(hDC, x+INDWIDTH, y);
853
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);
856
857 y += dy;
858 bit >>= 1;
859 }
860 x += INDWIDTH;
861 }
862 }
863 }
864
865 // repaint the window
866
867 static void gdu_WM_PAINT (HWND hWnd)
868 {
869 PAINTSTRUCT ps;
870 HDC hDC;
871 // code for display
872 hDC = BeginPaint(hWnd, &ps);
873 PaintImage(hDC, TRUE);
874 EndPaint(hWnd, &ps);
875 }
876
877 // the window has been resized
878
879 static void gdu_WM_SIZE (HWND hWnd, UINT state, int cx, int cy)
880 {
881 InvalidateRect(hWnd, NULL, TRUE);
882 }
883
884 // tweak the sizing rectangle during a resize to guarantee a square window
885
886 static void gdu_WM_SIZING (HWND hWnd, WPARAM fwSide, LPRECT r)
887 {
888 switch (fwSide) {
889 case WMSZ_LEFT:
890 case WMSZ_RIGHT:
891 case WMSZ_BOTTOMLEFT:
892 case WMSZ_BOTTOMRIGHT:
893 r->bottom = r->right - r->left - 2*INDWIDTH + r->top;
894 break;
895
896 case WMSZ_TOP:
897 case WMSZ_BOTTOM:
898 case WMSZ_TOPRIGHT:
899 r->right = r->bottom - r->top + r->left + 2*INDWIDTH;
900 break;
901
902 case WMSZ_TOPLEFT:
903 r->left = r->top - r->bottom + r->right - 2*INDWIDTH;
904 break;
905 }
906 }
907
908 // the refresh timer has gone off
909
910 static void gdu_WM_TIMER (HWND hWnd, UINT id)
911 {
912 HDC hDC;
913
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
919 return;
920 }
921 }
922 else
923 halted = 0;
924
925 #ifdef BLIT_MODE
926 hDC = GetDC(hWnd); // blit the new image right over the old
927 PaintImage(hDC, FALSE);
928 ReleaseDC(hWnd, hDC);
929 #else
930 InvalidateRect(hWnd, NULL, TRUE); // repaint
931 #endif
932 }
933 }
934
935 // window procedure ------------------------------------------------------------
936
937 #define HANDLE(msg) case msg: return HANDLE_##msg(hWnd, wParam, lParam, gdu_##msg);
938
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)
943 #endif
944
945 static LRESULT APIENTRY GDUWndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
946 {
947 switch (iMessage) {
948 HANDLE(WM_CLOSE);
949 HANDLE(WM_GETMINMAXINFO);
950 HANDLE(WM_DESTROY);
951 HANDLE(WM_PAINT);
952 HANDLE(WM_SIZE);
953 HANDLE(WM_SIZING);
954 HANDLE(WM_TIMER);
955 default: // any message we don't process
956 return DefWindowProc(hWnd, iMessage, wParam, lParam);
957 }
958 return 0L;
959 }
960
961 // graphic calls ----------------------------------------------------------------
962
963 static void DrawLine (int x0, int y0, int x1, int y1)
964 {
965 MoveToEx(hdcGDU, x0, y0, NULL);
966 LineTo(hdcGDU, x1, y1);
967 }
968
969 static void DrawPoint (int x, int y)
970 {
971 SetPixel(hdcGDU, x, y, RGB_GREEN);
972 }
973
974 static void UpdateGDUIndicators(void)
975 {
976 if (hwGDU != NULL)
977 InvalidateRect(hwGDU, NULL, FALSE); // no need to erase the background -- the draw routine fully paints the indicator
978 }
979
980 static void CheckGDUKeyboard (void)
981 {
982 }
983
984 static UINT idTimer = 0;
985
986 static void StartGDUUpdates (void)
987 {
988 int msec;
989
990 if (idTimer == 0) {
991 msec = (gdu_rate == 0) ? (1000 / DEFAULT_GDU_RATE) : 1000/gdu_rate;
992 idTimer = SetTimer(hwGDU, 1, msec, NULL);
993 }
994 halted = 0;
995 }
996
997 static void StopGDUUpdates (void)
998 {
999 if (idTimer != 0) {
1000 KillTimer(hwGDU, 1);
1001 idTimer = 0;
1002 halted = 10000;
1003 }
1004 }
1005
1006 static void GetMouseCoordinates()
1007 {
1008 POINT p;
1009 RECT r;
1010
1011 GetCursorPos(&p);
1012 GetClientRect(hwGDU, &r);
1013 if (! ScreenToClient(hwGDU, &p)) {
1014 xmouse = ymouse = -2000;
1015 mouse_present = FALSE;
1016 return;
1017 }
1018
1019 if (p.x < r.left || p.x >= r.right || p.y < r.top || p.y > r.bottom) {
1020 mouse_present = FALSE;
1021 return;
1022 }
1023
1024 // convert mouse coordinates to scaled coordinates
1025
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;
1029 }
1030
1031 t_bool gdu_active (void)
1032 {
1033 return gdu_dsw & GDU_DSW_BUSY;
1034 }
1035
1036 static void EraseGDUScreen (void)
1037 {
1038 if (hwGDU != NULL) /* redraw screen. it will be blank if GDU is not running */
1039 InvalidateRect(hwGDU, NULL, TRUE);
1040 }
1041
1042 /* GDUPump - thread responsible for creating and displaying the graphics window */
1043
1044 static DWORD WINAPI GDUPump (LPVOID arg)
1045 {
1046 MSG msg;
1047 WNDCLASS wc;
1048
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;
1057
1058 if (! RegisterClass(&wc)) {
1059 GDUPumpID = 0;
1060 return 0;
1061 }
1062
1063 wcInited = TRUE;
1064 }
1065
1066 if (hGreenPen == NULL)
1067 hGreenPen = CreatePen(PS_SOLID, 1, RGB_GREEN);
1068
1069 #ifdef DEBUG_LIGHTPEN
1070 if (hRedPen == NULL)
1071 hRedPen = CreatePen(PS_SOLID, 1, RGB_RED);
1072 #endif
1073
1074 if (hRedBrush == NULL)
1075 hRedBrush = CreateSolidBrush(RGB_RED);
1076
1077 hGrayBrush = GetStockObject(GRAY_BRUSH);
1078 hDarkBrush = GetStockObject(DKGRAY_BRUSH);
1079 hBlackPen = GetStockObject(BLACK_PEN);
1080
1081 if (hwGDU == NULL) { /* create window */
1082 hwGDU = CreateWindow(APPCLASS,
1083 "2250 Display",
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
1091
1092 if (hwGDU == NULL) {
1093 GDUPumpID = 0;
1094 return 0;
1095 }
1096 }
1097
1098 ShowWindow(hwGDU, SW_SHOWNOACTIVATE); /* display it */
1099 UpdateWindow(hwGDU);
1100
1101 while (GetMessage(&msg, hwGDU, 0, 0)) { /* message pump - this basically loops forevermore */
1102 TranslateMessage(&msg);
1103 DispatchMessage(&msg);
1104 }
1105
1106 if (hwGDU != NULL) {
1107 DestroyWindow(hwGDU); /* but if a quit message got posted, clean up */
1108 hwGDU = NULL;
1109 }
1110
1111 GDUPumpID = 0;
1112 return 0;
1113 }
1114
1115 #ifdef DEBUG_LIGHTPEN
1116 static void ShowPenHit (int x, int y)
1117 {
1118 HPEN hOldPen;
1119
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);
1124 }
1125 #endif
1126
1127 #endif // _WIN32 defined
1128 #endif // GUI_SUPPORT defined