tv.mod 81 KB

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