tv.mod 51 KB

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