First Commit of my working state
[simh.git] / Ibm1130 / ibm1130_gdu.c
CommitLineData
196ba1fc
PH
1#include "ibm1130_defs.h"\r
2\r
3/* ibm1130_gdu.c: IBM 1130 2250 Graphical Display Unit\r
4\r
5 (Under construction)\r
6 stuff to fix:\r
7 "store revert" might be backwards?\r
8 alpha keyboard is not implemented\r
9 pushbuttons are not implemented\r
10 there is something about interrupts being deferred during a subroutine transition?\r
11\r
12 Based on the SIMH package written by Robert M Supnik\r
13\r
14 * (C) Copyright 2002, Brian Knittel.\r
15 * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN\r
16 * RISK basis, there is no warranty of fitness for any purpose, and the rest of the\r
17 * usual yada-yada. Please keep this notice and the copyright in any distributions\r
18 * or modifications.\r
19 *\r
20 * This is not a supported product, but I welcome bug reports and fixes.\r
21 * Mail to simh@ibm1130.org\r
22 */\r
23\r
24#define BLIT_MODE /* define for better performance, undefine when debugging generate_image() */\r
25/* #define DEBUG_LIGHTPEN */ /* define to debug light-pen sensing */\r
26\r
27#define DEFAULT_GDU_RATE 20 /* default frame rate */\r
28#define DEFAULT_PEN_THRESHOLD 3 /* default looseness of light-pen hit */\r
29#define INDWIDTH 32 /* width of an indicator (there are two columns of these) */\r
30#define INITSIZE 512 /* initial window size */\r
31\r
32#define GDU_DSW_ORDER_CONTROLLED_INTERRUPT 0x8000\r
33#define GDU_DSW_KEYBOARD_INTERUPT 0x4000\r
34#define GDU_DSW_DETECT_INTERRUPT 0x2000\r
35#define GDU_DSW_CYCLE_STEAL_CHECK 0x1000\r
36#define GDU_DSW_DETECT_STATUS 0x0800\r
37#define GDU_DSW_LIGHT_PEN_SWITCH 0x0100\r
38#define GDU_DSW_BUSY 0x0080\r
39#define GDU_DSW_CHARACTER_MODE 0x0040\r
40#define GDU_DSW_POINT_MODE 0x0020\r
41#define GDU_DSW_ADDR_DISP 0x0003\r
42\r
43#define GDU_FKEY_DATA_AVAILABLE 0x8000\r
44#define GDU_FKEY_KEY_CODE 0x1F00\r
45#define GDU_FKEY_OVERLAY_CODE 0x00FF\r
46\r
47#define GDU_AKEY_DATA_AVAILABLE 0x8000\r
48#define GDU_AKEY_END 0x1000\r
49#define GDU_AKEY_CANCEL 0x0800\r
50#define GDU_AKEY_ADVANCE 0x0400\r
51#define GDU_AKEY_BACKSPACE 0x0200\r
52#define GDU_AKEY_JUMP 0x0100\r
53#define GDU_AKEY_KEY_CODE 0x00FF\r
54\r
55/* -------------------------------------------------------------------------------------- */\r
56\r
57#define UNIT_V_DISPLAYED (UNIT_V_UF + 0)\r
58#define UNIT_V_DETECTS_ENABLED (UNIT_V_UF + 1)\r
59#define UNIT_V_INTERRUPTS_DEFERRED (UNIT_V_UF + 2)\r
60#define UNIT_V_LARGE_CHARS (UNIT_V_UF + 3)\r
61\r
62#define UNIT_DISPLAYED (1u << UNIT_V_DISPLAYED) /* display windows is up */\r
63#define UNIT_DETECTS_ENABLED (1u << UNIT_V_DETECTS_ENABLED) /* light pen detects are enabled */\r
64#define UNIT_INTERRUPTS_DEFERRED (1u << UNIT_V_INTERRUPTS_DEFERRED) /* light pen interrupts are deferred */\r
65#define UNIT_LARGE_CHARS (1u << UNIT_V_LARGE_CHARS) /* large character mode */\r
66\r
67static t_stat gdu_reset (DEVICE *dptr);\r
68\r
69static int16 gdu_dsw = 1; /* device status word */\r
70static int16 gdu_ar = 0; /* address register */\r
71static int16 gdu_x = 0; /* X deflection */\r
72static int16 gdu_y = 0; /* Y deflection */\r
73static int16 gdu_fkey = 0; /* function keyboard register */\r
74static int16 gdu_akey = 0; /* alphanumeric keyboard register */\r
75static int16 gdu_revert = 0; /* revert address register */\r
76static int32 gdu_indicators = 0; /* programmed indicator lamps */\r
77static int32 gdu_threshold = DEFAULT_PEN_THRESHOLD; /* mouse must be within 3/1024 of line to be a hit */\r
78static int32 gdu_rate = DEFAULT_GDU_RATE; /* refresh rate. 0 = default */\r
79\r
80UNIT gdu_unit = { UDATA (NULL, 0, 0) };\r
81\r
82REG gdu_reg[] = {\r
83 { HRDATA (GDUDSW, gdu_dsw, 16) }, /* device status word */\r
84 { HRDATA (GDUAR, gdu_ar, 16) }, /* address register */\r
85 { HRDATA (GDUXREG, gdu_x, 16) }, /* X deflection register */\r
86 { HRDATA (GDUYREG, gdu_y, 16) }, /* Y deflection register */\r
87 { HRDATA (GDUFKEY, gdu_fkey, 16) }, /* function keyboard register */\r
88 { HRDATA (GDUAKEY, gdu_akey, 16) }, /* alphanumeric keyboard register */\r
89 { HRDATA (GDUREVERT,gdu_revert, 16) }, /* revert address register */\r
90 { HRDATA (GDUAKEY, gdu_indicators, 32) }, /* programmed indicators */\r
91 { DRDATA (GDUTHRESH,gdu_threshold, 32) }, /* mouse closeness threshhold */\r
92 { DRDATA (GDURATE, gdu_rate, 32) }, /* refresh rate in frames/sec */\r
93 { NULL } };\r
94\r
95DEVICE gdu_dev = {\r
96 "GDU", &gdu_unit, gdu_reg, NULL,\r
97 1, 16, 16, 1, 16, 16,\r
98 NULL, NULL, gdu_reset,\r
99 NULL, NULL, NULL};\r
100\r
101/* -------------------------------------------------------------------------------------- */\r
102\r
103#ifndef GUI_SUPPORT\r
104\r
105static t_stat gdu_reset (DEVICE *dptr)\r
106{\r
107 return SCPE_OK;\r
108}\r
109\r
110void xio_2250_display (int32 addr, int32 func, int32 modify)\r
111{\r
112 /* ignore commands if device is nonexistent */\r
113}\r
114\r
115t_bool gdu_active (void)\r
116{\r
117 return 0;\r
118}\r
119\r
120/* -------------------------------------------------------------------------------------- */\r
121#else /* GUI_SUPPORT defined */\r
122\r
123/******* PLATFORM INDEPENDENT CODE ********************************************************/\r
124\r
125static int32 gdu_instaddr; // address of first word of instruction\r
126static int xmouse, ymouse, lpen_dist, lpen_dist2; // current mouse pointer, scaled closeness threshhold, same squared\r
127static double sfactor; // current scaling factor\r
128static t_bool last_abs = TRUE; // last positioning instruction was absolute\r
129static t_bool mouse_present = FALSE; // mouse is/is not in the window\r
130static void clear_interrupts (void);\r
131static void set_indicators (int32 new_inds);\r
132static void start_regeneration (void);\r
133static void halt_regeneration (void);\r
134static void draw_characters (void);\r
135static void notify_window_closed (void);\r
136\r
137// routines that must be implemented per-platform\r
138\r
139static void DrawLine(int x0, int y0, int x1, int y1);\r
140static void DrawPoint(int x, int y);\r
141static void CheckGDUKeyboard(void);\r
142static t_bool CreateGDUWindow(void);\r
143static void StartGDUUpdates(void);\r
144static void StopGDUUpdates(void);\r
145static void GetMouseCoordinates(void);\r
146static void UpdateGDUIndicators(void);\r
147static void ShowPenHit (int x, int y);\r
148static void EraseGDUScreen (void);\r
149\r
150/* -------------------------------------------------------------------------------------- */\r
151\r
152void xio_2250_display (int32 addr, int32 func, int32 modify)\r
153{\r
154 switch (func) {\r
155 case XIO_SENSE_DEV:\r
156 ACC = (gdu_dsw & GDU_DSW_BUSY) ? GDU_DSW_BUSY : gdu_dsw;\r
157 if (modify & 1)\r
158 clear_interrupts();\r
159 break;\r
160\r
161 case XIO_READ: /* store status data into word pointed to by IOCC packet */\r
162 if (gdu_dsw & GDU_DSW_BUSY) /* not permitted while device is busy */\r
163 break;\r
164\r
165 WriteW(addr, gdu_ar); /* save status information */\r
166 WriteW(addr+1, gdu_dsw);\r
167 WriteW(addr+2, gdu_x & 0x7FF);\r
168 WriteW(addr+3, gdu_y & 0x7FF);\r
169 WriteW(addr+4, gdu_fkey);\r
170 WriteW(addr+5, gdu_akey);\r
171 gdu_ar = (int16) (addr+6); /* this alters the channel address register? */\r
172\r
173 clear_interrupts(); /* read status clears the interrupts */\r
174 break;\r
175\r
176 case XIO_WRITE:\r
177 if (gdu_dsw & GDU_DSW_BUSY) /* treated as no-op if display is busy */\r
178 break;\r
179\r
180 if (modify & 0x80) { /* bit 8 on means set indicators, 0 means start regeneration */\r
181 set_indicators((ReadW(addr) << 16) | ReadW(addr+1));\r
182 }\r
183 else {\r
184 gdu_ar = (int16) addr;\r
185 gdu_fkey = 0;\r
186 gdu_akey = 0;\r
187 clear_interrupts();\r
188 start_regeneration();\r
189 }\r
190 break;\r
191\r
192 case XIO_CONTROL:\r
193 if (modify & 0x80) { /* bit 8 on means reset, off is no-op */\r
194 gdu_reset(&gdu_dev);\r
195 set_indicators((addr << 16) | addr);\r
196 }\r
197 break;\r
198\r
199 default: /* all other commands are no-ops */\r
200 break;\r
201 }\r
202}\r
203\r
204static t_stat gdu_reset (DEVICE *dptr)\r
205{\r
206 halt_regeneration();\r
207 clear_interrupts();\r
208 set_indicators(0);\r
209 gdu_x = gdu_y = 512;\r
210 CLRBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED | UNIT_DETECTS_ENABLED | UNIT_LARGE_CHARS);\r
211 gdu_dsw = 1;\r
212 return SCPE_OK;\r
213}\r
214\r
215static void clear_interrupts (void)\r
216{\r
217 CLRBIT(gdu_dsw, GDU_DSW_ORDER_CONTROLLED_INTERRUPT | GDU_DSW_KEYBOARD_INTERUPT | GDU_DSW_DETECT_INTERRUPT);\r
218 CLRBIT(ILSW[3], ILSW_3_2250_DISPLAY);\r
219 calc_ints();\r
220}\r
221\r
222static void gdu_interrupt (int32 dswbit)\r
223{\r
224 SETBIT(gdu_dsw, dswbit);\r
225 SETBIT(ILSW[3], ILSW_3_2250_DISPLAY);\r
226 calc_ints();\r
227 halt_regeneration();\r
228}\r
229\r
230static void set_indicators (int32 new_inds)\r
231{\r
232 gdu_indicators = new_inds;\r
233 if (gdu_unit.flags & UNIT_DISPLAYED)\r
234 UpdateGDUIndicators();\r
235}\r
236\r
237static void start_regeneration (void)\r
238{\r
239 SETBIT(gdu_dsw, GDU_DSW_BUSY);\r
240\r
241 if ((gdu_unit.flags & UNIT_DISPLAYED) == 0) {\r
242 if (! CreateGDUWindow())\r
243 return;\r
244\r
245 SETBIT(gdu_unit.flags, UNIT_DISPLAYED);\r
246 }\r
247\r
248 StartGDUUpdates();\r
249}\r
250\r
251static void halt_regeneration (void)\r
252{\r
253 // halt_regeneration gets called at end of every refresh interation, so it should NOT black out the\r
254 // screen -- this is why it was flickering so badly. The lower level code (called on a timer)\r
255 // should check to see if GDU_DSW_BUSY is clear, and if it it still zero after several msec,\r
256 // only then should it black out the screen and call StopGDUUpdates.\r
257 if (gdu_dsw & GDU_DSW_BUSY) {\r
258// StopGDUUpdates(); // let lower level code discover this during next refresh\r
259 CLRBIT(gdu_dsw, GDU_DSW_BUSY);\r
260 }\r
261// EraseGDUScreen(); // let cessation of regeneration erase it (eventually)\r
262}\r
263\r
264static void notify_window_closed (void)\r
265{\r
266 if (gdu_dsw & GDU_DSW_BUSY) {\r
267 StopGDUUpdates();\r
268 CLRBIT(gdu_dsw, GDU_DSW_BUSY);\r
269 }\r
270\r
271 CLRBIT(gdu_unit.flags, UNIT_DISPLAYED);\r
272\r
273 gdu_reset(&gdu_dev);\r
274}\r
275\r
276static int32 read_gduword (void)\r
277{\r
278 int32 w;\r
279\r
280 w = M[gdu_ar++ & mem_mask];\r
281 gdu_dsw = (int16) ((gdu_dsw & ~GDU_DSW_ADDR_DISP) | ((gdu_ar - gdu_instaddr) & GDU_DSW_ADDR_DISP));\r
282\r
283 return w;\r
284}\r
285\r
286#define DIST2(x0,y0,x1,y1) (((x1)-(x0))*((x1)-(x0))+((y1)-(y0))*((y1)-(y0)))\r
287\r
288static void draw (int32 newx, int32 newy, t_bool beam)\r
289{\r
290 int xmin, xmax, ymin, ymax, xd, yd;\r
291 double s;\r
292 int hit = FALSE;\r
293\r
294 if (beam) {\r
295 if (gdu_dsw & GDU_DSW_POINT_MODE) {\r
296 DrawPoint(newx, newy);\r
297\r
298#ifdef DEBUG_LIGHTPEN\r
299 if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)\r
300 hit = TRUE;\r
301#else\r
302 if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)\r
303 if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)\r
304 hit = TRUE;\r
305#endif\r
306 }\r
307 else {\r
308 DrawLine(gdu_x, gdu_y, newx, newy);\r
309\r
310 // calculate proximity of light pen to the line\r
311#ifndef DEBUG_LIGHTPEN\r
312 if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present) {\r
313#endif\r
314 if (gdu_x <= newx)\r
315 xmin = gdu_x, xmax = newx;\r
316 else\r
317 xmin = newx, xmax = gdu_x;\r
318\r
319 if (gdu_y <= newy)\r
320 ymin = gdu_y, ymax = newy;\r
321 else\r
322 ymin = newy, ymax = gdu_y;\r
323\r
324 if (newx == gdu_x) {\r
325 // line is vertical. Nearest point is an endpoint if the mouse is above or\r
326 // below the line segment, otherwise the segment point at the same y as the mouse\r
327 xd = gdu_x;\r
328 yd = (ymouse <= ymin) ? ymin : (ymouse >= ymax) ? ymax : ymouse;\r
329\r
330 if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)\r
331 hit = TRUE;\r
332 }\r
333 else if (newy == gdu_y) {\r
334 // line is horizontal. Nearest point is an endpoint if the mouse is to the left or\r
335 // the right of the line segment, otherwise the segment point at the same x as the mouse\r
336 xd = (xmouse <= xmin) ? xmin : (xmouse >= xmax) ? xmax : xmouse;\r
337 yd = gdu_y;\r
338\r
339 if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)\r
340 hit = TRUE;\r
341 }\r
342 else {\r
343 // line is diagonal. See if the mouse is inside the box lpen_dist wider than the line segment's bounding rectangle\r
344 if (xmouse >= (xmin-lpen_dist) && xmouse <= (xmax+lpen_dist) && ymouse >= (ymin-lpen_dist) || ymouse <= (ymax+lpen_dist)) {\r
345 // compute the point at the intersection of the line through the line segment and the normal\r
346 // to that line through the mouse. This is the point on the line through the line segment\r
347 // nearest the mouse\r
348\r
349 s = (double)(newy - gdu_y) / (double)(newx - gdu_x); // slope of line segment\r
350 xd = (int) ((ymouse + xmouse/s - gdu_y + s*gdu_x) / (s + 1./s) + 0.5);\r
351\r
352 // if intersection is beyond either end of the line segment, the nearest point to the\r
353 // mouse is nearest segment end, otherwise it's the computed intersection point\r
354 if (xd < xmin || xd > xmax) {\r
355#ifdef DEBUG_LIGHTPEN\r
356 // if it's a hit, set xd and yd so we can display the hit\r
357 if (DIST2(gdu_x, gdu_y, xmouse, ymouse) <= lpen_dist2) {\r
358 hit = TRUE;\r
359 xd = gdu_x;\r
360 yd = gdu_y;\r
361 }\r
362 else if (DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2) {\r
363 hit = TRUE;\r
364 xd = newx;\r
365 yd = newy;\r
366 }\r
367#else\r
368 if (DIST2(gdu_x, gdu_y, xmouse, ymouse) <= lpen_dist2 || DIST2(newx, newy, xmouse, ymouse) <= lpen_dist2)\r
369 hit = TRUE;\r
370#endif\r
371 }\r
372 else {\r
373 yd = (int) (gdu_y + s*(xd - gdu_x) + 0.5);\r
374 if (DIST2(xd, yd, xmouse, ymouse) <= lpen_dist2)\r
375 hit = TRUE;\r
376 }\r
377 }\r
378 }\r
379#ifndef DEBUG_LIGHTPEN\r
380 }\r
381#endif\r
382 }\r
383 }\r
384\r
385 if (hit) {\r
386#ifdef DEBUG_LIGHTPEN\r
387 ShowPenHit(xd, yd);\r
388 if (gdu_unit.flags & UNIT_DETECTS_ENABLED && mouse_present)\r
389 SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);\r
390#else\r
391 SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);\r
392#endif\r
393 }\r
394\r
395 gdu_x = (int16) newx;\r
396 gdu_y = (int16) newy;\r
397}\r
398\r
399static void generate_image (void)\r
400{\r
401 int32 instr, new_addr, newx, newy;\r
402 t_bool run = TRUE, accept;\r
403\r
404 if (! (gdu_dsw & GDU_DSW_BUSY))\r
405 return;\r
406\r
407 GetMouseCoordinates();\r
408\r
409 lpen_dist = (int) (gdu_threshold/sfactor + 0.5); // mouse-to-line threshhold at current scaling factor\r
410 lpen_dist2 = lpen_dist * lpen_dist;\r
411\r
412 while (run) {\r
413 if ((gdu_dsw & GDU_DSW_DETECT_STATUS) && ! (gdu_unit.flags & UNIT_INTERRUPTS_DEFERRED)) {\r
414 CLRBIT(gdu_dsw, GDU_DSW_DETECT_STATUS); // clear when interrupt is activated\r
415 gdu_interrupt(GDU_DSW_DETECT_INTERRUPT);\r
416 run = FALSE;\r
417 break;\r
418 }\r
419\r
420 gdu_instaddr = gdu_ar; // remember address of GDU instruction\r
421 instr = read_gduword(); // fetch instruction (and we really are cycle stealing here!)\r
422\r
423 switch ((instr >> 12) & 0xF) { // decode instruction\r
424 case 0: // short branch\r
425 case 1:\r
426 gdu_revert = gdu_ar; // save revert address & get new address\r
427 gdu_ar = (int16) (read_gduword() & 0x1FFF);\r
428 if (gdu_dsw & GDU_DSW_CHARACTER_MODE) {\r
429 draw_characters(); // in character mode this means we are at character data\r
430 gdu_ar = gdu_revert;\r
431 }\r
432 break;\r
433\r
434 case 2: // long branch/interrupt\r
435 new_addr = read_gduword(); // get next word\r
436 accept = ((instr & 1) ? (gdu_dsw & GDU_DSW_LIGHT_PEN_SWITCH) : TRUE) && ((instr & 2) ? (gdu_dsw & GDU_DSW_DETECT_STATUS) : TRUE);\r
437\r
438 if (instr & 2) // clear after testing\r
439 CLRBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);\r
440\r
441 if (instr & 0x0400) // NOP\r
442 accept = FALSE;\r
443\r
444 if (accept) {\r
445 if (instr & 0x0800) { // branch\r
446 gdu_revert = gdu_ar;\r
447\r
448 if (instr & 0x0080) // indirect\r
449 new_addr = M[new_addr & mem_mask];\r
450\r
451 gdu_ar = (int16) new_addr;\r
452\r
453 if (gdu_dsw & GDU_DSW_CHARACTER_MODE) {\r
454 draw_characters();\r
455 gdu_ar = gdu_revert;\r
456 }\r
457 }\r
458 else { // interrupt\r
459 gdu_interrupt(GDU_DSW_ORDER_CONTROLLED_INTERRUPT);\r
460 run = FALSE;\r
461 }\r
462 }\r
463 break;\r
464\r
465 case 3: // control instructions\r
466 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);\r
467\r
468 switch ((instr >> 8) & 0xF) {\r
469 case 1: // set pen mode\r
470 if ((instr & 0xC) == 8)\r
471 SETBIT(gdu_unit.flags, UNIT_DETECTS_ENABLED);\r
472 else if ((instr & 0xC) == 4)\r
473 CLRBIT(gdu_unit.flags, UNIT_DETECTS_ENABLED);\r
474\r
475 if ((instr & 0x3) == 2)\r
476 SETBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED);\r
477 else if ((instr & 0x3) == 1)\r
478 CLRBIT(gdu_unit.flags, UNIT_INTERRUPTS_DEFERRED);\r
479 break;\r
480\r
481 case 2: // set graphic mode\r
482 if (instr & 1)\r
483 SETBIT(gdu_dsw, GDU_DSW_POINT_MODE);\r
484 else\r
485 CLRBIT(gdu_dsw, GDU_DSW_POINT_MODE);\r
486 break;\r
487\r
488 case 3: // set character mode\r
489 SETBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);\r
490 if (instr & 1)\r
491 SETBIT(gdu_unit.flags, UNIT_LARGE_CHARS);\r
492 else\r
493 CLRBIT(gdu_unit.flags, UNIT_LARGE_CHARS);\r
494 break;\r
495\r
496 case 4: // start timer\r
497 run = FALSE; // (which, for us, means stop processing until next timer message)\r
498 CheckGDUKeyboard();\r
499 break;\r
500\r
501 case 5: // store revert\r
502 M[gdu_ar & mem_mask] = gdu_revert;\r
503 read_gduword(); // skip to next address\r
504 break;\r
505\r
506 case 6: // revert\r
507 gdu_ar = gdu_revert;\r
508 break;\r
509\r
510 default: // all others treated as no-ops\r
511 break;\r
512 }\r
513 break;\r
514\r
515 case 4: // long absolute\r
516 case 5:\r
517 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);\r
518 newx = instr & 0x3FF;\r
519 newy = read_gduword() & 0x3FF;\r
520 draw(newx, newy, instr & 0x1000);\r
521 last_abs = TRUE;\r
522 break;\r
523\r
524 case 6: // short absolute\r
525 case 7:\r
526 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);\r
527 newx = gdu_x;\r
528 newy = gdu_y;\r
529 if (instr & 0x0800)\r
530 newy = instr & 0x3FF;\r
531 else\r
532 newx = instr & 0x3FF;\r
533 draw(newx, newy, instr & 0x1000);\r
534 last_abs = TRUE;\r
535 break;\r
536\r
537 default: // high bit set - it's a relative instruction\r
538 CLRBIT(gdu_dsw, GDU_DSW_CHARACTER_MODE);\r
539 newx = (instr >> 8) & 0x3F;\r
540 newy = instr & 0x3F;\r
541\r
542 if (instr & 0x4000) // sign extend x - values are in 2's complement\r
543 newx |= -1 & ~0x3F; // although documentation doesn't make that clear\r
544\r
545 if (instr & 0x0040) // sign extend y\r
546 newy |= -1 & ~0x3F;\r
547\r
548 newx = gdu_x + newx;\r
549 newy = gdu_y + newy;\r
550 draw(newx, newy, instr & 0x0080);\r
551 last_abs = FALSE;\r
552 break;\r
553 }\r
554 }\r
555}\r
556\r
557static struct charinfo { // character mode scaling info:\r
558 int dx, dy; // character and line spacing\r
559 double sx, sy; // scaling factors: character units to screen units\r
560 int xoff, yoff; // x and y offset to lower left corner of character\r
561 int suby; // subscript/superscript offset\r
562} cx[2] = {\r
563 {14, 20, 1.7, 2.0, -6, -7, 6}, // regular\r
564 {21, 30, 2.5, 3.0, -9, -11, 9} // large\r
565};\r
566\r
567static void draw_characters (void)\r
568{\r
569 int32 w, x0, y0, x1, y1, yoff = 0, ninstr = 0;\r
570 t_bool dospace, didstroke = FALSE;\r
571 struct charinfo *ci;\r
572\r
573 ci = &cx[(gdu_unit.flags & UNIT_LARGE_CHARS) ? 1 : 0];\r
574 x0 = gdu_x + ci->xoff; // starting position\r
575 y0 = gdu_y + ci->yoff;\r
576\r
577 do {\r
578 if (++ninstr > 29) { // too many control words\r
579 gdu_interrupt(GDU_DSW_CYCLE_STEAL_CHECK);\r
580 return;\r
581 }\r
582\r
583 dospace = TRUE;\r
584 w = M[gdu_ar++ & mem_mask]; // get next stroke or control word\r
585\r
586 x1 = (w >> 12) & 7;\r
587 y1 = (w >> 8) & 7;\r
588\r
589 if (x1 == 7) { // this is a character control word\r
590 dospace = FALSE; // inhibit character spacing\r
591\r
592 switch (y1) {\r
593 case 1: // subscript\r
594 if (yoff == 0) // (ignored if superscript is in effect)\r
595 yoff = -ci->suby;\r
596 break;\r
597\r
598// case 2: // no-op or null (nothing to do)\r
599// default: // all unknowns are no-ops\r
600// break;\r
601\r
602 case 4: // superscript\r
603 yoff = ci->suby;\r
604 break;\r
605\r
606 case 7: // new line\r
607 gdu_x = 0;\r
608 gdu_y -= (int16) ci->dy;\r
609 if (gdu_y < 0 && last_abs)\r
610 gdu_y = (int16) (1024 - ci->dy); // this is a guess\r
611 break;\r
612 }\r
613 }\r
614 else { // this is stroke data -- extract two strokes\r
615 x1 = gdu_x + (int) (x1*ci->sx + 0.5);\r
616 y1 = gdu_y + (int) ((y1+yoff)*ci->sy + 0.5);\r
617\r
618 if (w & 0x0800) {\r
619 didstroke = TRUE;\r
620 DrawLine(x0, y0, x1, y1);\r
621 }\r
622\r
623 x0 = (w >> 4) & 7;\r
624 y0 = w & 7;\r
625\r
626 x0 = gdu_x + (int) (x0*ci->sx + 0.5);\r
627 y0 = gdu_y + (int) ((y0+yoff)*ci->sy + 0.5);\r
628\r
629 if (w & 0x0008) {\r
630 didstroke = TRUE;\r
631 DrawLine(x1, y1, x0, y0);\r
632 } \r
633 }\r
634\r
635 if (dospace) {\r
636 gdu_x += ci->dx;\r
637 if (gdu_x > 1023 && last_abs) { // line wrap\r
638 gdu_x = 0;\r
639 gdu_y -= (int16) ci->dy;\r
640 }\r
641 }\r
642 } while ((w & 0x0080) == 0); // repeat until we hit revert bit\r
643\r
644 if (didstroke && mouse_present && (gdu_unit.flags & UNIT_DETECTS_ENABLED)) {\r
645 if (xmouse >= (gdu_x - ci->xoff/2) && xmouse <= (gdu_x + ci->xoff/2) &&\r
646 ymouse >= (gdu_y - ci->yoff/2) && ymouse <= (gdu_y + ci->yoff/2))\r
647 SETBIT(gdu_dsw, GDU_DSW_DETECT_STATUS);\r
648 }\r
649}\r
650\r
651/******* PLATFORM SPECIFIC CODE ***********************************************************/\r
652\r
653#ifdef _WIN32\r
654\r
655#include <windows.h>\r
656#include <windowsx.h>\r
657\r
658#define APPCLASS "IBM2250GDU" // window class name\r
659\r
660#define RGB_GREEN RGB(0,255,0) // handy colors\r
661#define RGB_RED RGB(255,0,0)\r
662\r
663static HINSTANCE hInstance;\r
664static HWND hwGDU = NULL;\r
665static HDC hdcGDU = NULL;\r
666static HBITMAP hBmp = NULL;\r
667static int curwid = 0;\r
668static int curht = 0;\r
669static BOOL wcInited = FALSE;\r
670static DWORD GDUPumpID = 0;\r
671static HANDLE hGDUPump = INVALID_HANDLE_VALUE;\r
672static HPEN hGreenPen = NULL;\r
673static HBRUSH hRedBrush = NULL;\r
674#ifdef DEBUG_LIGHTPEN\r
675static HPEN hRedPen = NULL;\r
676#endif\r
677static HBRUSH hGrayBrush, hDarkBrush;\r
678static HPEN hBlackPen;\r
679static int halted = 0; // number of time intervals that GDU has been halted w/o a regeneration\r
680static LRESULT APIENTRY GDUWndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);\r
681static DWORD WINAPI GDUPump (LPVOID arg);\r
682\r
683static void destroy_GDU_window (void)\r
684{\r
685 if (hwGDU != NULL)\r
686 SendMessage(hwGDU, WM_CLOSE, 0, 0); // cross thread call is OK\r
687\r
688 if (hGDUPump != INVALID_HANDLE_VALUE) { // this is not the most graceful way to do it\r
689 TerminateThread(hGDUPump, 0);\r
690 hGDUPump = INVALID_HANDLE_VALUE;\r
691 GDUPumpID = 0;\r
692 hwGDU = NULL;\r
693 }\r
694\r
695 if (hdcGDU != NULL) {\r
696 DeleteDC(hdcGDU);\r
697 hdcGDU = NULL;\r
698 }\r
699\r
700 if (hBmp != NULL) {\r
701 DeleteObject(hBmp);\r
702 hBmp = NULL;\r
703 }\r
704\r
705 if (hGreenPen != NULL) {\r
706 DeleteObject(hGreenPen);\r
707 hGreenPen = NULL;\r
708 }\r
709\r
710 if (hRedBrush != NULL) {\r
711 DeleteObject(hRedBrush);\r
712 hRedBrush = NULL;\r
713 }\r
714\r
715#ifdef DEBUG_LIGHTPEN\r
716 if (hRedPen != NULL) {\r
717 DeleteObject(hRedPen);\r
718 hRedPen = NULL;\r
719 }\r
720#endif\r
721}\r
722\r
723static t_bool CreateGDUWindow (void)\r
724{\r
725 static BOOL did_atexit = FALSE;\r
726\r
727 hInstance = GetModuleHandle(NULL);\r
728\r
729 if (hGDUPump == INVALID_HANDLE_VALUE)\r
730 hGDUPump = CreateThread(NULL, 0, GDUPump, 0, 0, &GDUPumpID);\r
731\r
732 if (! did_atexit) {\r
733 atexit(destroy_GDU_window);\r
734 did_atexit = TRUE;\r
735 }\r
736\r
737 return TRUE;\r
738}\r
739\r
740// windows message handlers ----------------------------------------------------\r
741\r
742// close the window\r
743\r
744static void gdu_WM_CLOSE (HWND hWnd)\r
745{\r
746 DestroyWindow(hWnd);\r
747}\r
748\r
749// the window is being destroyed\r
750\r
751static void gdu_WM_DESTROY (HWND hWnd)\r
752{\r
753 notify_window_closed();\r
754 hwGDU = NULL;\r
755}\r
756\r
757// adjust the min and max resizing boundaries\r
758\r
759static void gdu_WM_GETMINMAXINFO (HWND hWnd, LPMINMAXINFO mm)\r
760{\r
761 mm->ptMinTrackSize.x = 100 + 2*INDWIDTH;\r
762 mm->ptMinTrackSize.y = 100;\r
763}\r
764\r
765static void PaintImage (HDC hDC, BOOL draw_indicators)\r
766{\r
767 HPEN hOldPen;\r
768 RECT r;\r
769 int wid, ht, x, y, dy, i, j, ycirc;\r
770 unsigned long bit;\r
771\r
772 GetClientRect(hwGDU, &r);\r
773 wid = r.right+1 - 2*INDWIDTH;\r
774 ht = r.bottom+1;\r
775 sfactor = (double) MIN(wid,ht) / 1024.;\r
776\r
777 if (gdu_dsw & GDU_DSW_BUSY) {\r
778#ifdef BLIT_MODE\r
779 // if compiled for BLIT_MODE, draw the image into a memory display context, then\r
780 // blit the new image over window. This eliminates the flicker that a normal erase-and-\r
781 // repaint would cause.\r
782\r
783 if (wid != curwid || ht != curht) { // dimensions have changed, discard old memory display context\r
784 if (hdcGDU != NULL) {\r
785 DeleteDC(hdcGDU);\r
786 hdcGDU = NULL;\r
787 }\r
788 curwid = wid;\r
789 curht = ht;\r
790 }\r
791\r
792 if (hdcGDU == NULL) { // allocate memory display context & select a bitmap into it\r
793 hdcGDU = CreateCompatibleDC(hDC);\r
794 if (hBmp != NULL)\r
795 DeleteObject(hBmp);\r
796 hBmp = CreateCompatibleBitmap(hDC, wid, ht);\r
797 SelectObject(hdcGDU, hBmp);\r
798 }\r
799\r
800 PatBlt(hdcGDU, 0, 0, wid, ht, BLACKNESS); // start with a black screen\r
801\r
802 hOldPen = SelectObject(hdcGDU, hGreenPen);\r
803\r
804 SetMapMode(hdcGDU, MM_ANISOTROPIC);\r
805 SetWindowExtEx(hdcGDU, 1024, -1024, NULL);\r
806 SetViewportExtEx(hdcGDU, wid, ht, NULL);\r
807 SetWindowOrgEx(hdcGDU, 0, 1023, NULL);\r
808\r
809 generate_image(); // run the display program to paint the image into the memory context\r
810\r
811 SetWindowExtEx(hdcGDU, wid, ht, NULL); // undo the scaling so the blit isn't distorted\r
812 SetViewportExtEx(hdcGDU, wid, ht, NULL);\r
813 SetWindowOrgEx(hdcGDU, 0, 0, NULL);\r
814 BitBlt(hDC, 0, 0, wid, ht, hdcGDU, 0, 0, SRCCOPY); // blit the new image over the old\r
815\r
816 SelectObject(hdcGDU, hOldPen);\r
817#else\r
818 // for testing purposes -- draw the image directly onto the screen.\r
819 // Compile this way when you want to single-step through the image drawing routine,\r
820 // so you can see the draws occur.\r
821 hdcGDU = hDC;\r
822 hOldPen = SelectObject(hdcGDU, hGreenPen);\r
823\r
824 SetMapMode(hdcGDU, MM_ANISOTROPIC);\r
825 SetWindowExtEx(hdcGDU, 1024, -1024, NULL);\r
826 SetViewportExtEx(hdcGDU, wid, ht, NULL);\r
827 SetWindowOrgEx(hdcGDU, 0, 1023, NULL);\r
828\r
829 generate_image();\r
830\r
831 SelectObject(hdcGDU, hOldPen);\r
832 hdcGDU = NULL;\r
833#endif\r
834 }\r
835\r
836 if (draw_indicators) {\r
837 x = r.right-2*INDWIDTH+1;\r
838 dy = ht / 16;\r
839 ycirc = MIN(dy-2, 8);\r
840\r
841 r.left = x;\r
842 FillRect(hDC, &r, hGrayBrush);\r
843 SelectObject(hDC, hBlackPen);\r
844\r
845 bit = 0x80000000L;\r
846 for (i = 0; i < 2; i++) {\r
847 MoveToEx(hDC, x, 0, NULL);\r
848 LineTo(hDC, x, r.bottom);\r
849 y = 0;\r
850 for (j = 0; j < 16; j++) {\r
851 MoveToEx(hDC, x, y, NULL);\r
852 LineTo(hDC, x+INDWIDTH, y);\r
853\r
854 SelectObject(hDC, (gdu_indicators & bit) ? hRedBrush : hDarkBrush);\r
855 Pie(hDC, x+1, y+1, x+1+ycirc, y+1+ycirc, x+1, y+1, x+1, y+1);\r
856\r
857 y += dy;\r
858 bit >>= 1;\r
859 }\r
860 x += INDWIDTH;\r
861 }\r
862 }\r
863}\r
864\r
865// repaint the window\r
866\r
867static void gdu_WM_PAINT (HWND hWnd)\r
868{\r
869 PAINTSTRUCT ps;\r
870 HDC hDC;\r
871 // code for display\r
872 hDC = BeginPaint(hWnd, &ps);\r
873 PaintImage(hDC, TRUE);\r
874 EndPaint(hWnd, &ps);\r
875}\r
876\r
877// the window has been resized\r
878\r
879static void gdu_WM_SIZE (HWND hWnd, UINT state, int cx, int cy)\r
880{\r
881 InvalidateRect(hWnd, NULL, TRUE);\r
882}\r
883\r
884// tweak the sizing rectangle during a resize to guarantee a square window\r
885\r
886static void gdu_WM_SIZING (HWND hWnd, WPARAM fwSide, LPRECT r)\r
887{\r
888 switch (fwSide) {\r
889 case WMSZ_LEFT:\r
890 case WMSZ_RIGHT:\r
891 case WMSZ_BOTTOMLEFT:\r
892 case WMSZ_BOTTOMRIGHT:\r
893 r->bottom = r->right - r->left - 2*INDWIDTH + r->top;\r
894 break;\r
895\r
896 case WMSZ_TOP:\r
897 case WMSZ_BOTTOM:\r
898 case WMSZ_TOPRIGHT:\r
899 r->right = r->bottom - r->top + r->left + 2*INDWIDTH;\r
900 break;\r
901\r
902 case WMSZ_TOPLEFT:\r
903 r->left = r->top - r->bottom + r->right - 2*INDWIDTH;\r
904 break;\r
905 }\r
906}\r
907\r
908// the refresh timer has gone off\r
909\r
910static void gdu_WM_TIMER (HWND hWnd, UINT id)\r
911{\r
912 HDC hDC;\r
913\r
914 if (running) { // if CPU is running, update picture\r
915 if ((gdu_dsw & GDU_DSW_BUSY) == 0) { // regeneration is not to occur\r
916 if (++halted >= 4) { // stop the timer if four timer intervals go by with the display halted\r
917 EraseGDUScreen(); // screen goes black due to cessation of refreshing\r
918 StopGDUUpdates(); // might as well kill the timer\r
919 return;\r
920 }\r
921 }\r
922 else\r
923 halted = 0;\r
924\r
925#ifdef BLIT_MODE\r
926 hDC = GetDC(hWnd); // blit the new image right over the old\r
927 PaintImage(hDC, FALSE);\r
928 ReleaseDC(hWnd, hDC);\r
929#else\r
930 InvalidateRect(hWnd, NULL, TRUE); // repaint\r
931#endif\r
932 }\r
933}\r
934\r
935// window procedure ------------------------------------------------------------\r
936\r
937#define HANDLE(msg) case msg: return HANDLE_##msg(hWnd, wParam, lParam, gdu_##msg);\r
938\r
939#ifndef HANDLE_WM_SIZING\r
940// void Cls_OnSizing(HWND hwnd, UINT fwSide, LPRECT r)\r
941# define HANDLE_WM_SIZING(hwnd, wParam, lParam, fn) \\r
942 ((fn)((hwnd), (UINT)(wParam), (LPRECT)(lParam)), 0L)\r
943#endif\r
944\r
945static LRESULT APIENTRY GDUWndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)\r
946{\r
947 switch (iMessage) {\r
948 HANDLE(WM_CLOSE);\r
949 HANDLE(WM_GETMINMAXINFO);\r
950 HANDLE(WM_DESTROY);\r
951 HANDLE(WM_PAINT);\r
952 HANDLE(WM_SIZE);\r
953 HANDLE(WM_SIZING);\r
954 HANDLE(WM_TIMER);\r
955 default: // any message we don't process\r
956 return DefWindowProc(hWnd, iMessage, wParam, lParam);\r
957 }\r
958 return 0L;\r
959}\r
960\r
961// graphic calls ----------------------------------------------------------------\r
962\r
963static void DrawLine (int x0, int y0, int x1, int y1)\r
964{\r
965 MoveToEx(hdcGDU, x0, y0, NULL);\r
966 LineTo(hdcGDU, x1, y1);\r
967}\r
968\r
969static void DrawPoint (int x, int y)\r
970{\r
971 SetPixel(hdcGDU, x, y, RGB_GREEN);\r
972}\r
973\r
974static void UpdateGDUIndicators(void)\r
975{\r
976 if (hwGDU != NULL)\r
977 InvalidateRect(hwGDU, NULL, FALSE); // no need to erase the background -- the draw routine fully paints the indicator\r
978}\r
979\r
980static void CheckGDUKeyboard (void)\r
981{\r
982}\r
983\r
984static UINT idTimer = 0;\r
985\r
986static void StartGDUUpdates (void)\r
987{\r
988 int msec;\r
989\r
990 if (idTimer == 0) {\r
991 msec = (gdu_rate == 0) ? (1000 / DEFAULT_GDU_RATE) : 1000/gdu_rate;\r
992 idTimer = SetTimer(hwGDU, 1, msec, NULL);\r
993 }\r
994 halted = 0;\r
995}\r
996\r
997static void StopGDUUpdates (void)\r
998{\r
999 if (idTimer != 0) {\r
1000 KillTimer(hwGDU, 1);\r
1001 idTimer = 0;\r
1002 halted = 10000;\r
1003 }\r
1004}\r
1005\r
1006static void GetMouseCoordinates()\r
1007{\r
1008 POINT p;\r
1009 RECT r;\r
1010\r
1011 GetCursorPos(&p);\r
1012 GetClientRect(hwGDU, &r);\r
1013 if (! ScreenToClient(hwGDU, &p)) {\r
1014 xmouse = ymouse = -2000;\r
1015 mouse_present = FALSE;\r
1016 return;\r
1017 }\r
1018\r
1019 if (p.x < r.left || p.x >= r.right || p.y < r.top || p.y > r.bottom) {\r
1020 mouse_present = FALSE;\r
1021 return;\r
1022 }\r
1023\r
1024 // convert mouse coordinates to scaled coordinates\r
1025\r
1026 xmouse = (int) (1024./(r.right+1.-2*INDWIDTH)*p.x + 0.5);\r
1027 ymouse = 1023 - (int) (1024./(r.bottom+1.)*p.y + 0.5);\r
1028 mouse_present = TRUE;\r
1029}\r
1030\r
1031t_bool gdu_active (void)\r
1032{\r
1033 return gdu_dsw & GDU_DSW_BUSY;\r
1034}\r
1035\r
1036static void EraseGDUScreen (void)\r
1037{\r
1038 if (hwGDU != NULL) /* redraw screen. it will be blank if GDU is not running */\r
1039 InvalidateRect(hwGDU, NULL, TRUE);\r
1040}\r
1041\r
1042/* GDUPump - thread responsible for creating and displaying the graphics window */\r
1043\r
1044static DWORD WINAPI GDUPump (LPVOID arg)\r
1045{\r
1046 MSG msg;\r
1047 WNDCLASS wc;\r
1048\r
1049 if (! wcInited) { /* register Window class */\r
1050 memset(&wc, 0, sizeof(wc));\r
1051 wc.style = CS_NOCLOSE;\r
1052 wc.lpfnWndProc = GDUWndProc;\r
1053 wc.hInstance = hInstance;\r
1054 wc.hCursor = LoadCursor(NULL, IDC_ARROW);\r
1055 wc.hbrBackground = GetStockObject(BLACK_BRUSH);\r
1056 wc.lpszClassName = APPCLASS;\r
1057\r
1058 if (! RegisterClass(&wc)) {\r
1059 GDUPumpID = 0;\r
1060 return 0;\r
1061 }\r
1062\r
1063 wcInited = TRUE;\r
1064 }\r
1065\r
1066 if (hGreenPen == NULL)\r
1067 hGreenPen = CreatePen(PS_SOLID, 1, RGB_GREEN);\r
1068\r
1069#ifdef DEBUG_LIGHTPEN\r
1070 if (hRedPen == NULL)\r
1071 hRedPen = CreatePen(PS_SOLID, 1, RGB_RED);\r
1072#endif\r
1073\r
1074 if (hRedBrush == NULL)\r
1075 hRedBrush = CreateSolidBrush(RGB_RED);\r
1076\r
1077 hGrayBrush = GetStockObject(GRAY_BRUSH);\r
1078 hDarkBrush = GetStockObject(DKGRAY_BRUSH);\r
1079 hBlackPen = GetStockObject(BLACK_PEN);\r
1080\r
1081 if (hwGDU == NULL) { /* create window */\r
1082 hwGDU = CreateWindow(APPCLASS,\r
1083 "2250 Display",\r
1084 WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,\r
1085 CW_USEDEFAULT, CW_USEDEFAULT, // initial x, y position\r
1086 INITSIZE+2*INDWIDTH, INITSIZE, // initial width and height\r
1087 NULL, // parent window handle\r
1088 NULL, // use class menu\r
1089 hInstance, // program instance handle\r
1090 NULL); // create parameters\r
1091\r
1092 if (hwGDU == NULL) {\r
1093 GDUPumpID = 0;\r
1094 return 0;\r
1095 }\r
1096 }\r
1097\r
1098 ShowWindow(hwGDU, SW_SHOWNOACTIVATE); /* display it */\r
1099 UpdateWindow(hwGDU);\r
1100 \r
1101 while (GetMessage(&msg, hwGDU, 0, 0)) { /* message pump - this basically loops forevermore */\r
1102 TranslateMessage(&msg);\r
1103 DispatchMessage(&msg);\r
1104 }\r
1105\r
1106 if (hwGDU != NULL) {\r
1107 DestroyWindow(hwGDU); /* but if a quit message got posted, clean up */\r
1108 hwGDU = NULL;\r
1109 }\r
1110\r
1111 GDUPumpID = 0;\r
1112 return 0;\r
1113}\r
1114\r
1115#ifdef DEBUG_LIGHTPEN\r
1116static void ShowPenHit (int x, int y)\r
1117{\r
1118 HPEN hOldPen;\r
1119\r
1120 hOldPen = SelectObject(hdcGDU, hRedPen);\r
1121 DrawLine(x-10, y-10, x+10, y+10);\r
1122 DrawLine(x-10, y+10, x+10, y-10);\r
1123 SelectObject(hdcGDU, hOldPen);\r
1124}\r
1125#endif\r
1126\r
1127#endif // _WIN32 defined\r
1128#endif // GUI_SUPPORT defined\r