
{ Copyright (c) 1989,90 by Borland International, Inc. }

unit TCScreen;
{ Turbo Pascal 6.0 object-oriented example screen routines.
  This unit is used by TCALC.PAS.
  See TCALC.DOC for an more information about this example.
}

{$S-}

interface

uses Crt, Dos, TCUtil;

const
  ScreenCols = 80;
  ScreenRows = 50;
  MinScreenRows = 25;
  ESCPress = 'Press ESC.';    { Printed in error messages }

type
  Direction = (Up, Down, Left, Right);
  ScreenColRange = 1..ScreenCols;
  ScreenRowRange = 1..ScreenRows;
  VideoTypes = (MDA, CGA, MCGA, EGA, VGA);
  ScreenChar = record
    Data : Char;
    Attrib : Byte;
  end;
  ScreenArray = array[ScreenRowRange, ScreenColRange] of ScreenChar;
  ScreenRow = array[ScreenColRange] of ScreenChar;
  ScreenPointer = ^ScreenArray;
  ScreenPos = record
    Col : ScreenColRange;
    Row : ScreenRowRange;
  end;
  Screen = object
    CurrRows : ScreenRowRange;
    CurrCols : ScreenColRange;
    VideoType : VideoTypes;
    OldCursor : Word;
    InsCursor : Word;
    OldMode : Word;
    constructor Init;
    destructor Done;
    procedure ToggleMaxLinesMode;
    procedure PrintError(Error : String);
    procedure PrintMessage(Message : String);
    procedure ClearMessage;
    procedure PrintHelpLine(CommandString : String);
  end;
  ScreenArea = object
    UpperLeft, LowerRight : ScreenPos;
    Attrib : Byte;
    constructor Init(InitX1 : ScreenColRange; InitY1 : ScreenRowRange;
                     InitX2 : ScreenColRange; InitY2 : ScreenRowRange;
                     InitAttrib : Byte);
    procedure Scroll(Dir : Direction; Amt : Word);
    procedure Clear;
    procedure Erase;
  end;
  ColorTableType = (ColorMono, ColorBW, ColorColor);
  ColorTablePtr = ^ColorTable;
  ColorTable = object
    TableType : ColorTableType;
    BlankColor : Byte;
    ValueCellColor : Byte;
    TextCellColor : Byte;
    FormulaCellColor : Byte;
    RepeatCellColor : Byte;
    ColColor : Byte;
    RowColor : Byte;
    InfoColor : Byte;
    HighlightColor : Byte;
    BlockColor : Byte;
    InputColor : Byte;
    InputArrowColor : Byte;
    ErrorColor : Byte;
    CellErrorColor : Byte;
    MemoryColor : Byte;
    CellDataColor : Byte;
    PromptColor : Byte;
    FileNameColor : Byte;
    ChangedColor : Byte;
    TitleColor : Byte;
    ContentsColor : Byte;
    KeyNameColor : Byte;
    KeyDescColor : Byte;
    MenuHiColor : Byte;
    MenuLoColor : Byte;
    MessageColor : Byte;
    constructor Init;
    procedure FillColorTable;
  end;

const
  NoCursor = $2000;

var
  Colors : ColorTable;
  Scr : Screen;
  ScreenPtr : ScreenPointer;

procedure MoveToScreen(var Source, Dest; Len : Word);

procedure MoveFromScreen(var Source, Dest; Len : Word);

procedure ClrEOLXY(Col : ScreenColRange; Row : ScreenRowRange;
                   Color : Byte);

procedure WriteColor(S : String; Color : Byte);

procedure WriteXY(S : String; Col : ScreenColRange; Row : ScreenRowRange;
                      Color : Byte);

procedure WriteXYClr(S : String; Col : ScreenColRange; Row : ScreenRowRange;
                         Color : Byte);

procedure SetCursor(NewCursor : Word);

function GetCursor : Word;

implementation

const
  TotalColors = 26;
  WhiteOnRed = White + (Red shl 4);
  WhiteOnBlue = White + (Blue shl 4);
  WhiteOnCyan = White + (Cyan shl 4);
  BlackOnGray = LightGray shl 4;
  WhiteOnGray = White + (LightGray shl 4);
  BlinkingLightRed = LightRed + Blink;
  BlinkingWhite = White + Blink;
  LightCyanOnBlue = LightCyan + (Blue shl 4);
  YellowOnBlue = Yellow + (Blue shl 4);

type
  ColorArray = array[1..TotalColors] of Byte;

const
  ColorColors : ColorArray = (White, LightCyan, White, LightMagenta, White,
                              WhiteOnRed, WhiteOnRed, WhiteOnCyan,
                              WhiteOnBlue, WhiteOnCyan, White, LightCyan,
                              WhiteOnRed, BlinkingLightRed, LightRed,
                              LightGreen, Yellow, LightCyan, Yellow,
                              LightMagenta, Yellow, LightCyanOnBlue,
                              YellowOnBlue, LightCyan, White,
                              BlinkingLightRed);
  BWColors : ColorArray = (White, White, White, White, White, BlackOnGray,
                           BlackOnGray, WhiteOnGray, WhiteOnGray, BlackOnGray,
                           White, White, White, BlinkingWhite, White, White,
                           White, White, White, White, White, BlackOnGray,
                           White, White, LightGray, BlinkingWhite);
  MonoColors : ColorArray = (White, White, White, White, White, BlackOnGray,
                             BlackOnGray, BlackOnGray, BlackOnGray,
                             BlackOnGray, White, White, White, BlinkingWhite,
                             White, White, White, White, White, White, White,
                             BlackOnGray, White, White, LightGray,
                             BlinkingWhite);

const
  InsCursorSmall = $0007;
  InsCursorLarge = $000D;

var
  SavedExitProc : Pointer;

procedure ClearScreen(X1, Y1, X2, Y2, Attrib : Word);
{ Clears an area of the screen }
var
  Reg : Registers;
begin
  if (X1 > X2) or (Y1 > Y2) then   { Illegal values }
    Exit;
  with Reg do
  begin
    AX := $0600;              { Clear screen through the BIOS }
    BH := Attrib;
    CH := Pred(Y1);
    CL := Pred(X1);
    DH := Pred(Y2);
    DL := Pred(X2);
    Intr($10, Reg);
  end; { with }
end; { ClearScreen }

{$L TCMVSMEM}

procedure MoveToScreen(var Source, Dest; Len : Word); external;
{ Moves screen memory from normal RAM to screen memory - see TCMVSMEM.ASM
  for source }

procedure MoveFromScreen(var Source, Dest; Len : Word); external;
{ Moves screen memory to normal RAM from screen memory - see TCMVSMEM.ASM
  for source }

procedure MoveText(OldX1, OldY1, OldX2, OldY2, NewX1, NewY1 : Word);
{ Moves an area of text to a new position on the screen }
var
  Counter, Len : Word;
begin
  if (OldX2 < OldX1) or (OldY2 < OldY1) then
    Exit;
  Len := Succ(OldX2 - OldX1) shl 1;
  if NewY1 < OldY1 then
  begin                    { Move it row by row, going forwards }
    for Counter := 0 to OldY2 - OldY1 do
      MoveFromScreen(ScreenPtr^[OldY1 + Counter, OldX1],
                     ScreenPtr^[NewY1 + Counter, NewX1], Len)
  end
  else begin               { Move it row by row, going backwards }
    for Counter := OldY2 - OldY1 downto 0 do
      MoveFromScreen(ScreenPtr^[OldY1 + Counter, OldX1],
                     ScreenPtr^[NewY1 + Counter, NewX1], Len)
  end;
end; { MoveText }

procedure ScrollText(Dir : Direction; X1, Y1, X2, Y2, Amt, Attrib : Word);
{ Scrolls the screen by an amount in a direction - it does this by moving
  the text to be scrolled and then clearing the area that wasn't scrolled }
begin
  case Dir of
    Up : begin
      MoveText(X1, Y1 + Amt, X2, Y2, X1, Y1);
      ClearScreen(X1, Succ(Y2 - Amt), X2, Y2, Attrib);
    end;
    Down : begin
      MoveText(X1, Y1, X2, Y2 - Amt, X1, Succ(Y1));
      ClearScreen(X1, Y1, X2, Pred(Y1 + Amt), Attrib);
    end;
    Left : begin
      MoveText(X1 + Amt, Y1, X2, Y2, X1, Y1);
      ClearScreen(Succ(X2 - Amt), Y1, X2, Y2, Attrib);
    end;
    Right : begin
      MoveText(X1, Y1, X2 - Amt, Y2, X1 + Amt, Y1);
      ClearScreen(X1, Y1, Pred(X1 + Amt), Y2, Attrib);
    end;
  end; { case }
end; { ScrollText }

function EGAInstalled : Boolean;
{ Tests for the presence of an EGA }
var
  Reg : Registers;
begin
  Reg.AX := $1200;
  Reg.BX := $0010;
  Reg.CX := $FFFF;
  Intr($10, Reg);
  EGAInstalled := Reg.CX <> $FFFF;
end; { EGAInstalled }

function PS2 : Boolean;
{ This function returns True if we are running on a PS/2 type video adapter }
var
  Regs : Registers;
begin
  Regs.AX := $1A00;
  Intr($10, Regs);
  PS2 := ((Regs.AL and $FF) = $1A) and
         ((Regs.BL and $FF) in [$07, $08, $0B, $0C]);
end; { PS2 }

procedure ClrEOLXY(Col : ScreenColRange; Row : ScreenRowRange; Color : Byte);
{ Clears to the end-of-line in a color at a specified position }
begin
  GotoXY(Col, Row);
  TextAttr := Color;
  ClrEOL;
end; { ClrEOLXY }

procedure WriteColor(S : String; Color : Byte);
{ Writes a string in a color }
begin
  TextAttr := Color;
  Write(S);
end; { WriteColor }

procedure WriteXY(S : String; Col : ScreenColRange; Row : ScreenRowRange;
                  Color : Byte);
{ Writes a string in a color at a specified position }
begin
  GotoXY(Col, Row);
  WriteColor(S, Color);
end; { WriteXY }

procedure WriteXYClr(S : String; Col : ScreenColRange; Row : ScreenRowRange;
                     Color : Byte);
{ Clears to the end-of-line in a color at a specified position and then
  writes a string }
begin
  ClrEOLXY(Col, Row, Color);
  Write(S);
end; { WriteXYClr }

procedure SetCursor(NewCursor : Word);
{ Sets the value of the scan lines of the cursor }
var
  Reg : Registers;
begin
  with Reg do
  begin
    AH := 1;
    BH := 0;
    CX := NewCursor;
    Intr($10, Reg);
  end; { with }
end; { SetCursor }

function GetCursor : Word;
{ Returns the value of the scan lines of the cursor }
var
  Reg : Registers;
begin
  with Reg do
  begin
    AH := 3;
    BH := 0;
    Intr($10, Reg);
    GetCursor := CX;
  end; { Reg }
end; { GetCursor }

constructor Screen.Init;
{ Finds what type of video adapter is being run on, and initializes various
  variables based on this information }
var
  Reg : Registers;
begin
  OldMode := LastMode;
  Reg.AH := $0F;
  Intr($10, Reg);                    { Check for the current video mode }
  if Reg.AL <> 7 then
  begin
    if EGAInstalled then
    begin
      if PS2 then
        VideoType := VGA
      else
        VideoType := EGA;
    end
    else begin
      if PS2 then
        VideoType := MCGA
      else
        VideoType := CGA;
    end;
    ScreenPtr := Ptr($B800, 0);
    if Reg.AL < 2 then
      CurrCols := 40
    else
      CurrCols := 80;
  end
  else begin
    VideoType := MDA;
    ScreenPtr := Ptr($B000, 0);
    CurrCols := 80;
  end;
  CurrRows := Succ(Hi(WindMax));
  OldCursor := GetCursor;
  if (CurrRows = MinScreenRows) and (VideoType <> CGA) then
    InsCursor := InsCursorLarge
  else
    InsCursor := InsCursorSmall;
end; { Screen.Init }

destructor Screen.Done;
{ Restores the screen mode and cursor that existed at the start of the 
  program }
begin
  TextMode(OldMode);
  SetCursor(OldCursor);
  ExitProc := SavedExitProc;
end; { Screen.Done }

procedure Screen.ToggleMaxLinesMode;
{ Toggles the display in and out of 43/50-line mode }
begin
  if CurrRows = MinScreenRows then
  begin
    TextMode(Lo(LastMode) + Font8x8);
    InsCursor := InsCursorSmall;
  end
  else begin
    TextMode(Lo(LastMode));
    InsCursor := InsCursorLarge;
  end;
  CurrRows := Succ(Hi(WindMax));
end; { Screen.ToggleMaxLinesMode }

procedure Screen.PrintError(Error : String);
{ Prints an error message at the bottom of the screen }
var
  Ch : Word;
  Buffer : ScreenRow;
begin
  MoveFromScreen(ScreenPtr^[CurrRows, 1], Buffer,
                 SizeOf(ScreenChar) * CurrCols);  { Save bottom line }
  WriteXYClr(CenterStr(Error + '. ' + ESCPress, Pred(CurrCols)), 1, CurrRows,
             Colors.ErrorColor);
  Beep;
  repeat
    Ch := GetKey;
  until Ch = ESC;
  MoveToScreen(Buffer, ScreenPtr^[CurrRows, 1],  { Restore bottom line }
               SizeOf(ScreenChar) * CurrCols);
end; { Screen.PrintError }

procedure Screen.PrintMessage(Message : String);
{ Prints a message }
begin
  WriteXYClr(Message + '...', 1, Pred(CurrRows), Colors.MessageColor);
end; { Screen.PrintMessage }

procedure Screen.ClearMessage;
{ Clears the last printed message }
begin
  ClrEOLXY(1, Pred(CurrRows), Colors.MessageColor);
end; { Screen.ClearMessage }

procedure Screen.PrintHelpLine(CommandString : String);
{ Prints a help line at the bottom of the screen. The command string is
  made up of a series of keys and descriptions separated by backslashes.
  Example: 'F1\Help\F2\Save\F3\Load\Alt-X\Exit'}
var
  P : Integer;
  S : String[ScreenCols];
begin
  CommandString := CommandString + '\';
  ClrEOLXY(1, CurrRows, Colors.KeyDescColor);
  while CommandString <> '' do
  begin
    Write(' ');
    P := Pos('\', CommandString);
    WriteColor(Copy(CommandString, 1, Pred(P)), Colors.KeyNameColor);
    Delete(CommandString, 1, P);
    P := Pos('\', CommandString);
    if CommandString[1] = '\' then
      S := '-'
    else
      S := '-' + Copy(CommandString, 1, Pred(P));
    WriteColor(S, Colors.KeyDescColor);
    Delete(CommandString, 1, P);
  end;
end; { Screen.PrintHelpLine }

constructor ScreenArea.Init(InitX1 : ScreenColRange; InitY1 : ScreenRowRange;
                            InitX2 : ScreenColRange; InitY2 : ScreenRowRange;
                            InitAttrib : Byte);
{ Sets up a screen area }
begin
  UpperLeft.Col := InitX1;
  UpperLeft.Row := InitY1;
  LowerRight.Col := InitX2;
  LowerRight.Row := InitY2;
  Attrib := InitAttrib;
end; { ScreenArea.Init }

procedure ScreenArea.Scroll(Dir : Direction; Amt : Word);
{ Scrolls a screen area an certain amount in a direction }
begin
  ScrollText(Dir, UpperLeft.Col, UpperLeft.Row, LowerRight.Col,
             LowerRight.Row, Amt, Attrib);
end; { ScreenArea.Scroll }

procedure ScreenArea.Clear;
{ Clears a screen area }
begin
  ClearScreen(UpperLeft.Col, UpperLeft.Row, LowerRight.Col, LowerRight.Row,
              Attrib);
end; { ScreenArea.Clear }

procedure ScreenArea.Erase;
{ Erases a screen area by writing over it in black }
begin
  ClearScreen(UpperLeft.Col, UpperLeft.Row, LowerRight.Col, LowerRight.Row,
              Black);
end; { ScreenArea.Erase }

constructor ColorTable.Init;
{ Initializes the color table by finding the video mode that is being used }
begin
  case Lo(LastMode) of
    BW40, BW80 : TableType := ColorBW;
    CO40, CO80 : TableType := ColorColor;
    Mono : TableType := ColorMono;
  end; { case }
  FillColorTable;
end; { ColorTable.Init }

procedure ColorTable.FillColorTable;
{ Moves the correct built-in color table to the program's color table }
var
  P : Pointer;
begin
  case TableType of
    ColorColor : P := @ColorColors;
    ColorBW : P := @BWColors;
    ColorMono : P := @MonoColors;
  end; { case }
  Move(P^, BlankColor, TotalColors);
end; { ColorTable.FillColorTable }

{$F+}

procedure ScreenExit;
{ Clears the screen at exit }
begin
  Scr.Done;
end; { ScreenExit }

{$F-}

begin
  SavedExitProc := ExitProc;
  ExitProc := @ScreenExit;
  TextMode(LastMode);
  Scr.Init;
  Colors.Init;
end.
