DEFINITION MODULE Windows;

	(****************************************************************)
	(*								*)
	(*		    Text-mode screen windows.			*)
	(*								*)
	(*  Programmer:		P. Moylan				*)
	(*  Last edited:	10 December 1994			*)
	(*  Status:		Working					*)
	(*								*)
	(****************************************************************)

(************************************************************************)
(*									*)
(* REMARK: There are two versions of the basic 'write' procedure.	*)
(* Procedure WriteChar treats every character as a printable character,	*)
(* so it can write any character in the standard character set.  This	*)
(* is the output procedure recommended for general use.  Control	*)
(* operations are supplied by separate procedures such as WriteLn,	*)
(* SetCursor, etc., so there is no need to resort to the BASIC		*)
(* programmers' trick of writing long strings of obscure control codes.	*)
(* But for people who can't break this bad habit, procedure Write is	*)
(* supplied; this treats character codes 0..31 as control codes.	*)
(* Its main intended uses are to echo keyboard input, and to print a	*)
(* file which contains embedded control characters; but it may be	*)
(* used for other purposes if desired.					*)
(*									*)
(* Warning: I have not bothered to look after all the control codes,	*)
(* just the ones I found a use for.  Most of them print as a		*)
(* two-character sequence ^<letter>.					*)
(*									*)
(************************************************************************)

(************************************************************************)
(*									*)
(* NOTE ON CRITICAL SECTIONS: When using this module in a multitasking	*)
(* environment, there are numerous critical sections because several	*)
(* tasks may be using the same physical screen.  These sections are	*)
(* protected by semaphores.  (For the case where we are not using the	*)
(* multitasking kernel, there is a version of module Semaphores which	*)
(* contains dummy procedures).  The general philosophy is to assume	*)
(* that critical section protection is needed for inter-window		*)
(* conflicts, but that no protection is needed for operations on a	*)
(* single window because the most common situation is that each window	*)
(* is used by only one task.  If there happen to be windows which are	*)
(* shared by two or more tasks, the associated synchronization problems	*)
(* must be resolved by the caller; they are not resolved in module	*)
(* Windows, on the grounds that the extra overhead is not justified	*)
(* just to support a case which rarely happens in practice.		*)
(*									*)
(*									*)
(*			LATEST DEVELOPMENT				*)
(*									*)
(* Because of the addition of the MouseControl module, it can happen in	*)
(* practice, so I've added partial protection.  The following		*)
(* procedures are indivisible with respect to one another, as far as	*)
(* shared uses of a window are concerned:				*)
(*	OpenWindow, CloseWindow, ShiftWindowRel, ScrollUp, ScrollDown,	*)
(*	WriteChar, WriteLn, SetCursor, Hide, PutOnTop.			*)
(* Minor technicality: in some cases there is an implied PutOnTop, or	*)
(* similar side-effect, and this is not always part of the indivisible	*)
(* section.  I can't think of any case where this would matter to the	*)
(* user.  I don't like making protected sections too large, since this	*)
(* can degrade overall system responsiveness.				*)
(*									*)
(* Note: procedures WriteString and Write, while not indivisible in	*)
(* the above sense, are protected at the "write a character" level.	*)
(*									*)
(************************************************************************)

CONST
    MaxRowNumber = 24;
    MaxColumnNumber = 79;

TYPE
    Window;	(* is private *)
    Colour = (black, blue, green, cyan, red, magenta, brown, white,
		darkgrey, lightblue, lightgreen, lightcyan, lightred,
		lightmagenta, yellow, intensewhite);

    (* Note: Any of these colours may be used as a foreground colour,	*)
    (* but in most applications only [black..white] are suitable as	*)
    (* background colours.  The others may be used, but they can give	*)
    (* strange effects such as a blinking display.  Use with care!	*)

    RowRange = [0..MaxRowNumber];
    ColumnRange = [0..MaxColumnNumber];

    Rectangle = RECORD
		    top, bottom: RowRange;
		    left, right: ColumnRange;
		END (*RECORD*);

    FrameType = (noframe, simpleframe, doubleframe);
    DividerType = (nodivider, simpledivider, doubledivider);

    DisplayPage = [0..3];

PROCEDURE OpenWindow (VAR (*OUT*) w: Window;
			ForegroundColour, BackgroundColour: Colour;
			firstline, lastline: RowRange;
			firstcol, lastcol: ColumnRange;
			FrameDesired: FrameType;
			DividerDesired: DividerType);

    (* Create a new window.  Note that row and column numbers start	*)
    (* from 0.  NOTE: If the window has a box drawn around it (the case	*)
    (* FrameDesired <> noframe), this subtracts from the space		*)
    (* available for text.						*)

PROCEDURE OpenSimpleWindow (VAR (*OUT*) w: Window;
			firstline, lastline: RowRange;
			firstcol, lastcol: ColumnRange);

    (* Identical to OpenWindow, except that you don't get any choice	*)
    (* about the colours or frame.  The window is white-on-black with	*)
    (* a simple frame and no dividers for the scrolling region.  This	*)
    (* version of OpenWindow is useful for those with monochrome	*)
    (* displays who don't want to be bothered with importing the types	*)
    (* Colour, FrameType, and DividerType.				*)

PROCEDURE ChangeScrollingRegion (w: Window; firstline, lastline: RowRange);

    (* Changes the scrolling region of window w to the new line		*)
    (* boundaries given, and sets the cursor of this window to the	*)
    (* start of the scrolling region.  Row numbers are window-relative;	*)
    (* that is, line 0 is the top line of the window (which is where	*)
    (* the border is, unless you have no border).			*)

PROCEDURE NewScrollingRegion (w: Window;  firstline, lastline: RowRange;
				firstcolumn, lastcolumn: ColumnRange);

    (* Changes the scrolling region of w to be the specified rectangle,	*)
    (* but unlike ChangeScrollingRegion this procedure does not redraw	*)
    (* the dividers.  Furthermore the old scrolling region set by	*)
    (* ChangeScrollingRegion is remembered and may be restored by a	*)
    (* call to ResetScrollingRegion.					*)

PROCEDURE ResetScrollingRegion (w: Window);

    (* Changes the scrolling region of w back to what it was the last	*)
    (* time ChangeScrollingRegion was called.  If ChangeScrollingRegion	*)
    (* was never called, the scrolling region goes back to being the	*)
    (* entire window minus the frame (if any).				*)

PROCEDURE ShiftWindowRel (w: Window;  rowchange, columnchange: INTEGER);

    (* Moves w on the screen.  The second and third arguments may be	*)
    (* negative.  The amount of move may be reduced to prevent a move	*)
    (* off the edge of the screen.					*)

PROCEDURE ShiftWindowAbs (w: Window;  top: RowRange;  left: ColumnRange);

    (* Like ShiftWindowRel, except that we directly specify the target	*)
    (* position of the top left corner in screen coordinates.		*)

PROCEDURE CloseWindow (w: Window);

    (* Destroys the window.  Note that this could have side-effects	*)
    (* if procedure InstallCloseHandler (see below) has been called.	*)

PROCEDURE Hide (w: Window);

    (* Makes this window invisible on the screen.  It is still possible	*)
    (* to write to the window, but the output will not appear until	*)
    (* a PutOnTop(w) is executed.					*)

PROCEDURE PutOnPage (w: Window;  p: DisplayPage);

    (* Moves window w to another display page.  The default is to put	*)
    (* every window on page 0 when it is first opened.  To override	*)
    (* the default, call this procedure after opening the window.	*)

PROCEDURE PageOf (w: Window): DisplayPage;

    (* Returns the display page on which window w resides. *)

PROCEDURE PutOnTop (w: Window);

    (* Makes sure that window w is fully visible on the screen.  (This	*)
    (* also cancels the effect of a Hide(w).)  Rarely needed, since	*)
    (* many window operations automatically put their window on top.	*)

PROCEDURE IdentifyTopWindow (VAR (*OUT*) w: Window;
				VAR (*INOUT*) row: RowRange;
				VAR (*INOUT*) col: ColumnRange): BOOLEAN;

    (* On entry w is unspecified and (row,col) describes a position on	*)
    (* the screen.  On exit w is equal to the top window containing	*)
    (* this screen location, and (row,col) have been altered to be	*)
    (* window-relative coordinates.  Exception: if there is no visible	*)
    (* window containing the given point, the function result is FALSE,	*)
    (* the returned w is meaningless, and row and col are unchanged.	*)

PROCEDURE RefreshDisplay;

    (* Rewrites every open window.  Should not normally be needed, but	*)
    (* available for use in cases the display is corrupted by, for	*)
    (* example, software which bypasses this module and writes directly	*)
    (* to the screen.							*)

PROCEDURE SetActivePage (page: DisplayPage);

    (* Changes the active display page.  Not needed unless you are	*)
    (* switching among multiple pages.					*)

TYPE PageChangeHandler = PROCEDURE (DisplayPage);

PROCEDURE RequestPageChangeNotification (Proc: PageChangeHandler);

    (* Sets up Proc as a procedure to be called on a page change.	*)

TYPE CloseHandlerProc = PROCEDURE (Window, DisplayPage);

PROCEDURE InstallCloseHandler (w: Window;  P: CloseHandlerProc);

    (* Sets up P as a procedure to be called when the window is closed.	*)
    (* It is legal to define multiple handlers for the same window.	*)

PROCEDURE WindowLocation (w: Window): Rectangle;

    (* Returns the current location of w on the screen. *)

PROCEDURE SetCursor (w: Window; row: RowRange; column: ColumnRange);

    (* Sets the cursor for window w to the given row and column.  The	*)
    (* coordinates are window-relative; that is, they start at (0,0) at	*)
    (* the top left of the window.					*)

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

    (* Returns the current cursor position.  The coordinates are	*)
    (* window-relative; that is, they start at (0,0) at the top left of	*)
    (* the window.							*)

PROCEDURE CursorLeft (w: Window);

    (* Moves the window cursor one position left.  If it falls off the	*)
    (* left edge of the window, it moves to the right edge in the same	*)
    (* row.								*)

PROCEDURE CursorRight (w: Window);

    (* Moves the window cursor one position right.  If it falls off the	*)
    (* right edge of the window, it moves to the left edge in the same	*)
    (* row.								*)

PROCEDURE CursorUp (w: Window);

    (* Moves the window cursor one position up.  If it falls off the	*)
    (* top edge of the window, it moves to the bottom edge in the same	*)
    (* column.								*)

PROCEDURE CursorDown (w: Window);

    (* Moves the window cursor one position down.  If it falls off the	*)
    (* bottom edge of the window, it moves to the top edge in the same	*)
    (* column.								*)

PROCEDURE ScrollUp (w: Window);

    (* Scrolls the scrolling region of window w up by one row, filling	*)
    (* the vacated row with spaces.					*)

PROCEDURE ScrollDown (w: Window);

    (* Scrolls the scrolling region of window w down by one row,	*)
    (* filling the vacated row with spaces.				*)

PROCEDURE WriteLn (w: Window);

    (* Go to next line in window, scrolling if necessary.  N.B. The	*)
    (* window does not scroll if you are not in the scrolling region	*)
    (* at the time of the WriteLn.					*)

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

    (* Write one character.  Control characters are not given special	*)
    (* treatment; they produce something visible just like any other	*)
    (* character.  Wraps to the next line before writing if the write	*)
    (* would put us on or beyond the right border of w.			*)

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

    (* Like WriteChar, but codes in the range 0..31 are treated as	*)
    (* control characters.  This procedure is not recommended for	*)
    (* general use, as it leads to obscure programs.  (Instead, do the	*)
    (* control operations by direct calls to the cursor control		*)
    (* procedures which are also supplied in this module).  It is	*)
    (* supplied mainly to help those who are used to the conventions of	*)
    (* the "standard" Modula-2 I/O modules such as InOut.		*)

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

    (* Write a string of characters, stopping at the first NUL		*)
    (* character or the end of the array, whichever comes first.	*)

PROCEDURE ReadBack (w: Window;  r: RowRange;  c: ColumnRange): CHAR;

    (* Returns the character which currently occupies relative location	*)
    (* (r,c) on the display of window w.				*)

PROCEDURE ReadChar (w: Window;  VAR (*OUT*) ch: CHAR);

    (* Read one character, and echo it.	*)

PROCEDURE LookaheadChar (w: Window): CHAR;

    (* Reads a character without consuming it.  That is, the character	*)
    (* remains available to be read by ReadChar.  This allows the	*)
    (* caller to check whether the character is really wanted.		*)

PROCEDURE ReadCharWithoutEcho (w: Window;  VAR (*OUT*) ch: CHAR);

    (* Read one character, but don't echo it.  However, a blinking	*)
    (* cursor is still displayed to prompt for the character.  (If you	*)
    (* don't want the blinking cursor, use procedure GetKey).		*)

PROCEDURE GetKey (w: Window): CHAR;

    (* Read one character, without any prompt to the user.  The reason	*)
    (* for specifying a window parameter is to ensure that keyboard	*)
    (* input comes to us only when this window has input focus.		*)
    (* (If you want the input regardless of input focus, use procedure	*)
    (* Keyboard.InKey).							*)

PROCEDURE PressAnyKey (w: Window);

    (* Types a "Press any key to continue" message.	*)

PROCEDURE ReadString (w: Window;  VAR (*OUT*) result: ARRAY OF CHAR);

    (* Reads a character string, terminated by carriage return.		*)

PROCEDURE EditString (w: Window;  VAR (*INOUT*) result: ARRAY OF CHAR;
						fieldsize: CARDINAL);

    (* Reads a character string, where a default result is supplied by	*)
    (* the caller.  The final result is the state of the string at the	*)
    (* time where the keyboard user types a carriage return or Esc, or	*)
    (* uses a cursor movement key to move out of the displayed field.	*)
    (* The terminating character remains available, via Keyboard.InKey,	*)
    (* to the caller.  At most fieldsize characters of the string can	*)
    (* be edited, and perhaps fewer if the result array is smaller or	*)
    (* if there is insufficient space in the window.			*)

PROCEDURE EditAborted (): BOOLEAN;

    (* Checks the next keyboard input.  Returns TRUE for Escape, FALSE	*)
    (* for anything else.  Escape or Carriage Return are consumed, any	*)
    (* other character is returned to the Keyboard module.		*)

PROCEDURE SetColours (w: Window; row: RowRange; col: ColumnRange;
			nchar: CARDINAL;  foreground, background: Colour);

    (* Sets a field of nchar characters, starting at (row,col), to	*)
    (* the specified foreground and background colours.  The location	*)
    (* is given in window-relative coordinates, not absolute screen	*)
    (* positions.  NOTE: Do not assume that this procedure can wrap	*)
    (* around to a new line.  It normally cannot.			*)

PROCEDURE ColourSwap (w: Window; row: RowRange; col: ColumnRange;
							nchar: CARDINAL);

    (* Sets a field of nchar characters, starting at (row,col), to	*)
    (* "reverse video" by swapping the foreground and background	*)
    (* colours.  Notice that the process is reversible: you get back to	*)
    (* "normal video" by calling this procedure again.  The location is	*)
    (* given in window-relative coordinates, not absolute screen	*)
    (* positions.  NOTE: Do not assume that this procedure can wrap	*)
    (* around to a new line.  It normally cannot.			*)

PROCEDURE Blink (w: Window; r: RowRange; c: ColumnRange; nchar: CARDINAL);

    (* Toggles the blinking status - that is, turns blinking on if it	*)
    (* was off, and vice versa - for nchar characters, starting at	*)
    (* relative location (r,c) in window w.				*)
    (* NOTE: This procedure will not wrap around to a new row.		*)

PROCEDURE EraseLine (w: Window; option: CARDINAL);

    (* Erases some or all of the current line (but never the border).	*)
    (* The erased characters are replaced by space characters.  The	*)
    (* window cursor is moved to the location of the first erased	*)
    (* character.  If w is not the currently active window, the changes	*)
    (* will not be visible until w is on top again.  The options are:	*)
    (*		0	the whole of the line, except for the border	*)
    (*		1	from the current cursor position onwards	*)
    (*		2	from the start to just before the cursor	*)
    (* If we are inside a scrolling region, then only that part of the	*)
    (* line inside the scrolling region is affected.			*)

END Windows.
