{*************************************************************
 ***                   TEXT USER INTERFACE UNIT            ***
 *************************************************************}
{$I compflgs}
{This is a general TUI set of routines to help with the turbo vision
library, ie to provide some shortcuts to doing common operations
such as repositioning, inserting fields, etc}

unit TUI;

INTERFACE

uses views, dialogs,
			objects, drivers,
			menus;

{returns false for cmcancel, cmclose, etc - commands which you don't need
to validate a field for}
function DoValidFor(const Command : word) : boolean;

{does view exist - generally used for acceptor lines}
function IsView(const View : PView) : boolean;

{--- Positioning ----}
procedure CheckOnDesktop(var Bounds : TRect);
procedure CentreOnView(var Bounds : TRect; View : PView);
procedure CascadeOnView(var Bounds : TRect; View : PView);
procedure SeparateULFromView(var Bounds : TRect; View : PView);
procedure SeparateDRFromView(var Bounds : TRect; View : PView);
function IsViewAt(const X,Y : integer) : boolean;

{event processing}
procedure StartEvent(const What, CommandorChar : word);

{pop up menu}
function DoPopUpMenu(Menu : PMenu; Owner : PGroup) : word;


{--- Inserting views -----}
function AddLabel(const S : String; const Link : PView) : PLabel; {automatically inserts label to TL}
function SetLabel(const Group : PGroup; const X,Y : integer; const S : String; const Link : PView) : PLabel;
function InsText(	const Group : PGroup; 	const X,Y : integer; const S : string) : PStaticText;
function InsTextR(const Group : PGroup; 	const X,Y : integer; const S : string) : PStaticText; {Right set to X}
function InsTextCX(const Group : PGroup; 	const Y 	: integer; const S : string) : PStaticText; {Insert Text centrally}

procedure CloseAllViews;


IMPLEMENTATION

uses minilib, global, app;

function DoValidFor;
begin
	DoValidFor := (Command>255) or not
		(Command in [cmValid, cmCancel, cmQuit, cmCLose]);
end;

function IsView(const View : PView) : boolean;
begin
	IsView := (Message(Desktop, evBroadCast, cmIsView, View)<>nil);
end;

{***********************************************
 ***         CLEAR ALL VIEWS                 ***
 ***********************************************}
procedure CloseAllViews;
begin
	Message(Application, evCommand, cmCloseAll, nil);
end;



{*****************************************
 **       BOUND POSITIONING             **
 *****************************************}
{as MakeGlobal, but modifies back for *desktop* insertion, not menu insertion}
procedure MakeDesktop(Source: TPoint; var Dest: TPoint);
begin
	Desktop^.MakeGlobal(Source, Dest); {note also that this converts to 1,1 at top left...}
	with Desktop^.Origin do begin dec(Source.X, X+1); dec(Source.Y, Y+1); end;
end;


function IsViewAt;
var Point : TPoint;
begin
	Point.X := X;
	Point.Y := Y;
	IsViewAt := Message(Desktop, evBroadCast, cmCompareLoc, @Point)<>nil;
end;

procedure CheckOnDesktop(var Bounds : TRect);
begin
	{Check that it's not off the desktop}
	if Bounds.B.X > Desktop^.Size.X then Bounds.Move(Desktop^.Size.X - Bounds.B.X,0);
	if Bounds.B.Y > Desktop^.Size.Y then Bounds.Move(0, Desktop^.Size.Y - Bounds.B.Y);

	if Bounds.A.X < 0 then Bounds.Move(0 - Bounds.A.X, 0);
	if Bounds.A.Y < 0 then Bounds.Move(0,0 - Bounds.A.Y);
end;

{actually places slightly high so the screen doesn't pile up at the bottom
too much.  Assumes Bounds are for a view that is to be inserted onto
the *desktop*}
procedure CentreOnView(var Bounds : TRect; View : PView);
var ViewOrigin : TPoint;
		BoundsSize : TPoint;
begin
{	if View = nil then exit;

	{Translate position of caller into *desktop* position}
{	View^.Owner^.MakeGlobal(View^.Origin, ViewOrigin);
	ViewOrigin.Y := ViewOrigin.Y-1; {allow for menu bar line - assume it's going to be inserted into desktop}

	if View=nil then View := Desktop; {centre on desktop}

	with View^ do MakeGlobal(Origin, ViewOrigin);
	dec(ViewOrigin.Y,1); {allow for menu bar line - assume it's going to be inserted into desktop}

	BoundsSize.X := Bounds.B.X - Bounds.A.X;
	BoundsSize.Y := Bounds.B.Y - Bounds.A.Y;

	Bounds.A.X := ViewOrigin.X + ((View^.Size.X - BoundsSize.X) div 2);
	{5/12 is halfway between 1/3 and a 1/2}
	Bounds.A.Y := ViewOrigin.Y + ((View^.Size.Y - BoundsSize.Y)*5 div 12);

	Bounds.B.X := BOunds.A.X + BOundsSize.X;
	Bounds.B.Y := Bounds.A.Y + BoundsSize.Y;

	CheckOnDesktop(Bounds);
end;


procedure CascadeOnView(var Bounds : TRect; View : PView);
var BoundsSize : TPoint;
begin
	if View = nil then exit;

	BoundsSize.X := Bounds.B.X - Bounds.A.X;
	BoundsSize.Y := Bounds.B.Y - Bounds.A.Y;

	{Translate position of caller into desktop position}
	View^.Owner^.MakeGlobal(View^.Origin, Bounds.A);
	{Set size}
	Bounds.B.X := Bounds.A.X + (BoundsSize.X);
	Bounds.B.Y := Bounds.A.Y + (BoundsSize.Y);
	{Cascade}
	Bounds.Move(1, 1);{}

	CheckOnDesktop(Bounds);
end;


procedure SeparateULFromView(var Bounds : TRect; View : PView);

	function Clear : boolean;
	begin
		if ((Bounds.B.X >=View^.Origin.X) or (Bounds.B.Y>=View^.Origin.Y)) then Clear := False else Clear := True;
	end;

begin
	if View=nil then exit;

	{moves diagonally then along one axis until clear or jammed...}
	while not Clear	and (Bounds.A.X>1) and (Bounds.A.X>1) do
		if Desktop^.Size.Y>30 then
			Bounds.Move(-2,-1)
		else
			Bounds.Move(-3,-1);

	{now move x}
	while (Bounds.A.X>0) and not Clear do Bounds.Move(-1,0);

	{now move y}
	while (Bounds.A.Y>0) and not Clear do Bounds.Move(0,-1);
end;


procedure SeparateDRFromView(var Bounds : TRect; View : PView);

	function Clear : boolean;
	begin
		if ((View^.Origin.X+View^.Size.X >=Bounds.A.X)
			or (View^.Origin.Y+View^.Size.Y >=Bounds.A.Y)) then Clear := False else Clear := True;
	end;

begin
	{moves diagonally then along one axis until clear or jammed...}
	while not Clear	and (Bounds.B.X<Desktop^.Size.X) and (Bounds.B.Y<Desktop^.Size.Y) do
		if Desktop^.Size.Y>30 then
			Bounds.Move(+2,+1)
		else
			Bounds.Move(+3,+1);

	{now move x}
	while (Bounds.B.X<Desktop^.Size.X) and not Clear do Bounds.Move(+1,0);

	{now move y}
	while (Bounds.B.Y<Desktop^.Size.Y) and not Clear do Bounds.Move(0,+1);
end;


{*******************************************
 ***           START EVENT               ***
 *******************************************}

{differs from putevent in that you just pass the what and the keyress/command,
rather than having to set up an Event record}
procedure StartEvent(const What, CommandorChar : word);
var Event : TEvent;
begin
	Event.What := What;
	Event.InfoPtr := nil;
	case What of
		evCommand : Event.Command := CommandorChar;
		evKeyDown : Event.KeyCode := CommandorChar;
	end;
	Desktop^.PutEvent(Event);
end;


{**********************************************************
 ***               STANDARD FIELD INSERTING             ***
 **********************************************************}
{Some routines for making construction simpler}

{========== Insert Label Title ============}
function AddLabel;
begin
	if S <> '' then
		with Link^.Origin do	AddLabel := SetLabel(Link^.Owner,X,Y,S,Link);
end;

{========== Insert Label Title, Rset at position ============}
{Doing above must be done *after* inserting the link view, which means
the link view is current & focused, which means the label ends up being
lit all the time...}
function SetLabel;
var	R : TRect;
		View : PLAbel;
begin
	if S <> '' then begin
		{PLabel has a blank space at left for mono display markers}
		{Tildes don't display}
		R.Assign(X - length(S)+Count('~',S) -1, Y, X-1, Y+1);

		View := New(PLabel, init(R, S, Link));
		if (Link=nil) or (Link^.Owner=nil) then
			Group^.Insert(View)
		else
			Group^.InsertBefore(View, Link); {inserts just before link so that get/setdata can be tracked}
		SetLabel := View;
	end;
end;


{=========== Insert Plain Text ====================}
function InsText;
var R : TRect;
		P : PStaticText;{cant insert(new(P as then current isn't set for disabled view like pstatic text and so can't return it}
begin
	{safety check}
	if length(S)>MaxViewWidth then begin
		R.Assign(X,Y, X+MaxViewWidth,Y+1);
		New(P, init(R,Copy(S,1,MaxViewWidth)));
	end else begin
		R.Assign(X,Y, X+length(S)+1, Y+1);
		New(P, init(R,S));
	end;
	Group^.Insert(P);
	InsText := P;
end;

{Right aligned text}
function InsTextR;
begin InsTextR := InsText(Group, X - length(S), Y, S); end;

{Centred text}
function InsTextCX;
begin InsTextCX := InsText(Group, (Group^.Size.X - length(S)) div 2, Y, S); end;




{*************************************************************
 ***                  CREATE & RUN A MENU BOX              ***
 *************************************************************}
{Pass menu of type PMenu and view to insert it.
Returns command as given in menu item, or cancel if nothing entered}

function DoPopUpMenu(Menu : PMenu; Owner : PGroup) :word;
var R :TRect;{}
		MenuBox : PMenuBox;
begin
	{safety check}
	if (Menu = nil) or (Menu^.Items=nil) then begin
		DoPopUpMenu := 0; {cmcancel is 11, and there *might* be 11 items, I tend to start the command for first item at 1}
		exit;
	end;

	{assumes if menu only has one item, to automatically pick that one...}
	if Menu^.Items^.Next = nil then begin
		DoPopUpMenu := Menu^.Items^.Command;
		exit;
	end;

	{create menu box}
	R.Assign(0,0,30,10); {dummy R - centralised below, and fits automatically to number of options}
	if (Owner = nil) or (Owner = PGroup(Desktop)) then
		MenuBox := New(PMenuBox, init(R, menu,nil))		{create ordinary menu box}
	else
		MenuBox := New(PDlgMenuBox, init(R, menu,nil));		{create with palette for dialogs}

	MenuBox^.Options := MenuBox^.Options or ofCentered;

	{Connect box view to menu}
	if Menu^.Default = nil then Menu^.Default := Menu^.Items; {first one}
	MenuBox^.Current := Menu^.Default;

	{Execute}
	DoPopUpMenu := Owner^.ExecView(MenuBox);      {Pop up and display}
	dispose(MenuBox, done);{}
end;



end.
