Subversion Repositories oidplus

Rev

Rev 992 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit VTSCUI;
  2.  
  3. (************************************************)
  4. (* VTSCUI.PAS                                   *)
  5. (* Author:   Daniel Marschall                   *)
  6. (* Revision: 2022-10-10                         *)
  7. (* License:  Apache 2.0                         *)
  8. (* This file contains:                          *)
  9. (* - ViaThinkSoft CUI (Console User Interface)  *)
  10. (************************************************)
  11.  
  12. interface
  13.  
  14. uses
  15.   StrList;
  16.  
  17. const
  18.   (* These are available in DRIVERS.TPU, but require a call of InitVideo
  19.   ScreenWidth = 80;
  20.   ScreenHeight = 25;
  21.   *)
  22.   SINGLE_LINE_BOX_PADDING = 3;
  23.   SINGLE_LINE_BOX_PADDING_INNER = 10;
  24.  
  25. procedure DrawThinBorder(x, y, width, height: integer);
  26. procedure DrawDoubleBorder(x, y, width, height: integer);
  27. procedure DrawTextBar(str: string; line: integer);
  28. procedure DrawTitleBar(center, left, right: string);
  29. procedure DrawStatusBar(str: string);
  30. function DrawSelectionList(X, Y, ListWidth, ListHeight: integer;
  31.                            items: PStringList; allowESC: boolean;
  32.                            Title: string; borderStrength: integer): integer;
  33. procedure ClearSection(x, y, width, height: integer);
  34. function QueryVal(var s: string; initX, initY, width, height: integer;
  35.                   Title: string; borderStrength: integer): boolean;
  36. procedure ShowMessage(msg: string; title: string; dobeep: boolean);
  37. procedure CursorOn;
  38. procedure CursorOff;
  39. procedure ResetDefaultDosColors;
  40. procedure WriteLnKeepX(s: string);
  41.  
  42. implementation
  43.  
  44. uses
  45.   Crt, Drivers, VtsFuncs;
  46.  
  47. type
  48.   TCharDefs = array[0..7] of char;
  49.  
  50. const
  51.   ThinLineChars: TCharDefs =
  52.     ( #$DA, #$C4, #$BF,
  53.       #$B3,       #$B3,
  54.       #$C0, #$C4, #$D9
  55.     );
  56.   DoubleLineChars: TCharDefs =
  57.     ( #$C9, #$CD, #$BB,
  58.       #$BA,       #$BA,
  59.       #$C8, #$CD, #$BC
  60.     );
  61.  
  62. function FillRight(str: string; len: integer; c: char): string;
  63. var
  64.   s: string;
  65.   i: integer;
  66. begin
  67.   s := str;
  68.   for i := Length(str) to len-1 do
  69.   begin
  70.     if Length(s) >= 255 then break;
  71.     s := s + c;
  72.   end;
  73.   FillRight := s;
  74. end;
  75.  
  76. procedure DrawBorder(x, y, width, height, thickness: integer);
  77. var
  78.   ix,iy: integer;
  79.   chars: TCharDefs;
  80. begin
  81.   if thickness = 1 then
  82.     chars := ThinLineChars;
  83.   if thickness = 2 then
  84.     chars := DoubleLineChars;
  85.  
  86.   (* Top line *)
  87.   if y >= 1 then
  88.   begin
  89.     (* Top left corner *)
  90.     if x >= 1 then
  91.     begin
  92.       GotoXY(x,y);
  93.       Write(chars[0]);
  94.     end
  95.     else
  96.     begin
  97.       GotoXY(1,y);
  98.     end;
  99.  
  100.     (* Top edge *)
  101.     for ix := 1 to width-2 do
  102.       Write(chars[1]);
  103.  
  104.     (* Top right corner *)
  105.     if x+width-1 <= ScreenWidth then
  106.       Write(chars[2]);
  107.   end;
  108.  
  109.   (* Left edge *)
  110.   for iy := 1 to height-2 do
  111.   begin
  112.     if (x    >= 1) and (x    <= ScreenWidth) and
  113.        (y+iy >= 1) and (y+iy <= ScreenHeight) then
  114.     begin
  115.       GotoXY(x,y+iy);
  116.       Write(chars[3]);
  117.     end;
  118.   end;
  119.  
  120.   (* Right edge *)
  121.   for iy := 1 to height-2 do
  122.   begin
  123.     if (x+width-1 >= 1) and (x+width-1 <= ScreenWidth) and
  124.        (y+iy      >= 1) and (y+iy      <= ScreenHeight) then
  125.     begin
  126.       GotoXY(x+width-1,y+iy);
  127.       Write(chars[4]);
  128.     end;
  129.   end;
  130.  
  131.   (* Bottom line *)
  132.   if y+height-1 <= ScreenHeight then
  133.   begin
  134.     (* Bottom left corner *)
  135.     if x >= 1 then
  136.     begin
  137.       GotoXY(x,y+height-1);
  138.       Write(chars[5]);
  139.     end
  140.     else
  141.     begin
  142.       GotoXY(1,y+height-1);
  143.     end;
  144.  
  145.     (* Bottom edge *)
  146.     for ix := 1 to width-2 do
  147.       Write(chars[6]);
  148.  
  149.     (* Bottom right corner *)
  150.     if x+width-1 <= ScreenWidth then
  151.       Write(chars[7]);
  152.   end;
  153. end;
  154.  
  155. procedure DrawThinBorder(x, y, width, height: integer);
  156. begin
  157.   DrawBorder(x, y, width, height, 1);
  158. end;
  159.  
  160. procedure DrawDoubleBorder(x, y, width, height: integer);
  161. begin
  162.   DrawBorder(x, y, width, height, 2);
  163. end;
  164.  
  165. procedure DrawTextBar(str: string; line: integer);
  166. var
  167.   i, left, right: integer;
  168.   len: integer;
  169. begin
  170.   GotoXY(1,line);
  171.  
  172.   TextBackground(White);
  173.   TextColor(Black);
  174.   len := Length(str);
  175.  
  176.   left := round((ScreenWidth-len)/2);
  177.   right := ScreenWidth - left - len;
  178.  
  179.   for i := 1 to left do
  180.   begin
  181.     Write(' ');
  182.   end;
  183.  
  184.   Write(str);
  185.  
  186.   (* TODO: If we do "for i := 1 to right", then the console will scroll *)
  187.   (*       since the char in the right bottom corner is written!        *)
  188.   for i := 1 to right-1 do
  189.   begin
  190.     Write(' ');
  191.   end;
  192.   GoToXY(1,1);
  193.  
  194.   TextBackground(Black);
  195.   TextColor(White);
  196. end;
  197.  
  198. procedure DrawTitleBar(center, left, right: string);
  199. var
  200.   bakx, baky: integer;
  201. begin
  202.   bakx := WhereX;
  203.   baky := WhereY;
  204.  
  205.   DrawTextBar(center, 1);
  206.  
  207.   (* Put left text into the title bar *)
  208.   GoToXY(1,1);
  209.   TextBackground(White);
  210.   TextColor(Black);
  211.   WriteLn(left);
  212.   TextBackground(Black);
  213.   TextColor(White);
  214.  
  215.   (* Put right text into the title bar *)
  216.   GotoXY(ScreenWidth-Length(right)+1,1);
  217.   TextBackground(White);
  218.   TextColor(Black);
  219.   WriteLn(right);
  220.   TextBackground(Black);
  221.   TextColor(White);
  222.  
  223.   GoToXY(bakx, baky);
  224. end;
  225.  
  226. procedure DrawStatusBar(str: string);
  227. var
  228.   bakx, baky: integer;
  229. begin
  230.   bakx := WhereX;
  231.   baky := WhereY;
  232.   DrawTextBar(str, ScreenHeight);
  233.   GoToXY(bakx, baky);
  234. end;
  235.  
  236. function DrawSelectionList(X, Y, ListWidth, ListHeight: integer;
  237.                            items: PStringList; allowESC: boolean;
  238.                            Title: string; borderStrength: integer): integer;
  239. var
  240.   i: integer;
  241.   itemIndex: integer;
  242.   sc: char;
  243.   iStartScope, iEndScope: integer;
  244.   sTmp: string;
  245. label
  246.   doAgain;
  247. begin
  248.   if borderStrength = 1 then
  249.   begin
  250.     DrawThinBorder(X-1, Y-1, ListWidth+2, ListHeight+2);
  251.   end;
  252.  
  253.   if borderStrength = 2 then
  254.   begin
  255.     DrawDoubleBorder(X-1, Y-1, ListWidth+2, ListHeight+2);
  256.   end;
  257.  
  258.   if Title <> '' then
  259.   begin
  260.     if borderStrength > 0 then
  261.       GoToXY(X+1, Y-1)
  262.     else
  263.       GoToXY(X, Y-1);
  264.     Write(Title);
  265.   end;
  266.  
  267.   (*CursorOff;*)
  268.  
  269.   itemIndex := 0;
  270.   iStartScope := itemIndex;
  271.   iEndScope := itemIndex + ListHeight;
  272.  
  273. doAgain:
  274.  
  275.   if itemIndex < 0 then
  276.     itemIndex := 0;
  277.   if itemIndex > ListCount(items)-1 then
  278.     itemIndex := ListCount(items)-1;
  279.  
  280.   if itemIndex < iStartScope then
  281.   begin
  282.     Dec(iEndScope);
  283.     Dec(iStartScope);
  284.   end;
  285.  
  286.   if itemIndex > iEndScope-1 then
  287.   begin
  288.     Inc(iEndScope);
  289.     Inc(iStartScope);
  290.   end;
  291.  
  292.   if borderStrength > 0 then
  293.   begin
  294.     (* Show scroll arrows *)
  295.     GotoXY(X+ListWidth, Y);
  296.     if iStartScope > 0 then
  297.     begin
  298.       TextBackground(White);
  299.       TextColor(Black);
  300.       WriteLn(#$18(*ArrowUp*));
  301.       TextBackground(Black);
  302.       TextColor(White);
  303.     end
  304.     else if borderStrength = 1 then
  305.       WriteLn(ThinLineChars[4])
  306.     else if borderStrength = 2 then
  307.       WriteLn(DoubleLineChars[4]);
  308.  
  309.     GotoXY(X+ListWidth, Y+ListHeight-1);
  310.     if ListCount(items) > iEndScope then
  311.     begin
  312.       TextBackground(White);
  313.       TextColor(Black);
  314.       WriteLn(#$19(*ArrowDown*));
  315.       TextBackground(Black);
  316.       TextColor(White);
  317.     end
  318.     else if borderStrength = 1 then
  319.       WriteLn(ThinLineChars[4])
  320.     else if borderStrength = 2 then
  321.       WriteLn(DoubleLineChars[4]);
  322.   end;
  323.  
  324.   for i := iStartScope to iEndScope-1 do
  325.   begin
  326.     if itemIndex = i then
  327.     begin
  328.       TextColor(Black);
  329.       TextBackground(White);
  330.     end
  331.     else
  332.     begin
  333.       TextColor(White);
  334.       TextBackground(Black);
  335.     end;
  336.     GotoXY(x,y+i-iStartScope);
  337.     if i > ListCount(items)-1 then
  338.       Write(FillRight('', ListWidth, ' '))
  339.     else
  340.     begin
  341.       sTmp := ListGetElement(items, i);
  342.       sTmp := TrimLineToWidth(sTmp, ListWidth);
  343.       Write(FillRight(sTmp, ListWidth, ' '));
  344.     end;
  345.     TextColor(White);
  346.     TextBackground(Black);
  347.   end;
  348.  
  349.   repeat
  350.     GotoXY(ScreenWidth, ScreenHeight);
  351.  
  352.     sc := ReadKey;
  353.     if sc = #$00(*ExtendedKeyCode*) then
  354.     begin
  355.       sc := ReadKey;
  356.       if sc = #$48(*UpKey*) then
  357.       begin
  358.         dec(itemIndex);
  359.         goto doAgain;
  360.       end
  361.       else if sc = #$50(*DownKey*) then
  362.       begin
  363.         inc(itemIndex);
  364.         goto doAgain;
  365.       end
  366.       else if sc = #$47(*POS1*) then
  367.       begin
  368.         itemIndex := 0;
  369.         iStartScope := itemIndex;
  370.         iEndScope := iStartScope + ListHeight;
  371.         goto doAgain;
  372.       end
  373.       else if sc = #$4F(*END*) then
  374.       begin
  375.         itemIndex := ListCount(items)-1;
  376.         iStartScope := itemIndex - Min(ListHeight,ListCount(items));
  377.         iEndScope := itemIndex;
  378.         goto doAgain;
  379.       end
  380.       else if sc = #$49(*PgUp*) then
  381.       begin
  382.         Dec(itemIndex, ListHeight);
  383.         if itemIndex < 0 then
  384.           itemIndex := 0;
  385.         iStartScope := itemIndex;
  386.         iEndScope := itemIndex + ListHeight;
  387.         goto doAgain;
  388.       end
  389.       else if sc = #$51(*PgDown*) then
  390.       begin
  391.         Inc(itemIndex, ListHeight);
  392.         if itemIndex > ListCount(items)-1 then
  393.           itemIndex := ListCount(items)-1;
  394.         iStartScope := itemIndex - Min(ListHeight,ListCount(items));
  395.         iEndScope := itemIndex;
  396.         goto doAgain;
  397.       end;
  398.     end;
  399.  
  400.     if sc = #13(*Return*) then
  401.     begin
  402.       DrawSelectionList := itemIndex;
  403.       break;
  404.     end;
  405.  
  406.     if allowESC and (sc = #27(*ESC*)) then
  407.     begin
  408.       DrawSelectionList := -1;
  409.       break;
  410.     end;
  411.   until false;
  412.  
  413.   (*CursorOn;*)
  414. end;
  415.  
  416. procedure ClearSection(x, y, width, height: integer);
  417. var
  418.   ix, iy: integer;
  419. begin
  420.   for iy := y to y+height-1 do
  421.   begin
  422.     for ix := x to x+width-1 do
  423.     begin
  424.       GoToXY(ix,iy);
  425.       Write(' ');
  426.     end;
  427.   end;
  428. end;
  429.  
  430. function QueryVal(var s: string; initX, initY, width, height: integer;
  431.                   Title: string; borderStrength: integer): boolean;
  432. var
  433.   x, y: integer;
  434.   i, j: integer;
  435.   sc: char;
  436.   stmp: string;
  437. begin
  438.   if borderStrength = 1 then
  439.     DrawThinBorder(initX-1,initY-1,width+2,height+2);
  440.   if borderStrength = 2 then
  441.     DrawDoubleBorder(initX-1,initY-1,width+2,height+2);
  442.   if title <> '' then
  443.   begin
  444.     if borderStrength > 0 then
  445.       GoToXY(initX+1, initY-1)
  446.     else
  447.       GoToXY(initX, initY-1);
  448.     Write(title);
  449.   end;
  450.   ClearSection(initX,initY,width,height);
  451.  
  452.   x := initX;
  453.   y := initY;
  454.  
  455.   (* Write existing string value and set cursor *)
  456.   stmp := s;
  457.   s := '';
  458.   for i := 1 to Length(stmp) do
  459.   begin
  460.     if stmp[i] = #10 then
  461.     begin
  462.       if Length(s) >= 255 then
  463.       begin
  464.         Beep;
  465.         continue;
  466.       end;
  467.       s := s + stmp[i];
  468.       continue;
  469.     end;
  470.  
  471.     GoToXY(x,y);
  472.     if Length(s) >= 255 then
  473.     begin
  474.       Beep;
  475.       continue;
  476.     end;
  477.     s := s + stmp[i];
  478.     Write(stmp[i]);
  479.     Inc(x);
  480.  
  481.     if (x=initX+width-1) and (y=initY+height-1) then
  482.     begin
  483.       (* Attention: Data following after this will be lost! *)
  484.       break;
  485.     end;
  486.  
  487.     if stmp[i] = #13 then
  488.     begin
  489.       if y=initY+height-1 then
  490.       begin
  491.         (* Attention: Data following after this will be lost! *)
  492.         s := Copy(s, 1, Length(s)-1);
  493.         Dec(x);
  494.         break;
  495.       end;
  496.       x := initX;
  497.       Inc(y);
  498.       continue;
  499.     end;
  500.  
  501.     if x=initX+width then
  502.     begin
  503.       Inc(y);
  504.       x := initX;
  505.     end;
  506.   end;
  507.  
  508.   repeat
  509.     GotoXY(x, y);
  510.     sc := ReadKey;
  511.  
  512.     if sc = #0 then
  513.     begin
  514.       (* Extended key code *)
  515.       sc := ReadKey;
  516.       (* TODO: Implement keys like DEL, END, POS1, and ArrowKeys *)
  517.       Beep;
  518.       continue;
  519.     end
  520.     else if sc = #8(*Backspace*) then
  521.     begin
  522.       if x <= initX then
  523.       begin
  524.         if y = initY then
  525.         begin
  526.           Beep;
  527.           continue;
  528.         end;
  529.         Dec(y);
  530.  
  531.         (* Find out length of previous line *)
  532.         j := Length(s)-2(*CRLF*);
  533.         while (j >= 0) do
  534.         begin
  535.           if (s[j]=#13) or (s[j]=#10) then break;
  536.           Dec(j);
  537.         end;
  538.         if j < 0 then j := 0;
  539.         j := Length(s)-2(*CRLF*)-j;
  540.         if Copy(s,Length(s),1) <> #10 then j := j + 1;
  541.         j := j mod width;
  542.  
  543.         x := initX + j;
  544.         if Copy(s,Length(s),1) = #10 then
  545.           s := Copy(s, 1, Length(s)-1); (* Remove #10 now. #13 will be removed below *)
  546.       end
  547.       else
  548.       begin
  549.         Dec(x);
  550.       end;
  551.       GotoXY(x, y);
  552.       Write(' ');
  553.       GotoXY(x, y);
  554.       s := Copy(s, 1, Length(s)-1);
  555.       continue;
  556.     end
  557.     else if sc = #13(*Return*) then
  558.     begin
  559.       if GetShiftState and kbRightShift <> 0 then
  560.       begin
  561.         if y=initY+height-1 then
  562.         begin
  563.           Beep;
  564.           continue;
  565.         end;
  566.         if Length(s) >= 254 then
  567.         begin
  568.           Beep;
  569.           continue;
  570.         end;
  571.         s := s + #13 + #10;
  572.         x := initX;
  573.         Inc(y);
  574.       end
  575.       else
  576.       begin
  577.         QueryVal := true;
  578.         exit;
  579.       end;
  580.     end
  581.     else if sc = #27(*ESC*) then
  582.     begin
  583.       QueryVal := false;
  584.       exit;
  585.     end
  586.     else
  587.     begin
  588.       if (x=initX+width-1) and (y=initY+height-1) then
  589.       begin
  590.         Beep;
  591.         continue;
  592.       end;
  593.       if Length(s) >= 255-Length(sc)+1 then
  594.       begin
  595.         Beep;
  596.         continue;
  597.       end;
  598.       s := s + sc;
  599.       Write(sc);
  600.       Inc(x);
  601.       if x >= initX+width then
  602.       begin
  603.         Inc(y);
  604.         x := initX;
  605.       end;
  606.     end;
  607.   until false;
  608. end;
  609.  
  610. procedure ShowMessage(msg: string; title: string; dobeep: boolean);
  611. var
  612.   x, y, w, h: integer;
  613. begin
  614.   x := SINGLE_LINE_BOX_PADDING_INNER;
  615.   y := ScreenHeight div 2 - 1;
  616.   w := ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2;
  617.   h := 1;
  618.   DrawDoubleBorder(x-1, y, w+2, h+2);
  619.   ClearSection(x, y+1, w-2, h);
  620.   if title <> '' then
  621.   begin
  622.     GoToXY(x+1, y);
  623.     Write(title);
  624.   end;
  625.   GoToXY(x, y+1);
  626.   Write(msg);
  627.   if DoBeep then Beep;
  628. end;
  629.  
  630. procedure CursorOn; assembler;
  631. asm
  632.   mov ah,1     (* Set text-mode cursor shape *)
  633.   mov cx,0607h (* normal underline cursor *)
  634.   int 10h
  635. end;
  636.  
  637. procedure CursorOff; assembler;
  638. asm
  639.   mov ah,1     (* Set text-mode cursor shape *)
  640.   mov cx,2607h (* hide cursor (Start>End) *)
  641.   int 10h
  642. end;
  643.  
  644. procedure ResetDefaultDosColors;
  645. begin
  646.   TextBackground(Black);
  647.   TextColor(LightGray);
  648. end;
  649.  
  650. procedure WriteLnKeepX(s: string);
  651. var
  652.   i: integer;
  653.   initX: integer;
  654.   bytesToWrite: integer;
  655.   pNewLine: integer;
  656. begin
  657.   initX := WhereX;
  658.   while Length(s) > 0 do
  659.   begin
  660.     pNewLine := Pos(#13#10, s);
  661.     GoToXy(initX, WhereY);
  662.     bytesToWrite := ScreenWidth - initX + 1;
  663.     if (pNewLine > 0) and (pNewLine < bytesToWrite) then
  664.       bytesToWrite := pNewLine;
  665.     Write(Copy(s, 1, bytesToWrite)); (* No WriteLn because there is automatic scrolling *)
  666.     Delete(s, 1, bytesToWrite);
  667.   end;
  668.   WriteLn('');
  669. end;
  670.  
  671. end.
  672.