tv.mod 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149
  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, signal,
  6. fopen, fgets, fputs, fputc, fclose;
  7. CONST
  8. MAXROWS = 120;
  9. MAXCOLS = 300;
  10. MAXCELLS = MAXROWS * MAXCOLS;
  11. OUTCAP = 262144;
  12. ESCc = CHR(27);
  13. TCSAFLUSH = 2; TIOCGWINSZ = 5413H;
  14. ICANON = 2; ECHO = 8; ISIG = 1; IEXTEN = 32768;
  15. ICRNL = 256; IXON = 1024;
  16. CC_OFF = 17; VMIN = 6; VTIME = 5;
  17. H_SINGLE = 2500H; V_SINGLE = 2502H;
  18. TL_SINGLE = 250CH; TR_SINGLE = 2510H; BL_SINGLE = 2514H; BR_SINGLE = 2518H;
  19. H_DOUBLE = 2550H; V_DOUBLE = 2551H;
  20. TL_DOUBLE = 2554H; TR_DOUBLE = 2557H; BL_DOUBLE = 255AH; BR_DOUBLE = 255DH;
  21. vfPressed = 8; (* internal, transient *)
  22. MAXMENUS = 8;
  23. MAXMITEMS = 16;
  24. TYPE
  25. Cell = RECORD cp : CARDINAL; attr : CARDINAL; END;
  26. BytePtr = POINTER TO BYTE;
  27. MenuItem = RECORD
  28. text : ARRAY [0..47] OF CHAR;
  29. tag : INTEGER;
  30. sep : BOOLEAN;
  31. END;
  32. MenuDef = RECORD
  33. title : ARRAY [0..31] OF CHAR;
  34. items : ARRAY [0..MAXMITEMS - 1] OF MenuItem;
  35. count : INTEGER;
  36. END;
  37. VAR
  38. cols, rows : INTEGER;
  39. cells : ARRAY [0..MAXCELLS - 1] OF Cell;
  40. prev : ARRAY [0..MAXCELLS - 1] OF Cell;
  41. prevValid : BOOLEAN;
  42. outBuf : ARRAY [0..OUTCAP - 1] OF CHAR;
  43. outLen : CARDINAL;
  44. inBuf : ARRAY [0..255] OF BYTE;
  45. inLen, inPos : INTEGER;
  46. term : ARRAY [0..63] OF BYTE;
  47. cursorX, cursorY : INTEGER;
  48. cursorShown : BOOLEAN;
  49. clipStack : ARRAY [0..15] OF Rect;
  50. clipN : INTEGER;
  51. pool : ARRAY [0..MAXVIEWS - 1] OF View;
  52. used : ARRAY [0..MAXVIEWS - 1] OF BOOLEAN;
  53. desk : ViewPtr;
  54. modal : ViewPtr;
  55. dragWin : ViewPtr;
  56. lastSender : ViewPtr;
  57. dragOX, dragOY : INTEGER;
  58. focusList : ARRAY [0..MAXVIEWS - 1] OF ViewPtr;
  59. focusCount : INTEGER;
  60. menus : ARRAY [0..MAXMENUS - 1] OF MenuDef;
  61. menuCount : INTEGER;
  62. menuOpen : INTEGER;
  63. menuSel : INTEGER;
  64. menuShown : BOOLEAN;
  65. winch : BOOLEAN;
  66. oldWinch : ADDRESS;
  67. virtual : BOOLEAN;
  68. dlg : ViewPtr;
  69. dlgW : INTEGER;
  70. dlgWX, dlgWY, dlgX, dlgY, dlgInputX, dlgBtnX, dlgBtnY : INTEGER;
  71. dlgItems : ARRAY [0..31] OF ViewPtr;
  72. dlgCount : INTEGER;
  73. (*========================================================================*)
  74. (* libc struct helpers *)
  75. (*========================================================================*)
  76. PROCEDURE GetDWord(p : ADDRESS; off : INTEGER) : CARDINAL;
  77. VAR q : BytePtr; v : CARDINAL;
  78. BEGIN
  79. q := CAST(BytePtr, ADDADR(p, off));
  80. v := VAL(CARDINAL, q^);
  81. q := CAST(BytePtr, ADDADR(q, 1)); v := v + VAL(CARDINAL, q^) * 256;
  82. q := CAST(BytePtr, ADDADR(q, 1)); v := v + VAL(CARDINAL, q^) * 65536;
  83. q := CAST(BytePtr, ADDADR(q, 1)); v := v + VAL(CARDINAL, q^) * 16777216;
  84. RETURN v
  85. END GetDWord;
  86. PROCEDURE PutDWord(p : ADDRESS; off : INTEGER; v : CARDINAL);
  87. VAR q : BytePtr; x : CARDINAL;
  88. BEGIN
  89. x := v;
  90. q := CAST(BytePtr, ADDADR(p, off)); q^ := VAL(BYTE, x MOD 256); x := x DIV 256;
  91. q := CAST(BytePtr, ADDADR(q, 1)); q^ := VAL(BYTE, x MOD 256); x := x DIV 256;
  92. q := CAST(BytePtr, ADDADR(q, 1)); q^ := VAL(BYTE, x MOD 256); x := x DIV 256;
  93. q := CAST(BytePtr, ADDADR(q, 1)); q^ := VAL(BYTE, x MOD 256)
  94. END PutDWord;
  95. PROCEDURE GetWinSize(VAR r, c : INTEGER);
  96. VAR ws : ARRAY [0..7] OF BYTE;
  97. BEGIN
  98. IF ioctl(1, TIOCGWINSZ, ADR(ws)) = 0 THEN
  99. r := VAL(INTEGER, ws[0]) + VAL(INTEGER, ws[1]) * 256;
  100. c := VAL(INTEGER, ws[2]) + VAL(INTEGER, ws[3]) * 256
  101. ELSE
  102. r := 24; c := 80
  103. END;
  104. IF r < 3 THEN r := 24 END;
  105. IF c < 20 THEN c := 80 END;
  106. IF r > MAXROWS THEN r := MAXROWS END;
  107. IF c > MAXCOLS THEN c := MAXCOLS END
  108. END GetWinSize;
  109. (*========================================================================*)
  110. (* Output buffer + escape helpers *)
  111. (*========================================================================*)
  112. PROCEDURE OutCh(c : CHAR);
  113. BEGIN
  114. IF outLen < OUTCAP THEN
  115. outBuf[outLen] := c;
  116. outLen := outLen + 1
  117. END
  118. END OutCh;
  119. PROCEDURE OutStr(s : ARRAY OF CHAR);
  120. VAR i : INTEGER;
  121. BEGIN
  122. i := 0;
  123. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (s[i] # 0C) DO
  124. OutCh(s[i]);
  125. i := i + 1
  126. END
  127. END OutStr;
  128. PROCEDURE OutInt(n : INTEGER);
  129. VAR buf : ARRAY [0..15] OF CHAR;
  130. BEGIN
  131. IntToText(buf, n, 0);
  132. OutStr(buf)
  133. END OutInt;
  134. PROCEDURE OutCP(cp : CARDINAL);
  135. BEGIN
  136. IF cp < 80H THEN
  137. OutCh(CHR(cp))
  138. ELSIF cp < 800H THEN
  139. OutCh(CHR(0C0H + cp DIV 64));
  140. OutCh(CHR(80H + cp MOD 64))
  141. ELSIF cp < 10000H THEN
  142. OutCh(CHR(0E0H + cp DIV 4096));
  143. OutCh(CHR(80H + (cp DIV 64) MOD 64));
  144. OutCh(CHR(80H + cp MOD 64))
  145. ELSE
  146. OutCh(CHR(0F0H + cp DIV 262144));
  147. OutCh(CHR(80H + (cp DIV 4096) MOD 64));
  148. OutCh(CHR(80H + (cp DIV 64) MOD 64));
  149. OutCh(CHR(80H + cp MOD 64))
  150. END
  151. END OutCP;
  152. PROCEDURE EmitSGR(attr : Attr);
  153. VAR fg, bg, bl : INTEGER;
  154. BEGIN
  155. fg := VAL(INTEGER, attr MOD 16);
  156. bg := VAL(INTEGER, (attr DIV 16) MOD 16);
  157. bl := VAL(INTEGER, (attr DIV 256) MOD 2);
  158. OutCh(ESCc); OutCh('[');
  159. IF fg < 8 THEN OutInt(30 + fg) ELSE OutInt(90 + fg - 8) END;
  160. OutCh(';');
  161. IF bg < 8 THEN OutInt(40 + bg) ELSE OutInt(100 + bg - 8) END;
  162. (* blink is a mode: always emit it explicitly (5 = on, 25 = off) so that
  163. it does not leak into subsequent cells *)
  164. IF bl # 0 THEN OutStr(";5") ELSE OutStr(";25") END;
  165. OutCh('m')
  166. END EmitSGR;
  167. PROCEDURE EmitMove(x, y : INTEGER);
  168. BEGIN
  169. OutCh(ESCc); OutCh('[');
  170. OutInt(y + 1); OutCh(';'); OutInt(x + 1); OutCh('H')
  171. END EmitMove;
  172. PROCEDURE FlushOut;
  173. BEGIN
  174. IF outLen > 0 THEN
  175. IF write(1, ADR(outBuf), outLen) < 0 THEN END;
  176. outLen := 0
  177. END
  178. END FlushOut;
  179. (*========================================================================*)
  180. (* Terminal setup *)
  181. (*========================================================================*)
  182. PROCEDURE OnWinch (sig : INTEGER);
  183. BEGIN
  184. winch := TRUE
  185. END OnWinch;
  186. PROCEDURE CommonInit;
  187. VAR i : INTEGER;
  188. BEGIN
  189. prevValid := FALSE;
  190. inLen := 0; inPos := 0;
  191. cursorX := 0; cursorY := 0; cursorShown := FALSE;
  192. clipN := 0;
  193. modal := NIL; dragWin := NIL; lastSender := NIL;
  194. menuCount := 0; menuOpen := -1; menuSel := 0; menuShown := FALSE;
  195. winch := FALSE;
  196. dlg := NIL; dlgCount := 0;
  197. FOR i := 0 TO MAXVIEWS - 1 DO used[i] := FALSE END;
  198. desk := NIL;
  199. Clear(A(LightGray, Blue, FALSE));
  200. desk := NewView(VGroup, 0, 0, cols, rows)
  201. END CommonInit;
  202. PROCEDURE Init() : BOOLEAN;
  203. VAR lf, iff : CARDINAL;
  204. BEGIN
  205. IF tcgetattr(0, ADR(term)) # 0 THEN RETURN FALSE END;
  206. lf := GetDWord(ADR(term), 12);
  207. lf := VAL(CARDINAL, CAST(BITSET, lf) *
  208. CAST(BITSET, MAX(CARDINAL) - (ICANON + ECHO + ISIG + IEXTEN)));
  209. PutDWord(ADR(term), 12, lf);
  210. iff := GetDWord(ADR(term), 0);
  211. iff := VAL(CARDINAL, CAST(BITSET, iff) *
  212. CAST(BITSET, MAX(CARDINAL) - (ICRNL + IXON)));
  213. PutDWord(ADR(term), 0, iff);
  214. term[CC_OFF + VMIN] := VAL(BYTE, 0);
  215. term[CC_OFF + VTIME] := VAL(BYTE, 1);
  216. IF tcsetattr(0, TCSAFLUSH, ADR(term)) # 0 THEN RETURN FALSE END;
  217. GetWinSize(rows, cols);
  218. oldWinch := signal(28, ADR(OnWinch));
  219. virtual := FALSE;
  220. CommonInit;
  221. OutCh(ESCc); OutStr("[?1049h");
  222. OutCh(ESCc); OutStr("[2J");
  223. OutCh(ESCc); OutStr("[?1002h"); (* button/mouse-drag reporting *)
  224. OutCh(ESCc); OutStr("[?1006h"); (* SGR mouse encoding *)
  225. OutCh(ESCc); OutStr("[?25l");
  226. FlushOut;
  227. Present;
  228. RETURN TRUE
  229. END Init;
  230. PROCEDURE InitVirtual (c, r : INTEGER) : BOOLEAN;
  231. BEGIN
  232. IF (c < 10) OR (r < 3) THEN RETURN FALSE END;
  233. IF c > MAXCOLS THEN c := MAXCOLS END;
  234. IF r > MAXROWS THEN r := MAXROWS END;
  235. cols := c; rows := r;
  236. virtual := TRUE;
  237. CommonInit;
  238. RETURN TRUE
  239. END InitVirtual;
  240. PROCEDURE Done;
  241. BEGIN
  242. OutCh(ESCc); OutStr("[?1006l");
  243. OutCh(ESCc); OutStr("[?1002l");
  244. OutCh(ESCc); OutStr("[0m");
  245. OutCh(ESCc); OutStr("[?25h");
  246. OutCh(ESCc); OutStr("[?1049l");
  247. FlushOut;
  248. IF tcsetattr(0, TCSAFLUSH, ADR(term)) # 0 THEN END
  249. END Done;
  250. PROCEDURE Cols() : INTEGER;
  251. BEGIN RETURN cols END Cols;
  252. PROCEDURE Rows() : INTEGER;
  253. BEGIN RETURN rows END Rows;
  254. PROCEDURE Resized() : BOOLEAN;
  255. VAR r, c : INTEGER; changed : BOOLEAN;
  256. BEGIN
  257. GetWinSize(r, c);
  258. changed := winch OR (r # rows) OR (c # cols);
  259. winch := FALSE;
  260. IF changed THEN
  261. rows := r; cols := c;
  262. prevValid := FALSE;
  263. Clear(A(LightGray, Blue, FALSE));
  264. RETURN TRUE
  265. END;
  266. RETURN FALSE
  267. END Resized;
  268. (*========================================================================*)
  269. (* Drawing primitives (with a clip stack) *)
  270. (*========================================================================*)
  271. PROCEDURE A (fg, bg : INTEGER; blink : BOOLEAN) : Attr;
  272. BEGIN
  273. RETURN VAL(CARDINAL, fg) + VAL(CARDINAL, bg) * 16 +
  274. VAL(CARDINAL, blink) * 256
  275. END A;
  276. PROCEDURE Idx(x, y : INTEGER) : INTEGER;
  277. BEGIN RETURN y * cols + x END Idx;
  278. PROCEDURE CellAt (x, y : INTEGER; VAR cp, attr : CARDINAL);
  279. BEGIN
  280. IF (x >= 0) AND (x < cols) AND (y >= 0) AND (y < rows) THEN
  281. cp := cells[Idx(x, y)].cp;
  282. attr := cells[Idx(x, y)].attr
  283. ELSE
  284. cp := 0; attr := 0
  285. END
  286. END CellAt;
  287. PROCEDURE InClip(x, y : INTEGER) : BOOLEAN;
  288. BEGIN
  289. IF (x < 0) OR (x >= cols) OR (y < 0) OR (y >= rows) THEN RETURN FALSE END;
  290. IF clipN = 0 THEN RETURN TRUE END;
  291. RETURN (x >= clipStack[clipN - 1].x) AND
  292. (x < clipStack[clipN - 1].x + clipStack[clipN - 1].w) AND
  293. (y >= clipStack[clipN - 1].y) AND
  294. (y < clipStack[clipN - 1].y + clipStack[clipN - 1].h)
  295. END InClip;
  296. PROCEDURE PushClipRect(r : Rect);
  297. VAR cur : Rect; i1, j1, i2, j2 : INTEGER;
  298. BEGIN
  299. IF clipN = 0 THEN
  300. cur.x := 0; cur.y := 0; cur.w := cols; cur.h := rows
  301. ELSE
  302. cur := clipStack[clipN - 1]
  303. END;
  304. i1 := r.x; IF i1 < cur.x THEN i1 := cur.x END;
  305. j1 := r.y; IF j1 < cur.y THEN j1 := cur.y END;
  306. i2 := r.x + r.w; IF i2 > cur.x + cur.w THEN i2 := cur.x + cur.w END;
  307. j2 := r.y + r.h; IF j2 > cur.y + cur.h THEN j2 := cur.y + cur.h END;
  308. IF i2 < i1 THEN i2 := i1 END;
  309. IF j2 < j1 THEN j2 := j1 END;
  310. IF clipN < 16 THEN
  311. clipStack[clipN].x := i1; clipStack[clipN].y := j1;
  312. clipStack[clipN].w := i2 - i1; clipStack[clipN].h := j2 - j1;
  313. clipN := clipN + 1
  314. END
  315. END PushClipRect;
  316. PROCEDURE PopClip;
  317. BEGIN
  318. IF clipN > 0 THEN clipN := clipN - 1 END
  319. END PopClip;
  320. PROCEDURE DrawCh(x, y : INTEGER; cp : CARDINAL; attr : Attr);
  321. BEGIN
  322. IF InClip(x, y) THEN
  323. cells[Idx(x, y)].cp := cp;
  324. cells[Idx(x, y)].attr := attr
  325. END
  326. END DrawCh;
  327. PROCEDURE Fill(x, y, w, h : INTEGER; cp : CARDINAL; attr : Attr);
  328. VAR i, j : INTEGER;
  329. BEGIN
  330. FOR j := y TO y + h - 1 DO
  331. FOR i := x TO x + w - 1 DO
  332. DrawCh(i, j, cp, attr)
  333. END
  334. END
  335. END Fill;
  336. PROCEDURE Clear(attr : Attr);
  337. BEGIN
  338. clipN := 0;
  339. Fill(0, 0, cols, rows, VAL(CARDINAL, ORD(' ')), attr)
  340. END Clear;
  341. PROCEDURE DrawText(x, y : INTEGER; s : ARRAY OF CHAR; attr : Attr);
  342. VAR i : INTEGER;
  343. BEGIN
  344. i := 0;
  345. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (s[i] # 0C) DO
  346. DrawCh(x + i, y, VAL(CARDINAL, ORD(s[i])), attr);
  347. i := i + 1
  348. END
  349. END DrawText;
  350. PROCEDURE DrawHLine(x, y, w : INTEGER; cp : CARDINAL; attr : Attr);
  351. VAR i : INTEGER;
  352. BEGIN
  353. FOR i := x TO x + w - 1 DO DrawCh(i, y, cp, attr) END
  354. END DrawHLine;
  355. PROCEDURE DrawVLine(x, y, h : INTEGER; cp : CARDINAL; attr : Attr);
  356. VAR i : INTEGER;
  357. BEGIN
  358. FOR i := y TO y + h - 1 DO DrawCh(x, i, cp, attr) END
  359. END DrawVLine;
  360. PROCEDURE DrawBox(r : Rect; style : INTEGER; attr : Attr);
  361. VAR hz, vt, tl, tr, bl, br : CARDINAL;
  362. x2, y2 : INTEGER;
  363. BEGIN
  364. IF (r.w < 2) OR (r.h < 2) THEN RETURN END;
  365. IF style = DoubleFrame THEN
  366. hz := H_DOUBLE; vt := V_DOUBLE;
  367. tl := TL_DOUBLE; tr := TR_DOUBLE; bl := BL_DOUBLE; br := BR_DOUBLE
  368. ELSE
  369. hz := H_SINGLE; vt := V_SINGLE;
  370. tl := TL_SINGLE; tr := TR_SINGLE; bl := BL_SINGLE; br := BR_SINGLE
  371. END;
  372. x2 := r.x + r.w - 1;
  373. y2 := r.y + r.h - 1;
  374. DrawHLine(r.x + 1, r.y, r.w - 2, hz, attr);
  375. DrawHLine(r.x + 1, y2, r.w - 2, hz, attr);
  376. DrawVLine(r.x, r.y + 1, r.h - 2, vt, attr);
  377. DrawVLine(x2, r.y + 1, r.h - 2, vt, attr);
  378. DrawCh(r.x, r.y, tl, attr);
  379. DrawCh(x2, r.y, tr, attr);
  380. DrawCh(r.x, y2, bl, attr);
  381. DrawCh(x2, y2, br, attr)
  382. END DrawBox;
  383. PROCEDURE DrawShadow(r : Rect; attr : Attr);
  384. BEGIN
  385. Fill(r.x + 2, r.y + r.h, r.w - 1, 1, VAL(CARDINAL, ORD(' ')), attr);
  386. Fill(r.x + r.w, r.y + 1, 1, r.h, VAL(CARDINAL, ORD(' ')), attr)
  387. END DrawShadow;
  388. PROCEDURE SetCursor(x, y : INTEGER);
  389. BEGIN cursorX := x; cursorY := y; cursorShown := TRUE END SetCursor;
  390. PROCEDURE HideCursor;
  391. BEGIN cursorShown := FALSE END HideCursor;
  392. PROCEDURE Present;
  393. VAR x, y, i, outX, outY : INTEGER;
  394. outAttr : CARDINAL;
  395. c : Cell;
  396. BEGIN
  397. outLen := 0;
  398. OutCh(ESCc); OutStr("[?25l");
  399. outX := -1; outY := -1; outAttr := MAX(CARDINAL);
  400. FOR y := 0 TO rows - 1 DO
  401. FOR x := 0 TO cols - 1 DO
  402. i := Idx(x, y);
  403. c := cells[i];
  404. IF (NOT prevValid) OR (c.cp # prev[i].cp) OR (c.attr # prev[i].attr) THEN
  405. IF (x # outX) OR (y # outY) THEN
  406. EmitMove(x, y);
  407. outX := x; outY := y
  408. END;
  409. IF c.attr # outAttr THEN
  410. EmitSGR(c.attr);
  411. outAttr := c.attr
  412. END;
  413. OutCP(c.cp);
  414. outX := outX + 1
  415. END;
  416. prev[i] := c
  417. END
  418. END;
  419. prevValid := TRUE;
  420. OutCh(ESCc); OutStr("[0m");
  421. IF cursorShown AND (cursorX >= 0) AND (cursorX < cols) AND
  422. (cursorY >= 0) AND (cursorY < rows) THEN
  423. OutCh(ESCc); OutStr("[?25h");
  424. EmitMove(cursorX, cursorY)
  425. ELSE
  426. OutCh(ESCc); OutStr("[?25l")
  427. END;
  428. FlushOut
  429. END Present;
  430. (*========================================================================*)
  431. (* Text helpers *)
  432. (*========================================================================*)
  433. PROCEDURE TextLen (s : ARRAY OF CHAR) : INTEGER;
  434. VAR i : INTEGER;
  435. BEGIN
  436. i := 0;
  437. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (s[i] # 0C) DO i := i + 1 END;
  438. RETURN i
  439. END TextLen;
  440. PROCEDURE CopyText (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR);
  441. VAR i : INTEGER;
  442. BEGIN
  443. i := 0;
  444. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (i < VAL(INTEGER, HIGH(dest))) AND (s[i] # 0C) DO
  445. dest[i] := s[i];
  446. i := i + 1
  447. END;
  448. dest[i] := 0C
  449. END CopyText;
  450. PROCEDURE AppendText (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR);
  451. VAR i, j : INTEGER;
  452. BEGIN
  453. i := TextLen(dest);
  454. j := 0;
  455. WHILE (j <= VAL(INTEGER, HIGH(s))) AND (i < VAL(INTEGER, HIGH(dest))) AND (s[j] # 0C) DO
  456. dest[i] := s[j];
  457. i := i + 1;
  458. j := j + 1
  459. END;
  460. dest[i] := 0C
  461. END AppendText;
  462. PROCEDURE PadText (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR; width : INTEGER);
  463. VAR i, n : INTEGER;
  464. BEGIN
  465. n := TextLen(s);
  466. IF n > width THEN n := width END;
  467. FOR i := 0 TO width - 1 DO
  468. IF i < n THEN dest[i] := s[i] ELSE dest[i] := ' ' END
  469. END;
  470. dest[width] := 0C
  471. END PadText;
  472. PROCEDURE IntToText (VAR dest : ARRAY OF CHAR; n : INTEGER; width : INTEGER);
  473. VAR tmp : ARRAY [0..15] OF CHAR;
  474. k, i, p, v : INTEGER;
  475. neg : BOOLEAN;
  476. BEGIN
  477. k := 0; neg := FALSE; v := n;
  478. IF v < 0 THEN neg := TRUE; v := 0 - v END;
  479. IF v = 0 THEN
  480. tmp[0] := '0'; k := 1
  481. ELSE
  482. WHILE v > 0 DO
  483. tmp[k] := CHR(ORD('0') + VAL(CARDINAL, v MOD 10));
  484. k := k + 1;
  485. v := v DIV 10
  486. END
  487. END;
  488. p := 0;
  489. IF neg THEN dest[p] := '-'; p := p + 1 END;
  490. i := k;
  491. WHILE i > 0 DO
  492. i := i - 1;
  493. dest[p] := tmp[i];
  494. p := p + 1
  495. END;
  496. WHILE (width > 0) AND (p < width) DO
  497. dest[p] := ' ';
  498. p := p + 1
  499. END;
  500. dest[p] := 0C
  501. END IntToText;
  502. (*========================================================================*)
  503. (* Input *)
  504. (*========================================================================*)
  505. PROCEDURE FillIn;
  506. BEGIN
  507. IF inPos >= inLen THEN
  508. inLen := read(0, ADR(inBuf), 64);
  509. IF inLen < 0 THEN inLen := 0 END;
  510. inPos := 0
  511. END
  512. END FillIn;
  513. PROCEDURE PeekByte(VAR ok : BOOLEAN) : CARDINAL;
  514. BEGIN
  515. FillIn;
  516. IF inPos >= inLen THEN ok := FALSE; RETURN 0 END;
  517. ok := TRUE;
  518. RETURN VAL(CARDINAL, inBuf[inPos])
  519. END PeekByte;
  520. PROCEDURE TakeByte;
  521. BEGIN
  522. IF inPos < inLen THEN inPos := inPos + 1 END
  523. END TakeByte;
  524. PROCEDURE ReadEvent (VAR e : Event) : BOOLEAN;
  525. VAR b, b2, c1, c2, c3, cp, num : CARDINAL;
  526. ok : BOOLEAN;
  527. BEGIN
  528. e.kind := evNone; e.key := kNone; e.ch := 0; e.ctrl := FALSE;
  529. e.mpressed := FALSE; e.mreleased := FALSE; e.mwheel := 0;
  530. FillIn;
  531. IF inPos >= inLen THEN RETURN FALSE END;
  532. b := PeekByte(ok); TakeByte;
  533. IF b = 1BH THEN
  534. b2 := PeekByte(ok);
  535. IF NOT ok THEN e.kind := evKey; e.key := kEsc; RETURN TRUE END;
  536. IF b2 = VAL(CARDINAL, ORD('[')) THEN
  537. TakeByte;
  538. b2 := PeekByte(ok);
  539. IF NOT ok THEN e.kind := evKey; e.key := kEsc; RETURN TRUE END;
  540. IF b2 = VAL(CARDINAL, ORD('<')) THEN
  541. (* SGR mouse: ESC [ < b ; x ; y M/m *)
  542. TakeByte;
  543. num := 0;
  544. b2 := PeekByte(ok);
  545. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  546. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  547. num := num * 10 + (b2 - VAL(CARDINAL, ORD('0')));
  548. TakeByte; b2 := PeekByte(ok)
  549. END;
  550. IF ok THEN TakeByte END; (* ; *)
  551. e.mx := 0;
  552. b2 := PeekByte(ok);
  553. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  554. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  555. e.mx := e.mx * 10 + VAL(INTEGER, b2 - VAL(CARDINAL, ORD('0')));
  556. TakeByte; b2 := PeekByte(ok)
  557. END;
  558. IF ok THEN TakeByte END; (* ; *)
  559. e.my := 0;
  560. b2 := PeekByte(ok);
  561. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  562. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  563. e.my := e.my * 10 + VAL(INTEGER, b2 - VAL(CARDINAL, ORD('0')));
  564. TakeByte; b2 := PeekByte(ok)
  565. END;
  566. IF ok THEN
  567. IF b2 = VAL(CARDINAL, ORD('M')) THEN
  568. IF (num >= 64) AND (num <= 65) THEN
  569. e.mwheel := 1
  570. ELSIF (num >= 128) AND (num <= 129) THEN
  571. e.mwheel := -1
  572. ELSIF num >= 32 THEN
  573. e.mbtn := VAL(INTEGER, num - 32) + 1
  574. ELSE
  575. e.mbtn := VAL(INTEGER, num) + 1;
  576. e.mpressed := TRUE
  577. END
  578. ELSE
  579. e.mbtn := VAL(INTEGER, num) + 1;
  580. e.mreleased := TRUE
  581. END;
  582. TakeByte
  583. END;
  584. e.mx := e.mx - 1; e.my := e.my - 1;
  585. e.kind := evMouse;
  586. RETURN TRUE
  587. ELSIF (b2 >= VAL(CARDINAL, ORD('0'))) AND (b2 <= VAL(CARDINAL, ORD('9'))) THEN
  588. num := 0;
  589. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  590. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  591. num := num * 10 + (b2 - VAL(CARDINAL, ORD('0')));
  592. TakeByte; b2 := PeekByte(ok)
  593. END;
  594. IF NOT ok THEN e.kind := evKey; e.key := kEsc; RETURN TRUE END;
  595. WHILE ok AND (b2 = VAL(CARDINAL, ORD(';'))) DO
  596. TakeByte; b2 := PeekByte(ok);
  597. WHILE ok AND (b2 >= VAL(CARDINAL, ORD('0'))) AND
  598. (b2 <= VAL(CARDINAL, ORD('9'))) DO
  599. TakeByte; b2 := PeekByte(ok)
  600. END
  601. END;
  602. IF ok THEN TakeByte END;
  603. e.kind := evKey;
  604. IF b2 = VAL(CARDINAL, ORD('~')) THEN
  605. IF num = 1 THEN e.key := kHome
  606. ELSIF num = 2 THEN e.key := kIns
  607. ELSIF num = 3 THEN e.key := kDel
  608. ELSIF num = 4 THEN e.key := kEnd
  609. ELSIF num = 5 THEN e.key := kPgUp
  610. ELSIF num = 6 THEN e.key := kPgDn
  611. ELSIF num = 15 THEN e.key := kF5
  612. ELSIF num = 17 THEN e.key := kF6
  613. ELSIF num = 18 THEN e.key := kF7
  614. ELSIF num = 19 THEN e.key := kF8
  615. ELSIF num = 20 THEN e.key := kF9
  616. ELSIF num = 21 THEN e.key := kF10
  617. ELSIF num = 23 THEN e.key := kF11
  618. ELSIF num = 24 THEN e.key := kF12
  619. ELSE e.key := kNone
  620. END
  621. ELSIF b2 = VAL(CARDINAL, ORD('A')) THEN e.key := kUp
  622. ELSIF b2 = VAL(CARDINAL, ORD('B')) THEN e.key := kDown
  623. ELSIF b2 = VAL(CARDINAL, ORD('C')) THEN e.key := kRight
  624. ELSIF b2 = VAL(CARDINAL, ORD('D')) THEN e.key := kLeft
  625. ELSIF b2 = VAL(CARDINAL, ORD('H')) THEN e.key := kHome
  626. ELSIF b2 = VAL(CARDINAL, ORD('F')) THEN e.key := kEnd
  627. ELSIF b2 = VAL(CARDINAL, ORD('Z')) THEN e.key := kTab; e.ctrl := TRUE
  628. ELSIF b2 = VAL(CARDINAL, ORD('P')) THEN e.key := kF1
  629. ELSIF b2 = VAL(CARDINAL, ORD('Q')) THEN e.key := kF2
  630. ELSIF b2 = VAL(CARDINAL, ORD('R')) THEN e.key := kF3
  631. ELSIF b2 = VAL(CARDINAL, ORD('S')) THEN e.key := kF4
  632. ELSE e.key := kNone
  633. END;
  634. RETURN TRUE
  635. ELSE
  636. TakeByte;
  637. e.kind := evKey;
  638. IF b2 = VAL(CARDINAL, ORD('A')) THEN e.key := kUp
  639. ELSIF b2 = VAL(CARDINAL, ORD('B')) THEN e.key := kDown
  640. ELSIF b2 = VAL(CARDINAL, ORD('C')) THEN e.key := kRight
  641. ELSIF b2 = VAL(CARDINAL, ORD('D')) THEN e.key := kLeft
  642. ELSIF b2 = VAL(CARDINAL, ORD('H')) THEN e.key := kHome
  643. ELSIF b2 = VAL(CARDINAL, ORD('F')) THEN e.key := kEnd
  644. ELSIF b2 = VAL(CARDINAL, ORD('Z')) THEN e.key := kTab; e.ctrl := TRUE
  645. ELSE e.key := kNone
  646. END;
  647. RETURN TRUE
  648. END
  649. ELSIF b2 = VAL(CARDINAL, ORD('O')) THEN
  650. TakeByte;
  651. b2 := PeekByte(ok);
  652. IF ok THEN TakeByte END;
  653. e.kind := evKey;
  654. IF b2 = VAL(CARDINAL, ORD('P')) THEN e.key := kF1
  655. ELSIF b2 = VAL(CARDINAL, ORD('Q')) THEN e.key := kF2
  656. ELSIF b2 = VAL(CARDINAL, ORD('R')) THEN e.key := kF3
  657. ELSIF b2 = VAL(CARDINAL, ORD('S')) THEN e.key := kF4
  658. ELSIF b2 = VAL(CARDINAL, ORD('A')) THEN e.key := kUp
  659. ELSIF b2 = VAL(CARDINAL, ORD('B')) THEN e.key := kDown
  660. ELSIF b2 = VAL(CARDINAL, ORD('C')) THEN e.key := kRight
  661. ELSIF b2 = VAL(CARDINAL, ORD('D')) THEN e.key := kLeft
  662. ELSIF b2 = VAL(CARDINAL, ORD('H')) THEN e.key := kHome
  663. ELSIF b2 = VAL(CARDINAL, ORD('F')) THEN e.key := kEnd
  664. END;
  665. RETURN TRUE
  666. ELSE
  667. e.kind := evKey; e.key := kEsc;
  668. RETURN TRUE
  669. END
  670. ELSIF (b = 0DH) OR (b = 0AH) THEN e.kind := evKey; e.key := kEnter; RETURN TRUE
  671. ELSIF b = 09H THEN e.kind := evKey; e.key := kTab; RETURN TRUE
  672. ELSIF b = 20H THEN e.kind := evKey; e.key := kSpace; RETURN TRUE
  673. ELSIF (b = 7FH) OR (b = 08H) THEN e.kind := evKey; e.key := kBack; RETURN TRUE
  674. ELSIF b = 03H THEN e.kind := evKey; e.key := kCtrlC; RETURN TRUE
  675. ELSIF b < 20H THEN e.kind := evKey; e.key := kNone; RETURN TRUE
  676. ELSIF b < 80H THEN e.kind := evKey; e.key := kChar; e.ch := b; RETURN TRUE
  677. ELSE
  678. IF b < 0E0H THEN
  679. c1 := PeekByte(ok); IF ok THEN TakeByte ELSE c1 := 80H END;
  680. cp := (b - 0C0H) * 64 + (c1 - 80H)
  681. ELSIF b < 0F0H THEN
  682. c1 := PeekByte(ok); IF ok THEN TakeByte ELSE c1 := 80H END;
  683. c2 := PeekByte(ok); IF ok THEN TakeByte ELSE c2 := 80H END;
  684. cp := (b - 0E0H) * 4096 + (c1 - 80H) * 64 + (c2 - 80H)
  685. ELSE
  686. c1 := PeekByte(ok); IF ok THEN TakeByte ELSE c1 := 80H END;
  687. c2 := PeekByte(ok); IF ok THEN TakeByte ELSE c2 := 80H END;
  688. c3 := PeekByte(ok); IF ok THEN TakeByte ELSE c3 := 80H END;
  689. cp := (b - 0F0H) * 262144 + (c1 - 80H) * 4096 +
  690. (c2 - 80H) * 64 + (c3 - 80H)
  691. END;
  692. e.kind := evKey; e.key := kChar; e.ch := cp;
  693. RETURN TRUE
  694. END
  695. END ReadEvent;
  696. (*========================================================================*)
  697. (* View tree *)
  698. (*========================================================================*)
  699. PROCEDURE NewView (k : VKind; x, y, w, h : INTEGER) : ViewPtr;
  700. VAR i : INTEGER;
  701. BEGIN
  702. i := 0;
  703. WHILE (i < MAXVIEWS) AND used[i] DO i := i + 1 END;
  704. IF i >= MAXVIEWS THEN RETURN NIL END;
  705. used[i] := TRUE;
  706. pool[i].kind := k;
  707. pool[i].rect.x := x; pool[i].rect.y := y;
  708. pool[i].rect.w := w; pool[i].rect.h := h;
  709. pool[i].parent := NIL; pool[i].next := NIL; pool[i].prev := NIL;
  710. pool[i].first := NIL; pool[i].last := NIL; pool[i].focus := NIL;
  711. pool[i].clipKids := FALSE;
  712. pool[i].text[0] := 0C; pool[i].len := 0; pool[i].pos := 0;
  713. pool[i].value := 0; pool[i].minv := 0; pool[i].maxv := 0; pool[i].tag := 0;
  714. pool[i].flags := 0; pool[i].list := NIL; pool[i].ed := NIL;
  715. RETURN ADR(pool[i])
  716. END NewView;
  717. PROCEDURE FreeView (v : ViewPtr);
  718. VAR i : INTEGER;
  719. BEGIN
  720. IF v = NIL THEN RETURN END;
  721. i := 0;
  722. WHILE (i < MAXVIEWS) AND (ADR(pool[i]) # v) DO i := i + 1 END;
  723. IF i < MAXVIEWS THEN used[i] := FALSE END
  724. END FreeView;
  725. PROCEDURE AddView (parent, child : ViewPtr);
  726. BEGIN
  727. IF (parent = NIL) OR (child = NIL) THEN RETURN END;
  728. child^.parent := parent;
  729. child^.next := NIL;
  730. child^.prev := parent^.last;
  731. IF parent^.last # NIL THEN parent^.last^.next := child END;
  732. parent^.last := child;
  733. IF parent^.first = NIL THEN parent^.first := child END;
  734. IF parent^.kind = VWindow THEN child^.clipKids := FALSE END
  735. END AddView;
  736. PROCEDURE DelView (v : ViewPtr);
  737. VAR c, n : ViewPtr;
  738. BEGIN
  739. IF v = NIL THEN RETURN END;
  740. c := v^.first;
  741. WHILE c # NIL DO
  742. n := c^.next;
  743. DelView(c);
  744. c := n
  745. END;
  746. IF v^.prev # NIL THEN v^.prev^.next := v^.next END;
  747. IF v^.next # NIL THEN v^.next^.prev := v^.prev END;
  748. IF v^.parent # NIL THEN
  749. IF v^.parent^.first = v THEN v^.parent^.first := v^.next END;
  750. IF v^.parent^.last = v THEN v^.parent^.last := v^.prev END
  751. END;
  752. FreeView(v)
  753. END DelView;
  754. PROCEDURE SetText (v : ViewPtr; s : ARRAY OF CHAR);
  755. BEGIN
  756. IF v # NIL THEN CopyText(v^.text, s); v^.len := TextLen(v^.text) END
  757. END SetText;
  758. PROCEDURE SetTag (v : ViewPtr; t : INTEGER);
  759. BEGIN
  760. IF v # NIL THEN v^.tag := t END
  761. END SetTag;
  762. PROCEDURE Desk () : ViewPtr;
  763. BEGIN RETURN desk END Desk;
  764. PROCEDURE BringToFront (v : ViewPtr);
  765. VAR p : ViewPtr;
  766. BEGIN
  767. IF (v = NIL) OR (v^.parent = NIL) THEN RETURN END;
  768. IF v^.parent^.last = v THEN RETURN END;
  769. p := v^.parent;
  770. (* unlink *)
  771. IF v^.prev # NIL THEN v^.prev^.next := v^.next END;
  772. IF v^.next # NIL THEN v^.next^.prev := v^.prev END;
  773. IF p^.first = v THEN p^.first := v^.next END;
  774. (* append *)
  775. v^.prev := p^.last; v^.next := NIL;
  776. IF p^.last # NIL THEN p^.last^.next := v END;
  777. p^.last := v
  778. END BringToFront;
  779. PROCEDURE FocusView (v : ViewPtr);
  780. VAR p : ViewPtr;
  781. BEGIN
  782. p := v;
  783. WHILE (p # NIL) AND (p^.parent # NIL) DO
  784. p^.parent^.focus := p;
  785. p := p^.parent
  786. END
  787. END FocusView;
  788. PROCEDURE IsFocusable(v : ViewPtr) : BOOLEAN;
  789. BEGIN
  790. RETURN (v # NIL) AND ((v^.flags DIV vfDisabled) MOD 2 = 0) AND
  791. ((v^.kind = VInput) OR (v^.kind = VButton) OR (v^.kind = VCheck) OR
  792. (v^.kind = VRadio) OR (v^.kind = VList) OR (v^.kind = VScroll) OR
  793. (v^.kind = VEditor))
  794. END IsFocusable;
  795. PROCEDURE CollectFocus(v : ViewPtr);
  796. VAR c : ViewPtr;
  797. BEGIN
  798. IF v = NIL THEN RETURN END;
  799. IF IsFocusable(v) THEN
  800. IF focusCount < MAXVIEWS THEN
  801. focusList[focusCount] := v;
  802. focusCount := focusCount + 1
  803. END
  804. END;
  805. c := v^.first;
  806. WHILE c # NIL DO
  807. CollectFocus(c);
  808. c := c^.next
  809. END
  810. END CollectFocus;
  811. PROCEDURE CurrentFocus() : ViewPtr;
  812. VAR v : ViewPtr;
  813. BEGIN
  814. v := desk;
  815. WHILE (v # NIL) AND (v^.focus # NIL) DO v := v^.focus END;
  816. RETURN v
  817. END CurrentFocus;
  818. PROCEDURE FocusNext (backwards : BOOLEAN);
  819. VAR cur, nxt : ViewPtr; i, idx : INTEGER;
  820. BEGIN
  821. focusCount := 0;
  822. CollectFocus(desk);
  823. IF focusCount = 0 THEN RETURN END;
  824. cur := CurrentFocus();
  825. idx := -1;
  826. FOR i := 0 TO focusCount - 1 DO
  827. IF focusList[i] = cur THEN idx := i END
  828. END;
  829. IF idx < 0 THEN
  830. nxt := focusList[0]
  831. ELSIF backwards THEN
  832. nxt := focusList[(idx + focusCount - 1) MOD focusCount]
  833. ELSE
  834. nxt := focusList[(idx + 1) MOD focusCount]
  835. END;
  836. FocusView(nxt)
  837. END FocusNext;
  838. PROCEDURE MoveViewBy (v : ViewPtr; dx, dy : INTEGER);
  839. VAR c : ViewPtr;
  840. BEGIN
  841. IF v = NIL THEN RETURN END;
  842. v^.rect.x := v^.rect.x + dx;
  843. v^.rect.y := v^.rect.y + dy;
  844. c := v^.first;
  845. WHILE c # NIL DO
  846. MoveViewBy(c, dx, dy);
  847. c := c^.next
  848. END
  849. END MoveViewBy;
  850. PROCEDURE OffsetView(v : ViewPtr; dx, dy : INTEGER);
  851. BEGIN MoveViewBy(v, dx, dy) END OffsetView;
  852. (*========================================================================*)
  853. (* Widget drawing *)
  854. (*========================================================================*)
  855. PROCEDURE IsFocused(v : ViewPtr) : BOOLEAN;
  856. BEGIN RETURN CurrentFocus() = v END IsFocused;
  857. PROCEDURE DrawWindow(v : ViewPtr);
  858. VAR frame, title, shadow : Attr;
  859. x2 : INTEGER;
  860. BEGIN
  861. shadow := A(Black, Black, FALSE);
  862. frame := A(White, Blue, FALSE);
  863. title := A(Yellow, Blue, FALSE);
  864. DrawShadow(v^.rect, shadow);
  865. Fill(v^.rect.x, v^.rect.y, v^.rect.w, v^.rect.h,
  866. VAL(CARDINAL, ORD(' ')), A(Black, Cyan, FALSE));
  867. DrawBox(v^.rect, DoubleFrame, frame);
  868. DrawText(v^.rect.x + 2, v^.rect.y, v^.text, title);
  869. (* close box *)
  870. x2 := v^.rect.x + v^.rect.w - 3;
  871. DrawText(x2, v^.rect.y, " X ", A(White, Red, FALSE))
  872. END DrawWindow;
  873. PROCEDURE DrawInput(v : ViewPtr);
  874. VAR bg, fg : Attr; s : ARRAY [0..MAXLINE - 1] OF CHAR;
  875. BEGIN
  876. bg := A(Black, White, FALSE);
  877. Fill(v^.rect.x, v^.rect.y, v^.rect.w, v^.rect.h,
  878. VAL(CARDINAL, ORD(' ')), bg);
  879. DrawText(v^.rect.x, v^.rect.y, v^.text, bg);
  880. IF IsFocused(v) THEN
  881. SetCursor(v^.rect.x + v^.pos, v^.rect.y)
  882. END
  883. END DrawInput;
  884. PROCEDURE DrawButton(v : ViewPtr);
  885. VAR s : ARRAY [0..MAXLINE - 1] OF CHAR;
  886. attr : Attr; w : INTEGER;
  887. BEGIN
  888. CopyText(s, "[ ");
  889. AppendText(s, v^.text);
  890. AppendText(s, " ]");
  891. w := TextLen(s);
  892. IF IsFocused(v) THEN
  893. attr := A(White, Blue, FALSE)
  894. ELSE
  895. attr := A(Black, LightGray, FALSE)
  896. END;
  897. IF (v^.flags DIV vfPressed) MOD 2 = 1 THEN
  898. attr := A(White, Green, FALSE)
  899. END;
  900. Fill(v^.rect.x, v^.rect.y, w, 1, VAL(CARDINAL, ORD(' ')),
  901. A(Black, Cyan, FALSE));
  902. DrawText(v^.rect.x, v^.rect.y, s, attr)
  903. END DrawButton;
  904. PROCEDURE DrawCheck(v : ViewPtr);
  905. VAR s : ARRAY [0..1] OF CHAR; attr : Attr;
  906. BEGIN
  907. IF v^.flags DIV vfSelected MOD 2 = 1 THEN s := "X" ELSE s := " " END;
  908. attr := A(Black, Cyan, FALSE);
  909. DrawText(v^.rect.x, v^.rect.y, "[", attr);
  910. DrawText(v^.rect.x + 1, v^.rect.y, s, A(White, Blue, FALSE));
  911. DrawText(v^.rect.x + 2, v^.rect.y, "]", attr);
  912. DrawText(v^.rect.x + 4, v^.rect.y, v^.text, attr)
  913. END DrawCheck;
  914. PROCEDURE DrawRadio(v : ViewPtr);
  915. VAR m : ARRAY [0..1] OF CHAR; attr : Attr;
  916. BEGIN
  917. IF v^.flags DIV vfSelected MOD 2 = 1 THEN m := "*" ELSE m := " " END;
  918. attr := A(Black, Cyan, FALSE);
  919. DrawText(v^.rect.x, v^.rect.y, "(", attr);
  920. DrawText(v^.rect.x + 1, v^.rect.y, m, A(White, Blue, FALSE));
  921. DrawText(v^.rect.x + 2, v^.rect.y, ")", attr);
  922. DrawText(v^.rect.x + 4, v^.rect.y, v^.text, attr)
  923. END DrawRadio;
  924. PROCEDURE DrawList(v : ViewPtr);
  925. VAR i, first, vis, y : INTEGER; attr, sel : Attr;
  926. BEGIN
  927. IF v^.list = NIL THEN RETURN END;
  928. DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE));
  929. vis := v^.rect.h - 2;
  930. first := v^.pos;
  931. FOR i := 0 TO vis - 1 DO
  932. y := v^.rect.y + 1 + i;
  933. IF (first + i) < v^.list^.count THEN
  934. IF (v^.list # NIL) AND (first + i = v^.value) THEN
  935. attr := A(White, Blue, FALSE)
  936. ELSE
  937. attr := A(Black, Cyan, FALSE)
  938. END;
  939. Fill(v^.rect.x + 1, y, v^.rect.w - 2, 1,
  940. VAL(CARDINAL, ORD(' ')), attr);
  941. DrawText(v^.rect.x + 1, y, v^.list^.items[first + i], attr)
  942. END
  943. END;
  944. IF v^.list # NIL THEN
  945. IF v^.list^.count > vis THEN
  946. DrawText(v^.rect.x + v^.rect.w - 2, v^.rect.y + 1, "^",
  947. A(White, Cyan, FALSE));
  948. DrawText(v^.rect.x + v^.rect.w - 2, v^.rect.y + v^.rect.h - 2, "v",
  949. A(White, Cyan, FALSE))
  950. END
  951. END
  952. END DrawList;
  953. PROCEDURE DrawScroll(v : ViewPtr);
  954. VAR h, t, yy : INTEGER; attr : Attr;
  955. BEGIN
  956. attr := A(Black, LightGray, FALSE);
  957. Fill(v^.rect.x, v^.rect.y, v^.rect.w, v^.rect.h,
  958. VAL(CARDINAL, ORD(' ')), attr);
  959. IF v^.maxv > 0 THEN
  960. h := v^.rect.h;
  961. t := h DIV (v^.maxv + 1);
  962. IF t < 1 THEN t := 1 END;
  963. yy := v^.rect.y + (v^.value * (h - t)) DIV v^.maxv;
  964. Fill(v^.rect.x, yy, v^.rect.w, t, VAL(CARDINAL, ORD(' ')),
  965. A(White, Blue, FALSE))
  966. END
  967. END DrawScroll;
  968. PROCEDURE DrawTextCtl(v : ViewPtr);
  969. BEGIN
  970. Fill(v^.rect.x, v^.rect.y, v^.rect.w, 1,
  971. VAL(CARDINAL, ORD(' ')), A(Black, Cyan, FALSE));
  972. DrawText(v^.rect.x, v^.rect.y, v^.text, A(Black, Cyan, FALSE))
  973. END DrawTextCtl;
  974. (*========================================================================*)
  975. (* Editor *)
  976. (*========================================================================*)
  977. PROCEDURE EdClrLine (VAR l : ARRAY OF CHAR);
  978. VAR i : INTEGER;
  979. BEGIN
  980. FOR i := 0 TO EDMAXCOL - 1 DO l[i] := 0C END
  981. END EdClrLine;
  982. PROCEDURE EdEnsure (data : EditorDataPtr);
  983. BEGIN
  984. IF data = NIL THEN RETURN END;
  985. IF data^.count <= 0 THEN
  986. data^.count := 1;
  987. EdClrLine(data^.line[0]);
  988. data^.len[0] := 0;
  989. data^.cx := 0; data^.cy := 0
  990. END;
  991. IF data^.cy < 0 THEN data^.cy := 0 END;
  992. IF data^.cy >= data^.count THEN data^.cy := data^.count - 1 END;
  993. IF data^.cx < 0 THEN data^.cx := 0 END;
  994. IF data^.cx > data^.len[data^.cy] THEN data^.cx := data^.len[data^.cy] END
  995. END EdEnsure;
  996. PROCEDURE EdInsertChar (data : EditorDataPtr; cp : CARDINAL);
  997. VAR ln, i : INTEGER;
  998. BEGIN
  999. EdEnsure(data);
  1000. IF cp >= 256 THEN RETURN END;
  1001. ln := data^.cy;
  1002. IF data^.len[ln] >= EDMAXCOL - 1 THEN RETURN END;
  1003. IF data^.ins OR (data^.cx >= data^.len[ln]) THEN
  1004. i := data^.len[ln];
  1005. WHILE i > data^.cx DO
  1006. data^.line[ln][i] := data^.line[ln][i - 1];
  1007. i := i - 1
  1008. END;
  1009. data^.line[ln][data^.cx] := CHR(cp);
  1010. data^.len[ln] := data^.len[ln] + 1
  1011. ELSE
  1012. data^.line[ln][data^.cx] := CHR(cp)
  1013. END;
  1014. data^.cx := data^.cx + 1;
  1015. data^.dirty := TRUE
  1016. END EdInsertChar;
  1017. PROCEDURE EdNewLine (data : EditorDataPtr);
  1018. VAR ln, i, k, tail : INTEGER;
  1019. BEGIN
  1020. EdEnsure(data);
  1021. IF data^.count >= EDMAXLINES THEN RETURN END;
  1022. ln := data^.cy;
  1023. i := data^.count;
  1024. WHILE i > ln + 1 DO
  1025. FOR k := 0 TO EDMAXCOL - 1 DO data^.line[i][k] := data^.line[i - 1][k] END;
  1026. data^.len[i] := data^.len[i - 1];
  1027. i := i - 1
  1028. END;
  1029. tail := data^.len[ln] - data^.cx;
  1030. FOR k := 0 TO tail - 1 DO data^.line[ln + 1][k] := data^.line[ln][data^.cx + k] END;
  1031. data^.line[ln + 1][tail] := 0C;
  1032. data^.len[ln + 1] := tail;
  1033. FOR k := data^.cx TO EDMAXCOL - 1 DO data^.line[ln][k] := 0C END;
  1034. data^.len[ln] := data^.cx;
  1035. data^.count := data^.count + 1;
  1036. data^.cy := data^.cy + 1;
  1037. data^.cx := 0;
  1038. data^.dirty := TRUE
  1039. END EdNewLine;
  1040. PROCEDURE EdBackspace (data : EditorDataPtr);
  1041. VAR ln, i, plen, k : INTEGER;
  1042. BEGIN
  1043. EdEnsure(data);
  1044. ln := data^.cy;
  1045. IF data^.cx > 0 THEN
  1046. i := data^.cx - 1;
  1047. WHILE i < data^.len[ln] - 1 DO
  1048. data^.line[ln][i] := data^.line[ln][i + 1];
  1049. i := i + 1
  1050. END;
  1051. data^.len[ln] := data^.len[ln] - 1;
  1052. data^.line[ln][data^.len[ln]] := 0C;
  1053. data^.cx := data^.cx - 1;
  1054. data^.dirty := TRUE
  1055. ELSIF ln > 0 THEN
  1056. plen := data^.len[ln - 1];
  1057. IF plen + data^.len[ln] < EDMAXCOL - 1 THEN
  1058. FOR i := 0 TO data^.len[ln] - 1 DO
  1059. data^.line[ln - 1][plen + i] := data^.line[ln][i]
  1060. END;
  1061. data^.len[ln - 1] := plen + data^.len[ln];
  1062. data^.line[ln - 1][data^.len[ln - 1]] := 0C;
  1063. FOR i := ln TO data^.count - 2 DO
  1064. FOR k := 0 TO EDMAXCOL - 1 DO
  1065. data^.line[i][k] := data^.line[i + 1][k]
  1066. END;
  1067. data^.len[i] := data^.len[i + 1]
  1068. END;
  1069. data^.count := data^.count - 1;
  1070. data^.cy := ln - 1;
  1071. data^.cx := data^.len[data^.cy];
  1072. data^.dirty := TRUE
  1073. END
  1074. END
  1075. END EdBackspace;
  1076. PROCEDURE EdDelete (data : EditorDataPtr);
  1077. VAR ln, i, k : INTEGER;
  1078. BEGIN
  1079. EdEnsure(data);
  1080. ln := data^.cy;
  1081. IF data^.cx < data^.len[ln] THEN
  1082. i := data^.cx;
  1083. WHILE i < data^.len[ln] - 1 DO
  1084. data^.line[ln][i] := data^.line[ln][i + 1];
  1085. i := i + 1
  1086. END;
  1087. data^.len[ln] := data^.len[ln] - 1;
  1088. data^.line[ln][data^.len[ln]] := 0C;
  1089. data^.dirty := TRUE
  1090. ELSIF ln < data^.count - 1 THEN
  1091. FOR i := 0 TO data^.len[ln + 1] - 1 DO
  1092. IF data^.len[ln] < EDMAXCOL - 1 THEN
  1093. data^.line[ln][data^.len[ln]] := data^.line[ln + 1][i];
  1094. data^.len[ln] := data^.len[ln] + 1
  1095. END
  1096. END;
  1097. data^.line[ln][data^.len[ln]] := 0C;
  1098. FOR i := ln + 1 TO data^.count - 2 DO
  1099. FOR k := 0 TO EDMAXCOL - 1 DO
  1100. data^.line[i][k] := data^.line[i + 1][k]
  1101. END;
  1102. data^.len[i] := data^.len[i + 1]
  1103. END;
  1104. data^.count := data^.count - 1;
  1105. data^.cx := data^.len[ln];
  1106. data^.dirty := TRUE
  1107. END
  1108. END EdDelete;
  1109. PROCEDURE EdMove (data : EditorDataPtr; v : ViewPtr; key : INTEGER);
  1110. VAR step : INTEGER;
  1111. BEGIN
  1112. EdEnsure(data);
  1113. step := v^.rect.h - 3;
  1114. IF step < 1 THEN step := 1 END;
  1115. IF key = kLeft THEN
  1116. IF data^.cx > 0 THEN data^.cx := data^.cx - 1
  1117. ELSIF data^.cy > 0 THEN data^.cy := data^.cy - 1; data^.cx := data^.len[data^.cy] END
  1118. ELSIF key = kRight THEN
  1119. IF data^.cx < data^.len[data^.cy] THEN data^.cx := data^.cx + 1
  1120. ELSIF data^.cy < data^.count - 1 THEN data^.cy := data^.cy + 1; data^.cx := 0 END
  1121. ELSIF key = kUp THEN
  1122. IF data^.cy > 0 THEN data^.cy := data^.cy - 1 END;
  1123. IF data^.cx > data^.len[data^.cy] THEN data^.cx := data^.len[data^.cy] END
  1124. ELSIF key = kDown THEN
  1125. IF data^.cy < data^.count - 1 THEN data^.cy := data^.cy + 1 END;
  1126. IF data^.cx > data^.len[data^.cy] THEN data^.cx := data^.len[data^.cy] END
  1127. ELSIF key = kHome THEN data^.cx := 0
  1128. ELSIF key = kEnd THEN data^.cx := data^.len[data^.cy]
  1129. ELSIF key = kPgUp THEN
  1130. data^.cy := data^.cy - step;
  1131. IF data^.cy < 0 THEN data^.cy := 0 END;
  1132. IF data^.cx > data^.len[data^.cy] THEN data^.cx := data^.len[data^.cy] END
  1133. ELSIF key = kPgDn THEN
  1134. data^.cy := data^.cy + step;
  1135. IF data^.cy > data^.count - 1 THEN data^.cy := data^.count - 1 END;
  1136. IF data^.cx > data^.len[data^.cy] THEN data^.cx := data^.len[data^.cy] END
  1137. END
  1138. END EdMove;
  1139. PROCEDURE DrawEditor (v : ViewPtr);
  1140. VAR x0, y0, w, h, vis, r, ln, c : INTEGER;
  1141. ed : EditorDataPtr; s, t : ARRAY [0..95] OF CHAR;
  1142. BEGIN
  1143. DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE));
  1144. x0 := v^.rect.x + 1; y0 := v^.rect.y + 1;
  1145. w := v^.rect.w - 2; h := v^.rect.h - 2;
  1146. IF (w <= 0) OR (h <= 0) THEN RETURN END;
  1147. vis := h - 1;
  1148. IF vis < 1 THEN vis := h END;
  1149. ed := v^.ed;
  1150. IF ed = NIL THEN RETURN END;
  1151. EdEnsure(ed);
  1152. IF ed^.cy < ed^.top THEN ed^.top := ed^.cy END;
  1153. IF ed^.cy >= ed^.top + vis THEN ed^.top := ed^.cy - vis + 1 END;
  1154. IF ed^.cx < ed^.left THEN ed^.left := ed^.cx END;
  1155. IF ed^.cx >= ed^.left + w THEN ed^.left := ed^.cx - w + 1 END;
  1156. IF ed^.top < 0 THEN ed^.top := 0 END;
  1157. IF ed^.left < 0 THEN ed^.left := 0 END;
  1158. FOR r := 0 TO vis - 1 DO
  1159. Fill(x0, y0 + r, w, 1, VAL(CARDINAL, ORD(' ')), A(Black, Cyan, FALSE));
  1160. ln := ed^.top + r;
  1161. IF ln < ed^.count THEN
  1162. c := 0;
  1163. WHILE (c < w) AND (ed^.left + c < ed^.len[ln]) DO
  1164. DrawCh(x0 + c, y0 + r,
  1165. VAL(CARDINAL, ORD(ed^.line[ln][ed^.left + c])),
  1166. A(Black, Cyan, FALSE));
  1167. c := c + 1
  1168. END
  1169. END
  1170. END;
  1171. Fill(x0, y0 + vis, w, 1, VAL(CARDINAL, ORD(' ')), A(Black, LightGray, FALSE));
  1172. CopyText(s, " Ln ");
  1173. IntToText(t, ed^.cy + 1, 0); AppendText(s, t);
  1174. AppendText(s, " Col ");
  1175. IntToText(t, ed^.cx + 1, 0); AppendText(s, t);
  1176. IF ed^.ins THEN AppendText(s, " Insert") ELSE AppendText(s, " Overwrite") END;
  1177. IF ed^.dirty THEN AppendText(s, " Modified") END;
  1178. DrawText(x0, y0 + vis, s, A(Black, LightGray, FALSE));
  1179. IF IsFocused(v) THEN
  1180. SetCursor(x0 + (ed^.cx - ed^.left), y0 + (ed^.cy - ed^.top))
  1181. END
  1182. END DrawEditor;
  1183. PROCEDURE EditorAttach (v : ViewPtr; data : EditorDataPtr);
  1184. BEGIN
  1185. IF v # NIL THEN v^.ed := data END
  1186. END EditorAttach;
  1187. PROCEDURE EditorInit (data : EditorDataPtr);
  1188. BEGIN
  1189. IF data # NIL THEN
  1190. data^.count := 1;
  1191. EdClrLine(data^.line[0]);
  1192. data^.len[0] := 0;
  1193. data^.cx := 0; data^.cy := 0; data^.top := 0; data^.left := 0;
  1194. data^.ins := TRUE; data^.dirty := FALSE
  1195. END
  1196. END EditorInit;
  1197. PROCEDURE EditorAddLine (data : EditorDataPtr; s : ARRAY OF CHAR);
  1198. VAR i, n : INTEGER;
  1199. BEGIN
  1200. IF data = NIL THEN RETURN END;
  1201. IF data^.count >= EDMAXLINES THEN RETURN END;
  1202. i := data^.count;
  1203. n := TextLen(s);
  1204. IF n > EDMAXCOL - 1 THEN n := EDMAXCOL - 1 END;
  1205. CopyText(data^.line[i], s);
  1206. data^.len[i] := n;
  1207. data^.count := data^.count + 1
  1208. END EditorAddLine;
  1209. PROCEDURE EditorGetLine (data : EditorDataPtr; i : INTEGER; VAR s : ARRAY OF CHAR);
  1210. BEGIN
  1211. IF (data # NIL) AND (i >= 0) AND (i < data^.count) THEN
  1212. CopyText(s, data^.line[i])
  1213. ELSE
  1214. s[0] := 0C
  1215. END
  1216. END EditorGetLine;
  1217. PROCEDURE EditorLoad (data : EditorDataPtr; path : ARRAY OF CHAR) : BOOLEAN;
  1218. VAR f : ADDRESS; buf : ARRAY [0..EDMAXCOL] OF CHAR; n : INTEGER;
  1219. discard : INTEGER;
  1220. BEGIN
  1221. IF data = NIL THEN RETURN FALSE END;
  1222. f := fopen(path, "r");
  1223. IF f = NIL THEN RETURN FALSE END;
  1224. data^.count := 0;
  1225. WHILE fgets(ADR(buf), EDMAXCOL, f) # NIL DO
  1226. n := TextLen(buf);
  1227. WHILE (n > 0) AND ((buf[n - 1] = CHR(10)) OR (buf[n - 1] = CHR(13))) DO
  1228. buf[n - 1] := 0C;
  1229. n := n - 1
  1230. END;
  1231. EditorAddLine(data, buf)
  1232. END;
  1233. discard := fclose(f);
  1234. IF data^.count = 0 THEN
  1235. data^.count := 1;
  1236. EdClrLine(data^.line[0]);
  1237. data^.len[0] := 0
  1238. END;
  1239. data^.cx := 0; data^.cy := 0; data^.top := 0; data^.left := 0;
  1240. data^.dirty := FALSE;
  1241. RETURN TRUE
  1242. END EditorLoad;
  1243. PROCEDURE EditorSave (data : EditorDataPtr; path : ARRAY OF CHAR) : BOOLEAN;
  1244. VAR f : ADDRESS; i : INTEGER; d1, d2 : INTEGER;
  1245. BEGIN
  1246. IF data = NIL THEN RETURN FALSE END;
  1247. f := fopen(path, "w");
  1248. IF f = NIL THEN RETURN FALSE END;
  1249. FOR i := 0 TO data^.count - 1 DO
  1250. d1 := fputs(data^.line[i], f);
  1251. d2 := fputc(10, f)
  1252. END;
  1253. d1 := fclose(f);
  1254. data^.dirty := FALSE;
  1255. RETURN TRUE
  1256. END EditorSave;
  1257. PROCEDURE EditorDirty (data : EditorDataPtr) : BOOLEAN;
  1258. BEGIN
  1259. IF data = NIL THEN RETURN FALSE END;
  1260. RETURN data^.dirty
  1261. END EditorDirty;
  1262. PROCEDURE DrawView(v : ViewPtr);
  1263. VAR c : ViewPtr;
  1264. BEGIN
  1265. IF v = NIL THEN RETURN END;
  1266. CASE v^.kind OF
  1267. VGroup : (* nothing *)
  1268. |
  1269. VWindow : DrawWindow(v)
  1270. |
  1271. VText : DrawTextCtl(v)
  1272. |
  1273. VFrame : DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE))
  1274. |
  1275. VInput : DrawInput(v)
  1276. |
  1277. VButton : DrawButton(v)
  1278. |
  1279. VCheck : DrawCheck(v)
  1280. |
  1281. VRadio : DrawRadio(v)
  1282. |
  1283. VList : DrawList(v)
  1284. |
  1285. VScroll : DrawScroll(v)
  1286. |
  1287. VEditor : DrawEditor(v)
  1288. END;
  1289. IF v^.first # NIL THEN
  1290. IF (v^.kind = VWindow) OR v^.clipKids THEN
  1291. PushClipRect(v^.rect)
  1292. END;
  1293. c := v^.first;
  1294. WHILE c # NIL DO
  1295. DrawView(c);
  1296. c := c^.next
  1297. END;
  1298. IF (v^.kind = VWindow) OR v^.clipKids THEN PopClip END
  1299. END
  1300. END DrawView;
  1301. (*========================================================================*)
  1302. (* Menu bar + drop-down menus *)
  1303. (*========================================================================*)
  1304. PROCEDURE MenuBarInit;
  1305. BEGIN
  1306. menuCount := 0; menuOpen := -1; menuSel := 0; menuShown := FALSE
  1307. END MenuBarInit;
  1308. PROCEDURE MenuAdd (title : ARRAY OF CHAR);
  1309. BEGIN
  1310. IF menuCount < MAXMENUS THEN
  1311. CopyText(menus[menuCount].title, title);
  1312. menus[menuCount].count := 0;
  1313. menuCount := menuCount + 1;
  1314. menuShown := TRUE
  1315. END
  1316. END MenuAdd;
  1317. PROCEDURE MenuItemAdd (text : ARRAY OF CHAR; tag : INTEGER);
  1318. VAR i : INTEGER;
  1319. BEGIN
  1320. IF menuCount > 0 THEN
  1321. i := menus[menuCount - 1].count;
  1322. IF i < MAXMITEMS THEN
  1323. CopyText(menus[menuCount - 1].items[i].text, text);
  1324. menus[menuCount - 1].items[i].tag := tag;
  1325. menus[menuCount - 1].items[i].sep := FALSE;
  1326. menus[menuCount - 1].count := i + 1
  1327. END
  1328. END
  1329. END MenuItemAdd;
  1330. PROCEDURE MenuSep;
  1331. VAR i : INTEGER;
  1332. BEGIN
  1333. IF menuCount > 0 THEN
  1334. i := menus[menuCount - 1].count;
  1335. IF i < MAXMITEMS THEN
  1336. menus[menuCount - 1].items[i].text[0] := 0C;
  1337. menus[menuCount - 1].items[i].tag := 0;
  1338. menus[menuCount - 1].items[i].sep := TRUE;
  1339. menus[menuCount - 1].count := i + 1
  1340. END
  1341. END
  1342. END MenuSep;
  1343. PROCEDURE MenuBar (show : BOOLEAN);
  1344. BEGIN
  1345. menuShown := show;
  1346. IF NOT show THEN menuOpen := -1 END
  1347. END MenuBar;
  1348. PROCEDURE MenuTitleX (i : INTEGER) : INTEGER;
  1349. VAR k, x : INTEGER;
  1350. BEGIN
  1351. x := 1;
  1352. FOR k := 0 TO i - 1 DO
  1353. x := x + TextLen(menus[k].title) + 2
  1354. END;
  1355. RETURN x
  1356. END MenuTitleX;
  1357. PROCEDURE MenuWidth (i : INTEGER) : INTEGER;
  1358. VAR k, w, l : INTEGER;
  1359. BEGIN
  1360. w := TextLen(menus[i].title) + 2;
  1361. FOR k := 0 TO menus[i].count - 1 DO
  1362. l := TextLen(menus[i].items[k].text) + 4;
  1363. IF l > w THEN w := l END
  1364. END;
  1365. RETURN w
  1366. END MenuWidth;
  1367. PROCEDURE MenuTitleAt (x : INTEGER) : INTEGER;
  1368. VAR i, xx : INTEGER;
  1369. BEGIN
  1370. FOR i := 0 TO menuCount - 1 DO
  1371. xx := MenuTitleX(i);
  1372. IF (x >= xx) AND (x < xx + TextLen(menus[i].title)) THEN RETURN i END
  1373. END;
  1374. RETURN -1
  1375. END MenuTitleAt;
  1376. PROCEDURE FirstSel (i : INTEGER) : INTEGER;
  1377. VAR k : INTEGER;
  1378. BEGIN
  1379. k := 0;
  1380. WHILE (k < menus[i].count) AND menus[i].items[k].sep DO k := k + 1 END;
  1381. IF k >= menus[i].count THEN k := 0 END;
  1382. RETURN k
  1383. END FirstSel;
  1384. PROCEDURE MenuStep (i, cur, dir : INTEGER) : INTEGER;
  1385. VAR k, n : INTEGER; found : BOOLEAN;
  1386. BEGIN
  1387. n := menus[i].count;
  1388. IF n = 0 THEN RETURN 0 END;
  1389. k := cur; found := FALSE;
  1390. WHILE NOT found DO
  1391. k := (k + dir + n) MOD n;
  1392. IF (NOT menus[i].items[k].sep) OR (k = cur) THEN found := TRUE END
  1393. END;
  1394. RETURN k
  1395. END MenuStep;
  1396. PROCEDURE DrawMenuBar;
  1397. VAR i, k, x : INTEGER; r : Rect; attr : Attr;
  1398. space : CARDINAL;
  1399. BEGIN
  1400. IF (NOT menuShown) OR (menuCount = 0) THEN RETURN END;
  1401. space := VAL(CARDINAL, ORD(' '));
  1402. Fill(0, 0, cols, 1, space, A(Black, LightGray, FALSE));
  1403. FOR i := 0 TO menuCount - 1 DO
  1404. x := MenuTitleX(i);
  1405. IF menuOpen = i THEN attr := A(White, Blue, FALSE)
  1406. ELSE attr := A(Black, LightGray, FALSE) END;
  1407. DrawText(x, 0, menus[i].title, attr)
  1408. END;
  1409. IF menuOpen < 0 THEN RETURN END;
  1410. i := menuOpen;
  1411. r.x := MenuTitleX(i); r.y := 1;
  1412. r.w := MenuWidth(i); r.h := menus[i].count + 2;
  1413. DrawShadow(r, A(Black, Black, FALSE));
  1414. Fill(r.x, r.y, r.w, r.h, space, A(Black, LightGray, FALSE));
  1415. DrawBox(r, SingleFrame, A(Black, LightGray, FALSE));
  1416. FOR k := 0 TO menus[i].count - 1 DO
  1417. IF menus[i].items[k].sep THEN
  1418. DrawHLine(r.x + 1, r.y + 1 + k, r.w - 2, H_SINGLE,
  1419. A(DarkGray, LightGray, FALSE))
  1420. ELSE
  1421. IF k = menuSel THEN attr := A(White, Blue, FALSE)
  1422. ELSE attr := A(Black, LightGray, FALSE) END;
  1423. Fill(r.x + 1, r.y + 1 + k, r.w - 2, 1, space, attr);
  1424. DrawText(r.x + 2, r.y + 1 + k, menus[i].items[k].text, attr)
  1425. END
  1426. END
  1427. END DrawMenuBar;
  1428. PROCEDURE MenuHandle (e : Event; VAR handled : BOOLEAN) : INTEGER;
  1429. VAR i, idx : INTEGER; r : Rect;
  1430. BEGIN
  1431. handled := FALSE;
  1432. IF (NOT menuShown) OR (menuCount = 0) THEN RETURN cmNone END;
  1433. IF e.kind = evKey THEN
  1434. IF e.key = kCtrlC THEN RETURN cmNone END;
  1435. IF e.key = kF10 THEN
  1436. IF menuOpen < 0 THEN menuOpen := 0 ELSE menuOpen := (menuOpen + 1) MOD menuCount END;
  1437. menuSel := FirstSel(menuOpen);
  1438. handled := TRUE; RETURN cmNone
  1439. END;
  1440. IF menuOpen >= 0 THEN
  1441. handled := TRUE;
  1442. IF e.key = kEsc THEN menuOpen := -1
  1443. ELSIF e.key = kLeft THEN
  1444. menuOpen := (menuOpen + menuCount - 1) MOD menuCount;
  1445. menuSel := FirstSel(menuOpen)
  1446. ELSIF e.key = kRight THEN
  1447. menuOpen := (menuOpen + 1) MOD menuCount;
  1448. menuSel := FirstSel(menuOpen)
  1449. ELSIF e.key = kUp THEN menuSel := MenuStep(menuOpen, menuSel, -1)
  1450. ELSIF e.key = kDown THEN menuSel := MenuStep(menuOpen, menuSel, +1)
  1451. ELSIF (e.key = kEnter) OR (e.key = kSpace) THEN
  1452. IF NOT menus[menuOpen].items[menuSel].sep THEN
  1453. i := menus[menuOpen].items[menuSel].tag;
  1454. menuOpen := -1; lastSender := NIL;
  1455. RETURN i
  1456. END
  1457. END;
  1458. RETURN cmNone
  1459. END;
  1460. RETURN cmNone
  1461. END;
  1462. IF e.kind # evMouse THEN RETURN cmNone END;
  1463. (* mouse over a title *)
  1464. IF e.my = 0 THEN
  1465. i := MenuTitleAt(e.mx);
  1466. IF i >= 0 THEN
  1467. handled := TRUE;
  1468. IF e.mpressed THEN
  1469. IF menuOpen = i THEN menuOpen := -1
  1470. ELSE menuOpen := i; menuSel := FirstSel(i) END
  1471. ELSIF (e.mbtn > 0) OR (e.mwheel # 0) THEN
  1472. menuOpen := i; menuSel := FirstSel(i)
  1473. ELSIF menuOpen >= 0 THEN
  1474. menuOpen := i; menuSel := FirstSel(i)
  1475. END;
  1476. RETURN cmNone
  1477. END
  1478. END;
  1479. IF menuOpen >= 0 THEN
  1480. r.x := MenuTitleX(menuOpen); r.y := 1;
  1481. r.w := MenuWidth(menuOpen); r.h := menus[menuOpen].count + 2;
  1482. IF Contains(r, e.mx, e.my) THEN
  1483. handled := TRUE;
  1484. idx := e.my - (r.y + 1);
  1485. IF (idx >= 0) AND (idx < menus[menuOpen].count) AND
  1486. (NOT menus[menuOpen].items[idx].sep) THEN
  1487. menuSel := idx;
  1488. IF e.mpressed THEN
  1489. i := menus[menuOpen].items[idx].tag;
  1490. menuOpen := -1; lastSender := NIL;
  1491. RETURN i
  1492. END
  1493. END;
  1494. RETURN cmNone
  1495. ELSIF e.mpressed THEN
  1496. menuOpen := -1;
  1497. handled := TRUE
  1498. END
  1499. END;
  1500. RETURN cmNone
  1501. END MenuHandle;
  1502. PROCEDURE DrawTree;
  1503. BEGIN
  1504. Clear(A(LightGray, Blue, FALSE));
  1505. HideCursor;
  1506. clipN := 0;
  1507. IF desk # NIL THEN DrawView(desk) END;
  1508. DrawMenuBar
  1509. END DrawTree;
  1510. PROCEDURE Finish;
  1511. BEGIN
  1512. DrawTree;
  1513. Present
  1514. END Finish;
  1515. (*========================================================================*)
  1516. (* Geometry + hit testing *)
  1517. (*========================================================================*)
  1518. PROCEDURE Contains(r : Rect; x, y : INTEGER) : BOOLEAN;
  1519. BEGIN
  1520. RETURN (x >= r.x) AND (x < r.x + r.w) AND (y >= r.y) AND (y < r.y + r.h)
  1521. END Contains;
  1522. PROCEDURE HitTest(v : ViewPtr; x, y : INTEGER) : ViewPtr;
  1523. VAR c, hit : ViewPtr;
  1524. BEGIN
  1525. IF (v = NIL) OR (NOT Contains(v^.rect, x, y)) THEN RETURN NIL END;
  1526. c := v^.last;
  1527. WHILE c # NIL DO
  1528. IF Contains(c^.rect, x, y) THEN
  1529. hit := HitTest(c, x, y);
  1530. IF hit # NIL THEN RETURN hit END
  1531. END;
  1532. c := c^.prev
  1533. END;
  1534. RETURN v
  1535. END HitTest;
  1536. PROCEDURE WindowAncestor(v : ViewPtr) : ViewPtr;
  1537. VAR p : ViewPtr;
  1538. BEGIN
  1539. p := v;
  1540. WHILE p # NIL DO
  1541. IF p^.kind = VWindow THEN RETURN p END;
  1542. p := p^.parent
  1543. END;
  1544. RETURN NIL
  1545. END WindowAncestor;
  1546. (*========================================================================*)
  1547. (* Event dispatch *)
  1548. (*========================================================================*)
  1549. PROCEDURE EditKey(v : ViewPtr; VAR e : Event);
  1550. VAR i : INTEGER;
  1551. BEGIN
  1552. IF e.key = kLeft THEN
  1553. IF v^.pos > 0 THEN v^.pos := v^.pos - 1 END
  1554. ELSIF e.key = kRight THEN
  1555. IF v^.pos < v^.len THEN v^.pos := v^.pos + 1 END
  1556. ELSIF e.key = kHome THEN v^.pos := 0
  1557. ELSIF e.key = kEnd THEN v^.pos := v^.len
  1558. ELSIF e.key = kBack THEN
  1559. IF v^.pos > 0 THEN
  1560. i := v^.pos - 1;
  1561. WHILE i < v^.len - 1 DO v^.text[i] := v^.text[i + 1]; i := i + 1 END;
  1562. v^.len := v^.len - 1; v^.pos := v^.pos - 1; v^.text[v^.len] := 0C
  1563. END
  1564. ELSIF e.key = kDel THEN
  1565. IF v^.pos < v^.len THEN
  1566. i := v^.pos;
  1567. WHILE i < v^.len - 1 DO v^.text[i] := v^.text[i + 1]; i := i + 1 END;
  1568. v^.len := v^.len - 1; v^.text[v^.len] := 0C
  1569. END
  1570. ELSIF e.key = kChar THEN
  1571. IF (v^.len < MAXLINE - 1) AND (e.ch < 256) THEN
  1572. i := v^.len;
  1573. WHILE i > v^.pos DO v^.text[i] := v^.text[i - 1]; i := i - 1 END;
  1574. v^.text[v^.pos] := CHR(e.ch);
  1575. v^.len := v^.len + 1; v^.pos := v^.pos + 1
  1576. END
  1577. END
  1578. END EditKey;
  1579. PROCEDURE SelectRadio(v : ViewPtr);
  1580. VAR c : ViewPtr;
  1581. BEGIN
  1582. IF (v = NIL) OR (v^.parent = NIL) THEN RETURN END;
  1583. c := v^.parent^.first;
  1584. WHILE c # NIL DO
  1585. IF (c # v) AND (c^.kind = VRadio) AND
  1586. ((c^.flags DIV vfSelected) MOD 2 = 1) THEN
  1587. c^.flags := c^.flags - vfSelected
  1588. END;
  1589. c := c^.next
  1590. END;
  1591. IF (v^.flags DIV vfSelected) MOD 2 = 0 THEN
  1592. v^.flags := v^.flags + vfSelected
  1593. END
  1594. END SelectRadio;
  1595. PROCEDURE WidgetKey(v : ViewPtr; VAR e : Event) : INTEGER;
  1596. VAR cmd : INTEGER;
  1597. BEGIN
  1598. cmd := cmNone;
  1599. IF v^.kind = VInput THEN
  1600. EditKey(v, e)
  1601. ELSIF v^.kind = VButton THEN
  1602. IF (e.key = kEnter) OR (e.key = kSpace) THEN cmd := v^.tag END
  1603. ELSIF v^.kind = VCheck THEN
  1604. IF (e.key = kEnter) OR (e.key = kSpace) THEN
  1605. v^.flags := v^.flags / 4 * 4;
  1606. IF v^.flags DIV vfSelected MOD 2 = 0 THEN
  1607. v^.flags := v^.flags + vfSelected
  1608. END;
  1609. cmd := v^.tag
  1610. END
  1611. ELSIF v^.kind = VRadio THEN
  1612. IF (e.key = kEnter) OR (e.key = kSpace) THEN
  1613. SelectRadio(v);
  1614. cmd := v^.tag
  1615. END
  1616. ELSIF v^.kind = VList THEN
  1617. IF e.key = kUp THEN
  1618. IF v^.value > 0 THEN v^.value := v^.value - 1 END;
  1619. IF v^.value < v^.pos THEN v^.pos := v^.value END
  1620. ELSIF e.key = kDown THEN
  1621. IF (v^.list # NIL) AND (v^.value < v^.list^.count - 1) THEN
  1622. v^.value := v^.value + 1
  1623. END;
  1624. IF v^.value >= v^.pos + (v^.rect.h - 2) THEN
  1625. v^.pos := v^.value - (v^.rect.h - 2) + 1
  1626. END
  1627. ELSIF (e.key = kEnter) OR (e.key = kSpace) THEN
  1628. cmd := v^.tag
  1629. END
  1630. ELSIF v^.kind = VScroll THEN
  1631. IF e.key = kUp THEN
  1632. IF v^.value > 0 THEN v^.value := v^.value - 1 END
  1633. ELSIF e.key = kDown THEN
  1634. IF v^.value < v^.maxv THEN v^.value := v^.value + 1 END
  1635. END
  1636. ELSIF v^.kind = VEditor THEN
  1637. IF v^.ed # NIL THEN
  1638. IF e.key = kChar THEN EdInsertChar(v^.ed, e.ch)
  1639. ELSIF e.key = kEnter THEN EdNewLine(v^.ed)
  1640. ELSIF e.key = kBack THEN EdBackspace(v^.ed)
  1641. ELSIF e.key = kDel THEN EdDelete(v^.ed)
  1642. ELSIF e.key = kIns THEN v^.ed^.ins := NOT v^.ed^.ins
  1643. ELSIF e.key = kTab THEN
  1644. EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')));
  1645. WHILE (v^.ed^.cx MOD 4) # 0 DO
  1646. EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')))
  1647. END
  1648. ELSE EdMove(v^.ed, v, e.key)
  1649. END
  1650. END
  1651. END;
  1652. IF cmd # cmNone THEN lastSender := v END;
  1653. RETURN cmd
  1654. END WidgetKey;
  1655. PROCEDURE WidgetMouse(v : ViewPtr; VAR e : Event) : INTEGER;
  1656. VAR cmd : INTEGER; rel, ln, col : INTEGER;
  1657. BEGIN
  1658. cmd := cmNone;
  1659. IF v^.kind = VButton THEN
  1660. IF e.mpressed THEN
  1661. v^.flags := v^.flags + vfPressed
  1662. ELSIF e.mreleased THEN
  1663. IF (v^.flags DIV vfPressed) MOD 2 = 1 THEN
  1664. cmd := v^.tag
  1665. END;
  1666. v^.flags := v^.flags / 8 * 8
  1667. END
  1668. ELSIF v^.kind = VCheck THEN
  1669. IF e.mreleased THEN
  1670. IF v^.flags DIV vfSelected MOD 2 = 1 THEN
  1671. v^.flags := v^.flags - vfSelected
  1672. ELSE
  1673. v^.flags := v^.flags + vfSelected
  1674. END;
  1675. cmd := v^.tag
  1676. END
  1677. ELSIF v^.kind = VRadio THEN
  1678. IF e.mreleased THEN
  1679. SelectRadio(v);
  1680. cmd := v^.tag
  1681. END
  1682. ELSIF v^.kind = VList THEN
  1683. IF e.mwheel # 0 THEN
  1684. IF (e.mwheel < 0) AND (v^.pos > 0) THEN v^.pos := v^.pos - 1 END;
  1685. IF (e.mwheel > 0) AND (v^.list # NIL) AND
  1686. (v^.pos + (v^.rect.h - 2) < v^.list^.count) THEN
  1687. v^.pos := v^.pos + 1
  1688. END
  1689. ELSIF e.mreleased THEN
  1690. rel := e.my - v^.rect.y - 1;
  1691. IF (rel >= 0) AND (v^.list # NIL) AND
  1692. (v^.pos + rel < v^.list^.count) THEN
  1693. v^.value := v^.pos + rel
  1694. END
  1695. END
  1696. ELSIF v^.kind = VInput THEN
  1697. IF e.mpressed THEN
  1698. rel := e.mx - v^.rect.x;
  1699. IF rel < 0 THEN rel := 0 END;
  1700. IF rel > v^.len THEN rel := v^.len END;
  1701. v^.pos := rel
  1702. END
  1703. ELSIF v^.kind = VScroll THEN
  1704. IF e.mpressed OR e.mreleased THEN
  1705. IF v^.rect.h > 1 THEN
  1706. v^.value := ((e.my - v^.rect.y) * v^.maxv) DIV (v^.rect.h - 1)
  1707. END;
  1708. IF v^.value < 0 THEN v^.value := 0 END;
  1709. IF v^.value > v^.maxv THEN v^.value := v^.maxv END
  1710. END
  1711. ELSIF v^.kind = VEditor THEN
  1712. IF v^.ed # NIL THEN
  1713. IF e.mwheel # 0 THEN
  1714. v^.ed^.top := v^.ed^.top + e.mwheel * 3;
  1715. IF v^.ed^.top < 0 THEN v^.ed^.top := 0 END
  1716. ELSIF e.mpressed THEN
  1717. ln := v^.ed^.top + (e.my - (v^.rect.y + 1));
  1718. IF (ln >= 0) AND (ln < v^.ed^.count) THEN
  1719. v^.ed^.cy := ln;
  1720. col := e.mx - (v^.rect.x + 1) + v^.ed^.left;
  1721. IF col < 0 THEN col := 0 END;
  1722. IF col > v^.ed^.len[ln] THEN col := v^.ed^.len[ln] END;
  1723. v^.ed^.cx := col
  1724. END
  1725. END
  1726. END
  1727. END;
  1728. IF cmd # cmNone THEN lastSender := v END;
  1729. RETURN cmd
  1730. END WidgetMouse;
  1731. PROCEDURE HandleWindow(v : ViewPtr; VAR e : Event) : INTEGER;
  1732. VAR w : ViewPtr; nx, ny : INTEGER;
  1733. BEGIN
  1734. IF (e.kind = evMouse) AND e.mpressed THEN
  1735. (* close box? *)
  1736. IF (e.my = v^.rect.y) AND
  1737. (e.mx >= v^.rect.x + v^.rect.w - 3) AND (e.mx < v^.rect.x + v^.rect.w) THEN
  1738. lastSender := v;
  1739. RETURN cmClose
  1740. END;
  1741. (* drag by the title row *)
  1742. IF (e.my = v^.rect.y) AND (e.mx < v^.rect.x + v^.rect.w - 3) THEN
  1743. dragWin := v;
  1744. dragOX := e.mx - v^.rect.x;
  1745. dragOY := e.my - v^.rect.y
  1746. END
  1747. ELSIF (e.kind = evMouse) AND e.mreleased THEN
  1748. dragWin := NIL
  1749. ELSIF (e.kind = evMouse) AND (e.mbtn > 0) AND (NOT e.mpressed) AND
  1750. (NOT e.mreleased) THEN
  1751. IF dragWin # NIL THEN
  1752. nx := e.mx - dragOX;
  1753. ny := e.my - dragOY;
  1754. (* keep the window's title corner on screen *)
  1755. IF nx < 0 THEN nx := 0 END;
  1756. IF nx > cols - 6 THEN nx := cols - 6 END;
  1757. IF ny < 0 THEN ny := 0 END;
  1758. IF ny > rows - 1 THEN ny := rows - 1 END;
  1759. MoveViewBy(dragWin, nx - dragWin^.rect.x, ny - dragWin^.rect.y)
  1760. END
  1761. END;
  1762. RETURN cmNone
  1763. END HandleWindow;
  1764. PROCEDURE DispatchMouse(v : ViewPtr; VAR e : Event) : INTEGER;
  1765. VAR hit : ViewPtr; cmd : INTEGER; w : ViewPtr;
  1766. BEGIN
  1767. cmd := cmNone;
  1768. hit := HitTest(v, e.mx, e.my);
  1769. IF hit = NIL THEN RETURN cmNone END;
  1770. IF hit # v THEN
  1771. w := WindowAncestor(hit);
  1772. IF (w # NIL) AND e.mpressed THEN BringToFront(w) END
  1773. END;
  1774. IF e.mpressed AND IsFocusable(hit) THEN FocusView(hit) END;
  1775. IF hit^.kind = VWindow THEN
  1776. cmd := HandleWindow(hit, e)
  1777. ELSE
  1778. cmd := WidgetMouse(hit, e)
  1779. END;
  1780. RETURN cmd
  1781. END DispatchMouse;
  1782. PROCEDURE DispatchKey(v : ViewPtr; VAR e : Event) : INTEGER;
  1783. VAR cmd : INTEGER; f : ViewPtr;
  1784. BEGIN
  1785. cmd := cmNone;
  1786. IF (v^.kind = VGroup) OR (v^.kind = VWindow) THEN
  1787. f := v^.focus;
  1788. IF f # NIL THEN cmd := DispatchKey(f, e) END
  1789. ELSE
  1790. cmd := WidgetKey(v, e)
  1791. END;
  1792. RETURN cmd
  1793. END DispatchKey;
  1794. PROCEDURE HandleEvent (e : Event) : INTEGER;
  1795. VAR cmd : INTEGER; root : ViewPtr; handled : BOOLEAN;
  1796. BEGIN
  1797. IF e.kind = evNone THEN RETURN cmNone END;
  1798. IF modal = NIL THEN
  1799. cmd := MenuHandle(e, handled);
  1800. IF handled THEN RETURN cmd END
  1801. END;
  1802. root := modal;
  1803. IF root = NIL THEN root := desk END;
  1804. IF root = NIL THEN RETURN cmNone END;
  1805. IF e.kind = evKey THEN
  1806. IF e.key = kCtrlC THEN RETURN cmClose END;
  1807. IF e.key = kTab THEN
  1808. FocusNext(e.ctrl);
  1809. RETURN cmNone
  1810. END;
  1811. cmd := DispatchKey(root, e)
  1812. ELSE
  1813. (* while a window is being dragged, keep sending motion/release to it
  1814. even if the cursor has left the window (otherwise dragging up and
  1815. sideways stops as soon as the pointer leaves the frame) *)
  1816. IF dragWin # NIL THEN
  1817. cmd := HandleWindow(dragWin, e)
  1818. ELSE
  1819. cmd := DispatchMouse(root, e)
  1820. END
  1821. END;
  1822. RETURN cmd
  1823. END HandleEvent;
  1824. PROCEDURE Sender () : ViewPtr;
  1825. BEGIN RETURN lastSender END Sender;
  1826. (*========================================================================*)
  1827. (* Modal message box *)
  1828. (*========================================================================*)
  1829. (*========================================================================*)
  1830. (* Generic dialog builder *)
  1831. (*========================================================================*)
  1832. PROCEDURE NewDlgItem (k : VKind; x, y, w, h : INTEGER;
  1833. text : ARRAY OF CHAR) : ViewPtr;
  1834. VAR v : ViewPtr;
  1835. BEGIN
  1836. v := NewView(k, x, y, w, h);
  1837. SetText(v, text);
  1838. AddView(dlg, v);
  1839. IF dlgCount < 32 THEN
  1840. dlgItems[dlgCount] := v;
  1841. dlgCount := dlgCount + 1
  1842. END;
  1843. RETURN v
  1844. END NewDlgItem;
  1845. PROCEDURE Dialog (title : ARRAY OF CHAR; w, h : INTEGER);
  1846. VAR x, y : INTEGER;
  1847. BEGIN
  1848. x := (cols - w) DIV 2;
  1849. y := (rows - h) DIV 2;
  1850. IF x < 0 THEN x := 0 END;
  1851. IF y < 0 THEN y := 0 END;
  1852. dlg := NewView(VWindow, x, y, w, h);
  1853. SetText(dlg, title);
  1854. dlgW := w; dlgWX := x; dlgWY := y;
  1855. dlgX := x + 2; dlgY := y + 2; dlgInputX := x + 18;
  1856. dlgBtnX := x + 2; dlgBtnY := y + h - 2;
  1857. dlgCount := 0
  1858. END Dialog;
  1859. PROCEDURE DlgLabel (text : ARRAY OF CHAR);
  1860. VAR v : ViewPtr;
  1861. BEGIN
  1862. IF dlg = NIL THEN RETURN END;
  1863. v := NewView(VText, dlgX, dlgY, dlgW - 4, 1);
  1864. SetText(v, text);
  1865. AddView(dlg, v);
  1866. dlgY := dlgY + 1
  1867. END DlgLabel;
  1868. PROCEDURE DlgInput (label : ARRAY OF CHAR; initial : ARRAY OF CHAR) : INTEGER;
  1869. VAR lv : ViewPtr; iw : INTEGER;
  1870. BEGIN
  1871. IF dlg = NIL THEN RETURN -1 END;
  1872. IF TextLen(label) > 0 THEN
  1873. lv := NewView(VText, dlgX, dlgY, dlgInputX - dlgX - 1, 1);
  1874. SetText(lv, label);
  1875. AddView(dlg, lv)
  1876. END;
  1877. iw := dlgWX + dlgW - 2 - dlgInputX;
  1878. IF iw < 8 THEN iw := 8 END;
  1879. IF NewDlgItem(VInput, dlgInputX, dlgY, iw, 1, initial) = NIL THEN END;
  1880. dlgY := dlgY + 2;
  1881. RETURN dlgCount - 1
  1882. END DlgInput;
  1883. PROCEDURE DlgCheck (label : ARRAY OF CHAR; checked : BOOLEAN) : INTEGER;
  1884. VAR v : ViewPtr; idx : INTEGER;
  1885. BEGIN
  1886. IF dlg = NIL THEN RETURN -1 END;
  1887. v := NewDlgItem(VCheck, dlgX, dlgY, dlgW - 4, 1, label);
  1888. idx := dlgCount - 1;
  1889. IF (v # NIL) AND checked THEN v^.flags := v^.flags + vfSelected END;
  1890. dlgY := dlgY + 1;
  1891. RETURN idx
  1892. END DlgCheck;
  1893. PROCEDURE DlgRadio (label : ARRAY OF CHAR; selected : BOOLEAN) : INTEGER;
  1894. VAR v : ViewPtr; idx : INTEGER;
  1895. BEGIN
  1896. IF dlg = NIL THEN RETURN -1 END;
  1897. v := NewDlgItem(VRadio, dlgX, dlgY, dlgW - 4, 1, label);
  1898. idx := dlgCount - 1;
  1899. IF (v # NIL) AND selected THEN v^.flags := v^.flags + vfSelected END;
  1900. dlgY := dlgY + 1;
  1901. RETURN idx
  1902. END DlgRadio;
  1903. PROCEDURE DlgButton (text : ARRAY OF CHAR; tag : INTEGER);
  1904. VAR v : ViewPtr; w : INTEGER;
  1905. BEGIN
  1906. IF dlg = NIL THEN RETURN END;
  1907. w := TextLen(text) + 4;
  1908. v := NewView(VButton, dlgBtnX, dlgBtnY, w, 1);
  1909. SetText(v, text);
  1910. SetTag(v, tag);
  1911. AddView(dlg, v);
  1912. dlgBtnX := dlgBtnX + w + 2
  1913. END DlgButton;
  1914. PROCEDURE DlgTextAt (idx : INTEGER; VAR s : ARRAY OF CHAR);
  1915. BEGIN
  1916. IF (idx >= 0) AND (idx < dlgCount) AND (dlgItems[idx]^.kind = VInput) THEN
  1917. CopyText(s, dlgItems[idx]^.text)
  1918. ELSE
  1919. s[0] := 0C
  1920. END
  1921. END DlgTextAt;
  1922. PROCEDURE DlgValue (idx : INTEGER) : INTEGER;
  1923. BEGIN
  1924. IF (idx >= 0) AND (idx < dlgCount) AND
  1925. ((dlgItems[idx]^.flags DIV vfSelected) MOD 2 = 1) THEN
  1926. RETURN 1
  1927. END;
  1928. RETURN 0
  1929. END DlgValue;
  1930. PROCEDURE DlgRun () : INTEGER;
  1931. VAR e : Event; cmd : INTEGER; done : BOOLEAN; f : ViewPtr;
  1932. BEGIN
  1933. IF dlg = NIL THEN RETURN cmCancel END;
  1934. AddView(desk, dlg);
  1935. BringToFront(dlg);
  1936. f := dlg^.first;
  1937. WHILE (f # NIL) AND (NOT IsFocusable(f)) DO f := f^.next END;
  1938. IF f # NIL THEN FocusView(f) END;
  1939. modal := dlg;
  1940. done := FALSE; cmd := cmNone;
  1941. WHILE NOT done DO
  1942. Finish;
  1943. IF ReadEvent(e) THEN
  1944. IF (e.kind = evKey) AND (e.key = kEsc) THEN
  1945. cmd := cmCancel; done := TRUE
  1946. ELSE
  1947. cmd := HandleEvent(e);
  1948. IF cmd # cmNone THEN done := TRUE END
  1949. END
  1950. END
  1951. END;
  1952. modal := NIL;
  1953. DelView(dlg);
  1954. dlg := NIL;
  1955. RETURN cmd
  1956. END DlgRun;
  1957. PROCEDURE MessageBox (title : ARRAY OF CHAR; text : ARRAY OF CHAR;
  1958. kind : INTEGER) : INTEGER;
  1959. VAR w, total, left : INTEGER; b1, b2 : ViewPtr;
  1960. BEGIN
  1961. w := TextLen(text) + 6;
  1962. IF w < 30 THEN w := 30 END;
  1963. IF w > cols - 4 THEN w := cols - 4 END;
  1964. Dialog(title, w, 7);
  1965. DlgLabel(text);
  1966. b1 := NIL; b2 := NIL;
  1967. IF kind = mbOKCancel THEN
  1968. DlgButton("OK", cmOK); b1 := dlg^.last;
  1969. DlgButton("Cancel", cmCancel); b2 := dlg^.last
  1970. ELSIF kind = mbYesNo THEN
  1971. DlgButton("Yes", cmYes); b1 := dlg^.last;
  1972. DlgButton("No", cmNo); b2 := dlg^.last
  1973. ELSE
  1974. DlgButton("OK", cmOK); b1 := dlg^.last
  1975. END;
  1976. IF b1 # NIL THEN
  1977. IF b2 # NIL THEN total := b1^.rect.w + b2^.rect.w + 2
  1978. ELSE total := b1^.rect.w END;
  1979. left := dlgWX + (dlgW - total) DIV 2;
  1980. MoveViewBy(b1, left - b1^.rect.x, 0);
  1981. IF b2 # NIL THEN MoveViewBy(b2, left + b1^.rect.w + 2 - b2^.rect.x, 0) END
  1982. END;
  1983. RETURN DlgRun()
  1984. END MessageBox;
  1985. PROCEDURE Message (title : ARRAY OF CHAR; text : ARRAY OF CHAR);
  1986. VAR discard : INTEGER;
  1987. BEGIN
  1988. discard := MessageBox(title, text, mbOK)
  1989. END Message;
  1990. END tv.