tv.mod 82 KB

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