packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/gwindows.mod

IMPLEMENTATION MODULE GWindows;

	(********************************************************)
	(*							*)
	(*	    Windows module for screen graphics		*)
	(*							*)
	(*  Programmer:		P. Moylan			*)
	(*  Last edited:	21 February 1995		*)
	(*  Status:						*)
	(*	Points, lines, and text working.		*)
	(*	Still adding more features.			*)
	(*	Now working on scrolling, setting cursor, etc.	*)
	(*							*)
	(********************************************************)

FROM Graphics IMPORT
    (* proc *)	SetMode, SetDefaultMode, GetScreenShape;

FROM Storage1 IMPORT
    (* proc *)	ALLOCATE, DEALLOCATE;

FROM ScreenGeometry IMPORT
    (* proc *)	Inside, TrimLine;

FROM Tiles IMPORT
    (* type *)	TileSet,
    (* proc *)	CreateTileSet, DiscardTileSet, AddPoint, AddLine,
		AddRectangle, AddString, ClearTileSet, ScrollContents,
		AddRotatedString, TileSetMemory;

FROM Keyboard IMPORT
    (* proc *)	InKey;

FROM TerminationControl IMPORT
    (* proc *)	SetTerminationProcedure, TerminationMessage;

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

TYPE
    (* A window is described by a record with the following fields.	*)
    (*	absborder	the outside edge of the window, in absolute	*)
    (*			(i.e. screen-relative) coordinates.		*)
    (*	databorder	the rectangle, in screen-relative coordinates,	*)
    (*			outside which data should not be plotted.	*)
    (*	tiles		the set of tiles making up the window.		*)
    (*	background	the background colour for the window.		*)
    (*	foreground	the foreground colour for the window.		*)
    (*	cursor		the text cursor, in terms of points.		*)
    (*	row, column	the text cursor, in terms of characters.	*)
    (*	ScrollRegion	the "normal" region for text, measured in	*)
    (*			  terms of character positions.			*)

    Window = POINTER TO
		RECORD
		    absborder, databorder: Rectangle;
		    tiles: TileSet;
		    background, foreground: ColourType;
		    cursor: Point;

		    (* The following fields are in units of characters	*)
		    (* rather than (x,y) coordinates.			*)

		    row, column: INTEGER;
		    ScrollRegion: Rectangle;

		END (*RECORD*);

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

VAR
    (* Maximum screen coordinates and colour for the selected mode.	*)
    (* (Minimum value is zero in all three cases.)			*)

    XMAX, YMAX, CharHeight: CARDINAL;
    MaxColour: ColourType;

(************************************************************************)
(*			OPENING AND CLOSING WINDOWS			*)
(************************************************************************)

PROCEDURE DrawFrame (w: Window);

    (* Draws the window frame.  Used when first opening a window, or	*)
    (* for putting back the frame after clearing the window.		*)

    VAR R: Rectangle;

    BEGIN
	WITH w^ DO
	    AddRectangle (tiles, absborder, foreground);
	    IF databorder.left <> absborder.left+1 THEN
		R := databorder;
		WITH R DO
		    DEC(left);  INC (right);
		    DEC(bottom);  INC (top);
		END (*WITH*);
		AddRectangle (tiles, R, foreground);
	    END (*IF*);
	END (*WITH*);
    END DrawFrame;

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

PROCEDURE OpenWindow (VAR (*OUT*) w: Window;
				left, bottom, right, top: CARDINAL;
				Foregrnd, Backgrnd: ColourType;
				b: BorderType);

    (* Creates a new window.	*)

    VAR temp: CARDINAL;

    BEGIN
	(* Make sure that the parameters are within the legal ranges.	*)

	IF left > XMAX THEN left := XMAX END(*IF*);
	IF right > XMAX THEN right := XMAX END(*IF*);
	IF left > right THEN
	    temp := left;  left := right;  right := temp;
	END (*IF*);
	IF bottom > YMAX THEN bottom := YMAX END(*IF*);
	IF top > YMAX THEN top := YMAX END(*IF*);
	IF bottom > top THEN
	    temp := bottom;  bottom := top;  top := temp;
	END (*IF*);

	IF Backgrnd > MaxColour THEN Backgrnd := MaxColour END (*IF*);
	IF Foregrnd > MaxColour THEN Foregrnd := MaxColour END (*IF*);
	IF Foregrnd = Backgrnd THEN
	    Foregrnd := (Backgrnd + 1) MOD (MaxColour + 1);
	END (*IF*);

	NEW (w);
	WITH w^ DO
	    background := Backgrnd;  foreground := Foregrnd;
	    absborder.left := left;
	    absborder.right := right;
	    absborder.bottom := bottom;
	    absborder.top := top;
	    databorder := absborder;
	    IF b = double THEN temp := 3
	    ELSE temp := 1;
	    END (*IF*);
	    WITH databorder DO
		INC (left, temp);  DEC (right, temp);
		INC (bottom, temp);  DEC (top, temp);
	    END (*WITH*);
	    WITH ScrollRegion DO
		left := 0;  top := 0;

		(* The following two lines might need fine tuning.	*)

		right := (databorder.right - databorder.left) DIV 8 - 1;
		bottom := (databorder.top - databorder.bottom)
					DIV VAL(INTEGER,CharHeight) - 1;

	    END (*WITH*);
	    tiles := CreateTileSet (absborder, background);
	END (*WITH*);

	DrawFrame (w);
	SetCursor (w, 0, 0);

    END OpenWindow;

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

PROCEDURE OpenWindowR (VAR (*OUT*) w: Window;  location: Rectangle;
					Foregrnd, Backgrnd: ColourType;
					b: BorderType);

    (* Same as OpenWindow, except for method of specifying location.	*)

    BEGIN
	WITH location DO
	    IF left < 0 THEN left := 0 END(*IF*);
	    IF right < 0 THEN right := 0 END(*IF*);
	    IF bottom < 0 THEN bottom := 0 END(*IF*);
	    IF top < 0 THEN top := 0 END(*IF*);
	    OpenWindow (w, left, bottom, right, top, Backgrnd, Foregrnd, b);
	END (*WITH*);
    END OpenWindowR;

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

PROCEDURE WindowMemory (w: Window;  memory: BOOLEAN);

    (* Specifying a FALSE value for the memory parameter means that	*)
    (* subsequent data sent to this window will be written to the	*)
    (* screen but not remembered.  This saves time and memory, the only	*)
    (* penalty being that data covered by an overlapping window will	*)
    (* be lost.  Specifying TRUE restores the default condition, where	*)
    (* all window data are retained for refreshing the screen when	*)
    (* necessary.							*)

    BEGIN
	TileSetMemory (w^.tiles, memory);
    END WindowMemory;

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

PROCEDURE CloseWindow (VAR (*INOUT*) w: Window);

    (* Destroys the window.	*)

    BEGIN
	DiscardTileSet (w^.tiles);
	DISPOSE (w);
    END CloseWindow;

(************************************************************************)
(*			     CLEARING A WINDOW				*)
(************************************************************************)

PROCEDURE ClearWindow (w: Window);

    (* Erases all data from w, but keeps it open.	*)

    BEGIN
	ClearTileSet (w^.tiles);
	DrawFrame (w);  SetCursor (w, 0, 0);
    END ClearWindow;

(************************************************************************)
(*			SETTING THE FOREGROUND COLOUR			*)
(************************************************************************)

PROCEDURE SetColour (w: Window;  colour: ColourType);

    (* Specifies the foreground colour to be used until further notice.	*)

    BEGIN
	w^.foreground := colour;
    END SetColour;

(************************************************************************)
(*			     PLOTTING A POINT				*)
(************************************************************************)

PROCEDURE PutPixelC (w: Window;  p: Point;  c: ColourType);

    (* Plots a dot at the point (x,y) in window w.  The coordinates are	*)
    (* relative to the bottom left of the window.  If the dot lies	*)
    (* outside the window it will be ignored.				*)

    BEGIN
	WITH w^ DO
	    INC (p.x, absborder.left);
	    INC (p.y, absborder.bottom);
	    IF Inside (p.x, p.y, databorder) THEN
		AddPoint (tiles, p, c);
	    END (*WITH*);
	END (*WITH*);
    END PutPixelC;

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

PROCEDURE PutPixel (w: Window;  p: Point);

    (* Plots a dot at the point (x,y) in window w.  The coordinates are	*)
    (* relative to the bottom left of the window.  If the dot lies	*)
    (* outside the window it will be ignored.				*)

    BEGIN
	WITH w^ DO
	    INC (p.x, absborder.left);
	    INC (p.y, absborder.bottom);
	    IF Inside (p.x, p.y, databorder) THEN
		AddPoint (tiles, p, foreground);
	    END (*WITH*);
	END (*WITH*);
    END PutPixel;

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

PROCEDURE PutPixel2 (w: Window;  x, y: INTEGER);

    (* Same as PutPixel, with a different way of specifying the point.	*)

    VAR p: Point;

    BEGIN
	p.x := x;  p.y := y;
	PutPixel (w, p);
    END PutPixel2;

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

PROCEDURE PutPixel2C (w: Window;  x, y: INTEGER;  colour: ColourType);

    (* Same as PutPixel2, with the colour explicitly specified.	*)

    VAR p: Point;

    BEGIN
	p.x := x;  p.y := y;
	PutPixelC (w, p, colour);
    END PutPixel2C;

(************************************************************************)
(*			DRAWING A STRAIGHT LINE				*)
(************************************************************************)

PROCEDURE LineC (w: Window;  start, end: Point;  colour: ColourType);

    (* Draws a straight line.  The points are relative to the bottom	*)
    (* left corner of w.  Parts of the line lying outside the window	*)
    (* are clipped.							*)

    BEGIN
	WITH w^ DO
	    WITH absborder DO
		INC (start.x, left);  INC (start.y, bottom);
		INC (end.x, left);  INC (end.y, bottom);
	    END (*WITH*);
	    IF TrimLine (start, end, databorder) THEN
		AddLine (tiles, start, end, colour);
	    END (*IF*);
	END (*WITH*);
    END LineC;

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

PROCEDURE Line (w: Window;  start, end: Point);

    (* Draws a straight line.  The points are relative to the bottom	*)
    (* left corner of w.  Parts of the line lying outside the window	*)
    (* are clipped.							*)

    BEGIN
	LineC (w, start, end, w^.foreground);
    END Line;

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

PROCEDURE Line2 (w: Window;  xstart, ystart, xend, yend: INTEGER);

    (* The same operation as Line, with a different way of specifying	*)
    (* the parameters.							*)

    VAR start, end: Point;

    BEGIN
	start.x := xstart;  start.y := ystart;
	end.x := xend;  end.y := yend;
	Line (w, start, end);
    END Line2;

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

PROCEDURE Line2C (w: Window;  xstart, ystart, xend, yend: INTEGER;
							colour: ColourType);

    (* The same operation as Line2, but with the colour explicitly	*)
    (* specified.							*)

    VAR start, end: Point;

    BEGIN
	start.x := xstart;  start.y := ystart;
	end.x := xend;  end.y := yend;
	LineC (w, start, end, colour);
    END Line2C;

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

PROCEDURE StringLength (VAR (*IN*) text: ARRAY OF CHAR): CARDINAL;

    (* Finds the true size of the text string, given that there	*)
    (* could be a nul terminator in the middle.			*)

    VAR count: CARDINAL;

    BEGIN
	count := 0;
	WHILE (count <= HIGH(text)) AND (text[count] <> CHR(0)) DO
	    INC (count);
	END (*WHILE*);
	RETURN count;
    END StringLength;

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

PROCEDURE GString (w: Window;  x, y: CARDINAL;  text: ARRAY OF CHAR);

    (* Writes a horizontal character string at graphics position (x,y)	*)
    (* relative to window w.  Characters which do not fit are not	*)
    (* displayed.  This is not considered to be a text operation since	*)
    (* the text cursor is not affected and there is no line wrap.	*)

    VAR count: CARDINAL;  place: Point;

    BEGIN
	count := StringLength (text);
	IF count = 0 THEN RETURN END(*IF*);

	(* Add the string to the TileSet.	*)

	WITH w^ DO
	    place.x := x + CARDINAL (absborder.left);
	    place.y := y + CARDINAL (absborder.bottom);
	    AddString (tiles, place, text, count, foreground, databorder);
	END (*WITH*);

    END GString;

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

PROCEDURE GStringUp (w: Window;  x, y: CARDINAL;  text: ARRAY OF CHAR);

    (* Like GString, but the string is rotated counterclockwise by	*)
    (* 90 degrees, i.e. it is written in the +Y direction.		*)

    VAR count: CARDINAL;  place: Point;

    BEGIN
	count := StringLength (text);
	IF count = 0 THEN RETURN END(*IF*);

	(* Add the string to the TileSet.	*)

	WITH w^ DO
	    place.x := x + CARDINAL (absborder.left);
	    place.y := y + CARDINAL (absborder.bottom);
	    AddRotatedString (tiles, place, text, count, foreground,
							databorder);
	END (*WITH*);

    END GStringUp;

(************************************************************************)
(*			    TEXT OPERATIONS				*)
(************************************************************************)
(*									*)
(*  Every open window has a "text cursor" which is used only for text	*)
(*  operations and is independent of any operations on dots and lines.	*)
(*  The text cursor is updated after any text operation in such a way	*)
(*  that the characters follow one another in the way one would expect	*)
(*  for non-graphics windows.						*)
(*									*)
(************************************************************************)

PROCEDURE ComputeGraphicCursor (w: Window);

    (* Converts character position to graphics coordinates in w^.cursor	*)

    BEGIN
	WITH w^ DO
	    WITH cursor DO
		x := databorder.left + 8*column + 3;
		y := databorder.top - VAL(INTEGER,CharHeight)*(row+1) - 2;
	    END (*WITH*);
	END (*WITH*);
    END ComputeGraphicCursor;

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

PROCEDURE SetCursor (w: Window;  row, column: CARDINAL);

    (* Sets the text cursor to the specified row and column.  The row	*)
    (* and column are measured in units of characters (not pixels),	*)
    (* with (0,0) representing the first character position at the	*)
    (* upper left of the window.					*)

    BEGIN
	w^.row := row;  w^.column := column;
	ComputeGraphicCursor (w);
    END SetCursor;

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

PROCEDURE SaveCursor (w: Window;  VAR (*OUT*) row, column: CARDINAL);

    (* Returns the current row and column of the text cursor. *)

    BEGIN
	row := w^.row;  column := w^.column;
    END SaveCursor;

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

PROCEDURE ScrollUp (w: Window);

    (* Moves all the text in w up by one line, discarding what falls	*)
    (* off the top.							*)

    BEGIN
	ScrollContents (w^.tiles, CharHeight, w^.databorder);
    END ScrollUp;

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

PROCEDURE WriteLn (w: Window);

    (* Sets the text cursor to the start of the next text line down.	*)
    (* If the cursor reaches the bottom of the window, the text in the	*)
    (* window is scrolled.						*)

    BEGIN
	WITH w^ DO
	    column := ScrollRegion.left;
	    IF row >= ScrollRegion.bottom THEN ScrollUp (w)
	    ELSE INC (row);
	    END (*IF*);
	END (*WITH*);
	ComputeGraphicCursor (w);
    END WriteLn;

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

PROCEDURE WriteString (w: Window;  text: ARRAY OF CHAR);

    (* Writes a horizontal character string at the current text cursor	*)
    (* position for window w.  Characters which do not fit on the	*)
    (* current line are wrapped around to a new row.			*)

    VAR count, amount, pos, j, canfit: CARDINAL;

    BEGIN
	amount := StringLength (text);
	pos := 0;
	WITH w^ DO
	    WITH ScrollRegion DO
		IF (amount = 0) OR (right < left) THEN RETURN END(*IF*);
	    END (*WITH*);
	    LOOP
		IF column > ScrollRegion.right THEN
		    WriteLn (w);
		END (*IF*);
		canfit := ScrollRegion.right - column + 1;
		IF amount > canfit THEN count := canfit
		ELSE count := amount;
		END (*IF*);

		AddString (tiles, cursor, text, count, foreground, databorder);
		INC (column, count);  INC (cursor.x, 8*count);

		DEC (amount, count);
		IF amount = 0 THEN EXIT(*LOOP*) END(*IF*);

		WriteLn (w);
		INC (pos, count);
		FOR j := 0 TO amount-1 DO
		    text[j] := text[count+j];
		END (*FOR*);

	    END (*LOOP*);

	END (*WITH*);

    END WriteString;

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

PROCEDURE WriteChar (w: Window;  ch: CHAR);

    (* Writes a horizontal character at the current text cursor		*)
    (* position for window w.  The text cursor is updated.		*)

    VAR buffer: ARRAY [0..0] OF CHAR;

    BEGIN
	buffer[0] := ch;
	WriteString (w, buffer);
    END WriteChar;

(************************************************************************)
(*				SHUTDOWN				*)
(************************************************************************)

PROCEDURE ShutDown;

    VAR w: Window;  dummy: CHAR;
	message: ARRAY [0..79] OF CHAR;
	
    BEGIN
	(* For abnormal termination, put an error diagnostic on the	*)
	(* screen.							*)

	IF TerminationMessage(message) THEN
	    OpenWindow (w, 0,0, 300,30, 0, MaxColour, single);
	    WriteString (w, message);
	    dummy := InKey();
	    CloseWindow (w);
	END (*IF*);

    END ShutDown;

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

PROCEDURE InitGraphics (mode: CARDINAL);

    (* Sets up the Graphics mode.  Optional, since the module starts up	*)
    (* with a best estimate of the "best" mode possible on the		*)
    (* available hardware.						*)

    BEGIN
	SetMode (mode, TRUE);
	GetScreenShape (XMAX, YMAX, MaxColour, CharHeight);
    END InitGraphics;

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

BEGIN
    SetDefaultMode;
    GetScreenShape (XMAX, YMAX, MaxColour, CharHeight);
    SetTerminationProcedure (ShutDown);
END GWindows.