{********************************************************************
 ***                       INPUT FIELDS                           ***
 ********************************************************************}
{See also Inp*.pas}
{$I compflgs}
unit TuiEdit;

INTERFACE

uses
	tui,views,dialogs,	{text}
	tuicalc,
	{$IFDEF WIndows} windrvrs, {$ELSE} drivers, {$ENDIF}
	objects;

const
	MaxLinks = 5; {maximum number of views that any linker can handle}

const
	{additional bfxxxx constants}
	bfClose 		= $10;
	bfGetData 	= $20;
	bfForceLink = $40;

	{where markers for tabbed views}
	whLeft 	= 0;
	whRight = 1;
	whTop		= 2;
	whBottom= 3;

type
	PPoint = ^TPoint;

	PEditBox = ^TEditBox;

{	PTagLabel = ^TTagLabel;
	TTagLabel = object(TLabel)
		constructor Init(var Bounds: TRect; const AText: String; ALink: PView);
		function GetPalette: PPalette; virtual; {returns same colour as nlink when focused}
{	end;{}

	{==== EXTENDED INPUTLINE =================================}
	{With forced uppercase, blind entry, links to other fields, etc}
	PInputELine = ^TInputELine;
	TInputELine = object(TInputLine)
		{Automatic link fields}
		Linkers : PCollection;

		{Entry-type Flags}
		MustInput : boolean; {set to true if something has to be entered}
		MustInputtoClose : boolean;  {set to true if something has to be entered in order to close the box,
																	but allows user to move around inside box without entry}
		Asterisk  : boolean; {Set to true to disguises entry with asterisks}
		UpperCase : boolean; {set to force all letters into upper case}

		{Internal flags}
		IgnoreChanges : boolean; {used to temporarily switch off link checking, eg for setdata}
		LinkChanged : boolean; {marked if data has changed since last link}
		OrigChanged : boolean; {marked automatically if field has been edited}

		constructor Init(Bounds : TRect; const NFieldLen : integer);
		destructor Done; virtual;

		procedure SetData(var Rec); virtual;

		procedure HandleEvent(var Event  : TEvent); virtual;
		procedure SetChanged; virtual;
		function Valid(Command : word) : boolean; virtual;

		procedure CheckLink; virtual;
		procedure ForceLink; virtual;

		procedure Draw; virtual;
		function GetPalette: PPalette; virtual;{}
	end;

	PInputNum = ^TInputNum;
	TInputNum = object(TInputELine)
		Calculator : PCalculator;
		DecPlaces : integer; {number of decimal places allowed}
		constructor Init(Bounds : TRect; const NFieldLen : integer);
		procedure HandleEvent(var Event : TEVent); virtual; {}
		function Valid (Command : Word) : boolean; virtual;
	end;


	PInputLint = ^TInputLint;
	TInputLint = object(TInputNum)
		constructor Init(Bounds : TRect; const NFieldLen : integer);
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	PInputInt = ^TInputInt;
	TInputInt = object(TInputLint)
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	PInputWord = ^TInputWord;
	TInputWord = object(TInputLint)
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
		function Valid(Command : word) : boolean; virtual;
	end;

	PInputByte = ^TInputByte;
	TInputByte = object(TInputLint)
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
		function Valid(COmmand : word) : boolean; virtual;
	end;


	PInputReal = ^TInputReal;
	TInputReal = object(TInputNum)
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	PInputSingle = ^TInputSingle;
	TInputSingle = object(TInputNum)
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	{Input percentage - 1dp, single}
	PInputPerc = ^TInputPerc;
	TInputPerc = object(TInputSingle)
		function Valid (Command : Word) : boolean; virtual;
		procedure Draw; virtual;
	end;

	{Input percentage - 0dp, byte}
	PInputBytePerc = ^TInputBytePerc;
	TInputBytePerc = object(TInputPerc)
		constructor Init(Bounds : TRect; const NFieldLen : integer);
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	PInputBoolean = ^TInputBoolean;          {'twould be nice to make this into a proper check box}
	TInputBoolean = object(TInputELine)
		constructor Init(Bounds : TRect);
		procedure HandleEvent(var Event : TEvent); virtual;
		procedure Draw; virtual;
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	PInputPStr = ^TInputPStr;
	TInputPStr = object(TInputELine)
		procedure GetData(var Rec); virtual;
		procedure SetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	{======== RADIO BUTTONS ============}
	{with targetlink & changed marker}
	PERadioButtons = ^TERadioButtons;
	TERadioButtons = object(TRadioButtons)

		{Automatic link fields}
		Linkers 		: PCollection;

		LinkChanged : boolean;     {has it been edited since last link update?}
		OrigChanged : boolean;  	{Has it been edited since originally set?}

		constructor Init(var Bounds: TRect; AStrings: PSItem);
		destructor Done; virtual;

		function GetHelpCtx(const Level : byte): Word; virtual;
		procedure HandleEvent(var Event  : TEvent); virtual;
		procedure SetChanged;
		procedure CheckLink; virtual;
		procedure ForceLink; virtual;
	end;

	{====== SUB GROUPS OF INPUT FIELDS ================}
	PInputGroup = ^TInputGroup;
	TinputGroup = object(TGroup)
{		FirstEditField, LastEditField : PView; {must set in inserteditfields to first & last editable fields}
		LastReleased : PView;
		OrigChanged : boolean;

		constructor Init(Bounds : TRect);
		procedure HandleEvent(var Event : TEvent); virtual;
		procedure InsertEditFields; virtual;
		procedure ReInsertEditFields; virtual;
		procedure Draw; virtual;
		procedure SetChanged; {passes changed message up tree}
	end;

	{===== TABBED GROUPS ==============================}
	{for keeping track of where each tab is for each group}
	PTabLink = ^TTabLink;
	TTabLink = object(TObject)
		View 	: PView;
		Lbl 	: PLabel;
		Where, Offset : integer;
	end;

	PGroupOfTabbed = ^TGroupOfTabbed;
	TGroupOfTabbed = object(TGroup)
		TabList : PCollection;
		CurrentTab : PTabLink;
		DataRegion : TRect;

		constructor Init(Bounds : TRect);
		destructor Done; virtual;
		{MUST use this to insert each group, *after* this group has been inserted}
		procedure AddTabbedView(const View : PView; const Where, Offset : integer; const TabText : string);
		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	{for use between linked input lines - descendants should set sourcelines,
		etc and change the calculatelink method}
	{They self attach to the edit box (pass as a parameter), and set their
		source views eventmasks to include broadcast events, so they can catch
		force update links, acceptor messages, etc, and particularly so they
		can do a checklink when releasing focus.  See TInputELine, also any
		other views that have linkers, eg PERadioBoxes}
	PInputLinker = ^TInputLinker;
	TInputLinker = object(TObject)
		SourceView : array[1..MaxLinks] of PView;
		TargetView : array[1..MaxLinks] of PView;
		LinkCalculator : pointer;
		ForceInitLink : boolean;
		constructor Init(NLinkCalculator : pointer; const EditBox : PEditBox);
		destructor Done; virtual;
		procedure SetSourceView(const View : PView; const svtype : byte);
		procedure SetTargetView(const View : PView; const tvtype : byte);
		procedure CalculateLink(const CallingView : pView); virtual;
	end;


	TLinkCalculator = procedure(Const Linker : PInputLinker; const CallingView : PView);

	{for skipping pointers, etc}

	PSkipBytes = ^TSkipBytes;
	TskipBytes = object(TView)
		BytesToSkip : word;
		constructor Init(NBytesToSkip : word);
		function DataSize : word; virtual;
	end;

	{when data is not to be displayed, but is to be preserved between
	setdata and getdata - see TInputAddress, where ForWho & date ranges
	need to be stored when editing just the address fields in eg a TPerson
	editbox}
	PStoreBytes = ^TStoreBytes;
	TStoreBytes = object(TView)
		BytesToStore : word;
		StoreField : PString;
		constructor Init(NBytesToStore : word);
		destructor Done; virtual;
		procedure SetData(var Rec); virtual;
		procedure GetData(var Rec); virtual;
		function DataSize : word; virtual;
	end;

	{============== EXTENDED BUTTON TYPES=================}
	{This button provides several more functions.  Essentially:
		kbType : an alternative short.cut key that acts like a hotkey, but
			may be, say, a function key.
		DataItem : meant for use by bfGetData and various descendants (such as
			the JimmyStoreButton
		OwnerValid is marked as 0 if the owner has not been validated by the
				button, -1 if invalid and +1 if valid, so descendants can check in
				their .press method
		bfflags : extended bfflags:
				bfClose tells the button to close the owner when pressed.  Dialog boxes
					will only close/endmodal on cmYes,cmNo,cmCancel or cmOK; with this
					flag set....
				bfGetData tells the button to do a getdata from its owner to the tied
					object when it's pressed, if the owner's valid routine returns true
					for the buttons command}

	POurButton = ^TOurButton;
	TOurButton = object(TButton)
		kbType : word; {short-cut key}
		DataItem : pointer;
		OwnerValid : integer;
		constructor Init(const X,Y : integer; ATitle : TTitleStr; ACommand : word; Abf : word; ADataITem : pointer);
		procedure HandleEvent(var Event : TEvent); virtual;
		procedure Press; virtual;
	end;

	{--- Asks "Are You Sure" if owner data has changed, before generating
		cmCancel message ----}
	PAYSCancelBUtton = ^TAYSCancelButton;
	TAYSCancelButton = object(TOurButton)

		procedure Press; virtual;
	end;

	PChangedIndicator = ^TChangedIndicator;
	TChangedIndicator = object(TView)
		Changed : boolean;
		constructor Init(const X,Y : integer);
		procedure HandleEvent(var Event : TEvent); virtual;
		procedure SetState(AState: Word; Enable: Boolean); virtual;
		procedure Draw; virtual;
	end;

	{------ General edit box ------}
	TEditBox = object(TDialog)      {extended dialog - etvision.inc}

		Linkers 		: PCollection;
		ChangedIndicator : PChangedIndicator;
		CallingView : PView; {The view that set this box up...}
		Precascade 	: TRect; {save position when cascading}
		CopyData 		: pointer; {pointer to copy data - eg last item input - used for
															Ctrl-Ins & Shift-Ins to repeat last text input}
		ViewOnly    : boolean; {a view-only box allows cursor movements, etc, but nothing else...}


		constructor Init(Bounds : TRect; NTitle : STring; NCallingView : PView);
		destructor Done; virtual;
		procedure EndInit; virtual;      {Used after all inserts by descendant to tidy up etc}

		function InsBox(      const   X,Y, BoxLen, BoxDepth, FieldLen  : integer) : PInputELine;
		function InsTitledBox(const   X,Y, BoxLen, BoxDepth : integer; S : string; FieldLen : integer) : PInputELine;{}
		function InsTitledField(const X,Y, BoxLen, BoxDepth : integer; S : string; Field : PView) : PView;

		procedure InsOKButton(const X,Y : integer; ABoxData : pointer);
		procedure InsCancelButton(const X,Y : integer);
		procedure InsAYSCancelButton(const X,Y : integer);

		procedure AddLinker(const Linker : PInputLinker);
		procedure CheckInitLinks(const Force : boolean);

		function Valid(Command : Word) : boolean; virtual;{}
		procedure HandleEvent(var Event : TEvent); virtual;

		function GetHelpCtx(const Level : byte): Word; virtual;
		procedure SetViewData(var Rec; View : PView);{}

{{$IFDEF MSDOS}
{		procedure Idle; virtual; {to redraw more-about lists, etc in boxes}
{{$ENDIF}
		procedure Cascade;
		procedure Uncascade;
	end;

	PObjecTEditBox = ^TObjecTEditBox;
	TObjecTEditBox = object(TEditBox)

		constructor Init(Bounds : TRect; NTitle : STring; NCallingView : PView);
	end;





IMPLEMENTATION

uses app, {tuiApp,{}
			tuiMsgs, {for input error/keypress error warning}
			global, minilib, help;



{====== TRANSLATE UP/DOWN ETC TO TABS ===============}
procedure SensibleKeys(var Event : TEvent);
begin
	if Event.What = evKeyBoard then
		case Event.KeyCode of
			kbUp   : Event.KeyCode := kbShiftTab;
			kbDown : Event.KeyCode := kbTab;
			kbEnter: Event.KeyCode := kbTab;
		end;  {case}
end;

{constructor TTagLabel.Init(var Bounds: TRect; const AText: String; ALink: PView);
begin
	inherited Init(Bounds, AText, Alink);
	Options := Options or ofFramed;
	ALink^.Options := ALink^.Options or ofFramed;
end;

function TTagLabel.GetPalette : PPalette;
const
	P: String[Length(CLabel)] = #7#26#9#9;

begin
	GetPalette := @P;
end; {}

{*********************************
 ***     CHANGED INDICATOR     ***
 *********************************}

constructor TChangedIndicator.Init;
var Bounds : TREct;
begin
	Bounds.Assign(X,Y,X+10,Y+1);
	inherited Init(Bounds);
	Changed := False;
	EventMask	:= EventMask or evBroadCast; {so it gets the message...}
	GrowMode := gfGrowLoY + gfGrowHiY;
end;

procedure TChangedIndicator.HandleEvent;
begin
	inherited HandleEvent(Event);

	if (Event.What = evBroadCast) and (Event.Command = cmSetChangedIndicator) then begin
		if Event.InfoPtr = nil then Changed := False else Changed := True; {clear or set}
		DrawView;
		ClearEvent(Event);
	end;

	if (Event.What = evBroadCast) and (EVent.Command = cmGetChangedIndicator) then begin
		if Changed then ClearEvent(Event) else Event.InfoPtr := nil; {use infoptr to mark whether set or not - nil = no change}
	end;
end;

procedure TChangedIndicator.SetState;
begin
	inherited SetState(AState, Enable);
	DrawView;
end;

procedure TChangedIndicator.Draw;
var B : TDrawBuffer;
		Colour : byte;
		Frame : char;
begin
	{Code copied from TIndicator in editors unit}
	if (State and sfDragging = 0) and ((State and sfActive) <> 0) then begin
		{Double line}
		Colour := GetColor(2);
		Frame := #205;
	end else begin
		Colour := GetColor(1);
    Frame := #196;
	end;
	MoveChar(B, Frame, Colour, Size.X);
	if Changed then WordRec(B[0]).Lo := 15;

	WriteBuf(0, 0, Size.X, 1, B);
end;


{************************************************************
 ***            GROUP OF LINES INPUT (EG ADDRESS)         ***
 ************************************************************}

constructor TInputGroup.Init;
begin
	inherited Init(Bounds);

	EventMask := $FFFF and not evMouseMove; {catch all except mouse *movements* - still pick up clicks}
	Options := Options or (ofPostProcess); {means it can catch hotkeys}

	LastReleased := nil;
end;

{============ HANDLE EVENT ===========================}
procedure TInputGroup.HandleEvent;

	{tests to see if there is a selectable field before the Last field}
	{wierdly enough, PView^.Prev points to the *next* user selectable field...}
	function OnLast : boolean;
	var P : PView;
			On : boolean;
	begin
		On := True;
		if Current<>nil then begin
			P := Current^.PrevView;
			while On and (P<>nil) do begin
				if (P^.State and (sfVisible + sfDisabled) = sfVisible) and
					 (P^.Options and ofSelectable<>0) then
						On := False; {there is a selectable field}
				P := P^.PrevView;
			end;
		end;
		OnLast := On;
	end;

	function OnFirst : boolean;
	var P : PView;
			On : boolean;
	begin
		On := True;
		if Current<>nil then begin
			P := Current^.NextView;
			while On and (P<>nil) do begin
				if (P^.State and (sfVisible + sfDisabled) = sfVisible) and
					 (P^.Options and ofSelectable<>0) then
						On := False; {there is a selectable field}
				P := P^.NextView;
			end;
		end;
		OnFirst := On;
	end;

	function MatchLastOrSelf(View : PView) : boolean; far;
	begin
		MatchLastOrSelf := (View = @Self) or (View = LastReleased);
	end;


begin
	if (Event.What = evBroadCast) and (Event.Command = cmIsView) and (Event.InfoPtr=@Self) then ClearEvent(Event);

	inherited HandleEvent(Event);

	{---- Focusing/Refocusing/Movement -------------}
	{mark which view the focus came from}
	if (Event.What = evBroadCast) and (Event.Command = cmReleasedFocus) then
		LastReleased := PView(Event.InfoPtr);

	if (Event.What = evBroadCast) and (Event.Command = cmReceivedFocus) and (Event.InfoPtr = @Self) then
		if Owner^.FirstThat(@MatchLastOrSelf)=LastReleased then
			{focus on last editable}
			while not OnLast do FocusNext(False) {slow & clumsy...}
		else
			{focus on first editable}
			while not OnFirst do FocusNext(True);

	{Tab key movement is handled by TWindow, which means that tabbing through
	the owning windows fields will tab *onto* this field, then tab *off* this
	field, without tabbing thru any of the subfields...}
	{So if this group is focused, we'll tab thru it until we get to the
	end/beginning}
	if GetState(SfFocused) and (Event.What = evKeyDown) then
		case Event.KeyCode of
			kbTab: if not OnLast then begin
				FocusNext(False);
				ClearEvent(Event);
			end;
			kbShiftTab: if not OnFirst then begin
				FocusNext(True);
				ClearEvent(Event);
			end;
		end;

	if (Event.What=evBroadCast) and (Event.Command=cmSetChangedIndicator) and (Event.InfoPtr<>@Self) then begin
		{subviews pass this event to the owner (normally edit box) so need to
			pass it up to edit box}
		Message(Owner, evBroadCast, cmSetChangedIndicator, @Self);
		ClearEvent(Event);
	end;

end;

procedure TInputGroup.SetChanged;
begin
	if not OrigChanged then begin
		OrigChanged := True;
		Message(Owner, evBroadCast, cmSetChangedIndicator, @Self);
	end;
end;

procedure TInputGroup.InsertEditFields;
var R : TRect;
begin
{	LastEditField := nil; FirstEditField := nil;{}

	{---- Add blank view for background ------}
	{blank view to blank out}
	GetExtent(R);
	Insert(New(PView, init(R)));{}
end;

procedure TInputGroup.ReInsertEditFields;
begin
	{--- delete all existing ----}
	Lock;
	while First<>nil do dispose(First, done);

	Current := nil;

	InsertEditFields;

	Unlock;
end;

procedure TInputGroup.Draw;
begin
{	inherited Draw; {} Redraw; {override drawing from buffer}
end;

{************************************************************
 ***           TABBED VIEWS                               ***
 ************************************************************}
{a view that acts as a facilitator (?!) for stacked other
views, each with a wee tab sticking out.  V useful for quick
access across a lot of info, eg in person dialog boxes, for switching
between different kinds of info}

type
	{TabLabels have different palettes than ordinary ones, to cope with being
	the "top" tab, even if not focused}
	PTabLabel = ^TTabLabel;
	TTabLabel = object(TLabel)
		GroupOfTabbed : PGroupOfTabbed;
		Top : boolean;
		procedure HandleEvent(var Event: TEvent); virtual;
		function GetPalette: PPalette; virtual;
	end;


{*******************************
 ***    TAB OBJECTS          ***
 *******************************}
{Now, the link is a subview of a group, and so their messages do not
get broadcast about this level.  So the group itself, broadcasts a message
to the tab saying whether or not it's focused}

procedure TTabLabel.HandleEvent;
begin
	if (Event.What = evBroadcast) then
		if ((Event.Command = cmReceivedFocus) or
			 (Event.Command = cmReleasedFocus)) and
			 (Link<>nil) then
		begin
			{must be done *before* inherited as that clears the event}
			Top := Link^.State and sfSelected<>0;
			{tell group that this one's been selected}
			if Top and (Event.Command = cmReceivedFocus) then
				Message(GroupOfTabbed, evCommand, cmTabSelected, @Self);
		end;

	inherited HandleEvent(Event);
end;

const
	{draw{draw uses colour 4 (shortcut),2 when lit, 3 (shortcut),1 when not}
	CTabLabel = #26#28#28#28;

function TTabLabel.GetPalette: PPalette;
const
	TabP: String[Length(CTabLabel)] = CTabLabel;
	P : string[Length(CLabel)] = CLabel;
begin
{	if Top then
		GetPalette := @TabP
	else{}
		GetPalette := @P;
end;




type
	PTabFrame = ^TTabFrame;
	TTabFrame = object(TFrame)
		function GetPalette: PPalette; virtual; {returns same colour as nlink when focused}
		procedure HandleEvent(var Event : TEVent); virtual;
		procedure Draw; virtual;
	end;

	function TTabFrame.GetPalette;
	begin GetPalette := nil; end; {go straight to owner's getpalette}


procedure TTabFrame.HandleEvent;
begin
{	if (Event.What = evBroadcast) then
		if ((Event.Command = cmReceivedFocus) or
			 (Event.Command = cmReleasedFocus)) then DrawView;{}

	inherited HandleEvent(Event);
end;

procedure TTabFrame.Draw;
const	FrameChars : array [1..9] of Char	= 'ÀÚÙ¿Ä³ ÂÁ';
			CGap	 		= 28;
var X,Y : integer;
		B : TDrawBuffer;
		C : word;
		LOn, ROn : boolean; {markers for left/right showing bar or removing}
		LastPos : byte;
		Ch : byte;
		Colour : byte;
begin

	{=== DO MAIN FRAME DRAWING ===========}
	if State and sfActive<>0 then C := GetColor(2) else C := GetColor(1);
	LOn := False; ROn := False;
	MoveChar(B[0], ' ', byte(C), Size.X);

	for Y := 0 to Size.Y-1 do begin
		FrameLine(B,Y,3,Byte(C));

		{--- Remove l/r bars ------}
		Ch := 0;
		case byte(B[0]) of
			179 : if not LOn then Ch := 32; {make first char a space}
			195 : begin  {Ã}
				LOn := not LOn;
				if LOn then	Ch := ord('Ú') else Ch := ord('À');
			end;
		end;
		if Ch<>0 then B[0] := (B[0] and $FF00) or Ch;

		Ch := 0;	LastPos := Size.X-1;
		case byte(B[LastPos]) of
			179 : if not ROn then Ch := 32;
			180 : begin {´}
				ROn := not ROn;
				if ROn then Ch := ord('¿') else Ch := ord('Ù');
			end;
		end;
		if Ch<>0 then B[LastPos] := (B[LastPos] and $FF00) or Ch;

		WriteLine(0, Y, Size.X, 1, B);
	end;

	{==== Put in Break for current tab ======}
	{assume owner is GroupOfTabbed}
	if State and sfActive <>0 then Colour := 2 else Colour := 1;

	if PGroupOfTabbed(Owner)^.CurrentTab<>nil then
		with PGroupOfTabbed(Owner)^ do begin
			case CurrentTab^.Where of
				whLeft : begin
					X := DataRegion.A.X-1;
					Y := CurrentTab^.Offset;
					if Y=0 then
						WriteChar(X,Y,	FrameChars[5],Colour,1)
					else
						WriteChar(X,Y,	FrameChars[3],Colour,1);
					WriteChar(	X,Y+1,#32,					Colour,1);
					if Y=(Size.Y-3) then
						WriteChar(X,Y+2,FrameChars[5],Colour,1)
					else
						WriteChar(X,Y+2,FrameChars[4],Colour,1);
				end;
			end;
		end;
end;




{*******************************
 ***    GROUP CONTROLLER     ***
 *******************************}
constructor TGroupOfTabbed.Init;
var R : TRect;
begin
	inherited Init(Bounds);
	Options := (Options or ofFirstClick
							or ofIdle {for displaying sublists}
							or ofPostProcess) {so it catches hotkeys}
							and not ofBuffered;{} {removed for now because of some problem with above frame drawing}

	New(TabList, init(10,10));
	R.Assign(0,0,0,0);
	CurrentTab := nil;

	GetExtent(R);
	Insert(New(PTabFrame, init(R)));

	DataRegion.ASsign(0,0,0,0);
end;

destructor TGroupOfTabbed.Done;
begin
	dispose(TabList, done);
	{tab frame disposed of as part of dialog box}
	inherited Done;
end;

{=========== Add Tag/Tabbed-type label ====================
pass Where as 0 (left), 1 (Right), 2 (top) & 3 (bottom)
			Offset as X-pos (top/bottom), Y-pos (left/right)
THIS TGROUPOFTABBED VIEW MUST ALREADY HAVE BEEN INSERTED!}
procedure TGroupOfTabbed.AddTabbedView(const View : PView; const Where, Offset : integer; const TabText : string);
var TabLink : PTabLink;
		X,Y : integer;
		R : TRect;
		Lbl : PTabLabel;
begin
	if TabList^.Count=0 then begin
		{first view going in, so insert frame to fit}
		{try later...}
		View^.Options := View^.Options or ofFramed;
	end;

	{add views}
	View^.Options := View^.Options or ofTopSelect;
	Insert(View);

	if TabList^.Count=0 then begin
		View^.GetExtent(DataRegion); {so break knows where to go}
		DataREgion.Move(View^.Origin.X, View^.Origin.Y);
	end;

	{add label}
	if TabText <> '' then begin
		case Where of
			whLeft : begin
				{Left}
				X := View^.Origin.X - length(TabText)+Count('~',TabText)-1;
				Y := View^.Origin.Y + Offset;
			end;
			whRight : begin
				{Right}
				X := Origin.X + Size.X;
				Y := Origin.Y + Offset;
			end;
			whTop : begin
				{top}
				X := Origin.X + Offset;
				Y := Origin.Y-1;
			end;
			whBottom : begin
				{bottom}
				X := Origin.X + Offset;
				Y := Origin.Y+Size.Y;
			end;
		else
			ProgramError('TUIEDIT.PAS - AddTabbedView, illegal Where', hcInternalErrorMsg);
		end;

		R.Assign(X,Y, X+length(TabText)-Count('~',TabText), Y+1);
		New(Lbl, init(R,TabText, View));
		Lbl^.Options := Lbl^.Options or ofFramed;
		Lbl^.GroupOfTabbed := @Self;
		Insert(Lbl);
	end;

	{register with tablist}
	New(TabLink, init);
	TabLink^.View 	:= View;
	TabLink^.Lbl    := Lbl;
	TabLink^.Where 	:= Where;
	TabLink^.Offset := Offset;

	TabList^.Insert(TabLink);
end;



procedure TGroupOfTabbed.HandleEvent;
var TabLink : PTabLink;

	function FindTab(Item : pointer) : boolean; far;
	begin
		FindTab :=  PTabLink(Item)^.Lbl = Event.InfoPtr;
	end;

	procedure TellLabel(Item : PTabLink); far;
	begin
		Message(Item^.Lbl, evBroadCast, Event.Command, Event.InfoPtr);
	end;

begin
	if (Event.What = evCommand) and (Event.Command = cmTabSelected) then begin
		{find which tab}
		TabLink := TabList^.FirstThat(@FindTab);
		if TabLink<>CurrentTab then begin
			CurrentTab := TabLink;
			ReDraw;{}
		end;

		ClearEvent(Event);
	end;


	inherited HandleEvent(Event);
end;





{*********************************************************
 ***            AUTO INPUT LINKER                      ***
 *********************************************************}
{To automatically update some fields depending on changes in
others.  To set up a linker, it is handy to set some constants
referring to the array position of the "SourceLines" or "TargetLines"
array, (eg in the
TLetter module ilToWho is 1, and the Line[1] points to the
ToWho line).  Set the links by using the .SetSourceLine or SetTargetLine
method below,
which also sets the backlink (from sourceline to linker).  Disposing is
done by the inputline - the first one calls the linkers' done
method, which clears all SourceLines' Linker pointers so they don't then
dispose of either.

SourceLines are lines which call the linker if they have been changed (they
may also be lines that are changed) but TargetLines have nil linker pointer}
constructor TINputLinker.Init;
var I : byte;
begin
	inherited Init;
	for I := 1 to MaxLinks do begin SourceView[I] := nil; TargetView[I] := nil; end;
	LinkCalculator := NLinkCalculator; {@BlankLinkCalculator;{}
	ForceInitLink := false;
	EditBox^.AddLinker(@Self);
end;

destructor TInputLinker.Done;
var I : byte;
begin
	for I := 1 to MaxLinks do begin
		if SourceView[I]<>nil then Message(SourceView[I],evCommand,cmClearLinker, @Self);
{no need - not set in view		if TargetView[I]<>nil then Message(TargetView[I],evCommand,cmClearLinker, @Self);{}
	end;
	inherited Done;
end;

procedure TInputLinker.SetSourceView(const View : PView; const svtype : byte);
begin
	Message(View, evCommand, cmSetLinker, @Self);
	SourceView[svType] := View;
end;

procedure TInputLinker.SetTargetView(const View : PView; const tvtype : byte);
begin
	TargetView[tvType] := View;
end;


procedure TInputLinker.CalculateLink(const CallingView : PView);
var I : byte;
begin
	if LinkCalculator<>nil then
		TLinkCalculator(LinkCalculator)(@Self, CallingView)
	else
		{do cmupdatefromlink for all targets}
		for I :=1 to MaxLinks do Message(TargetView[I], evCommand, cmUpdateFromLink, CallingView);
end;


{constructor TLinkerLink.INit;
begin
	inherited Init;
	InputLinker := NLinker;
end;


{************************************************************
 ***            EXTENDED LINE INPUT                       ***
 ************************************************************}

constructor TInputELIne.Init;
var B : byte;
begin
	inherited Init(Bounds, NFieldLen);
	Options := Options or ofValidate;  {Forces validation before exiting}

	{clear flags}
	MustInput := False;
	MustInputToClose := False;
	UpperCase := False;
	Asterisk := False;

	{clear links}
	Linkers := nil;
	IgnoreChanges := False;

	{Two changed markers.  One to mark if changed from original, ie has the
	user changed the data originally in the box, and the other used for the
	checklink - ie has the data changed since the last checklink}
	LinkChanged := False;
	OrigChanged := False;

	HelpCtx := hcInputField;
end;

destructor TInputELine.Done;
begin
	if Linkers<>nil then dispose(Linkers, done); {doesn't dispose of
	inputlinkers - that's up to the EditBox}
	inherited Done;
end;

procedure TInputELine.SetData;
begin
{	if UpperCase then
		inherited SetData(ucase(string(Rec)))
	else{}
		inherited SetData(Rec);
end;

{don't know why I can't define this within the handleevent and test
for Event.InfoPtr - see chains.pas}
{var ClearLinkTestP : pointer;{}

procedure TInputELine.HandleEVent;
var S : string;

begin
	if (Event.What = evBroadCast) then
		case Event.Command of
			{validator used by lists' cmaccept commands before passing back selected info}
			cmIsView 		: if (Event.InfoPtr=@Self) then ClearEvent(Event);

			cmForceLink : ForceLink; {make sure this is outside the S :=/setchanged bit below;
															linkers must setchanged or not as required}

			cmCheckLink : CheckLink;
		end;

	{--- Force upper case ----}
	if UpperCase and (Event.What = evKeyBoard)
		 and (Event.CharCode>#64) and (Event.CharCode<#128) then {if alphanumeric}
					Event.CharCode := chr(ord(Event.CharCode) and (255-32)); {Force into upper case}

	{update from link should be sorted out by descendants - this will then just
	tidy up. Used by linker if no func given}
	if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
		SetChanged;
		CheckLink;
		Draw;
		ClearEvent(Event);
	end;

	{--- set linker ---}
	if (Event.WHat = evCommand) and (Event.COmmand = cmSetLinker) then begin
		if Linkers = nil then New(Linkers, init(1,1));
		Linkers^.Insert(Event.InfoPtr); {add to collection}
		EventMask	:= EventMask or evBroadCast; {so it gets linker events}
		ClearEvent(Event);
	end;

	{--- clear linker ---}
	if (Event.WHat = evCommand) and (Event.COmmand = cmClearLinker) then begin
		if Linkers = nil then
			ProgramError('Trying to clear Linker which isnt there'#13'TUIEDIT.PAS', hcInternalErrorMsg)
		else begin
			Linkers^.Delete(Event.InfoPtr);
		end;
		ClearEvent(Event);
	end;

	S := Data^;

	inherited HandleEvent(Event);

	if S<>Data^ then SetChanged;

	{--- CHeck Link ---- }
	if (Event.What = evBroadcast) and (Event.Command = cmReleasedFocus) and (Event.InfoPtr = @Self) then
		CheckLink;
end;


procedure TInputEline.Draw;
begin
	inherited Draw;
	if Asterisk then WriteChar(1,0, '*', 1, Length(Data^));
end;


procedure TInputELine.SetChanged;
begin
	{actual checklink is now done on releasefocus in handleevent}
	if not IgnoreChanges then begin {sometimes used in setdata etc to ignore changes}
		LinkChanged := True;
		if not OrigChanged then begin
			OrigChanged := True;
			Message(Owner, evBroadCast, cmSetChangedIndicator, @Self);
		end;
	end;
end;


{Not really best place to put this, but it checks for the links}
function TinputELine.Valid(Command : word) : boolean;
var V : boolean;
begin
	V := inherited Valid(Command);

	if V and DoValidFor(Command) then begin

		{check for mustinput - not for link valids though}
		if (Command<>cmForceLink) then
			if (MustInput or (MustInputToClose and (Command = cmOK))) and (delspace(Data^)='') then begin
				{need to do this as editbox.valid does not do it until after all valids done,
				BUT must not do if just mustinput as stack overflow occurs if doing
				a focus after box creation}
				if MustInputToClose then Focus;
				InputWarning('Entry Required', hcIWEntryReqMsg);
				V := False;
			end;
	end;

	Valid := V;
end;

procedure TInputELine.CheckLink;
begin
	if LinkChanged then begin
		ForceLink;
		LinkChanged := False;
	end;
end;

procedure TInputELine.ForceLink;

	procedure DoLink(INputLinker : PInputLinker); far;
	begin
		InputLinker^.CalculateLink(@Self);
	end;

begin
	if (Linkers<>nil) and Valid(cmForceLink) then
		Linkers^.ForEach(@DoLink);
end;

function TInputELine.GetPalette: PPalette;
const
	P: String[Length(CInputLine)] = #19#19#20#21;
	P1 : string[length(CInputLine)] = #6#6#6#6; {for disabled/not selectable lines}
begin
	if GetState(sfDisabled) then
		GetPalette := @P1
	else
		GetPalette := @P;
end;

{************************************************************
 ***                     NUMERIC INPUT                    ***
 ************************************************************}
constructor TInputNum.Init;
begin
	inherited Init(Bounds, NFieldLen);
	Calculator := nil;
	DecPlaces := -1;  {any}
	HelpCtx := hcInputNum;
end;

procedure TInputNum.HandleEvent(var Event : TEvent);
var Bounds : TRect;
begin
	if GetState(sfSelected) and
			(Event.what = evCommand) and (Event.Command = cmAcceptNum)
			and (Event.InfoPtr = pointer(Calculator^.Display)) and (Calculator<>nil) then begin
				{updated from calculator}
		Data^ := Copy(delspace(Calculator^.Display^.Number),1,MaxLen);
		ClearEvent(Event);
	end;

	if GetState(sfFocused) then begin
		if (Event.What = evKeyBoard) then begin
			if (Event.KeyCode=kbUp) then begin
				{do calculator}
				New(Calculator, init(5,3));
				Calculator^.Display^.Acceptor := @Self;
				Calculator^.Display^.Number := Data^;
				Desktop^.ExecView(Calculator);
				dispose(Calculator, done);
				ClearEvent(Event);
			end;

			if ((Event.CharCode<#48) or (Event.Charcode>#57))
				 and (Event.Charcode <> #0)
				 and (Event.Charcode <> '.')   {Full stop}
				 and (Event.Charcode > #32)
				 and (Event.CharCode <> '-')   {Minus sign}
				 then begin
					 WrongKeyBleep;
					 ClearEvent(Event);
				 end;

			if (Event.CharCode = '.') and ((DecPlaces = 0) or (pos('.',Data^)>0)) then begin
				WrongKeyBleep;
				ClearEvent(Event);
			end;
		end;
	end; {sffocused}

	Inherited HandleEvent(Event);
end;

	{--- Check at end of input to see if OK ----}
	function TInputNum.Valid(Command: Word) : boolean;
	var	R : real;
			V : boolean;
			B : byte;

	begin
		V := Inherited Valid(Command);

		if V and DoValidFor(Command) then begin

			if (Data^ <> space(length(Data^))) then begin
				{test if valid number}
				R := S2Real(delspace(Data^));  {Sets global var valerr if a problem}
				if ValErr>0 then begin
					V := False;
					if Command<>cmForceLink then begin
						B := length(Delspace(Data^));
						InputWarning('Value Error'#13#10
												+Space(ValErr-1)+#25+space(B-ValErr)+#13#10
												+delspace(Data^)+#13#10
												+Space(ValErr-1)+#24+space(B-ValErr), hcIWValueMsg);
					end;
				end;

				{number decimal places}
				if pos('.',Data^)>0 then
					if DecPlaces=0 then begin
						V := False;
						InputWarning('No Decimal places please', hcIWNoDecsMsg);
					end {else
						if (DecPlaces>0) and ((length(delspaceR(Data^))-pos('.',Data^))>DecPlaces) then begin
							V := False;
							InputWarning('Too many decimal places');
							Draw;
						end;{}
			end;

		end;
		Valid := V;
	end;

{***********************************************************
 ***                   NUMERIC INPUT - LONG INTEGER      ***
 ************************************************************}
	{Base type for other integer inputs (ie word, byte, etc)}
	constructor TInputLint.Init;
	begin
		inherited Init(Bounds, NFieldLen);
		DecPlaces := 0; {set default to no dec places - integer!}
	end;

	procedure TInputLint.GetData(var Rec);
	begin
		if DecPlaces>0 then
			longint(Rec) := round(S2Real(Data^)*Exp10(DecPlaces)) {store in lowest allowed pts}
		else
			longint(Rec) := S2Num(Data^);
	end;

	procedure TInputLint.SetData(var Rec);
	begin
		if longint(Rec)=0 then
			Data^ := ''
		else
			if DecPlaces>0 then
				Data^ := R2Str(longint(Rec)/Exp10(DecPlaces),DecPlaces,Maxlen) {don't allow overspill of reserved mem}
			else
				Data^ := Copy(N2Str(longint(Rec)),1,Maxlen); {don't allow overspill of reserved mem}
	end;

	function TInputLint.DataSize : word;
	begin DataSize := Sizeof(Longint); end;


{************************************************************
 ***                   NUMERIC INPUT - A WORD             ***
 ************************************************************}
	procedure TInputWord.GetData(var Rec);
	var L : longint;
	begin
		inherited GetData(L); word(Rec) := L;
	end;

	procedure TInputWord.SetData(var Rec);
	var L : longint;
	begin
		L := word(rec); inherited SetData(L);
	end;

	function TInputWord.DataSize : word;
	begin DataSize := Sizeof(word); end;

	function TInputWord.Valid(Command: Word) : boolean;
	var V : boolean;
	begin
		V := inherited Valid(Command);

		{Check size is OK for word}
		if V and DoValidFor(Command) then begin
			if (S2Num(Data^)>65535) or (S2Num(Data^)<0) then begin
				V := False;
				Focus;
				InputWarning('Out of Range'#13#10'Should be 0-65,535',hcIWRangeMsg);
			end;
		end;

		Valid := V;
	end;

{************************************************************
 ***                   NUMERIC INPUT - A BYTE             ***
 ************************************************************}
	procedure TInputByte.GetData(var Rec);
	begin
		Byte(Rec) := S2Num(Data^);
	end;

	procedure TInputByte.SetData(var Rec);
	begin
		if byte(Rec)=0 then Data^ := '' else Data^ := Copy(N2Str(byte(Rec)),1,Maxlen); {don't allow overspill of reserved mem}
	end;

	function TInputByte.DataSize : word;
	begin DataSize := Sizeof(byte); end;

	function TInputByte.Valid(Command: Word) : boolean;
	var V : boolean;
	begin
		V := True;

		{Check size is OK for byte}
		if V and DoValidFor(Command) then
			if (S2Num(Data^)>255) or (S2Num(Data^)<0) then begin
				V := False;
				Focus;
				InputWarning('Out of Range'#13#10'Should be 0-255', hcIWRangeMsg);
			end;

		if V then Valid := Inherited Valid(Command) else Valid := False;
	end;


{***********************************************************
 ***                   NUMERIC INPUT - INTEGER           ***
 ************************************************************}
	procedure TInputInt.GetData(var Rec);
	begin
		integer(Rec) := S2Num(Data^);
	end;

	procedure TInputInt.SetData(var Rec);
	begin
		if integer(Rec) = 0 then Data^ := '' else Data^ := Copy(N2Str(integer(Rec)),1,Maxlen); {don't allow overspill of got mem}
	end;

	function TInputInt.DataSize : word;
	begin DataSize := Sizeof(integer); end;

{************************************************************
 ***                   NUMERIC INPUT - A REAL             ***
 ************************************************************}
	procedure TInputReal.GetData(var Rec);
	begin
		real(Rec) := S2Real(Data^);
	end;

	procedure TInputReal.SetData(var Rec);
	begin
		if real(Rec) = 0 then Data^ := '' else Data^ := R2Str(real(Rec),dcAll,Maxlen);
	end;

	function TInputReal.DataSize : word;
	begin DataSize := Sizeof(real); end;

{************************************************************
 ***                   NUMERIC INPUT - A SINGLE PREC REAL ***
 ************************************************************}
	procedure TInputSingle.GetData(var Rec);
	begin
		single(Rec) := S2Real(Data^);
	end;

	procedure TInputSingle.SetData(var Rec);
	begin
		if single(Rec) = 0 then Data^ := '' else Data^ := R2Str(single(Rec),dcAll,Maxlen);
	end;

	function TInputSingle.DataSize : word;
	begin DataSize := Sizeof(single); end;

{************************************************************
 ***            INPUT PERCENTAGE (SINGLE REAL)            ***
 ************************************************************}
function TInputPerc.Valid (Command : Word) : boolean;
var V : boolean;
begin
	V := inherited Valid(Command);
	if V and DoValidFor(Command) then begin
		if (S2Real(Data^)>100) or (S2Real(Data^)<0) then begin
			V := False;
			Focus;
			InputWarning('Out of Range'#13#10'Should be 0-100', hcIWRangeMsg);
		end;
	end;
	Valid := V;
end;

procedure TInputPerc.Draw;
begin
	inherited Draw;
	writechar(Size.X-1,0, '%', 4,1);   {Draws luminous green % at end}
end;

constructor TInputBytePerc.Init(Bounds : TRect; const NFieldLen : integer);
begin
	inherited Init(Bounds, NFieldLen);
	DecPlaces := 0;
end;

procedure TInputBytePerc.GetData;
begin
	Byte(Rec) := S2Num(Data^);
end;

procedure TInputBytePerc.SetData;
begin
	if byte(Rec)=0 then Data^ := '' else Data^ := Copy(N2Str(byte(Rec)),1,Maxlen); {don't allow overspill of reserved mem}
end;

function TInputBytePerc.DataSize;
begin DataSize := 1; end;

{************************************************************
 ***                   BOOLEAN INPUT                      ***
 ************************************************************}
	constructor TInputBoolean.Init;
	begin
		inherited Init(Bounds, 1);
		Data^ := ' '; {set to clear as default - o/w defaults to '' not ' '}
		HelpCtx := hcInputBoolean;
	end;

	procedure TInputBoolean.HandleEvent;
	begin

		{mouse - focus & toggle}
		if (Event.What = evMouseDown) and MouseInView(Event.Where) then begin {Button clicked}
			focus;
			Event.What := evKeyboard;
			Event.CharCode := ' ';
		end;

		if (Event.What = evKeyboard) then begin

			case Event.CharCode of
				{Space toggles}
				' ' : if Data^ = ' ' then Event.CharCode := 'X';  {Toggle on/off}

				{X to turn on}
				'x','X' : Event.CharCode := 'X';

			else
				if (Event.CharCode>#32) then begin {not cleared and ascii letter}
					WrongKeyBleep;
					ClearEvent(Event);
				end;
			end; {case}
		end; {if}

		inherited HandleEvent(Event);
	end;

	{Make it look like a check box}
	procedure TInputBoolean.Draw;
	begin
		inherited Draw;                  {Sets cursor pos & data}
		writeChar(0,0, '[',4,1);         {First bracket}
		writeChar(2,0, ']',4,1);         {Second}
	end;
{}
	procedure TInputBoolean.GetData(var Rec);
	begin
		Boolean(Rec) := (Data^ = 'X');
	end;

	procedure TInputBoolean.SetData(var Rec);
	begin
		if boolean(Rec) = True then Data^ := 'X' else Data^ := ' ';
	end;

{ Suitable for check box parent - ideal parent}
{	procedure TInputBoolean.GetData(var Rec);
	begin Boolean(Rec) := Mark(0); end;

	procedure TInputBoolean.SetData(var Rec);
	begin if boolean(Rec) = True then Press(0);  end;
{}
	function TInputBoolean.DataSize : word;
	begin DataSize := Sizeof(boolean); end;

{************************************************************
 ***                   LINE INPUT - FROM A STRING PTR     ***
 ************************************************************}
	procedure TInputPStr.GetData(var Rec);
	begin
		if PString(Rec) <>nil then disposeStr(PString(Rec));
		PString(Rec) := NewStr(Data^); {Set contents of Rec ptr to contents of Data}
	end;

	procedure TInputPstr.SetData(var Rec);
	begin
		if PString(rec)<>nil then Data^ := PString(Rec)^ else Data^ := '';
	end;

	function TInputPstr.DataSize : word;
	begin DataSize := Sizeof(pstring); end;

{****************************************************
 ***             RADIO BUTTONS WITH TARGET LINK   ***
 ****************************************************}
constructor TERadioButtons.Init(var Bounds: TRect; AStrings: PSItem);
var B : byte;
begin
	inherited Init(Bounds, AStrings);
	{clear linkers}
	Linkers := nil;
	Options := Options or ofValidate; {force checklink on focusrelease}
{	EventMask := EventMask or evBroadcast; {for link messages}
	LinkChanged := False;
	OrigChanged := False;
	HelpCtx := hcRadioButtons;
end;

destructor TERadioButtons.Done;
begin
	if Linkers<>nil then dispose(Linkers, done);
	inherited Done;
end;

{when getting help, if it's the standard help, use that
(inherited adds current selection to get very context sensitive stuff..}
function TERadioButtons.GetHelpCtx(Const Level : byte): Word;
begin
	{if standard help, then use that...}
	if HelpCtx = hcRadioButtons then GetHelpCtx := hcRadioButtons
	{if specific help, then use that plus selection}
	else GetHelpCtx := inherited GetHelpCtx(Level);
end;
procedure TERadioButtons.HandleEvent;
var OldValue : word;

begin
	OldValue := Value;

	if (Event.What = evCommand) and (Event.Command = cmUpdateFromLink) then begin
		CheckLink;
		Draw;
		ClearEvent(Event);
	end;

	if (Event.WHat = evCommand) and (Event.COmmand = cmSetLinker) then begin
		if Linkers=nil then New(Linkers, init(1,1));
		Linkers^.Insert(Event.InfoPtr);
		EventMask := eventMask or evBroadcast;
		ClearEvent(Event);
	end;

	{--- clear linker ---}
	if (Event.WHat = evCommand) and (Event.COmmand = cmClearLinker) then begin
		if Linkers = nil then
			ProgramError('Trying to clear Linker which isnt there'#13'TUIEDIT.PAS', hcInternalErrorMsg)
		else
			Linkers^.Delete(Event.InfoPtr);

		ClearEvent(Event);
	end;


	if (Event.What = evBroadCast) and (Event.Command = cmForceLink) then ForceLink;
	if (Event.What = evBroadCast) and (Event.Command = cmCheckLink) then CheckLink;

	inherited HandleEvent(Event);

	if Value <> OldValue then begin SetChanged; CheckLink; end; {check links all the time}

	{--- CHeck Link ---- }
{	if (Event.What = evBroadcast) and (Event.Command = cmReleaseFocus) then
		CheckLink; done all the time above}
end;

procedure TERadioButtons.SetChanged;
begin
	LinkChanged := True;
	if not OrigChanged then begin
		OrigChanged := True;
		Message(Owner, evBroadCast, cmSetChangedIndicator, @Self);
	end;
end;


procedure TERadioButtons.CheckLink;
begin
	if LinkChanged then begin
		LinkChanged := False;
		ForceLink;
	end;
end;

procedure TERadioButtons.ForceLink;

	procedure DoLink(InputLinker : PInputLinker); far;
	begin
		InputLinker^.CalculateLink(@Self);
	end;

begin
	if (Linkers<>nil) then Linkers^.ForEach(@DoLink);
end;

{************************************************
 ***            STANDARD BUTTON TYPES         ***
 ************************************************}
{Add key trapping}
constructor TOurButton.Init;
var Bounds : TRect;
begin
	Bounds.Assign(X,Y,X+10,Y+2);
	if length(deltildes(ATitle))>7 then Bounds.Grow(length(deltildes(Atitle))-7,0);
	inherited Init(Bounds, ATitle, ACommand, Abf);
	kbType := kbNone;
	DataItem := ADataItem;
	OwnerValid := 0; {not tested}
	HelpCtx := hcButtons;
end;

procedure TOurButton.HandleEvent;
begin
	inherited HandleEvent(Event);

	if (Event.What = evKeyDown) and (Event.KeyCOde = kbType) then begin
		Press;
		ClearEvent(Event);
	end;

	{this could be done in the .press method, but then the command generated
	by pressing the button is left in the event queue.  THis takes that event
	and converts it into an endmodal if nec}
	if (Event.What = evCommand) and (Event.Command = Command) and
			(Event.InfoPtr = @Self) and (Flags and bfClose <> 0) then begin

		if Owner^.GetState(sfModal) then Owner^.EndModal(Command) else
			Message(Owner, evCommand, cmClose, nil);
		ClearEvent(Event);
	end;{}
end;


procedure TOurButton.Press;
var V : boolean;
begin
	OwnerValid := 0; {allow for pressing and re-pressing}

	if COmmandEnabled(Command) then begin
		DrawState(True);   {Draw as pressed}

		{do a force-link on pressing button}
		if (Flags and bfForceLink<>0) then
			Message(Owner, evBroadCast, cmForceLink, nil){}
		else
			if Command<>cmCancel then {allow a bfForceLink to override this check}
				if (Owner^.Current^.EventMask and evBroadCast)<>0 then
					Message(Owner^.Current, evBroadCast, cmCheckLink, nil); {but anyway tell current view to do one}


		{do get data and mark as having been done - ie the owner was valid}
		if (Flags and bfGetData <>0) and (DataItem<>nil) then begin
			V := Owner^.Valid(Command); {check fields are valid}
			if V then begin
				Owner^.GetData(DataItem^);  {Read data from box into data}
				OwnerValid := +1; {owner tested & valid}
			end else
				OwnerValid := -1; {owner tested and NOT valid}
		end;
	end;

	if OwnerValid=-1 then
		DrawState(False) {draw as up again}
	else
		if Command<>cmNone then begin
			if OwnerValid = 0 then {not tested}
				if Owner^.Valid(Command) then OwnerValid := +1 else OwnerValid := -1;
			if OwnerValid = 1 then inherited Press;
		end;

end;


{==== ARE YOU SURE? CANCEL BUTTON ===========}
{Asks are you sure if changes have been made}
procedure TAYSCancelButton.Press;
begin
	DrawState(True);

	if (Message(Owner, evBroadcast, cmGetChangedIndicator, nil)=nil) {has owner been changed}
		or (MessageBox('CONFIRM',
										'Contents have been changed'#13#10'Confirm Yes to abandon',
										mfConfirmation+mfYesNo+mfWarningBleep,hcNoContext) = cmYes) then {user confirms}
			inherited Press
	else
		DrawState(False); {redraw and don't do anything}
end;



{************************************************************
 ***                  SKIP DATA                           ***
 ************************************************************}
 {Eg when a data field in an object should not be inputtable, something
 must skip past it for the dialog boxes Set and GetData methods}
	constructor TSkipBytes.Init;
	var R : TRect;
	begin
		R.Assign(0,0,0,0);
		inherited Init(R);
		BytesToSkip := NBytesToSkip;
	end;

	function TSkipBytes.DataSize;
	begin
		DataSize := BytesToSkip;
	end;

	constructor TStoreBytes.Init;
	var R : TRect;
	begin
		R.Assign(0,0,0,0);
		inherited Init(R);
		BytesToStore := NBytesToStore;
		GetMem(StoreField, BytesToStore);
	end;

	destructor TStoreBytes.Done;
	begin
		FreeMem(StoreField, BytesToStore);
		inherited Done;
	end;

	procedure TStoreBytes.GetData;
	begin
		Move(StoreField^, Rec, BytesToStore);
	end;

	procedure TStoreBytes.SetData;
	begin
		Move(Rec,StoreField^, BytesToStore);
	end;

	function TStoreBytes.DataSize;
	begin
		DataSize := BytesToStore;
	end;


{***********************************************************************
 ***                                                                 ***
 ***                          EDITOR DIALOG BOXES                    ***
 ***                                                                 ***
 ***********************************************************************}
{A few mods to make construction/validation easier}

{============= INIT ==============================}
constructor TEditBox.Init;
begin
	inherited Init(Bounds, NTitle);
	CallingView := NCallingView;
	HelpCtx := hcDialogBox;
	CopyData := nil;
	EventMask := EventMask or evBroadCAst;
	Options := Options or ofIdle;
	Linkers := nil;
	ViewOnly := False;

	{add changed indicator}
	Insert(New(PChangedIndicator, init(2,Size.Y-1)));
end;

destructor TEditBox.Done;
begin
	{need to dispose of inputlinkers}
	if Linkers<>nil then begin
		Linkers^.FreeAll; {runs through deleting & disposing of all.  Part of
										the inputlinker's done routine is to run through all
										source views removing themselves from those linkers}
		dispose(Linkers, done);
	end;

	inherited Done;
end;

{=========== Insert Input Line ====================}
function TEditBox.InsBox;
var
	R : TRect;
	Line : byte;

begin
	R.Assign(X, Y, X+BoxLen+2, Y+1);{}
	for Line := 0 to Boxdepth-1 do begin
		Insert(New(PInputELine,  Init(R, FieldLen)));
		if Line = 0 then InsBox := PInputELine(Current);
		R.Move(0,1);
{		if NStorePt<>nil then NStorePt := @String(NStorePt^)[FieldLen]; {increment pointer by FieldLen bytes}
	end;
end;

{=========== Insert Titled Std Input Line ====================}
function TEditBox.InsTitledBox;
var R : TRect;
begin
	R.Assign(0,0,BoxLen,1);    {Rough size}
	InsTitledBox := PInputELine(InsTitledField(X, Y, BoxLen, 1, S, New(PInputELine, init(R, FieldLen))));
	if BoxDepth>1 then begin
		InsTitledBox := InsBox(X,Y+1,BoxLen, Boxdepth-1,FieldLen);
	end;
end;

{=========== Insert Titled Field ====================}
function TEditBox.InsTitledField;
var	R : TRect;

begin
	R.Assign(X,Y,X+BoxLen+2,Y+BoxDepth); {set up correct boundary for field}
	Field^.Locate(R);
	if S <> '' then SetLabel(@Self, X,Y,S, Field);    {Inserts label, text S, right set to Field}
	Insert(Field);                     {Field inserted last so that's the current one}
	InsTitledField := Field;
end;


{=========== Insert OK (Save) Button ====================}
procedure TEditBox.InsOKButton;
begin
	Insert(New(POurButton, init(X,Y, 'O~K~', cmOK, bfDefault+bfGetData, ABoxData)));
	Current^.HelpCtx := hcOKBtn;
	POurButton(Current)^.kbType := kbFinish; {system standard}
end;

{=========== Insert Cancel (Abandon) Button ====================}
procedure TEditBox.InsCancelButton;
begin
	Insert(New(POurButton, init(X,Y, '~C~ancel', cmCancel, bfNormal,nil)));
	Current^.HelpCtx := hcCancelBtn;
	POurButton(Current)^.kbType := kbESC; {system standard}
end;

{=========== Insert "Are you sure" Cancel (Abandon) Button ====================}
procedure TEditBox.InsAYSCancelButton;
begin
	Insert(New(PAYSCancelButton, init(X,Y, '~C~ancel', cmCancel, bfNormal,nil)));
	POurButton(Current)^.kbType := kbESC; {system standard}
end;


procedure TEditBox.EndInit;
var Bounds :TRect;
begin
	{add changed indicator}
{	Bounds.Assign(2,Size.Y-1,3,Size.Y);
	New(ChangedIndicator, init(Bounds));
	Insert(ChangedIndicator); {already done in init....}

	SelectNext(False);  {Move from last inserted field to first field}
end;


procedure TEditBox.AddLinker(const Linker : PInputLinker);
begin
	if Linkers = nil then New(Linkers, init(1,1));
	Linkers^.Insert(Linker);
end;

procedure TEditBox.CheckInitLinks(const Force : boolean);

	procedure CheckLink(InputLinker : PInputLinker); far;
	var B : byte;
	begin
		if InputLinker^.ForceInitLink or Force then
			{direct link from each view through linker}
			for B := 1 to MaxLinks do
				if InputLinker^.SourceView[B]<>nil then
					Message(InputLinker^.SourceView[B], evBroadCast, cmForceLink, nil);
	end;

begin
	if Linkers<>nil then
		Linkers^.ForEach(@CheckLink);
end;


{================ EDIT BOX VALIDATOR =======================}
function TEditBox.Valid(Command : Word) : boolean;
begin
	{Validates current line first, then does standard validation on
	rest of lines}
	if Current^.Valid(Command) then
		Valid := inherited Valid(Command)
	else
		Valid := False;
end;

{Because the TGroup idle method is partially disabled in real mode, the edit
box has to reinstate it to allow for more-about lists, etc, which need
drawn even if not focused}
{{$IFDEF MSDOS}
{procedure TEditBox.Idle;

	procedure DoIdle(P : PView); far;
	begin
		if (ofIdle and P^.Options <>0) or (P = Current) then P^.Idle;
	end;

begin
	ForEach(@DoIdle);{}
{end;
{{$ENDIF}


{Set data for one subview only}
{Reads just appropriate bit from complete data record for whole box}
procedure TEditBox.SetViewData(var Rec; View : PView);
var
	I: Word;
	V: PView;

begin
	I := 0;
	V := Last; {last item in group - first for setdata}

	{find location of data in Rec}
	repeat
		Inc(I, V^.DataSize);
		V := V^.Prev;
	until (V = Last) or (V=View);

	if V = View then begin
		View^.SetData(TByteArray(Rec)[I]);
		View^.DrawView;
	end;

end;


{======= GET HELP CONTEXT ================}
function TEditBox.GetHelpCtx(const Level : byte): Word;
begin
	if Level = hlNormal then
		GetHelpCtx := HelpCtx  {direct of box, not inputfield}
	else
		GetHelpCtx := inherited GetHelpCtx(Level);
end;

{============ HANDLE EVENT ===================================}
procedure TEditBox.HandleEvent;
begin
	if ViewOnly then
		{if it's a view only, don't let any editing-style keys through - ie
		only movement}
		if (Event.What = evKeyBoard) then
			case Event.KeyCode of
				kbLeft, kbRight, kbUp, kbDown, kbShiftTab, kbTab, kbHelp,kbESC,kbFinish,
				kbChange, kbEnter, {for default button, and allow edit in lists}
				kbPgDn, kbPgUp {lists again}
					: begin end;
			else
				exit; {don't go no further}
			end;


	if (Event.What = evBroadCast) and (Event.Command = cmIsView) and (Event.InfoPtr=@Self) then ClearEvent(Event);

	{check location}
	if (Event.What = evBroadCast) and (Event.Command = cmCompareLoc) then
		if (Origin.X = PPoint(Event.InfoPtr)^.X) and (Origin.Y = PPOint(Event.InfoPtr)^.Y) then
			ClearEvent(Event);

	{copy whole box in from copydata pointer}
	if (Event.What = evKeyBoard) and (Event.KeyCOde = kbCtrlIns) and (CopyData<>nil) then begin
		{Copy whole from copydata}
		SetData(CopyData^);
		Redraw;
		ClearEvent(Event);
	end;

	{copy in just this field from copydata pointer}
	if (Event.What = evKeyBoard) and (Event.KeyCOde = kbShiftIns) and (CopyData<>nil) then begin
		SetViewData(CopyData^, Current);
		FocusNext(False); {Move on...}
		ClearEvent(Event);
	end;

	inherited HandleEvent(Event);

	if (Event.What = evKeyBoard) and (Event.KeyCode = kbFinish)
			and CommandEnabled(cmOK) then begin
		{F10 is normally tied to the OK button - but if there is no OK button
		(eg in file editor boxes) then the event won't be trapped in above
		handleevent and we need to handle "manually"}
		StartEvent(evCommand, cmOK); {inherited method then closes}
		ClearEvent(Event);{}
	end;

	{======= CASCADING ===============}
{	if (Event.What = evCommand) then begin
		if (Event.Command = cmCascadeView) then begin
			if DesktopSetup.AutoCascade then Cascade;
			ClearEvent(Event);
		end;
		if (Event.Command = cmUnCascadeView) then begin
			if DesktopSetup.AutoCascade then UnCascade;
			ClearEvent(Event);
		end;
	end;{}

end;


{====== Cascade to calling view ================}
procedure TEditBox.Cascade;
var	Point : TPoint;
begin
	GetBounds(PreCascade); {precascadeposition}
	if CallingView<>nil then begin
		Point := CallingView^.Origin;
		MoveTo(Point.X+1, Point.Y+1);
	end;
end;

procedure TEditBox.UnCascade;
begin
	Locate(PreCascade);
end;




{************************************
 ***        OBJECT EDITOR BOX     ***
 ************************************}
{Designed for inputting objects, where the input fields exactly match the layout
of the fields in the object; this box inserts a skipbytes of 2 at the start to
skip the VMT table}

constructor TObjectEditBox.Init;
begin
	inherited Init(Bounds, NTitle, NCallingView);
	insert(New(PSkipBytes, init(2)));
end;




end.
