tv.mod 56 KB

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