tv.mod 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360
  1. IMPLEMENTATION MODULE tv;
  2. (* Terminal (ANSI + termios) backend and a small retained view-tree toolkit.
  3. See tv.def. Pure ISO GNU Modula-2 apart from the libc binding tvtty.def. *)
  4. FROM SYSTEM IMPORT ADDRESS, ADR, ADDADR, CAST, BYTE;
  5. FROM tvtty IMPORT tcgetattr, tcsetattr, ioctl, read, write;
  6. CONST
  7. MAXROWS = 120;
  8. MAXCOLS = 300;
  9. MAXCELLS = MAXROWS * MAXCOLS;
  10. OUTCAP = 262144;
  11. ESCc = CHR(27);
  12. TCSAFLUSH = 2; TIOCGWINSZ = 5413H;
  13. ICANON = 2; ECHO = 8; ISIG = 1; IEXTEN = 32768;
  14. ICRNL = 256; IXON = 1024;
  15. CC_OFF = 17; VMIN = 6; VTIME = 5;
  16. H_SINGLE = 2500H; V_SINGLE = 2502H;
  17. TL_SINGLE = 250CH; TR_SINGLE = 2510H; BL_SINGLE = 2514H; BR_SINGLE = 2518H;
  18. H_DOUBLE = 2550H; V_DOUBLE = 2551H;
  19. TL_DOUBLE = 2554H; TR_DOUBLE = 2557H; BL_DOUBLE = 255AH; BR_DOUBLE = 255DH;
  20. vfPressed = 8; (* internal, transient *)
  21. TYPE
  22. Cell = RECORD cp : CARDINAL; attr : CARDINAL; END;
  23. BytePtr = POINTER TO BYTE;
  24. VAR
  25. cols, rows : INTEGER;
  26. cells : ARRAY [0..MAXCELLS - 1] OF Cell;
  27. prev : ARRAY [0..MAXCELLS - 1] OF Cell;
  28. prevValid : BOOLEAN;
  29. outBuf : ARRAY [0..OUTCAP - 1] OF CHAR;
  30. outLen : CARDINAL;
  31. inBuf : ARRAY [0..255] OF BYTE;
  32. inLen, inPos : INTEGER;
  33. term : ARRAY [0..63] OF BYTE;
  34. cursorX, cursorY : INTEGER;
  35. cursorShown : BOOLEAN;
  36. clipStack : ARRAY [0..15] OF Rect;
  37. clipN : INTEGER;
  38. pool : ARRAY [0..MAXVIEWS - 1] OF View;
  39. used : ARRAY [0..MAXVIEWS - 1] OF BOOLEAN;
  40. desk : ViewPtr;
  41. modal : ViewPtr;
  42. dragWin : ViewPtr;
  43. lastSender : ViewPtr;
  44. dragOX, dragOY : INTEGER;
  45. focusList : ARRAY [0..MAXVIEWS - 1] OF ViewPtr;
  46. focusCount : INTEGER;
  47. (*========================================================================*)
  48. (* libc struct helpers *)
  49. (*========================================================================*)
  50. PROCEDURE GetDWord(p : ADDRESS; off : INTEGER) : CARDINAL;
  51. VAR q : BytePtr; v : CARDINAL;
  52. BEGIN
  53. q := CAST(BytePtr, ADDADR(p, off));
  54. v := VAL(CARDINAL, q^);
  55. q := CAST(BytePtr, ADDADR(q, 1)); v := v + VAL(CARDINAL, q^) * 256;
  56. q := CAST(BytePtr, ADDADR(q, 1)); v := v + VAL(CARDINAL, q^) * 65536;
  57. q := CAST(BytePtr, ADDADR(q, 1)); v := v + VAL(CARDINAL, q^) * 16777216;
  58. RETURN v
  59. END GetDWord;
  60. PROCEDURE PutDWord(p : ADDRESS; off : INTEGER; v : CARDINAL);
  61. VAR q : BytePtr; x : CARDINAL;
  62. BEGIN
  63. x := v;
  64. q := CAST(BytePtr, ADDADR(p, off)); q^ := VAL(BYTE, x MOD 256); x := x DIV 256;
  65. q := CAST(BytePtr, ADDADR(q, 1)); q^ := VAL(BYTE, x MOD 256); x := x DIV 256;
  66. q := CAST(BytePtr, ADDADR(q, 1)); q^ := VAL(BYTE, x MOD 256); x := x DIV 256;
  67. q := CAST(BytePtr, ADDADR(q, 1)); q^ := VAL(BYTE, x MOD 256)
  68. END PutDWord;
  69. PROCEDURE GetWinSize(VAR r, c : INTEGER);
  70. VAR ws : ARRAY [0..7] OF BYTE;
  71. BEGIN
  72. IF ioctl(1, TIOCGWINSZ, ADR(ws)) = 0 THEN
  73. r := VAL(INTEGER, ws[0]) + VAL(INTEGER, ws[1]) * 256;
  74. c := VAL(INTEGER, ws[2]) + VAL(INTEGER, ws[3]) * 256
  75. ELSE
  76. r := 24; c := 80
  77. END;
  78. IF r < 3 THEN r := 24 END;
  79. IF c < 20 THEN c := 80 END;
  80. IF r > MAXROWS THEN r := MAXROWS END;
  81. IF c > MAXCOLS THEN c := MAXCOLS END
  82. END GetWinSize;
  83. (*========================================================================*)
  84. (* Output buffer + escape helpers *)
  85. (*========================================================================*)
  86. PROCEDURE OutCh(c : CHAR);
  87. BEGIN
  88. IF outLen < OUTCAP THEN
  89. outBuf[outLen] := c;
  90. outLen := outLen + 1
  91. END
  92. END OutCh;
  93. PROCEDURE OutStr(s : ARRAY OF CHAR);
  94. VAR i : INTEGER;
  95. BEGIN
  96. i := 0;
  97. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (s[i] # 0C) DO
  98. OutCh(s[i]);
  99. i := i + 1
  100. END
  101. END OutStr;
  102. PROCEDURE OutInt(n : INTEGER);
  103. VAR buf : ARRAY [0..15] OF CHAR;
  104. BEGIN
  105. IntToText(buf, n, 0);
  106. OutStr(buf)
  107. END OutInt;
  108. PROCEDURE OutCP(cp : CARDINAL);
  109. BEGIN
  110. IF cp < 80H THEN
  111. OutCh(CHR(cp))
  112. ELSIF cp < 800H THEN
  113. OutCh(CHR(0C0H + cp DIV 64));
  114. OutCh(CHR(80H + cp MOD 64))
  115. ELSIF cp < 10000H THEN
  116. OutCh(CHR(0E0H + cp DIV 4096));
  117. OutCh(CHR(80H + (cp DIV 64) MOD 64));
  118. OutCh(CHR(80H + cp MOD 64))
  119. ELSE
  120. OutCh(CHR(0F0H + cp DIV 262144));
  121. OutCh(CHR(80H + (cp DIV 4096) MOD 64));
  122. OutCh(CHR(80H + (cp DIV 64) MOD 64));
  123. OutCh(CHR(80H + cp MOD 64))
  124. END
  125. END OutCP;
  126. PROCEDURE EmitSGR(attr : Attr);
  127. VAR fg, bg, bl : INTEGER;
  128. BEGIN
  129. fg := VAL(INTEGER, attr MOD 16);
  130. bg := VAL(INTEGER, (attr DIV 16) MOD 16);
  131. bl := VAL(INTEGER, (attr DIV 256) MOD 2);
  132. OutCh(ESCc); OutCh('[');
  133. IF fg < 8 THEN OutInt(30 + fg) ELSE OutInt(90 + fg - 8) END;
  134. OutCh(';');
  135. IF bg < 8 THEN OutInt(40 + bg) ELSE OutInt(100 + bg - 8) END;
  136. (* blink is a mode: always emit it explicitly (5 = on, 25 = off) so that
  137. it does not leak into subsequent cells *)
  138. IF bl # 0 THEN OutStr(";5") ELSE OutStr(";25") END;
  139. OutCh('m')
  140. END EmitSGR;
  141. PROCEDURE EmitMove(x, y : INTEGER);
  142. BEGIN
  143. OutCh(ESCc); OutCh('[');
  144. OutInt(y + 1); OutCh(';'); OutInt(x + 1); OutCh('H')
  145. END EmitMove;
  146. PROCEDURE FlushOut;
  147. BEGIN
  148. IF outLen > 0 THEN
  149. IF write(1, ADR(outBuf), outLen) < 0 THEN END;
  150. outLen := 0
  151. END
  152. END FlushOut;
  153. (*========================================================================*)
  154. (* Terminal setup *)
  155. (*========================================================================*)
  156. PROCEDURE Init() : BOOLEAN;
  157. VAR lf, iff : CARDINAL; i : INTEGER;
  158. BEGIN
  159. IF tcgetattr(0, ADR(term)) # 0 THEN RETURN FALSE END;
  160. lf := GetDWord(ADR(term), 12);
  161. lf := VAL(CARDINAL, CAST(BITSET, lf) *
  162. CAST(BITSET, MAX(CARDINAL) - (ICANON + ECHO + ISIG + IEXTEN)));
  163. PutDWord(ADR(term), 12, lf);
  164. iff := GetDWord(ADR(term), 0);
  165. iff := VAL(CARDINAL, CAST(BITSET, iff) *
  166. CAST(BITSET, MAX(CARDINAL) - (ICRNL + IXON)));
  167. PutDWord(ADR(term), 0, iff);
  168. term[CC_OFF + VMIN] := VAL(BYTE, 0);
  169. term[CC_OFF + VTIME] := VAL(BYTE, 1);
  170. IF tcsetattr(0, TCSAFLUSH, ADR(term)) # 0 THEN RETURN FALSE END;
  171. GetWinSize(rows, cols);
  172. prevValid := FALSE;
  173. inLen := 0; inPos := 0;
  174. cursorX := 0; cursorY := 0; cursorShown := FALSE;
  175. clipN := 0;
  176. modal := NIL; dragWin := NIL; lastSender := NIL;
  177. (* reset view pool *)
  178. FOR i := 0 TO MAXVIEWS - 1 DO used[i] := FALSE END;
  179. desk := NIL;
  180. OutCh(ESCc); OutStr("[?1049h");
  181. OutCh(ESCc); OutStr("[2J");
  182. OutCh(ESCc); OutStr("[?1002h"); (* button/mouse-drag reporting *)
  183. OutCh(ESCc); OutStr("[?1006h"); (* SGR mouse encoding *)
  184. OutCh(ESCc); OutStr("[?25l");
  185. FlushOut;
  186. Clear(A(LightGray, Blue, FALSE));
  187. Present;
  188. desk := NewView(VGroup, 0, 0, cols, rows);
  189. RETURN TRUE
  190. END Init;
  191. PROCEDURE Done;
  192. BEGIN
  193. OutCh(ESCc); OutStr("[?1006l");
  194. OutCh(ESCc); OutStr("[?1002l");
  195. OutCh(ESCc); OutStr("[0m");
  196. OutCh(ESCc); OutStr("[?25h");
  197. OutCh(ESCc); OutStr("[?1049l");
  198. FlushOut;
  199. IF tcsetattr(0, TCSAFLUSH, ADR(term)) # 0 THEN END
  200. END Done;
  201. PROCEDURE Cols() : INTEGER;
  202. BEGIN RETURN cols END Cols;
  203. PROCEDURE Rows() : INTEGER;
  204. BEGIN RETURN rows END Rows;
  205. PROCEDURE Resized() : BOOLEAN;
  206. VAR r, c : INTEGER;
  207. BEGIN
  208. GetWinSize(r, c);
  209. IF (r # rows) OR (c # cols) THEN
  210. rows := r; cols := c;
  211. prevValid := FALSE;
  212. Clear(A(LightGray, Blue, FALSE));
  213. RETURN TRUE
  214. END;
  215. RETURN FALSE
  216. END Resized;
  217. (*========================================================================*)
  218. (* Drawing primitives (with a clip stack) *)
  219. (*========================================================================*)
  220. PROCEDURE A (fg, bg : INTEGER; blink : BOOLEAN) : Attr;
  221. BEGIN
  222. RETURN VAL(CARDINAL, fg) + VAL(CARDINAL, bg) * 16 +
  223. VAL(CARDINAL, blink) * 256
  224. END A;
  225. PROCEDURE Idx(x, y : INTEGER) : INTEGER;
  226. BEGIN RETURN y * cols + x END Idx;
  227. PROCEDURE InClip(x, y : INTEGER) : BOOLEAN;
  228. BEGIN
  229. IF (x < 0) OR (x >= cols) OR (y < 0) OR (y >= rows) THEN RETURN FALSE END;
  230. IF clipN = 0 THEN RETURN TRUE END;
  231. RETURN (x >= clipStack[clipN - 1].x) AND
  232. (x < clipStack[clipN - 1].x + clipStack[clipN - 1].w) AND
  233. (y >= clipStack[clipN - 1].y) AND
  234. (y < clipStack[clipN - 1].y + clipStack[clipN - 1].h)
  235. END InClip;
  236. PROCEDURE PushClipRect(r : Rect);
  237. VAR cur : Rect; i1, j1, i2, j2 : INTEGER;
  238. BEGIN
  239. IF clipN = 0 THEN
  240. cur.x := 0; cur.y := 0; cur.w := cols; cur.h := rows
  241. ELSE
  242. cur := clipStack[clipN - 1]
  243. END;
  244. i1 := r.x; IF i1 < cur.x THEN i1 := cur.x END;
  245. j1 := r.y; IF j1 < cur.y THEN j1 := cur.y END;
  246. i2 := r.x + r.w; IF i2 > cur.x + cur.w THEN i2 := cur.x + cur.w END;
  247. j2 := r.y + r.h; IF j2 > cur.y + cur.h THEN j2 := cur.y + cur.h END;
  248. IF i2 < i1 THEN i2 := i1 END;
  249. IF j2 < j1 THEN j2 := j1 END;
  250. IF clipN < 16 THEN
  251. clipStack[clipN].x := i1; clipStack[clipN].y := j1;
  252. clipStack[clipN].w := i2 - i1; clipStack[clipN].h := j2 - j1;
  253. clipN := clipN + 1
  254. END
  255. END PushClipRect;
  256. PROCEDURE PopClip;
  257. BEGIN
  258. IF clipN > 0 THEN clipN := clipN - 1 END
  259. END PopClip;
  260. PROCEDURE DrawCh(x, y : INTEGER; cp : CARDINAL; attr : Attr);
  261. BEGIN
  262. IF InClip(x, y) THEN
  263. cells[Idx(x, y)].cp := cp;
  264. cells[Idx(x, y)].attr := attr
  265. END
  266. END DrawCh;
  267. PROCEDURE Fill(x, y, w, h : INTEGER; cp : CARDINAL; attr : Attr);
  268. VAR i, j : INTEGER;
  269. BEGIN
  270. FOR j := y TO y + h - 1 DO
  271. FOR i := x TO x + w - 1 DO
  272. DrawCh(i, j, cp, attr)
  273. END
  274. END
  275. END Fill;
  276. PROCEDURE Clear(attr : Attr);
  277. BEGIN
  278. clipN := 0;
  279. Fill(0, 0, cols, rows, VAL(CARDINAL, ORD(' ')), attr)
  280. END Clear;
  281. PROCEDURE DrawText(x, y : INTEGER; s : ARRAY OF CHAR; attr : Attr);
  282. VAR i : INTEGER;
  283. BEGIN
  284. i := 0;
  285. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (s[i] # 0C) DO
  286. DrawCh(x + i, y, VAL(CARDINAL, ORD(s[i])), attr);
  287. i := i + 1
  288. END
  289. END DrawText;
  290. PROCEDURE DrawHLine(x, y, w : INTEGER; cp : CARDINAL; attr : Attr);
  291. VAR i : INTEGER;
  292. BEGIN
  293. FOR i := x TO x + w - 1 DO DrawCh(i, y, cp, attr) END
  294. END DrawHLine;
  295. PROCEDURE DrawVLine(x, y, h : INTEGER; cp : CARDINAL; attr : Attr);
  296. VAR i : INTEGER;
  297. BEGIN
  298. FOR i := y TO y + h - 1 DO DrawCh(x, i, cp, attr) END
  299. END DrawVLine;
  300. PROCEDURE DrawBox(r : Rect; style : INTEGER; attr : Attr);
  301. VAR hz, vt, tl, tr, bl, br : CARDINAL;
  302. x2, y2 : INTEGER;
  303. BEGIN
  304. IF (r.w < 2) OR (r.h < 2) THEN RETURN END;
  305. IF style = DoubleFrame THEN
  306. hz := H_DOUBLE; vt := V_DOUBLE;
  307. tl := TL_DOUBLE; tr := TR_DOUBLE; bl := BL_DOUBLE; br := BR_DOUBLE
  308. ELSE
  309. hz := H_SINGLE; vt := V_SINGLE;
  310. tl := TL_SINGLE; tr := TR_SINGLE; bl := BL_SINGLE; br := BR_SINGLE
  311. END;
  312. x2 := r.x + r.w - 1;
  313. y2 := r.y + r.h - 1;
  314. DrawHLine(r.x + 1, r.y, r.w - 2, hz, attr);
  315. DrawHLine(r.x + 1, y2, r.w - 2, hz, attr);
  316. DrawVLine(r.x, r.y + 1, r.h - 2, vt, attr);
  317. DrawVLine(x2, r.y + 1, r.h - 2, vt, attr);
  318. DrawCh(r.x, r.y, tl, attr);
  319. DrawCh(x2, r.y, tr, attr);
  320. DrawCh(r.x, y2, bl, attr);
  321. DrawCh(x2, y2, br, attr)
  322. END DrawBox;
  323. PROCEDURE DrawShadow(r : Rect; attr : Attr);
  324. BEGIN
  325. Fill(r.x + 2, r.y + r.h, r.w - 1, 1, VAL(CARDINAL, ORD(' ')), attr);
  326. Fill(r.x + r.w, r.y + 1, 1, r.h, VAL(CARDINAL, ORD(' ')), attr)
  327. END DrawShadow;
  328. PROCEDURE SetCursor(x, y : INTEGER);
  329. BEGIN cursorX := x; cursorY := y; cursorShown := TRUE END SetCursor;
  330. PROCEDURE HideCursor;
  331. BEGIN cursorShown := FALSE END HideCursor;
  332. PROCEDURE Present;
  333. VAR x, y, i, outX, outY : INTEGER;
  334. outAttr : CARDINAL;
  335. c : Cell;
  336. BEGIN
  337. outLen := 0;
  338. OutCh(ESCc); OutStr("[?25l");
  339. outX := -1; outY := -1; outAttr := MAX(CARDINAL);
  340. FOR y := 0 TO rows - 1 DO
  341. FOR x := 0 TO cols - 1 DO
  342. i := Idx(x, y);
  343. c := cells[i];
  344. IF (NOT prevValid) OR (c.cp # prev[i].cp) OR (c.attr # prev[i].attr) THEN
  345. IF (x # outX) OR (y # outY) THEN
  346. EmitMove(x, y);
  347. outX := x; outY := y
  348. END;
  349. IF c.attr # outAttr THEN
  350. EmitSGR(c.attr);
  351. outAttr := c.attr
  352. END;
  353. OutCP(c.cp);
  354. outX := outX + 1
  355. END;
  356. prev[i] := c
  357. END
  358. END;
  359. prevValid := TRUE;
  360. OutCh(ESCc); OutStr("[0m");
  361. IF cursorShown AND (cursorX >= 0) AND (cursorX < cols) AND
  362. (cursorY >= 0) AND (cursorY < rows) THEN
  363. OutCh(ESCc); OutStr("[?25h");
  364. EmitMove(cursorX, cursorY)
  365. ELSE
  366. OutCh(ESCc); OutStr("[?25l")
  367. END;
  368. FlushOut
  369. END Present;
  370. (*========================================================================*)
  371. (* Text helpers *)
  372. (*========================================================================*)
  373. PROCEDURE TextLen (s : ARRAY OF CHAR) : INTEGER;
  374. VAR i : INTEGER;
  375. BEGIN
  376. i := 0;
  377. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (s[i] # 0C) DO i := i + 1 END;
  378. RETURN i
  379. END TextLen;
  380. PROCEDURE CopyText (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR);
  381. VAR i : INTEGER;
  382. BEGIN
  383. i := 0;
  384. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (i < VAL(INTEGER, HIGH(dest))) AND (s[i] # 0C) DO
  385. dest[i] := s[i];
  386. i := i + 1
  387. END;
  388. dest[i] := 0C
  389. END CopyText;
  390. PROCEDURE AppendText (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR);
  391. VAR i, j : INTEGER;
  392. BEGIN
  393. i := TextLen(dest);
  394. j := 0;
  395. WHILE (j <= VAL(INTEGER, HIGH(s))) AND (i < VAL(INTEGER, HIGH(dest))) AND (s[j] # 0C) DO
  396. dest[i] := s[j];
  397. i := i + 1;
  398. j := j + 1
  399. END;
  400. dest[i] := 0C
  401. END AppendText;
  402. PROCEDURE PadText (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR; width : INTEGER);
  403. VAR i, n : INTEGER;
  404. BEGIN
  405. n := TextLen(s);
  406. IF n > width THEN n := width END;
  407. FOR i := 0 TO width - 1 DO
  408. IF i < n THEN dest[i] := s[i] ELSE dest[i] := ' ' END
  409. END;
  410. dest[width] := 0C
  411. END PadText;
  412. PROCEDURE IntToText (VAR dest : ARRAY OF CHAR; n : INTEGER; width : INTEGER);
  413. VAR tmp : ARRAY [0..15] OF CHAR;
  414. k, i, p, v : INTEGER;
  415. neg : BOOLEAN;
  416. BEGIN
  417. k := 0; neg := FALSE; v := n;
  418. IF v < 0 THEN neg := TRUE; v := 0 - v END;
  419. IF v = 0 THEN
  420. tmp[0] := '0'; k := 1
  421. ELSE
  422. WHILE v > 0 DO
  423. tmp[k] := CHR(ORD('0') + VAL(CARDINAL, v MOD 10));
  424. k := k + 1;
  425. v := v DIV 10
  426. END
  427. END;
  428. p := 0;
  429. IF neg THEN dest[p] := '-'; p := p + 1 END;
  430. i := k;
  431. WHILE i > 0 DO
  432. i := i - 1;
  433. dest[p] := tmp[i];
  434. p := p + 1
  435. END;
  436. WHILE (width > 0) AND (p < width) DO
  437. dest[p] := ' ';
  438. p := p + 1
  439. END;
  440. dest[p] := 0C
  441. END IntToText;
  442. (*========================================================================*)
  443. (* Input *)
  444. (*========================================================================*)
  445. PROCEDURE FillIn;
  446. BEGIN
  447. IF inPos >= inLen THEN
  448. inLen := read(0, ADR(inBuf), 64);
  449. IF inLen < 0 THEN inLen := 0 END;
  450. inPos := 0
  451. END
  452. END FillIn;
  453. PROCEDURE PeekByte(VAR ok : BOOLEAN) : CARDINAL;
  454. BEGIN
  455. FillIn;
  456. IF inPos >= inLen THEN ok := FALSE; RETURN 0 END;
  457. ok := TRUE;
  458. RETURN VAL(CARDINAL, inBuf[inPos])
  459. END PeekByte;
  460. PROCEDURE TakeByte;
  461. BEGIN
  462. IF inPos < inLen THEN inPos := inPos + 1 END
  463. END TakeByte;
  464. PROCEDURE ReadEvent (VAR e : Event) : BOOLEAN;
  465. VAR b, b2, c1, c2, c3, cp, num : CARDINAL;
  466. ok : BOOLEAN;
  467. BEGIN
  468. e.kind := evNone; e.key := kNone; e.ch := 0; e.ctrl := FALSE;
  469. e.mpressed := FALSE; e.mreleased := FALSE; e.mwheel := 0;
  470. FillIn;
  471. IF inPos >= inLen THEN RETURN FALSE END;
  472. b := PeekByte(ok); TakeByte;
  473. IF b = 1BH THEN
  474. b2 := PeekByte(ok);
  475. IF NOT ok THEN e.kind := evKey; e.key := kEsc; RETURN TRUE END;
  476. IF b2 = VAL(CARDINAL, ORD('[')) THEN
  477. TakeByte;
  478. b2 := PeekByte(ok);
  479. IF NOT ok THEN e.kind := evKey; e.key := kEsc; RETURN TRUE END;
  480. IF b2 = VAL(CARDINAL, ORD('<')) THEN
  481. (* SGR mouse: ESC [ < b ; x ; y M/m *)
  482. TakeByte;
  483. num := 0;
  484. b2 := PeekByte(ok);
  485. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  486. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  487. num := num * 10 + (b2 - VAL(CARDINAL, ORD('0')));
  488. TakeByte; b2 := PeekByte(ok)
  489. END;
  490. IF ok THEN TakeByte END; (* ; *)
  491. e.mx := 0;
  492. b2 := PeekByte(ok);
  493. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  494. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  495. e.mx := e.mx * 10 + VAL(INTEGER, b2 - VAL(CARDINAL, ORD('0')));
  496. TakeByte; b2 := PeekByte(ok)
  497. END;
  498. IF ok THEN TakeByte END; (* ; *)
  499. e.my := 0;
  500. b2 := PeekByte(ok);
  501. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  502. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  503. e.my := e.my * 10 + VAL(INTEGER, b2 - VAL(CARDINAL, ORD('0')));
  504. TakeByte; b2 := PeekByte(ok)
  505. END;
  506. IF ok THEN
  507. IF b2 = VAL(CARDINAL, ORD('M')) THEN
  508. IF (num >= 64) AND (num <= 65) THEN
  509. e.mwheel := 1
  510. ELSIF (num >= 128) AND (num <= 129) THEN
  511. e.mwheel := -1
  512. ELSIF num >= 32 THEN
  513. e.mbtn := VAL(INTEGER, num - 32) + 1
  514. ELSE
  515. e.mbtn := VAL(INTEGER, num) + 1;
  516. e.mpressed := TRUE
  517. END
  518. ELSE
  519. e.mbtn := VAL(INTEGER, num) + 1;
  520. e.mreleased := TRUE
  521. END;
  522. TakeByte
  523. END;
  524. e.mx := e.mx - 1; e.my := e.my - 1;
  525. e.kind := evMouse;
  526. RETURN TRUE
  527. ELSIF (b2 >= VAL(CARDINAL, ORD('0'))) AND (b2 <= VAL(CARDINAL, ORD('9'))) THEN
  528. num := 0;
  529. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  530. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  531. num := num * 10 + (b2 - VAL(CARDINAL, ORD('0')));
  532. TakeByte; b2 := PeekByte(ok)
  533. END;
  534. IF NOT ok THEN e.kind := evKey; e.key := kEsc; RETURN TRUE END;
  535. WHILE ok AND (b2 = VAL(CARDINAL, ORD(';'))) DO
  536. TakeByte; b2 := PeekByte(ok);
  537. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  538. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  539. TakeByte; b2 := PeekByte(ok)
  540. END
  541. END;
  542. IF ok THEN TakeByte END;
  543. e.kind := evKey;
  544. IF b2 = VAL(CARDINAL, ORD('~')) THEN
  545. IF num = 1 THEN e.key := kHome
  546. ELSIF num = 2 THEN e.key := kIns
  547. ELSIF num = 3 THEN e.key := kDel
  548. ELSIF num = 4 THEN e.key := kEnd
  549. ELSIF num = 5 THEN e.key := kPgUp
  550. ELSIF num = 6 THEN e.key := kPgDn
  551. ELSIF num = 15 THEN e.key := kF5
  552. ELSIF num = 17 THEN e.key := kF6
  553. ELSIF num = 18 THEN e.key := kF7
  554. ELSIF num = 19 THEN e.key := kF8
  555. ELSIF num = 20 THEN e.key := kF9
  556. ELSIF num = 21 THEN e.key := kF10
  557. ELSIF num = 23 THEN e.key := kF11
  558. ELSIF num = 24 THEN e.key := kF12
  559. ELSE e.key := kNone
  560. END
  561. ELSIF b2 = VAL(CARDINAL, ORD('A')) THEN e.key := kUp
  562. ELSIF b2 = VAL(CARDINAL, ORD('B')) THEN e.key := kDown
  563. ELSIF b2 = VAL(CARDINAL, ORD('C')) THEN e.key := kRight
  564. ELSIF b2 = VAL(CARDINAL, ORD('D')) THEN e.key := kLeft
  565. ELSIF b2 = VAL(CARDINAL, ORD('H')) THEN e.key := kHome
  566. ELSIF b2 = VAL(CARDINAL, ORD('F')) THEN e.key := kEnd
  567. ELSIF b2 = VAL(CARDINAL, ORD('Z')) THEN e.key := kTab; e.ctrl := TRUE
  568. ELSIF b2 = VAL(CARDINAL, ORD('P')) THEN e.key := kF1
  569. ELSIF b2 = VAL(CARDINAL, ORD('Q')) THEN e.key := kF2
  570. ELSIF b2 = VAL(CARDINAL, ORD('R')) THEN e.key := kF3
  571. ELSIF b2 = VAL(CARDINAL, ORD('S')) THEN e.key := kF4
  572. ELSE e.key := kNone
  573. END;
  574. RETURN TRUE
  575. ELSE
  576. TakeByte;
  577. e.kind := evKey;
  578. IF b2 = VAL(CARDINAL, ORD('A')) THEN e.key := kUp
  579. ELSIF b2 = VAL(CARDINAL, ORD('B')) THEN e.key := kDown
  580. ELSIF b2 = VAL(CARDINAL, ORD('C')) THEN e.key := kRight
  581. ELSIF b2 = VAL(CARDINAL, ORD('D')) THEN e.key := kLeft
  582. ELSIF b2 = VAL(CARDINAL, ORD('H')) THEN e.key := kHome
  583. ELSIF b2 = VAL(CARDINAL, ORD('F')) THEN e.key := kEnd
  584. ELSIF b2 = VAL(CARDINAL, ORD('Z')) THEN e.key := kTab; e.ctrl := TRUE
  585. ELSE e.key := kNone
  586. END;
  587. RETURN TRUE
  588. END
  589. ELSIF b2 = VAL(CARDINAL, ORD('O')) THEN
  590. TakeByte;
  591. b2 := PeekByte(ok);
  592. IF ok THEN TakeByte END;
  593. e.kind := evKey;
  594. IF b2 = VAL(CARDINAL, ORD('P')) THEN e.key := kF1
  595. ELSIF b2 = VAL(CARDINAL, ORD('Q')) THEN e.key := kF2
  596. ELSIF b2 = VAL(CARDINAL, ORD('R')) THEN e.key := kF3
  597. ELSIF b2 = VAL(CARDINAL, ORD('S')) THEN e.key := kF4
  598. ELSIF b2 = VAL(CARDINAL, ORD('A')) THEN e.key := kUp
  599. ELSIF b2 = VAL(CARDINAL, ORD('B')) THEN e.key := kDown
  600. ELSIF b2 = VAL(CARDINAL, ORD('C')) THEN e.key := kRight
  601. ELSIF b2 = VAL(CARDINAL, ORD('D')) THEN e.key := kLeft
  602. ELSIF b2 = VAL(CARDINAL, ORD('H')) THEN e.key := kHome
  603. ELSIF b2 = VAL(CARDINAL, ORD('F')) THEN e.key := kEnd
  604. END;
  605. RETURN TRUE
  606. ELSE
  607. e.kind := evKey; e.key := kEsc;
  608. RETURN TRUE
  609. END
  610. ELSIF (b = 0DH) OR (b = 0AH) THEN e.kind := evKey; e.key := kEnter; RETURN TRUE
  611. ELSIF b = 09H THEN e.kind := evKey; e.key := kTab; RETURN TRUE
  612. ELSIF b = 20H THEN e.kind := evKey; e.key := kSpace; RETURN TRUE
  613. ELSIF (b = 7FH) OR (b = 08H) THEN e.kind := evKey; e.key := kBack; RETURN TRUE
  614. ELSIF b = 03H THEN e.kind := evKey; e.key := kCtrlC; RETURN TRUE
  615. ELSIF b < 20H THEN e.kind := evKey; e.key := kNone; RETURN TRUE
  616. ELSIF b < 80H THEN e.kind := evKey; e.key := kChar; e.ch := b; RETURN TRUE
  617. ELSE
  618. IF b < 0E0H THEN
  619. c1 := PeekByte(ok); IF ok THEN TakeByte ELSE c1 := 80H END;
  620. cp := (b - 0C0H) * 64 + (c1 - 80H)
  621. ELSIF b < 0F0H THEN
  622. c1 := PeekByte(ok); IF ok THEN TakeByte ELSE c1 := 80H END;
  623. c2 := PeekByte(ok); IF ok THEN TakeByte ELSE c2 := 80H END;
  624. cp := (b - 0E0H) * 4096 + (c1 - 80H) * 64 + (c2 - 80H)
  625. ELSE
  626. c1 := PeekByte(ok); IF ok THEN TakeByte ELSE c1 := 80H END;
  627. c2 := PeekByte(ok); IF ok THEN TakeByte ELSE c2 := 80H END;
  628. c3 := PeekByte(ok); IF ok THEN TakeByte ELSE c3 := 80H END;
  629. cp := (b - 0F0H) * 262144 + (c1 - 80H) * 4096 +
  630. (c2 - 80H) * 64 + (c3 - 80H)
  631. END;
  632. e.kind := evKey; e.key := kChar; e.ch := cp;
  633. RETURN TRUE
  634. END
  635. END ReadEvent;
  636. (*========================================================================*)
  637. (* View tree *)
  638. (*========================================================================*)
  639. PROCEDURE NewView (k : VKind; x, y, w, h : INTEGER) : ViewPtr;
  640. VAR i : INTEGER;
  641. BEGIN
  642. i := 0;
  643. WHILE (i < MAXVIEWS) AND used[i] DO i := i + 1 END;
  644. IF i >= MAXVIEWS THEN RETURN NIL END;
  645. used[i] := TRUE;
  646. pool[i].kind := k;
  647. pool[i].rect.x := x; pool[i].rect.y := y;
  648. pool[i].rect.w := w; pool[i].rect.h := h;
  649. pool[i].parent := NIL; pool[i].next := NIL; pool[i].prev := NIL;
  650. pool[i].first := NIL; pool[i].last := NIL; pool[i].focus := NIL;
  651. pool[i].clipKids := FALSE;
  652. pool[i].text[0] := 0C; pool[i].len := 0; pool[i].pos := 0;
  653. pool[i].value := 0; pool[i].minv := 0; pool[i].maxv := 0; pool[i].tag := 0;
  654. pool[i].flags := 0; pool[i].list := NIL;
  655. RETURN ADR(pool[i])
  656. END NewView;
  657. PROCEDURE FreeView (v : ViewPtr);
  658. VAR i : INTEGER;
  659. BEGIN
  660. IF v = NIL THEN RETURN END;
  661. i := 0;
  662. WHILE (i < MAXVIEWS) AND (ADR(pool[i]) # v) DO i := i + 1 END;
  663. IF i < MAXVIEWS THEN used[i] := FALSE END
  664. END FreeView;
  665. PROCEDURE AddView (parent, child : ViewPtr);
  666. BEGIN
  667. IF (parent = NIL) OR (child = NIL) THEN RETURN END;
  668. child^.parent := parent;
  669. child^.next := NIL;
  670. child^.prev := parent^.last;
  671. IF parent^.last # NIL THEN parent^.last^.next := child END;
  672. parent^.last := child;
  673. IF parent^.first = NIL THEN parent^.first := child END;
  674. IF parent^.kind = VWindow THEN child^.clipKids := FALSE END
  675. END AddView;
  676. PROCEDURE DelView (v : ViewPtr);
  677. VAR c, n : ViewPtr;
  678. BEGIN
  679. IF v = NIL THEN RETURN END;
  680. c := v^.first;
  681. WHILE c # NIL DO
  682. n := c^.next;
  683. DelView(c);
  684. c := n
  685. END;
  686. IF v^.prev # NIL THEN v^.prev^.next := v^.next END;
  687. IF v^.next # NIL THEN v^.next^.prev := v^.prev END;
  688. IF v^.parent # NIL THEN
  689. IF v^.parent^.first = v THEN v^.parent^.first := v^.next END;
  690. IF v^.parent^.last = v THEN v^.parent^.last := v^.prev END
  691. END;
  692. FreeView(v)
  693. END DelView;
  694. PROCEDURE SetText (v : ViewPtr; s : ARRAY OF CHAR);
  695. BEGIN
  696. IF v # NIL THEN CopyText(v^.text, s); v^.len := TextLen(v^.text) END
  697. END SetText;
  698. PROCEDURE SetTag (v : ViewPtr; t : INTEGER);
  699. BEGIN
  700. IF v # NIL THEN v^.tag := t END
  701. END SetTag;
  702. PROCEDURE Desk () : ViewPtr;
  703. BEGIN RETURN desk END Desk;
  704. PROCEDURE BringToFront (v : ViewPtr);
  705. VAR p : ViewPtr;
  706. BEGIN
  707. IF (v = NIL) OR (v^.parent = NIL) THEN RETURN END;
  708. IF v^.parent^.last = v THEN RETURN END;
  709. p := v^.parent;
  710. (* unlink *)
  711. IF v^.prev # NIL THEN v^.prev^.next := v^.next END;
  712. IF v^.next # NIL THEN v^.next^.prev := v^.prev END;
  713. IF p^.first = v THEN p^.first := v^.next END;
  714. (* append *)
  715. v^.prev := p^.last; v^.next := NIL;
  716. IF p^.last # NIL THEN p^.last^.next := v END;
  717. p^.last := v
  718. END BringToFront;
  719. PROCEDURE FocusView (v : ViewPtr);
  720. VAR p : ViewPtr;
  721. BEGIN
  722. p := v;
  723. WHILE (p # NIL) AND (p^.parent # NIL) DO
  724. p^.parent^.focus := p;
  725. p := p^.parent
  726. END
  727. END FocusView;
  728. PROCEDURE IsFocusable(v : ViewPtr) : BOOLEAN;
  729. BEGIN
  730. RETURN (v # NIL) AND ((v^.flags DIV vfDisabled) MOD 2 = 0) AND
  731. ((v^.kind = VInput) OR (v^.kind = VButton) OR (v^.kind = VCheck) OR
  732. (v^.kind = VRadio) OR (v^.kind = VList) OR (v^.kind = VScroll))
  733. END IsFocusable;
  734. PROCEDURE CollectFocus(v : ViewPtr);
  735. VAR c : ViewPtr;
  736. BEGIN
  737. IF v = NIL THEN RETURN END;
  738. IF IsFocusable(v) THEN
  739. IF focusCount < MAXVIEWS THEN
  740. focusList[focusCount] := v;
  741. focusCount := focusCount + 1
  742. END
  743. END;
  744. c := v^.first;
  745. WHILE c # NIL DO
  746. CollectFocus(c);
  747. c := c^.next
  748. END
  749. END CollectFocus;
  750. PROCEDURE CurrentFocus() : ViewPtr;
  751. VAR v : ViewPtr;
  752. BEGIN
  753. v := desk;
  754. WHILE (v # NIL) AND (v^.focus # NIL) DO v := v^.focus END;
  755. RETURN v
  756. END CurrentFocus;
  757. PROCEDURE FocusNext (backwards : BOOLEAN);
  758. VAR cur, nxt : ViewPtr; i, idx : INTEGER;
  759. BEGIN
  760. focusCount := 0;
  761. CollectFocus(desk);
  762. IF focusCount = 0 THEN RETURN END;
  763. cur := CurrentFocus();
  764. idx := -1;
  765. FOR i := 0 TO focusCount - 1 DO
  766. IF focusList[i] = cur THEN idx := i END
  767. END;
  768. IF idx < 0 THEN
  769. nxt := focusList[0]
  770. ELSIF backwards THEN
  771. nxt := focusList[(idx + focusCount - 1) MOD focusCount]
  772. ELSE
  773. nxt := focusList[(idx + 1) MOD focusCount]
  774. END;
  775. FocusView(nxt)
  776. END FocusNext;
  777. PROCEDURE MoveViewBy (v : ViewPtr; dx, dy : INTEGER);
  778. VAR c : ViewPtr;
  779. BEGIN
  780. IF v = NIL THEN RETURN END;
  781. v^.rect.x := v^.rect.x + dx;
  782. v^.rect.y := v^.rect.y + dy;
  783. c := v^.first;
  784. WHILE c # NIL DO
  785. MoveViewBy(c, dx, dy);
  786. c := c^.next
  787. END
  788. END MoveViewBy;
  789. PROCEDURE OffsetView(v : ViewPtr; dx, dy : INTEGER);
  790. BEGIN MoveViewBy(v, dx, dy) END OffsetView;
  791. (*========================================================================*)
  792. (* Widget drawing *)
  793. (*========================================================================*)
  794. PROCEDURE IsFocused(v : ViewPtr) : BOOLEAN;
  795. BEGIN RETURN CurrentFocus() = v END IsFocused;
  796. PROCEDURE DrawWindow(v : ViewPtr);
  797. VAR frame, title, shadow : Attr;
  798. x2 : INTEGER;
  799. BEGIN
  800. shadow := A(Black, Black, FALSE);
  801. frame := A(White, Blue, FALSE);
  802. title := A(Yellow, Blue, FALSE);
  803. DrawShadow(v^.rect, shadow);
  804. Fill(v^.rect.x, v^.rect.y, v^.rect.w, v^.rect.h,
  805. VAL(CARDINAL, ORD(' ')), A(Black, Cyan, FALSE));
  806. DrawBox(v^.rect, DoubleFrame, frame);
  807. DrawText(v^.rect.x + 2, v^.rect.y, v^.text, title);
  808. (* close box *)
  809. x2 := v^.rect.x + v^.rect.w - 3;
  810. DrawText(x2, v^.rect.y, " X ", A(White, Red, FALSE))
  811. END DrawWindow;
  812. PROCEDURE DrawInput(v : ViewPtr);
  813. VAR bg, fg : Attr; s : ARRAY [0..MAXLINE - 1] OF CHAR;
  814. BEGIN
  815. bg := A(Black, White, FALSE);
  816. Fill(v^.rect.x, v^.rect.y, v^.rect.w, v^.rect.h,
  817. VAL(CARDINAL, ORD(' ')), bg);
  818. DrawText(v^.rect.x, v^.rect.y, v^.text, bg);
  819. IF IsFocused(v) THEN
  820. SetCursor(v^.rect.x + v^.pos, v^.rect.y)
  821. END
  822. END DrawInput;
  823. PROCEDURE DrawButton(v : ViewPtr);
  824. VAR s : ARRAY [0..MAXLINE - 1] OF CHAR;
  825. attr : Attr; w : INTEGER;
  826. BEGIN
  827. CopyText(s, "[ ");
  828. AppendText(s, v^.text);
  829. AppendText(s, " ]");
  830. w := TextLen(s);
  831. IF IsFocused(v) THEN
  832. attr := A(White, Blue, FALSE)
  833. ELSE
  834. attr := A(Black, LightGray, FALSE)
  835. END;
  836. IF (v^.flags DIV vfPressed) MOD 2 = 1 THEN
  837. attr := A(White, Green, FALSE)
  838. END;
  839. Fill(v^.rect.x, v^.rect.y, w, 1, VAL(CARDINAL, ORD(' ')),
  840. A(Black, Cyan, FALSE));
  841. DrawText(v^.rect.x, v^.rect.y, s, attr)
  842. END DrawButton;
  843. PROCEDURE DrawCheck(v : ViewPtr);
  844. VAR s : ARRAY [0..1] OF CHAR; attr : Attr;
  845. BEGIN
  846. IF v^.flags DIV vfSelected MOD 2 = 1 THEN s := "X" ELSE s := " " END;
  847. attr := A(Black, Cyan, FALSE);
  848. DrawText(v^.rect.x, v^.rect.y, "[", attr);
  849. DrawText(v^.rect.x + 1, v^.rect.y, s, A(White, Blue, FALSE));
  850. DrawText(v^.rect.x + 2, v^.rect.y, "]", attr);
  851. DrawText(v^.rect.x + 4, v^.rect.y, v^.text, attr)
  852. END DrawCheck;
  853. PROCEDURE DrawRadio(v : ViewPtr);
  854. VAR m : ARRAY [0..1] OF CHAR; attr : Attr;
  855. BEGIN
  856. IF v^.flags DIV vfSelected MOD 2 = 1 THEN m := "*" ELSE m := " " END;
  857. attr := A(Black, Cyan, FALSE);
  858. DrawText(v^.rect.x, v^.rect.y, "(", attr);
  859. DrawText(v^.rect.x + 1, v^.rect.y, m, A(White, Blue, FALSE));
  860. DrawText(v^.rect.x + 2, v^.rect.y, ")", attr);
  861. DrawText(v^.rect.x + 4, v^.rect.y, v^.text, attr)
  862. END DrawRadio;
  863. PROCEDURE DrawList(v : ViewPtr);
  864. VAR i, first, vis, y : INTEGER; attr, sel : Attr;
  865. BEGIN
  866. IF v^.list = NIL THEN RETURN END;
  867. DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE));
  868. vis := v^.rect.h - 2;
  869. first := v^.pos;
  870. FOR i := 0 TO vis - 1 DO
  871. y := v^.rect.y + 1 + i;
  872. IF (first + i) < v^.list^.count THEN
  873. IF (v^.list # NIL) AND (first + i = v^.value) THEN
  874. attr := A(White, Blue, FALSE)
  875. ELSE
  876. attr := A(Black, Cyan, FALSE)
  877. END;
  878. Fill(v^.rect.x + 1, y, v^.rect.w - 2, 1,
  879. VAL(CARDINAL, ORD(' ')), attr);
  880. DrawText(v^.rect.x + 1, y, v^.list^.items[first + i], attr)
  881. END
  882. END;
  883. IF v^.list # NIL THEN
  884. IF v^.list^.count > vis THEN
  885. DrawText(v^.rect.x + v^.rect.w - 2, v^.rect.y + 1, "^",
  886. A(White, Cyan, FALSE));
  887. DrawText(v^.rect.x + v^.rect.w - 2, v^.rect.y + v^.rect.h - 2, "v",
  888. A(White, Cyan, FALSE))
  889. END
  890. END
  891. END DrawList;
  892. PROCEDURE DrawScroll(v : ViewPtr);
  893. VAR h, t, yy : INTEGER; attr : Attr;
  894. BEGIN
  895. attr := A(Black, LightGray, FALSE);
  896. Fill(v^.rect.x, v^.rect.y, v^.rect.w, v^.rect.h,
  897. VAL(CARDINAL, ORD(' ')), attr);
  898. IF v^.maxv > 0 THEN
  899. h := v^.rect.h;
  900. t := h DIV (v^.maxv + 1);
  901. IF t < 1 THEN t := 1 END;
  902. yy := v^.rect.y + (v^.value * (h - t)) DIV v^.maxv;
  903. Fill(v^.rect.x, yy, v^.rect.w, t, VAL(CARDINAL, ORD(' ')),
  904. A(White, Blue, FALSE))
  905. END
  906. END DrawScroll;
  907. PROCEDURE DrawTextCtl(v : ViewPtr);
  908. BEGIN
  909. Fill(v^.rect.x, v^.rect.y, v^.rect.w, 1,
  910. VAL(CARDINAL, ORD(' ')), A(Black, Cyan, FALSE));
  911. DrawText(v^.rect.x, v^.rect.y, v^.text, A(Black, Cyan, FALSE))
  912. END DrawTextCtl;
  913. PROCEDURE DrawView(v : ViewPtr);
  914. VAR c : ViewPtr;
  915. BEGIN
  916. IF v = NIL THEN RETURN END;
  917. CASE v^.kind OF
  918. VGroup : (* nothing *)
  919. |
  920. VWindow : DrawWindow(v)
  921. |
  922. VText : DrawTextCtl(v)
  923. |
  924. VFrame : DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE))
  925. |
  926. VInput : DrawInput(v)
  927. |
  928. VButton : DrawButton(v)
  929. |
  930. VCheck : DrawCheck(v)
  931. |
  932. VRadio : DrawRadio(v)
  933. |
  934. VList : DrawList(v)
  935. |
  936. VScroll : DrawScroll(v)
  937. END;
  938. IF v^.first # NIL THEN
  939. IF (v^.kind = VWindow) OR v^.clipKids THEN
  940. PushClipRect(v^.rect)
  941. END;
  942. c := v^.first;
  943. WHILE c # NIL DO
  944. DrawView(c);
  945. c := c^.next
  946. END;
  947. IF (v^.kind = VWindow) OR v^.clipKids THEN PopClip END
  948. END
  949. END DrawView;
  950. PROCEDURE DrawTree;
  951. BEGIN
  952. Clear(A(LightGray, Blue, FALSE));
  953. HideCursor;
  954. clipN := 0;
  955. IF desk # NIL THEN DrawView(desk) END
  956. END DrawTree;
  957. PROCEDURE Finish;
  958. BEGIN
  959. DrawTree;
  960. Present
  961. END Finish;
  962. (*========================================================================*)
  963. (* Geometry + hit testing *)
  964. (*========================================================================*)
  965. PROCEDURE Contains(r : Rect; x, y : INTEGER) : BOOLEAN;
  966. BEGIN
  967. RETURN (x >= r.x) AND (x < r.x + r.w) AND (y >= r.y) AND (y < r.y + r.h)
  968. END Contains;
  969. PROCEDURE HitTest(v : ViewPtr; x, y : INTEGER) : ViewPtr;
  970. VAR c, hit : ViewPtr;
  971. BEGIN
  972. IF (v = NIL) OR (NOT Contains(v^.rect, x, y)) THEN RETURN NIL END;
  973. c := v^.last;
  974. WHILE c # NIL DO
  975. IF Contains(c^.rect, x, y) THEN
  976. hit := HitTest(c, x, y);
  977. IF hit # NIL THEN RETURN hit END
  978. END;
  979. c := c^.prev
  980. END;
  981. RETURN v
  982. END HitTest;
  983. PROCEDURE WindowAncestor(v : ViewPtr) : ViewPtr;
  984. VAR p : ViewPtr;
  985. BEGIN
  986. p := v;
  987. WHILE p # NIL DO
  988. IF p^.kind = VWindow THEN RETURN p END;
  989. p := p^.parent
  990. END;
  991. RETURN NIL
  992. END WindowAncestor;
  993. (*========================================================================*)
  994. (* Event dispatch *)
  995. (*========================================================================*)
  996. PROCEDURE EditKey(v : ViewPtr; VAR e : Event);
  997. VAR i : INTEGER;
  998. BEGIN
  999. IF e.key = kLeft THEN
  1000. IF v^.pos > 0 THEN v^.pos := v^.pos - 1 END
  1001. ELSIF e.key = kRight THEN
  1002. IF v^.pos < v^.len THEN v^.pos := v^.pos + 1 END
  1003. ELSIF e.key = kHome THEN v^.pos := 0
  1004. ELSIF e.key = kEnd THEN v^.pos := v^.len
  1005. ELSIF e.key = kBack THEN
  1006. IF v^.pos > 0 THEN
  1007. i := v^.pos - 1;
  1008. WHILE i < v^.len - 1 DO v^.text[i] := v^.text[i + 1]; i := i + 1 END;
  1009. v^.len := v^.len - 1; v^.pos := v^.pos - 1; v^.text[v^.len] := 0C
  1010. END
  1011. ELSIF e.key = kDel THEN
  1012. IF v^.pos < v^.len THEN
  1013. i := v^.pos;
  1014. WHILE i < v^.len - 1 DO v^.text[i] := v^.text[i + 1]; i := i + 1 END;
  1015. v^.len := v^.len - 1; v^.text[v^.len] := 0C
  1016. END
  1017. ELSIF e.key = kChar THEN
  1018. IF (v^.len < MAXLINE - 1) AND (e.ch < 256) THEN
  1019. i := v^.len;
  1020. WHILE i > v^.pos DO v^.text[i] := v^.text[i - 1]; i := i - 1 END;
  1021. v^.text[v^.pos] := CHR(e.ch);
  1022. v^.len := v^.len + 1; v^.pos := v^.pos + 1
  1023. END
  1024. END
  1025. END EditKey;
  1026. PROCEDURE WidgetKey(v : ViewPtr; VAR e : Event) : INTEGER;
  1027. VAR cmd : INTEGER;
  1028. BEGIN
  1029. cmd := cmNone;
  1030. IF v^.kind = VInput THEN
  1031. EditKey(v, e)
  1032. ELSIF v^.kind = VButton THEN
  1033. IF (e.key = kEnter) OR (e.key = kSpace) THEN cmd := v^.tag END
  1034. ELSIF v^.kind = VCheck THEN
  1035. IF (e.key = kEnter) OR (e.key = kSpace) THEN
  1036. v^.flags := v^.flags / 4 * 4;
  1037. IF v^.flags DIV vfSelected MOD 2 = 0 THEN
  1038. v^.flags := v^.flags + vfSelected
  1039. END;
  1040. cmd := v^.tag
  1041. END
  1042. ELSIF v^.kind = VRadio THEN
  1043. IF (e.key = kEnter) OR (e.key = kSpace) THEN cmd := v^.tag END
  1044. ELSIF v^.kind = VList THEN
  1045. IF e.key = kUp THEN
  1046. IF v^.value > 0 THEN v^.value := v^.value - 1 END;
  1047. IF v^.value < v^.pos THEN v^.pos := v^.value END
  1048. ELSIF e.key = kDown THEN
  1049. IF (v^.list # NIL) AND (v^.value < v^.list^.count - 1) THEN
  1050. v^.value := v^.value + 1
  1051. END;
  1052. IF v^.value >= v^.pos + (v^.rect.h - 2) THEN
  1053. v^.pos := v^.value - (v^.rect.h - 2) + 1
  1054. END
  1055. ELSIF (e.key = kEnter) OR (e.key = kSpace) THEN
  1056. cmd := v^.tag
  1057. END
  1058. ELSIF v^.kind = VScroll THEN
  1059. IF e.key = kUp THEN
  1060. IF v^.value > 0 THEN v^.value := v^.value - 1 END
  1061. ELSIF e.key = kDown THEN
  1062. IF v^.value < v^.maxv THEN v^.value := v^.value + 1 END
  1063. END
  1064. END;
  1065. IF cmd # cmNone THEN lastSender := v END;
  1066. RETURN cmd
  1067. END WidgetKey;
  1068. PROCEDURE WidgetMouse(v : ViewPtr; VAR e : Event) : INTEGER;
  1069. VAR cmd : INTEGER; rel : INTEGER;
  1070. BEGIN
  1071. cmd := cmNone;
  1072. IF v^.kind = VButton THEN
  1073. IF e.mpressed THEN
  1074. v^.flags := v^.flags + vfPressed
  1075. ELSIF e.mreleased THEN
  1076. IF (v^.flags DIV vfPressed) MOD 2 = 1 THEN
  1077. cmd := v^.tag
  1078. END;
  1079. v^.flags := v^.flags / 8 * 8
  1080. END
  1081. ELSIF v^.kind = VCheck THEN
  1082. IF e.mreleased THEN
  1083. IF v^.flags DIV vfSelected MOD 2 = 1 THEN
  1084. v^.flags := v^.flags - vfSelected
  1085. ELSE
  1086. v^.flags := v^.flags + vfSelected
  1087. END;
  1088. cmd := v^.tag
  1089. END
  1090. ELSIF v^.kind = VRadio THEN
  1091. IF e.mreleased THEN cmd := v^.tag END
  1092. ELSIF v^.kind = VList THEN
  1093. IF e.mwheel # 0 THEN
  1094. IF (e.mwheel < 0) AND (v^.pos > 0) THEN v^.pos := v^.pos - 1 END;
  1095. IF (e.mwheel > 0) AND (v^.list # NIL) AND
  1096. (v^.pos + (v^.rect.h - 2) < v^.list^.count) THEN
  1097. v^.pos := v^.pos + 1
  1098. END
  1099. ELSIF e.mreleased THEN
  1100. rel := e.my - v^.rect.y - 1;
  1101. IF (rel >= 0) AND (v^.list # NIL) AND
  1102. (v^.pos + rel < v^.list^.count) THEN
  1103. v^.value := v^.pos + rel
  1104. END
  1105. END
  1106. ELSIF v^.kind = VInput THEN
  1107. IF e.mpressed THEN
  1108. rel := e.mx - v^.rect.x;
  1109. IF rel < 0 THEN rel := 0 END;
  1110. IF rel > v^.len THEN rel := v^.len END;
  1111. v^.pos := rel
  1112. END
  1113. ELSIF v^.kind = VScroll THEN
  1114. IF e.mpressed OR e.mreleased THEN
  1115. IF v^.rect.h > 1 THEN
  1116. v^.value := ((e.my - v^.rect.y) * v^.maxv) DIV (v^.rect.h - 1)
  1117. END;
  1118. IF v^.value < 0 THEN v^.value := 0 END;
  1119. IF v^.value > v^.maxv THEN v^.value := v^.maxv END
  1120. END
  1121. END;
  1122. IF cmd # cmNone THEN lastSender := v END;
  1123. RETURN cmd
  1124. END WidgetMouse;
  1125. PROCEDURE HandleWindow(v : ViewPtr; VAR e : Event) : INTEGER;
  1126. VAR w : ViewPtr; nx, ny : INTEGER;
  1127. BEGIN
  1128. IF (e.kind = evMouse) AND e.mpressed THEN
  1129. (* close box? *)
  1130. IF (e.my = v^.rect.y) AND
  1131. (e.mx >= v^.rect.x + v^.rect.w - 3) AND (e.mx < v^.rect.x + v^.rect.w) THEN
  1132. lastSender := v;
  1133. RETURN cmClose
  1134. END;
  1135. (* drag by the title row *)
  1136. IF (e.my = v^.rect.y) AND (e.mx < v^.rect.x + v^.rect.w - 3) THEN
  1137. dragWin := v;
  1138. dragOX := e.mx - v^.rect.x;
  1139. dragOY := e.my - v^.rect.y
  1140. END
  1141. ELSIF (e.kind = evMouse) AND e.mreleased THEN
  1142. dragWin := NIL
  1143. ELSIF (e.kind = evMouse) AND (e.mbtn > 0) AND (NOT e.mpressed) AND
  1144. (NOT e.mreleased) THEN
  1145. IF dragWin # NIL THEN
  1146. nx := e.mx - dragOX;
  1147. ny := e.my - dragOY;
  1148. (* keep the window's title corner on screen *)
  1149. IF nx < 0 THEN nx := 0 END;
  1150. IF nx > cols - 6 THEN nx := cols - 6 END;
  1151. IF ny < 0 THEN ny := 0 END;
  1152. IF ny > rows - 1 THEN ny := rows - 1 END;
  1153. MoveViewBy(dragWin, nx - dragWin^.rect.x, ny - dragWin^.rect.y)
  1154. END
  1155. END;
  1156. RETURN cmNone
  1157. END HandleWindow;
  1158. PROCEDURE DispatchMouse(v : ViewPtr; VAR e : Event) : INTEGER;
  1159. VAR hit : ViewPtr; cmd : INTEGER; w : ViewPtr;
  1160. BEGIN
  1161. cmd := cmNone;
  1162. hit := HitTest(v, e.mx, e.my);
  1163. IF hit = NIL THEN RETURN cmNone END;
  1164. IF hit # v THEN
  1165. w := WindowAncestor(hit);
  1166. IF (w # NIL) AND e.mpressed THEN BringToFront(w) END
  1167. END;
  1168. IF e.mpressed AND IsFocusable(hit) THEN FocusView(hit) END;
  1169. IF hit^.kind = VWindow THEN
  1170. cmd := HandleWindow(hit, e)
  1171. ELSE
  1172. cmd := WidgetMouse(hit, e)
  1173. END;
  1174. RETURN cmd
  1175. END DispatchMouse;
  1176. PROCEDURE DispatchKey(v : ViewPtr; VAR e : Event) : INTEGER;
  1177. VAR cmd : INTEGER; f : ViewPtr;
  1178. BEGIN
  1179. cmd := cmNone;
  1180. IF (v^.kind = VGroup) OR (v^.kind = VWindow) THEN
  1181. f := v^.focus;
  1182. IF f # NIL THEN cmd := DispatchKey(f, e) END
  1183. ELSE
  1184. cmd := WidgetKey(v, e)
  1185. END;
  1186. RETURN cmd
  1187. END DispatchKey;
  1188. PROCEDURE HandleEvent (e : Event) : INTEGER;
  1189. VAR cmd : INTEGER; root : ViewPtr;
  1190. BEGIN
  1191. IF e.kind = evNone THEN RETURN cmNone END;
  1192. root := modal;
  1193. IF root = NIL THEN root := desk END;
  1194. IF root = NIL THEN RETURN cmNone END;
  1195. IF e.kind = evKey THEN
  1196. IF e.key = kCtrlC THEN RETURN cmClose END;
  1197. IF e.key = kTab THEN
  1198. FocusNext(e.ctrl);
  1199. RETURN cmNone
  1200. END;
  1201. cmd := DispatchKey(root, e)
  1202. ELSE
  1203. (* while a window is being dragged, keep sending motion/release to it
  1204. even if the cursor has left the window (otherwise dragging up and
  1205. sideways stops as soon as the pointer leaves the frame) *)
  1206. IF dragWin # NIL THEN
  1207. cmd := HandleWindow(dragWin, e)
  1208. ELSE
  1209. cmd := DispatchMouse(root, e)
  1210. END
  1211. END;
  1212. RETURN cmd
  1213. END HandleEvent;
  1214. PROCEDURE Sender () : ViewPtr;
  1215. BEGIN RETURN lastSender END Sender;
  1216. (*========================================================================*)
  1217. (* Modal message box *)
  1218. (*========================================================================*)
  1219. PROCEDURE Message (title : ARRAY OF CHAR; text : ARRAY OF CHAR);
  1220. VAR w, t, b : ViewPtr; e : Event;
  1221. tw, ww, wx, wy : INTEGER; cmd : INTEGER; done : BOOLEAN;
  1222. BEGIN
  1223. tw := TextLen(text);
  1224. ww := tw + 6;
  1225. IF ww < 24 THEN ww := 24 END;
  1226. IF ww > cols - 4 THEN ww := cols - 4 END;
  1227. wx := (cols - ww) DIV 2; wy := (rows - 7) DIV 2;
  1228. w := NewView(VWindow, wx, wy, ww, 7);
  1229. SetText(w, title);
  1230. t := NewView(VText, wx + 2, wy + 2, ww - 4, 1);
  1231. SetText(t, text);
  1232. AddView(w, t);
  1233. b := NewView(VButton, wx + (ww - 8) DIV 2, wy + 4, 8, 1);
  1234. SetText(b, "OK");
  1235. SetTag(b, cmOK);
  1236. AddView(w, b);
  1237. AddView(desk, w);
  1238. BringToFront(w);
  1239. FocusView(b);
  1240. modal := w;
  1241. done := FALSE;
  1242. WHILE NOT done DO
  1243. Finish;
  1244. cmd := cmNone;
  1245. IF ReadEvent(e) THEN
  1246. IF e.kind = evKey THEN
  1247. IF e.key = kEsc THEN cmd := cmOK END
  1248. END;
  1249. IF cmd = cmNone THEN cmd := HandleEvent(e) END
  1250. END;
  1251. IF (cmd = cmOK) OR (cmd = cmClose) THEN done := TRUE END
  1252. END;
  1253. modal := NIL;
  1254. DelView(w)
  1255. END Message;
  1256. END tv.