tv.mod 78 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374
  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. (* --- syntax highlighting + keyword upcasing ---------------------------- *)
  1140. PROCEDURE Eq (a, b : ARRAY OF CHAR) : BOOLEAN;
  1141. VAR i : INTEGER;
  1142. BEGIN
  1143. i := 0;
  1144. WHILE (i <= VAL(INTEGER, HIGH(a))) AND (i <= VAL(INTEGER, HIGH(b))) AND
  1145. (a[i] # 0C) AND (b[i] # 0C) AND (a[i] = b[i]) DO
  1146. i := i + 1
  1147. END;
  1148. RETURN a[i] = b[i]
  1149. END Eq;
  1150. PROCEDURE IsIdentChar (c : CHAR) : BOOLEAN;
  1151. BEGIN
  1152. RETURN ((c >= 'A') AND (c <= 'Z')) OR ((c >= 'a') AND (c <= 'z')) OR
  1153. ((c >= '0') AND (c <= '9')) OR (c = '_')
  1154. END IsIdentChar;
  1155. PROCEDURE UpcaseCopy (VAR dest : ARRAY OF CHAR; s : ARRAY OF CHAR);
  1156. VAR i : INTEGER; c : CHAR;
  1157. BEGIN
  1158. i := 0;
  1159. WHILE (i <= VAL(INTEGER, HIGH(s))) AND (i < VAL(INTEGER, HIGH(dest))) AND (s[i] # 0C) DO
  1160. c := s[i];
  1161. IF (c >= 'a') AND (c <= 'z') THEN c := CHR(ORD(c) - 32) END;
  1162. dest[i] := c;
  1163. i := i + 1
  1164. END;
  1165. dest[i] := 0C
  1166. END UpcaseCopy;
  1167. PROCEDURE IsKeywordM2 (w : ARRAY OF CHAR) : BOOLEAN;
  1168. BEGIN
  1169. RETURN Eq(w,"AND") OR Eq(w,"ARRAY") OR Eq(w,"BEGIN") OR Eq(w,"BY") OR
  1170. Eq(w,"CASE") OR Eq(w,"CONST") OR Eq(w,"DEFINITION") OR Eq(w,"DIV") OR
  1171. Eq(w,"DO") OR Eq(w,"ELSE") OR Eq(w,"ELSIF") OR Eq(w,"END") OR
  1172. Eq(w,"EXIT") OR Eq(w,"EXPORT") OR Eq(w,"FOR") OR Eq(w,"FROM") OR
  1173. Eq(w,"IF") OR Eq(w,"IMPLEMENTATION") OR Eq(w,"IMPORT") OR Eq(w,"IN") OR
  1174. Eq(w,"LOOP") OR Eq(w,"MOD") OR Eq(w,"MODULE") OR Eq(w,"NOT") OR
  1175. Eq(w,"OF") OR Eq(w,"OR") OR Eq(w,"POINTER") OR Eq(w,"PROCEDURE") OR
  1176. Eq(w,"QUALIFIED") OR Eq(w,"RECORD") OR Eq(w,"REPEAT") OR Eq(w,"RETURN") OR
  1177. Eq(w,"SET") OR Eq(w,"THEN") OR Eq(w,"TO") OR Eq(w,"TYPE") OR
  1178. Eq(w,"UNTIL") OR Eq(w,"VAR") OR Eq(w,"WHILE") OR Eq(w,"WITH")
  1179. END IsKeywordM2;
  1180. PROCEDURE IsKeywordOberon (w : ARRAY OF CHAR) : BOOLEAN;
  1181. BEGIN
  1182. RETURN Eq(w,"ARRAY") OR Eq(w,"BEGIN") OR Eq(w,"BY") OR Eq(w,"CASE") OR
  1183. Eq(w,"CONST") OR Eq(w,"DIV") OR Eq(w,"DO") OR Eq(w,"ELSE") OR
  1184. Eq(w,"ELSIF") OR Eq(w,"END") OR Eq(w,"FALSE") OR Eq(w,"FOR") OR
  1185. Eq(w,"IF") OR Eq(w,"IMPORT") OR Eq(w,"IN") OR Eq(w,"IS") OR
  1186. Eq(w,"MOD") OR Eq(w,"MODULE") OR Eq(w,"NIL") OR Eq(w,"OF") OR
  1187. Eq(w,"OR") OR Eq(w,"POINTER") OR Eq(w,"PROCEDURE") OR Eq(w,"RECORD") OR
  1188. Eq(w,"REPEAT") OR Eq(w,"RETURN") OR Eq(w,"THEN") OR Eq(w,"TO") OR
  1189. Eq(w,"TRUE") OR Eq(w,"TYPE") OR Eq(w,"UNTIL") OR Eq(w,"VAR") OR
  1190. Eq(w,"WHILE") OR Eq(w,"WITH")
  1191. END IsKeywordOberon;
  1192. PROCEDURE IsKeywordPascal (w : ARRAY OF CHAR) : BOOLEAN;
  1193. BEGIN
  1194. RETURN Eq(w,"AND") OR Eq(w,"ARRAY") OR Eq(w,"BEGIN") OR Eq(w,"CASE") OR
  1195. Eq(w,"CONST") OR Eq(w,"DIV") OR Eq(w,"DO") OR Eq(w,"DOWNTO") OR
  1196. Eq(w,"ELSE") OR Eq(w,"END") OR Eq(w,"FILE") OR Eq(w,"FOR") OR
  1197. Eq(w,"FUNCTION") OR Eq(w,"GOTO") OR Eq(w,"IF") OR Eq(w,"IN") OR
  1198. Eq(w,"LABEL") OR Eq(w,"MOD") OR Eq(w,"NIL") OR Eq(w,"NOT") OR
  1199. Eq(w,"OF") OR Eq(w,"OR") OR Eq(w,"PACKED") OR Eq(w,"PROCEDURE") OR
  1200. Eq(w,"PROGRAM") OR Eq(w,"RECORD") OR Eq(w,"REPEAT") OR Eq(w,"SET") OR
  1201. Eq(w,"THEN") OR Eq(w,"TO") OR Eq(w,"TYPE") OR Eq(w,"UNTIL") OR
  1202. Eq(w,"VAR") OR Eq(w,"WHILE") OR Eq(w,"WITH")
  1203. END IsKeywordPascal;
  1204. PROCEDURE IsKeywordLang (lang : INTEGER; w : ARRAY OF CHAR) : BOOLEAN;
  1205. BEGIN
  1206. IF lang = langModula2 THEN RETURN IsKeywordM2(w) END;
  1207. IF lang = langOberon THEN RETURN IsKeywordOberon(w) END;
  1208. IF lang = langPascal THEN RETURN IsKeywordPascal(w) END;
  1209. RETURN FALSE
  1210. END IsKeywordLang;
  1211. PROCEDURE HiPut (v : ViewPtr; c : INTEGER; ch : CHAR; attr : Attr;
  1212. x0, y0, w : INTEGER; draw : BOOLEAN);
  1213. BEGIN
  1214. IF draw AND (c >= v^.ed^.left) AND (c - v^.ed^.left < w) THEN
  1215. DrawCh(x0 + (c - v^.ed^.left), y0, VAL(CARDINAL, ORD(ch)), attr)
  1216. END
  1217. END HiPut;
  1218. PROCEDURE DrawLineHi (v : ViewPtr; ln : INTEGER; x0, y0, w : INTEGER;
  1219. draw : BOOLEAN; VAR state : INTEGER);
  1220. VAR ed : EditorDataPtr; lang, i, j, n : INTEGER; ch, q : CHAR; done : BOOLEAN;
  1221. kw, str, com, num : Attr; word : ARRAY [0..63] OF CHAR;
  1222. BEGIN
  1223. ed := v^.ed;
  1224. lang := ed^.lang;
  1225. kw := A(White, Cyan, FALSE);
  1226. str := A(Red, Cyan, FALSE);
  1227. com := A(DarkGray, Cyan, FALSE);
  1228. num := A(Red, Cyan, FALSE);
  1229. i := 0;
  1230. WHILE i < ed^.len[ln] DO
  1231. ch := ed^.line[ln][i];
  1232. IF state > 0 THEN
  1233. (* inside a (* ... *) comment (nestable for M2/Oberon) *)
  1234. IF (ch = '(') AND (i + 1 < ed^.len[ln]) AND
  1235. (ed^.line[ln][i + 1] = '*') THEN
  1236. HiPut(v, i, ch, com, x0, y0, w, draw);
  1237. HiPut(v, i + 1, '*', com, x0, y0, w, draw);
  1238. state := state + 1; i := i + 2
  1239. ELSIF (ch = '*') AND (i + 1 < ed^.len[ln]) AND
  1240. (ed^.line[ln][i + 1] = ')') THEN
  1241. HiPut(v, i, ch, com, x0, y0, w, draw);
  1242. HiPut(v, i + 1, ')', com, x0, y0, w, draw);
  1243. state := state - 1; i := i + 2
  1244. ELSE
  1245. HiPut(v, i, ch, com, x0, y0, w, draw); i := i + 1
  1246. END
  1247. ELSIF state < 0 THEN
  1248. (* inside a Pascal { ... } comment *)
  1249. HiPut(v, i, ch, com, x0, y0, w, draw);
  1250. IF ch = '}' THEN state := 0 END;
  1251. i := i + 1
  1252. ELSIF (ch = '(') AND (i + 1 < ed^.len[ln]) AND
  1253. (ed^.line[ln][i + 1] = '*') THEN
  1254. HiPut(v, i, '(', com, x0, y0, w, draw);
  1255. HiPut(v, i + 1, '*', com, x0, y0, w, draw);
  1256. state := 1; i := i + 2
  1257. ELSIF (lang = langPascal) AND (ch = '{') THEN
  1258. HiPut(v, i, ch, com, x0, y0, w, draw);
  1259. state := -1; i := i + 1
  1260. ELSIF (ch = '/') AND (i + 1 < ed^.len[ln]) AND
  1261. (ed^.line[ln][i + 1] = '/') AND (lang # langNone) THEN
  1262. WHILE i < ed^.len[ln] DO
  1263. HiPut(v, i, ed^.line[ln][i], com, x0, y0, w, draw); i := i + 1
  1264. END
  1265. ELSIF (ch = CHR(39)) OR (ch = CHR(34)) THEN
  1266. q := ch;
  1267. HiPut(v, i, ch, str, x0, y0, w, draw); i := i + 1;
  1268. done := FALSE;
  1269. WHILE (i < ed^.len[ln]) AND (NOT done) DO
  1270. HiPut(v, i, ed^.line[ln][i], str, x0, y0, w, draw);
  1271. IF ed^.line[ln][i] = q THEN
  1272. IF (i + 1 < ed^.len[ln]) AND (ed^.line[ln][i + 1] = q) THEN
  1273. i := i + 1;
  1274. HiPut(v, i, q, str, x0, y0, w, draw)
  1275. ELSE
  1276. done := TRUE
  1277. END
  1278. END;
  1279. i := i + 1
  1280. END
  1281. ELSIF (ch >= '0') AND (ch <= '9') THEN
  1282. WHILE (i < ed^.len[ln]) AND (ed^.line[ln][i] >= '0') AND
  1283. (ed^.line[ln][i] <= '9') DO
  1284. HiPut(v, i, ed^.line[ln][i], num, x0, y0, w, draw); i := i + 1
  1285. END
  1286. ELSIF IsIdentChar(ch) THEN
  1287. n := 0;
  1288. WHILE (i < ed^.len[ln]) AND IsIdentChar(ed^.line[ln][i]) AND (n < 63) DO
  1289. word[n] := ed^.line[ln][i];
  1290. n := n + 1; i := i + 1
  1291. END;
  1292. word[n] := 0C;
  1293. UpcaseCopy(word, word);
  1294. FOR j := i - n TO i - 1 DO
  1295. IF IsKeywordLang(lang, word) THEN
  1296. HiPut(v, j, ed^.line[ln][j], kw, x0, y0, w, draw)
  1297. ELSE
  1298. HiPut(v, j, ed^.line[ln][j], A(Black, Cyan, FALSE), x0, y0, w, draw)
  1299. END
  1300. END
  1301. ELSE
  1302. HiPut(v, i, ch, A(Black, Cyan, FALSE), x0, y0, w, draw);
  1303. i := i + 1
  1304. END
  1305. END
  1306. END DrawLineHi;
  1307. PROCEDURE EdUpcaseWord (data : EditorDataPtr);
  1308. VAR ln, i, n, k : INTEGER; w, up : ARRAY [0..63] OF CHAR;
  1309. BEGIN
  1310. IF (data = NIL) OR ((data^.lang # langModula2) AND (data^.lang # langOberon)) THEN
  1311. RETURN
  1312. END;
  1313. EdEnsure(data);
  1314. ln := data^.cy;
  1315. i := data^.cx;
  1316. WHILE (i > 0) AND IsIdentChar(data^.line[ln][i - 1]) DO i := i - 1 END;
  1317. n := data^.cx - i;
  1318. IF (n <= 0) OR (n > 63) THEN RETURN END;
  1319. FOR k := 0 TO n - 1 DO w[k] := data^.line[ln][i + k] END;
  1320. w[n] := 0C;
  1321. UpcaseCopy(up, w);
  1322. IF IsKeywordLang(data^.lang, up) AND (NOT Eq(w, up)) THEN
  1323. FOR k := 0 TO n - 1 DO data^.line[ln][i + k] := up[k] END;
  1324. data^.dirty := TRUE
  1325. END
  1326. END EdUpcaseWord;
  1327. PROCEDURE DrawEditor (v : ViewPtr);
  1328. VAR x0, y0, w, h, vis, r, ln, c, hstate, l : INTEGER;
  1329. ed : EditorDataPtr; s, t : ARRAY [0..95] OF CHAR;
  1330. BEGIN
  1331. DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE));
  1332. x0 := v^.rect.x + 1; y0 := v^.rect.y + 1;
  1333. w := v^.rect.w - 2; h := v^.rect.h - 2;
  1334. IF (w <= 0) OR (h <= 0) THEN RETURN END;
  1335. vis := h - 1;
  1336. IF vis < 1 THEN vis := h END;
  1337. ed := v^.ed;
  1338. IF ed = NIL THEN RETURN END;
  1339. EdEnsure(ed);
  1340. IF ed^.cy < ed^.top THEN ed^.top := ed^.cy END;
  1341. IF ed^.cy >= ed^.top + vis THEN ed^.top := ed^.cy - vis + 1 END;
  1342. IF ed^.cx < ed^.left THEN ed^.left := ed^.cx END;
  1343. IF ed^.cx >= ed^.left + w THEN ed^.left := ed^.cx - w + 1 END;
  1344. IF ed^.top < 0 THEN ed^.top := 0 END;
  1345. IF ed^.left < 0 THEN ed^.left := 0 END;
  1346. hstate := 0;
  1347. IF ed^.lang # langNone THEN
  1348. FOR l := 0 TO ed^.top - 1 DO
  1349. IF l < ed^.count THEN DrawLineHi(v, l, x0, y0, w, FALSE, hstate) END
  1350. END
  1351. END;
  1352. FOR r := 0 TO vis - 1 DO
  1353. Fill(x0, y0 + r, w, 1, VAL(CARDINAL, ORD(' ')), A(Black, Cyan, FALSE));
  1354. ln := ed^.top + r;
  1355. IF ln < ed^.count THEN
  1356. IF ed^.lang = langNone THEN
  1357. c := 0;
  1358. WHILE (c < w) AND (ed^.left + c < ed^.len[ln]) DO
  1359. DrawCh(x0 + c, y0 + r,
  1360. VAL(CARDINAL, ORD(ed^.line[ln][ed^.left + c])),
  1361. A(Black, Cyan, FALSE));
  1362. c := c + 1
  1363. END
  1364. ELSE
  1365. DrawLineHi(v, ln, x0, y0 + r, w, TRUE, hstate)
  1366. END
  1367. END
  1368. END;
  1369. Fill(x0, y0 + vis, w, 1, VAL(CARDINAL, ORD(' ')), A(Black, LightGray, FALSE));
  1370. CopyText(s, " Ln ");
  1371. IntToText(t, ed^.cy + 1, 0); AppendText(s, t);
  1372. AppendText(s, " Col ");
  1373. IntToText(t, ed^.cx + 1, 0); AppendText(s, t);
  1374. IF ed^.ins THEN AppendText(s, " Insert") ELSE AppendText(s, " Overwrite") END;
  1375. IF ed^.dirty THEN AppendText(s, " Modified") END;
  1376. DrawText(x0, y0 + vis, s, A(Black, LightGray, FALSE));
  1377. IF IsFocused(v) THEN
  1378. SetCursor(x0 + (ed^.cx - ed^.left), y0 + (ed^.cy - ed^.top))
  1379. END
  1380. END DrawEditor;
  1381. PROCEDURE EditorAttach (v : ViewPtr; data : EditorDataPtr);
  1382. BEGIN
  1383. IF v # NIL THEN v^.ed := data END
  1384. END EditorAttach;
  1385. PROCEDURE EditorInit (data : EditorDataPtr);
  1386. BEGIN
  1387. IF data # NIL THEN
  1388. data^.count := 1;
  1389. EdClrLine(data^.line[0]);
  1390. data^.len[0] := 0;
  1391. data^.cx := 0; data^.cy := 0; data^.top := 0; data^.left := 0;
  1392. data^.ins := TRUE; data^.dirty := FALSE; data^.lang := langNone
  1393. END
  1394. END EditorInit;
  1395. PROCEDURE EditorAddLine (data : EditorDataPtr; s : ARRAY OF CHAR);
  1396. VAR i, n : INTEGER;
  1397. BEGIN
  1398. IF data = NIL THEN RETURN END;
  1399. IF data^.count >= EDMAXLINES THEN RETURN END;
  1400. IF (data^.count = 1) AND (data^.len[0] = 0) THEN
  1401. i := 0; data^.count := 0
  1402. ELSE
  1403. i := data^.count
  1404. END;
  1405. n := TextLen(s);
  1406. IF n > EDMAXCOL - 1 THEN n := EDMAXCOL - 1 END;
  1407. CopyText(data^.line[i], s);
  1408. data^.len[i] := n;
  1409. data^.count := data^.count + 1
  1410. END EditorAddLine;
  1411. PROCEDURE EditorGetLine (data : EditorDataPtr; i : INTEGER; VAR s : ARRAY OF CHAR);
  1412. BEGIN
  1413. IF (data # NIL) AND (i >= 0) AND (i < data^.count) THEN
  1414. CopyText(s, data^.line[i])
  1415. ELSE
  1416. s[0] := 0C
  1417. END
  1418. END EditorGetLine;
  1419. PROCEDURE EditorLoad (data : EditorDataPtr; path : ARRAY OF CHAR) : BOOLEAN;
  1420. VAR f : ADDRESS; buf : ARRAY [0..EDMAXCOL] OF CHAR; n : INTEGER;
  1421. discard : INTEGER;
  1422. BEGIN
  1423. IF data = NIL THEN RETURN FALSE END;
  1424. f := fopen(path, "r");
  1425. IF f = NIL THEN RETURN FALSE END;
  1426. data^.count := 0;
  1427. WHILE fgets(ADR(buf), EDMAXCOL, f) # NIL DO
  1428. n := TextLen(buf);
  1429. WHILE (n > 0) AND ((buf[n - 1] = CHR(10)) OR (buf[n - 1] = CHR(13))) DO
  1430. buf[n - 1] := 0C;
  1431. n := n - 1
  1432. END;
  1433. EditorAddLine(data, buf)
  1434. END;
  1435. discard := fclose(f);
  1436. IF data^.count = 0 THEN
  1437. data^.count := 1;
  1438. EdClrLine(data^.line[0]);
  1439. data^.len[0] := 0
  1440. END;
  1441. data^.cx := 0; data^.cy := 0; data^.top := 0; data^.left := 0;
  1442. data^.dirty := FALSE;
  1443. RETURN TRUE
  1444. END EditorLoad;
  1445. PROCEDURE EditorSave (data : EditorDataPtr; path : ARRAY OF CHAR) : BOOLEAN;
  1446. VAR f : ADDRESS; i : INTEGER; d1, d2 : INTEGER;
  1447. BEGIN
  1448. IF data = NIL THEN RETURN FALSE END;
  1449. f := fopen(path, "w");
  1450. IF f = NIL THEN RETURN FALSE END;
  1451. FOR i := 0 TO data^.count - 1 DO
  1452. d1 := fputs(data^.line[i], f);
  1453. d2 := fputc(10, f)
  1454. END;
  1455. d1 := fclose(f);
  1456. data^.dirty := FALSE;
  1457. RETURN TRUE
  1458. END EditorSave;
  1459. PROCEDURE EditorDirty (data : EditorDataPtr) : BOOLEAN;
  1460. BEGIN
  1461. IF data = NIL THEN RETURN FALSE END;
  1462. RETURN data^.dirty
  1463. END EditorDirty;
  1464. PROCEDURE EditorSetLanguage (data : EditorDataPtr; lang : INTEGER);
  1465. BEGIN
  1466. IF data # NIL THEN data^.lang := lang END
  1467. END EditorSetLanguage;
  1468. PROCEDURE DrawView(v : ViewPtr);
  1469. VAR c : ViewPtr;
  1470. BEGIN
  1471. IF v = NIL THEN RETURN END;
  1472. CASE v^.kind OF
  1473. VGroup : (* nothing *)
  1474. |
  1475. VWindow : DrawWindow(v)
  1476. |
  1477. VText : DrawTextCtl(v)
  1478. |
  1479. VFrame : DrawBox(v^.rect, SingleFrame, A(White, Cyan, FALSE))
  1480. |
  1481. VInput : DrawInput(v)
  1482. |
  1483. VButton : DrawButton(v)
  1484. |
  1485. VCheck : DrawCheck(v)
  1486. |
  1487. VRadio : DrawRadio(v)
  1488. |
  1489. VList : DrawList(v)
  1490. |
  1491. VScroll : DrawScroll(v)
  1492. |
  1493. VEditor : DrawEditor(v)
  1494. END;
  1495. IF v^.first # NIL THEN
  1496. IF (v^.kind = VWindow) OR v^.clipKids THEN
  1497. PushClipRect(v^.rect)
  1498. END;
  1499. c := v^.first;
  1500. WHILE c # NIL DO
  1501. DrawView(c);
  1502. c := c^.next
  1503. END;
  1504. IF (v^.kind = VWindow) OR v^.clipKids THEN PopClip END
  1505. END
  1506. END DrawView;
  1507. (*========================================================================*)
  1508. (* Menu bar + drop-down menus *)
  1509. (*========================================================================*)
  1510. PROCEDURE MenuBarInit;
  1511. BEGIN
  1512. menuCount := 0; menuOpen := -1; menuSel := 0; menuShown := FALSE
  1513. END MenuBarInit;
  1514. PROCEDURE MenuAdd (title : ARRAY OF CHAR);
  1515. BEGIN
  1516. IF menuCount < MAXMENUS THEN
  1517. CopyText(menus[menuCount].title, title);
  1518. menus[menuCount].count := 0;
  1519. menuCount := menuCount + 1;
  1520. menuShown := TRUE
  1521. END
  1522. END MenuAdd;
  1523. PROCEDURE MenuItemAdd (text : ARRAY OF CHAR; tag : INTEGER);
  1524. VAR i : INTEGER;
  1525. BEGIN
  1526. IF menuCount > 0 THEN
  1527. i := menus[menuCount - 1].count;
  1528. IF i < MAXMITEMS THEN
  1529. CopyText(menus[menuCount - 1].items[i].text, text);
  1530. menus[menuCount - 1].items[i].tag := tag;
  1531. menus[menuCount - 1].items[i].sep := FALSE;
  1532. menus[menuCount - 1].count := i + 1
  1533. END
  1534. END
  1535. END MenuItemAdd;
  1536. PROCEDURE MenuSep;
  1537. VAR i : INTEGER;
  1538. BEGIN
  1539. IF menuCount > 0 THEN
  1540. i := menus[menuCount - 1].count;
  1541. IF i < MAXMITEMS THEN
  1542. menus[menuCount - 1].items[i].text[0] := 0C;
  1543. menus[menuCount - 1].items[i].tag := 0;
  1544. menus[menuCount - 1].items[i].sep := TRUE;
  1545. menus[menuCount - 1].count := i + 1
  1546. END
  1547. END
  1548. END MenuSep;
  1549. PROCEDURE MenuBar (show : BOOLEAN);
  1550. BEGIN
  1551. menuShown := show;
  1552. IF NOT show THEN menuOpen := -1 END
  1553. END MenuBar;
  1554. PROCEDURE MenuTitleX (i : INTEGER) : INTEGER;
  1555. VAR k, x : INTEGER;
  1556. BEGIN
  1557. x := 1;
  1558. FOR k := 0 TO i - 1 DO
  1559. x := x + TextLen(menus[k].title) + 2
  1560. END;
  1561. RETURN x
  1562. END MenuTitleX;
  1563. PROCEDURE MenuWidth (i : INTEGER) : INTEGER;
  1564. VAR k, w, l : INTEGER;
  1565. BEGIN
  1566. w := TextLen(menus[i].title) + 2;
  1567. FOR k := 0 TO menus[i].count - 1 DO
  1568. l := TextLen(menus[i].items[k].text) + 4;
  1569. IF l > w THEN w := l END
  1570. END;
  1571. RETURN w
  1572. END MenuWidth;
  1573. PROCEDURE MenuTitleAt (x : INTEGER) : INTEGER;
  1574. VAR i, xx : INTEGER;
  1575. BEGIN
  1576. FOR i := 0 TO menuCount - 1 DO
  1577. xx := MenuTitleX(i);
  1578. IF (x >= xx) AND (x < xx + TextLen(menus[i].title)) THEN RETURN i END
  1579. END;
  1580. RETURN -1
  1581. END MenuTitleAt;
  1582. PROCEDURE FirstSel (i : INTEGER) : INTEGER;
  1583. VAR k : INTEGER;
  1584. BEGIN
  1585. k := 0;
  1586. WHILE (k < menus[i].count) AND menus[i].items[k].sep DO k := k + 1 END;
  1587. IF k >= menus[i].count THEN k := 0 END;
  1588. RETURN k
  1589. END FirstSel;
  1590. PROCEDURE MenuStep (i, cur, dir : INTEGER) : INTEGER;
  1591. VAR k, n : INTEGER; found : BOOLEAN;
  1592. BEGIN
  1593. n := menus[i].count;
  1594. IF n = 0 THEN RETURN 0 END;
  1595. k := cur; found := FALSE;
  1596. WHILE NOT found DO
  1597. k := (k + dir + n) MOD n;
  1598. IF (NOT menus[i].items[k].sep) OR (k = cur) THEN found := TRUE END
  1599. END;
  1600. RETURN k
  1601. END MenuStep;
  1602. PROCEDURE DrawMenuBar;
  1603. VAR i, k, x : INTEGER; r : Rect; attr : Attr;
  1604. space : CARDINAL;
  1605. BEGIN
  1606. IF (NOT menuShown) OR (menuCount = 0) THEN RETURN END;
  1607. space := VAL(CARDINAL, ORD(' '));
  1608. Fill(0, 0, cols, 1, space, A(Black, LightGray, FALSE));
  1609. FOR i := 0 TO menuCount - 1 DO
  1610. x := MenuTitleX(i);
  1611. IF menuOpen = i THEN attr := A(White, Blue, FALSE)
  1612. ELSE attr := A(Black, LightGray, FALSE) END;
  1613. DrawText(x, 0, menus[i].title, attr)
  1614. END;
  1615. IF menuOpen < 0 THEN RETURN END;
  1616. i := menuOpen;
  1617. r.x := MenuTitleX(i); r.y := 1;
  1618. r.w := MenuWidth(i); r.h := menus[i].count + 2;
  1619. DrawShadow(r, A(Black, Black, FALSE));
  1620. Fill(r.x, r.y, r.w, r.h, space, A(Black, LightGray, FALSE));
  1621. DrawBox(r, SingleFrame, A(Black, LightGray, FALSE));
  1622. FOR k := 0 TO menus[i].count - 1 DO
  1623. IF menus[i].items[k].sep THEN
  1624. DrawHLine(r.x + 1, r.y + 1 + k, r.w - 2, H_SINGLE,
  1625. A(DarkGray, LightGray, FALSE))
  1626. ELSE
  1627. IF k = menuSel THEN attr := A(White, Blue, FALSE)
  1628. ELSE attr := A(Black, LightGray, FALSE) END;
  1629. Fill(r.x + 1, r.y + 1 + k, r.w - 2, 1, space, attr);
  1630. DrawText(r.x + 2, r.y + 1 + k, menus[i].items[k].text, attr)
  1631. END
  1632. END
  1633. END DrawMenuBar;
  1634. PROCEDURE MenuHandle (e : Event; VAR handled : BOOLEAN) : INTEGER;
  1635. VAR i, idx : INTEGER; r : Rect;
  1636. BEGIN
  1637. handled := FALSE;
  1638. IF (NOT menuShown) OR (menuCount = 0) THEN RETURN cmNone END;
  1639. IF e.kind = evKey THEN
  1640. IF e.key = kCtrlC THEN RETURN cmNone END;
  1641. IF e.key = kF10 THEN
  1642. IF menuOpen < 0 THEN menuOpen := 0 ELSE menuOpen := (menuOpen + 1) MOD menuCount END;
  1643. menuSel := FirstSel(menuOpen);
  1644. handled := TRUE; RETURN cmNone
  1645. END;
  1646. IF menuOpen >= 0 THEN
  1647. handled := TRUE;
  1648. IF e.key = kEsc THEN menuOpen := -1
  1649. ELSIF e.key = kLeft THEN
  1650. menuOpen := (menuOpen + menuCount - 1) MOD menuCount;
  1651. menuSel := FirstSel(menuOpen)
  1652. ELSIF e.key = kRight THEN
  1653. menuOpen := (menuOpen + 1) MOD menuCount;
  1654. menuSel := FirstSel(menuOpen)
  1655. ELSIF e.key = kUp THEN menuSel := MenuStep(menuOpen, menuSel, -1)
  1656. ELSIF e.key = kDown THEN menuSel := MenuStep(menuOpen, menuSel, +1)
  1657. ELSIF (e.key = kEnter) OR (e.key = kSpace) THEN
  1658. IF NOT menus[menuOpen].items[menuSel].sep THEN
  1659. i := menus[menuOpen].items[menuSel].tag;
  1660. menuOpen := -1; lastSender := NIL;
  1661. RETURN i
  1662. END
  1663. END;
  1664. RETURN cmNone
  1665. END;
  1666. RETURN cmNone
  1667. END;
  1668. IF e.kind # evMouse THEN RETURN cmNone END;
  1669. (* mouse over a title *)
  1670. IF e.my = 0 THEN
  1671. i := MenuTitleAt(e.mx);
  1672. IF i >= 0 THEN
  1673. handled := TRUE;
  1674. IF e.mpressed THEN
  1675. IF menuOpen = i THEN menuOpen := -1
  1676. ELSE menuOpen := i; menuSel := FirstSel(i) END
  1677. ELSIF (e.mbtn > 0) OR (e.mwheel # 0) THEN
  1678. menuOpen := i; menuSel := FirstSel(i)
  1679. ELSIF menuOpen >= 0 THEN
  1680. menuOpen := i; menuSel := FirstSel(i)
  1681. END;
  1682. RETURN cmNone
  1683. END
  1684. END;
  1685. IF menuOpen >= 0 THEN
  1686. r.x := MenuTitleX(menuOpen); r.y := 1;
  1687. r.w := MenuWidth(menuOpen); r.h := menus[menuOpen].count + 2;
  1688. IF Contains(r, e.mx, e.my) THEN
  1689. handled := TRUE;
  1690. idx := e.my - (r.y + 1);
  1691. IF (idx >= 0) AND (idx < menus[menuOpen].count) AND
  1692. (NOT menus[menuOpen].items[idx].sep) THEN
  1693. menuSel := idx;
  1694. IF e.mpressed THEN
  1695. i := menus[menuOpen].items[idx].tag;
  1696. menuOpen := -1; lastSender := NIL;
  1697. RETURN i
  1698. END
  1699. END;
  1700. RETURN cmNone
  1701. ELSIF e.mpressed THEN
  1702. menuOpen := -1;
  1703. handled := TRUE
  1704. END
  1705. END;
  1706. RETURN cmNone
  1707. END MenuHandle;
  1708. PROCEDURE DrawTree;
  1709. BEGIN
  1710. Clear(A(LightGray, Blue, FALSE));
  1711. HideCursor;
  1712. clipN := 0;
  1713. IF desk # NIL THEN DrawView(desk) END;
  1714. DrawMenuBar
  1715. END DrawTree;
  1716. PROCEDURE Finish;
  1717. BEGIN
  1718. DrawTree;
  1719. Present
  1720. END Finish;
  1721. (*========================================================================*)
  1722. (* Geometry + hit testing *)
  1723. (*========================================================================*)
  1724. PROCEDURE Contains(r : Rect; x, y : INTEGER) : BOOLEAN;
  1725. BEGIN
  1726. RETURN (x >= r.x) AND (x < r.x + r.w) AND (y >= r.y) AND (y < r.y + r.h)
  1727. END Contains;
  1728. PROCEDURE HitTest(v : ViewPtr; x, y : INTEGER) : ViewPtr;
  1729. VAR c, hit : ViewPtr;
  1730. BEGIN
  1731. IF (v = NIL) OR (NOT Contains(v^.rect, x, y)) THEN RETURN NIL END;
  1732. c := v^.last;
  1733. WHILE c # NIL DO
  1734. IF Contains(c^.rect, x, y) THEN
  1735. hit := HitTest(c, x, y);
  1736. IF hit # NIL THEN RETURN hit END
  1737. END;
  1738. c := c^.prev
  1739. END;
  1740. RETURN v
  1741. END HitTest;
  1742. PROCEDURE WindowAncestor(v : ViewPtr) : ViewPtr;
  1743. VAR p : ViewPtr;
  1744. BEGIN
  1745. p := v;
  1746. WHILE p # NIL DO
  1747. IF p^.kind = VWindow THEN RETURN p END;
  1748. p := p^.parent
  1749. END;
  1750. RETURN NIL
  1751. END WindowAncestor;
  1752. (*========================================================================*)
  1753. (* Event dispatch *)
  1754. (*========================================================================*)
  1755. PROCEDURE EditKey(v : ViewPtr; VAR e : Event);
  1756. VAR i : INTEGER;
  1757. BEGIN
  1758. IF e.key = kLeft THEN
  1759. IF v^.pos > 0 THEN v^.pos := v^.pos - 1 END
  1760. ELSIF e.key = kRight THEN
  1761. IF v^.pos < v^.len THEN v^.pos := v^.pos + 1 END
  1762. ELSIF e.key = kHome THEN v^.pos := 0
  1763. ELSIF e.key = kEnd THEN v^.pos := v^.len
  1764. ELSIF e.key = kBack THEN
  1765. IF v^.pos > 0 THEN
  1766. i := v^.pos - 1;
  1767. WHILE i < v^.len - 1 DO v^.text[i] := v^.text[i + 1]; i := i + 1 END;
  1768. v^.len := v^.len - 1; v^.pos := v^.pos - 1; v^.text[v^.len] := 0C
  1769. END
  1770. ELSIF e.key = kDel THEN
  1771. IF v^.pos < v^.len THEN
  1772. i := v^.pos;
  1773. WHILE i < v^.len - 1 DO v^.text[i] := v^.text[i + 1]; i := i + 1 END;
  1774. v^.len := v^.len - 1; v^.text[v^.len] := 0C
  1775. END
  1776. ELSIF e.key = kChar THEN
  1777. IF (v^.len < MAXLINE - 1) AND (e.ch < 256) THEN
  1778. i := v^.len;
  1779. WHILE i > v^.pos DO v^.text[i] := v^.text[i - 1]; i := i - 1 END;
  1780. v^.text[v^.pos] := CHR(e.ch);
  1781. v^.len := v^.len + 1; v^.pos := v^.pos + 1
  1782. END
  1783. END
  1784. END EditKey;
  1785. PROCEDURE SelectRadio(v : ViewPtr);
  1786. VAR c : ViewPtr;
  1787. BEGIN
  1788. IF (v = NIL) OR (v^.parent = NIL) THEN RETURN END;
  1789. c := v^.parent^.first;
  1790. WHILE c # NIL DO
  1791. IF (c # v) AND (c^.kind = VRadio) AND
  1792. ((c^.flags DIV vfSelected) MOD 2 = 1) THEN
  1793. c^.flags := c^.flags - vfSelected
  1794. END;
  1795. c := c^.next
  1796. END;
  1797. IF (v^.flags DIV vfSelected) MOD 2 = 0 THEN
  1798. v^.flags := v^.flags + vfSelected
  1799. END
  1800. END SelectRadio;
  1801. PROCEDURE WidgetKey(v : ViewPtr; VAR e : Event) : INTEGER;
  1802. VAR cmd : INTEGER;
  1803. BEGIN
  1804. cmd := cmNone;
  1805. IF v^.kind = VInput THEN
  1806. EditKey(v, e)
  1807. ELSIF v^.kind = VButton THEN
  1808. IF (e.key = kEnter) OR (e.key = kSpace) THEN cmd := v^.tag END
  1809. ELSIF v^.kind = VCheck THEN
  1810. IF (e.key = kEnter) OR (e.key = kSpace) THEN
  1811. v^.flags := v^.flags / 4 * 4;
  1812. IF v^.flags DIV vfSelected MOD 2 = 0 THEN
  1813. v^.flags := v^.flags + vfSelected
  1814. END;
  1815. cmd := v^.tag
  1816. END
  1817. ELSIF v^.kind = VRadio THEN
  1818. IF (e.key = kEnter) OR (e.key = kSpace) THEN
  1819. SelectRadio(v);
  1820. cmd := v^.tag
  1821. END
  1822. ELSIF v^.kind = VList THEN
  1823. IF e.key = kUp THEN
  1824. IF v^.value > 0 THEN v^.value := v^.value - 1 END;
  1825. IF v^.value < v^.pos THEN v^.pos := v^.value END
  1826. ELSIF e.key = kDown THEN
  1827. IF (v^.list # NIL) AND (v^.value < v^.list^.count - 1) THEN
  1828. v^.value := v^.value + 1
  1829. END;
  1830. IF v^.value >= v^.pos + (v^.rect.h - 2) THEN
  1831. v^.pos := v^.value - (v^.rect.h - 2) + 1
  1832. END
  1833. ELSIF (e.key = kEnter) OR (e.key = kSpace) THEN
  1834. cmd := v^.tag
  1835. END
  1836. ELSIF v^.kind = VScroll THEN
  1837. IF e.key = kUp THEN
  1838. IF v^.value > 0 THEN v^.value := v^.value - 1 END
  1839. ELSIF e.key = kDown THEN
  1840. IF v^.value < v^.maxv THEN v^.value := v^.value + 1 END
  1841. END
  1842. ELSIF v^.kind = VEditor THEN
  1843. IF v^.ed # NIL THEN
  1844. IF e.key = kChar THEN
  1845. IF NOT IsIdentChar(CHR(e.ch)) THEN EdUpcaseWord(v^.ed) END;
  1846. EdInsertChar(v^.ed, e.ch)
  1847. ELSIF e.key = kSpace THEN
  1848. EdUpcaseWord(v^.ed);
  1849. EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')))
  1850. ELSIF e.key = kEnter THEN
  1851. EdUpcaseWord(v^.ed); EdNewLine(v^.ed)
  1852. ELSIF e.key = kBack THEN EdBackspace(v^.ed)
  1853. ELSIF e.key = kDel THEN EdDelete(v^.ed)
  1854. ELSIF e.key = kIns THEN v^.ed^.ins := NOT v^.ed^.ins
  1855. ELSIF e.key = kTab THEN
  1856. EdUpcaseWord(v^.ed);
  1857. EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')));
  1858. WHILE (v^.ed^.cx MOD 4) # 0 DO
  1859. EdInsertChar(v^.ed, VAL(CARDINAL, ORD(' ')))
  1860. END
  1861. ELSE EdMove(v^.ed, v, e.key)
  1862. END
  1863. END
  1864. END;
  1865. IF cmd # cmNone THEN lastSender := v END;
  1866. RETURN cmd
  1867. END WidgetKey;
  1868. PROCEDURE WidgetMouse(v : ViewPtr; VAR e : Event) : INTEGER;
  1869. VAR cmd : INTEGER; rel, ln, col : INTEGER;
  1870. BEGIN
  1871. cmd := cmNone;
  1872. IF v^.kind = VButton THEN
  1873. IF e.mpressed THEN
  1874. v^.flags := v^.flags + vfPressed
  1875. ELSIF e.mreleased THEN
  1876. IF (v^.flags DIV vfPressed) MOD 2 = 1 THEN
  1877. cmd := v^.tag
  1878. END;
  1879. v^.flags := v^.flags / 8 * 8
  1880. END
  1881. ELSIF v^.kind = VCheck THEN
  1882. IF e.mreleased THEN
  1883. IF v^.flags DIV vfSelected MOD 2 = 1 THEN
  1884. v^.flags := v^.flags - vfSelected
  1885. ELSE
  1886. v^.flags := v^.flags + vfSelected
  1887. END;
  1888. cmd := v^.tag
  1889. END
  1890. ELSIF v^.kind = VRadio THEN
  1891. IF e.mreleased THEN
  1892. SelectRadio(v);
  1893. cmd := v^.tag
  1894. END
  1895. ELSIF v^.kind = VList THEN
  1896. IF e.mwheel # 0 THEN
  1897. IF (e.mwheel < 0) AND (v^.pos > 0) THEN v^.pos := v^.pos - 1 END;
  1898. IF (e.mwheel > 0) AND (v^.list # NIL) AND
  1899. (v^.pos + (v^.rect.h - 2) < v^.list^.count) THEN
  1900. v^.pos := v^.pos + 1
  1901. END
  1902. ELSIF e.mreleased THEN
  1903. rel := e.my - v^.rect.y - 1;
  1904. IF (rel >= 0) AND (v^.list # NIL) AND
  1905. (v^.pos + rel < v^.list^.count) THEN
  1906. v^.value := v^.pos + rel
  1907. END
  1908. END
  1909. ELSIF v^.kind = VInput THEN
  1910. IF e.mpressed THEN
  1911. rel := e.mx - v^.rect.x;
  1912. IF rel < 0 THEN rel := 0 END;
  1913. IF rel > v^.len THEN rel := v^.len END;
  1914. v^.pos := rel
  1915. END
  1916. ELSIF v^.kind = VScroll THEN
  1917. IF e.mpressed OR e.mreleased THEN
  1918. IF v^.rect.h > 1 THEN
  1919. v^.value := ((e.my - v^.rect.y) * v^.maxv) DIV (v^.rect.h - 1)
  1920. END;
  1921. IF v^.value < 0 THEN v^.value := 0 END;
  1922. IF v^.value > v^.maxv THEN v^.value := v^.maxv END
  1923. END
  1924. ELSIF v^.kind = VEditor THEN
  1925. IF v^.ed # NIL THEN
  1926. IF e.mwheel # 0 THEN
  1927. v^.ed^.top := v^.ed^.top + e.mwheel * 3;
  1928. IF v^.ed^.top < 0 THEN v^.ed^.top := 0 END
  1929. ELSIF e.mpressed THEN
  1930. ln := v^.ed^.top + (e.my - (v^.rect.y + 1));
  1931. IF (ln >= 0) AND (ln < v^.ed^.count) THEN
  1932. v^.ed^.cy := ln;
  1933. col := e.mx - (v^.rect.x + 1) + v^.ed^.left;
  1934. IF col < 0 THEN col := 0 END;
  1935. IF col > v^.ed^.len[ln] THEN col := v^.ed^.len[ln] END;
  1936. v^.ed^.cx := col
  1937. END
  1938. END
  1939. END
  1940. END;
  1941. IF cmd # cmNone THEN lastSender := v END;
  1942. RETURN cmd
  1943. END WidgetMouse;
  1944. PROCEDURE HandleWindow(v : ViewPtr; VAR e : Event) : INTEGER;
  1945. VAR w : ViewPtr; nx, ny : INTEGER;
  1946. BEGIN
  1947. IF (e.kind = evMouse) AND e.mpressed THEN
  1948. (* close box? *)
  1949. IF (e.my = v^.rect.y) AND
  1950. (e.mx >= v^.rect.x + v^.rect.w - 3) AND (e.mx < v^.rect.x + v^.rect.w) THEN
  1951. lastSender := v;
  1952. RETURN cmClose
  1953. END;
  1954. (* drag by the title row *)
  1955. IF (e.my = v^.rect.y) AND (e.mx < v^.rect.x + v^.rect.w - 3) THEN
  1956. dragWin := v;
  1957. dragOX := e.mx - v^.rect.x;
  1958. dragOY := e.my - v^.rect.y
  1959. END
  1960. ELSIF (e.kind = evMouse) AND e.mreleased THEN
  1961. dragWin := NIL
  1962. ELSIF (e.kind = evMouse) AND (e.mbtn > 0) AND (NOT e.mpressed) AND
  1963. (NOT e.mreleased) THEN
  1964. IF dragWin # NIL THEN
  1965. nx := e.mx - dragOX;
  1966. ny := e.my - dragOY;
  1967. (* keep the window's title corner on screen *)
  1968. IF nx < 0 THEN nx := 0 END;
  1969. IF nx > cols - 6 THEN nx := cols - 6 END;
  1970. IF ny < 0 THEN ny := 0 END;
  1971. IF ny > rows - 1 THEN ny := rows - 1 END;
  1972. MoveViewBy(dragWin, nx - dragWin^.rect.x, ny - dragWin^.rect.y)
  1973. END
  1974. END;
  1975. RETURN cmNone
  1976. END HandleWindow;
  1977. PROCEDURE DispatchMouse(v : ViewPtr; VAR e : Event) : INTEGER;
  1978. VAR hit : ViewPtr; cmd : INTEGER; w : ViewPtr;
  1979. BEGIN
  1980. cmd := cmNone;
  1981. hit := HitTest(v, e.mx, e.my);
  1982. IF hit = NIL THEN RETURN cmNone END;
  1983. IF hit # v THEN
  1984. w := WindowAncestor(hit);
  1985. IF (w # NIL) AND e.mpressed THEN BringToFront(w) END
  1986. END;
  1987. IF e.mpressed AND IsFocusable(hit) THEN FocusView(hit) END;
  1988. IF hit^.kind = VWindow THEN
  1989. cmd := HandleWindow(hit, e)
  1990. ELSE
  1991. cmd := WidgetMouse(hit, e)
  1992. END;
  1993. RETURN cmd
  1994. END DispatchMouse;
  1995. PROCEDURE DispatchKey(v : ViewPtr; VAR e : Event) : INTEGER;
  1996. VAR cmd : INTEGER; f : ViewPtr;
  1997. BEGIN
  1998. cmd := cmNone;
  1999. IF (v^.kind = VGroup) OR (v^.kind = VWindow) THEN
  2000. f := v^.focus;
  2001. IF f # NIL THEN cmd := DispatchKey(f, e) END
  2002. ELSE
  2003. cmd := WidgetKey(v, e)
  2004. END;
  2005. RETURN cmd
  2006. END DispatchKey;
  2007. PROCEDURE HandleEvent (e : Event) : INTEGER;
  2008. VAR cmd : INTEGER; root : ViewPtr; handled : BOOLEAN;
  2009. BEGIN
  2010. IF e.kind = evNone THEN RETURN cmNone END;
  2011. IF modal = NIL THEN
  2012. cmd := MenuHandle(e, handled);
  2013. IF handled THEN RETURN cmd END
  2014. END;
  2015. root := modal;
  2016. IF root = NIL THEN root := desk END;
  2017. IF root = NIL THEN RETURN cmNone END;
  2018. IF e.kind = evKey THEN
  2019. IF e.key = kCtrlC THEN RETURN cmClose END;
  2020. IF e.key = kTab THEN
  2021. FocusNext(e.ctrl);
  2022. RETURN cmNone
  2023. END;
  2024. cmd := DispatchKey(root, e)
  2025. ELSE
  2026. (* while a window is being dragged, keep sending motion/release to it
  2027. even if the cursor has left the window (otherwise dragging up and
  2028. sideways stops as soon as the pointer leaves the frame) *)
  2029. IF dragWin # NIL THEN
  2030. cmd := HandleWindow(dragWin, e)
  2031. ELSE
  2032. cmd := DispatchMouse(root, e)
  2033. END
  2034. END;
  2035. RETURN cmd
  2036. END HandleEvent;
  2037. PROCEDURE Sender () : ViewPtr;
  2038. BEGIN RETURN lastSender END Sender;
  2039. (*========================================================================*)
  2040. (* Modal message box *)
  2041. (*========================================================================*)
  2042. (*========================================================================*)
  2043. (* Generic dialog builder *)
  2044. (*========================================================================*)
  2045. PROCEDURE NewDlgItem (k : VKind; x, y, w, h : INTEGER;
  2046. text : ARRAY OF CHAR) : ViewPtr;
  2047. VAR v : ViewPtr;
  2048. BEGIN
  2049. v := NewView(k, x, y, w, h);
  2050. SetText(v, text);
  2051. AddView(dlg, v);
  2052. IF dlgCount < 32 THEN
  2053. dlgItems[dlgCount] := v;
  2054. dlgCount := dlgCount + 1
  2055. END;
  2056. RETURN v
  2057. END NewDlgItem;
  2058. PROCEDURE Dialog (title : ARRAY OF CHAR; w, h : INTEGER);
  2059. VAR x, y : INTEGER;
  2060. BEGIN
  2061. x := (cols - w) DIV 2;
  2062. y := (rows - h) DIV 2;
  2063. IF x < 0 THEN x := 0 END;
  2064. IF y < 0 THEN y := 0 END;
  2065. dlg := NewView(VWindow, x, y, w, h);
  2066. SetText(dlg, title);
  2067. dlgW := w; dlgWX := x; dlgWY := y;
  2068. dlgX := x + 2; dlgY := y + 2; dlgInputX := x + 18;
  2069. dlgBtnX := x + 2; dlgBtnY := y + h - 2;
  2070. dlgCount := 0
  2071. END Dialog;
  2072. PROCEDURE DlgLabel (text : ARRAY OF CHAR);
  2073. VAR v : ViewPtr;
  2074. BEGIN
  2075. IF dlg = NIL THEN RETURN END;
  2076. v := NewView(VText, dlgX, dlgY, dlgW - 4, 1);
  2077. SetText(v, text);
  2078. AddView(dlg, v);
  2079. dlgY := dlgY + 1
  2080. END DlgLabel;
  2081. PROCEDURE DlgInput (label : ARRAY OF CHAR; initial : ARRAY OF CHAR) : INTEGER;
  2082. VAR lv : ViewPtr; iw : INTEGER;
  2083. BEGIN
  2084. IF dlg = NIL THEN RETURN -1 END;
  2085. IF TextLen(label) > 0 THEN
  2086. lv := NewView(VText, dlgX, dlgY, dlgInputX - dlgX - 1, 1);
  2087. SetText(lv, label);
  2088. AddView(dlg, lv)
  2089. END;
  2090. iw := dlgWX + dlgW - 2 - dlgInputX;
  2091. IF iw < 8 THEN iw := 8 END;
  2092. IF NewDlgItem(VInput, dlgInputX, dlgY, iw, 1, initial) = NIL THEN END;
  2093. dlgY := dlgY + 2;
  2094. RETURN dlgCount - 1
  2095. END DlgInput;
  2096. PROCEDURE DlgCheck (label : ARRAY OF CHAR; checked : BOOLEAN) : INTEGER;
  2097. VAR v : ViewPtr; idx : INTEGER;
  2098. BEGIN
  2099. IF dlg = NIL THEN RETURN -1 END;
  2100. v := NewDlgItem(VCheck, dlgX, dlgY, dlgW - 4, 1, label);
  2101. idx := dlgCount - 1;
  2102. IF (v # NIL) AND checked THEN v^.flags := v^.flags + vfSelected END;
  2103. dlgY := dlgY + 1;
  2104. RETURN idx
  2105. END DlgCheck;
  2106. PROCEDURE DlgRadio (label : ARRAY OF CHAR; selected : BOOLEAN) : INTEGER;
  2107. VAR v : ViewPtr; idx : INTEGER;
  2108. BEGIN
  2109. IF dlg = NIL THEN RETURN -1 END;
  2110. v := NewDlgItem(VRadio, dlgX, dlgY, dlgW - 4, 1, label);
  2111. idx := dlgCount - 1;
  2112. IF (v # NIL) AND selected THEN v^.flags := v^.flags + vfSelected END;
  2113. dlgY := dlgY + 1;
  2114. RETURN idx
  2115. END DlgRadio;
  2116. PROCEDURE DlgButton (text : ARRAY OF CHAR; tag : INTEGER);
  2117. VAR v : ViewPtr; w : INTEGER;
  2118. BEGIN
  2119. IF dlg = NIL THEN RETURN END;
  2120. w := TextLen(text) + 4;
  2121. v := NewView(VButton, dlgBtnX, dlgBtnY, w, 1);
  2122. SetText(v, text);
  2123. SetTag(v, tag);
  2124. AddView(dlg, v);
  2125. dlgBtnX := dlgBtnX + w + 2
  2126. END DlgButton;
  2127. PROCEDURE DlgTextAt (idx : INTEGER; VAR s : ARRAY OF CHAR);
  2128. BEGIN
  2129. IF (idx >= 0) AND (idx < dlgCount) AND (dlgItems[idx]^.kind = VInput) THEN
  2130. CopyText(s, dlgItems[idx]^.text)
  2131. ELSE
  2132. s[0] := 0C
  2133. END
  2134. END DlgTextAt;
  2135. PROCEDURE DlgValue (idx : INTEGER) : INTEGER;
  2136. BEGIN
  2137. IF (idx >= 0) AND (idx < dlgCount) AND
  2138. ((dlgItems[idx]^.flags DIV vfSelected) MOD 2 = 1) THEN
  2139. RETURN 1
  2140. END;
  2141. RETURN 0
  2142. END DlgValue;
  2143. PROCEDURE DlgRun () : INTEGER;
  2144. VAR e : Event; cmd : INTEGER; done : BOOLEAN; f : ViewPtr;
  2145. BEGIN
  2146. IF dlg = NIL THEN RETURN cmCancel END;
  2147. AddView(desk, dlg);
  2148. BringToFront(dlg);
  2149. f := dlg^.first;
  2150. WHILE (f # NIL) AND (NOT IsFocusable(f)) DO f := f^.next END;
  2151. IF f # NIL THEN FocusView(f) END;
  2152. modal := dlg;
  2153. done := FALSE; cmd := cmNone;
  2154. WHILE NOT done DO
  2155. Finish;
  2156. IF ReadEvent(e) THEN
  2157. IF (e.kind = evKey) AND (e.key = kEsc) THEN
  2158. cmd := cmCancel; done := TRUE
  2159. ELSE
  2160. cmd := HandleEvent(e);
  2161. IF cmd # cmNone THEN done := TRUE END
  2162. END
  2163. END
  2164. END;
  2165. modal := NIL;
  2166. DelView(dlg);
  2167. dlg := NIL;
  2168. RETURN cmd
  2169. END DlgRun;
  2170. PROCEDURE MessageBox (title : ARRAY OF CHAR; text : ARRAY OF CHAR;
  2171. kind : INTEGER) : INTEGER;
  2172. VAR w, total, left : INTEGER; b1, b2 : ViewPtr;
  2173. BEGIN
  2174. w := TextLen(text) + 6;
  2175. IF w < 30 THEN w := 30 END;
  2176. IF w > cols - 4 THEN w := cols - 4 END;
  2177. Dialog(title, w, 7);
  2178. DlgLabel(text);
  2179. b1 := NIL; b2 := NIL;
  2180. IF kind = mbOKCancel THEN
  2181. DlgButton("OK", cmOK); b1 := dlg^.last;
  2182. DlgButton("Cancel", cmCancel); b2 := dlg^.last
  2183. ELSIF kind = mbYesNo THEN
  2184. DlgButton("Yes", cmYes); b1 := dlg^.last;
  2185. DlgButton("No", cmNo); b2 := dlg^.last
  2186. ELSE
  2187. DlgButton("OK", cmOK); b1 := dlg^.last
  2188. END;
  2189. IF b1 # NIL THEN
  2190. IF b2 # NIL THEN total := b1^.rect.w + b2^.rect.w + 2
  2191. ELSE total := b1^.rect.w END;
  2192. left := dlgWX + (dlgW - total) DIV 2;
  2193. MoveViewBy(b1, left - b1^.rect.x, 0);
  2194. IF b2 # NIL THEN MoveViewBy(b2, left + b1^.rect.w + 2 - b2^.rect.x, 0) END
  2195. END;
  2196. RETURN DlgRun()
  2197. END MessageBox;
  2198. PROCEDURE Message (title : ARRAY OF CHAR; text : ARRAY OF CHAR);
  2199. VAR discard : INTEGER;
  2200. BEGIN
  2201. discard := MessageBox(title, text, mbOK)
  2202. END Message;
  2203. END tv.