IMPLEMENTATION MODULE GlassTTY;

	(********************************************************)
	(*							*)
	(*	     Simple screen output routines.		*)
	(*							*)
	(*  This module handles screen output at a very low	*)
	(*  level, without supplying the advanced features	*)
	(*  which may be found in, for example, module Windows.	*)
	(*  It is intended for things like error message	*)
	(*  output, and is designed for compactness rather	*)
	(*  than comprehensiveness.				*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	27 February 1995		*)
	(*  Status:		OK				*)
	(*							*)
	(********************************************************)

FROM SYSTEM IMPORT
    (* proc *)	ADR;

FROM Types IMPORT
    (* proc *)	FarPointer, FarCharPointer;

FROM TextVideo IMPORT
    (* proc *)	VideoKind, PositionCursor;

FROM LowLevel IMPORT
    (* proc *)	LowByte, HighByte, RSB, IANDB,
		Far, MakePointer, SEGMENT, OFFSET, FarCopy;

(************************************************************************)

CONST
    BytesPerChar = 2;			(* # bytes/char in video buffer	*)
    NoOfColumns = 80;
    BytesPerRow = NoOfColumns*BytesPerChar;  (* bytes per screen row	*)
    bottomrow = 24;			(* row # of last screen row	*)

TYPE
    RowRange = [0..bottomrow];
    HexDigit = SHORTCARD [0..15];

(*************************************************************************)

VAR
    (* BlankRow is set up by the initialisation code as a row of space	*)
    (* characters.							*)

    BlankRow: ARRAY [0..BytesPerRow-1] OF CHAR;

    (* 16*ScreenSeg is the physical address of the video buffer.  The	*)
    (* value depends on whether we are using black-and-white or colour.	*)

    ScreenSeg: CARDINAL;

    (* ScreenPosition is the current cursor location, relative to the	*)
    (* start of the screen.  SavedScreenPosition is a copy made by	*)
    (* procedure SaveCursor.						*)

    ScreenPosition, SavedScreenPosition: CARDINAL;

    (* CurrentRow is the number of the current screen line.		*)

    CurrentRow: RowRange;

    (* CurrentColumn is the number of the current screen column.  The	*)
    (* special case CurrentColumn = NoOfColumns means that we have run	*)
    (* off the end of the current row, and must do a WriteLn or		*)
    (* SetCursor before writing a new character.			*)

    CurrentColumn: [0..NoOfColumns];

(************************************************************************)
(*			SCROLLING AND CURSOR MOVEMENTS			*)
(************************************************************************)

PROCEDURE SetCursor (row, column: CARDINAL);

    (* Moves the screen cursor to the specified row and column.		*)

    BEGIN
	CurrentRow := row;  CurrentColumn := column;
	ScreenPosition := BytesPerRow*row + BytesPerChar*column;
	PositionCursor (TRUE, ScreenPosition, FALSE);
    END SetCursor;

(************************************************************************)

PROCEDURE SaveCursor;

    (* Remembers the current cursor position, for use by a subsequent	*)
    (* call to RestoreCursor.  Note that nesting is not supported, i.e.	*)
    (* a call to SaveCursor destroys the information saved by any	*)
    (* earlier call to SaveCursor.					*)

    BEGIN
	SavedScreenPosition := ScreenPosition;
    END SaveCursor;

(************************************************************************)

PROCEDURE RestoreCursor;

    (* Sets the cursor back to where it was at the time of the last	*)
    (* call to SaveCursor.						*)

    BEGIN
	ScreenPosition := SavedScreenPosition;
	CurrentRow := ScreenPosition DIV BytesPerRow;
	CurrentColumn := (ScreenPosition MOD BytesPerRow) DIV BytesPerChar;
	PositionCursor (TRUE, ScreenPosition, FALSE);
    END RestoreCursor;

(************************************************************************)

PROCEDURE ScrollUp;

    (* Scrolls the screen contents up by one line.  The last row is	*)
    (* filled with spaces.						*)

    VAR screenloc: FarPointer;

    BEGIN
	FarCopy (MakePointer (ScreenSeg, BytesPerRow),
			MakePointer (ScreenSeg, 0),
				BytesPerRow*bottomrow);
	ScreenPosition := BytesPerRow*bottomrow;
	screenloc := MakePointer (ScreenSeg, ScreenPosition);
	FarCopy (Far(ADR(BlankRow)), screenloc, BytesPerRow);
    END ScrollUp;

(************************************************************************)

PROCEDURE WriteLn;

    (* Moves the screen cursor to the beginning of the next line,	*)
    (* scrolling if necessary.						*)

    BEGIN
	IF CurrentRow = bottomrow THEN ScrollUp
	ELSE INC (CurrentRow);
	END (*IF*);
	CurrentColumn := 0;
	ScreenPosition := BytesPerRow*CurrentRow;
	PositionCursor (TRUE, ScreenPosition, FALSE);
    END WriteLn;

(************************************************************************)
(*			CHARACTER AND STRING OUTPUT			*)
(************************************************************************)

PROCEDURE WriteChar (ch: CHAR);

    (* Writes one character, and updates the cursor.  This procedure	*)
    (* does not recognise the concept of a control character.  Every	*)
    (* possible value of ch produces something readable on the screen.	*)
    (* If we have run off the end of the current row, wraps to a	*)
    (* new line.							*)

    VAR screenloc: FarCharPointer;

    BEGIN
	IF CurrentColumn = NoOfColumns THEN
	    WriteLn;
	END (*IF*);
	screenloc := MakePointer (ScreenSeg, ScreenPosition);
	screenloc^ := ch;
	INC (ScreenPosition, BytesPerChar);
	INC (CurrentColumn);
	PositionCursor (TRUE, ScreenPosition, FALSE);
    END WriteChar;

(************************************************************************)

PROCEDURE WriteString (text: ARRAY OF CHAR);

    (* Writes a sequence of characters, terminated either by NUL or by	*)
    (* the end of the array.						*)

    VAR j: CARDINAL;

    BEGIN
	j := 0;
	LOOP
	    IF ORD (text[j]) = 0 THEN EXIT(*LOOP*)  END (*IF*);
	    WriteChar (text[j]);  INC (j);
	    IF j > HIGH (text) THEN EXIT(*LOOP*)  END (*IF*);
	END (*LOOP*);
    END WriteString;

(************************************************************************)
(*			NUMERIC OUTPUT (HEXADECIMAL)			*)
(************************************************************************)

PROCEDURE WriteHexDigit (number: HexDigit);

    (* Writes a one-digit hexadecimal number.	*)

    BEGIN
	IF number < 10 THEN
	    WriteChar (CHR(ORD("0")+ORD(number)))
	ELSE
	    WriteChar (CHR(ORD("A")+ORD(number)-10))
	END (*IF*);
    END WriteHexDigit;

(*************************************************************************)

PROCEDURE WriteHexByte (number: BYTE);

    (* Writes its argument as a two-digit hexadecimal number.	*)

    BEGIN

	(* The obscure function names from LowLevel are:	*)
	(*	RSB = right shift	IANDB = logical AND	*)

	WriteHexDigit (RSB(number,4));
	WriteHexDigit (IANDB(number,15));
    END WriteHexByte;

(*************************************************************************)

PROCEDURE WriteHexWord (number: CARDINAL);

    (* Writes its argument as a four-digit hexadecimal number.	*)

    BEGIN
	WriteHexByte (HighByte(number));
	WriteHexByte (LowByte(number));
    END WriteHexWord;

(************************************************************************)

PROCEDURE WriteAddress (addr: ADDRESS);

    (* Writes a segmented address to the screen.	*)

    BEGIN
	WriteHexWord (SEGMENT(addr));  WriteChar (":");
	WriteHexWord (OFFSET(addr));
    END WriteAddress;

(************************************************************************)
(*			NUMERIC OUTPUT (DECIMAL)			*)
(************************************************************************)

PROCEDURE WriteLongCard (number: LONGCARD);

    (* Writes a number to the screen.	*)

    VAR remainder: CARDINAL;

    BEGIN
	IF number > 9 THEN
	    WriteLongCard (number DIV 10);
	END (*IF*);
	remainder := CARDINAL (number MOD 10);
	WriteChar (CHR(remainder + ORD("0")));
    END WriteLongCard;

(************************************************************************)

PROCEDURE WriteCard (number: CARDINAL);

    (* Writes a number to the screen.	*)

    BEGIN
	WriteLongCard (VAL(LONGCARD,number));
    END WriteCard;

(************************************************************************)

PROCEDURE WriteInt (number: INTEGER);

    (* Writes a number to the screen.	*)

    BEGIN
	IF number < 0 THEN
	    WriteChar ('-');  number := -number;
	END (*IF*);
	WriteCard (VAL(CARDINAL,number));
    END WriteInt;

(************************************************************************)
(*			    INITIALISATION				*)
(************************************************************************)

PROCEDURE Initialise;

    VAR j: CARDINAL;  dummy: BOOLEAN;

    BEGIN
	VideoKind (ScreenSeg, dummy);

	FOR j := 0 TO HIGH(BlankRow)-1 BY 2 DO
	    BlankRow[j] := " ";  BlankRow[j+1] := CHR(07H);
	END (*FOR*);

	ScreenPosition := 0;  SavedScreenPosition := 0;
	CurrentRow := 0;  CurrentColumn := 0;

    END Initialise;

(************************************************************************)

BEGIN
    Initialise;
END GlassTTY.