{********************************************************************
 ***                                                              ***
 ***              OBJECTS & METHODS FOR EDIT BOXES                ***
 ***                                                              ***
 ********************************************************************}
{$I compflgs}
unit WinEdit;

INTERFACE

uses
	objects,
	win,
	wintypes, winprocs,
	OWindows, ODialogs;

const
	MaxLinks = 4;

	{commonly used constants}
{	ofCenterX = views.ofCenterX;
	ofCenterY = views.ofCenterY;
	ofCentered = views.ofCentered;{}

const
	bfNormal    = $00;
	bfDefault   = $01;
	bfLeftJust  = $02;
	bfBroadcast = $03;
	bfGrabFocus = $04;

{	cmRecordHistory = Dialogs.cmRecordHistory;{}

	bfClose = $10;
	bfGetData = $20;
	bfForceLink = $40;


type
	PChangedIndicator = ^TChangedIndicator;
	TChangedIndicator = object(TWinView)
		Changed : boolean;
		constructor Init(AParent : PWindowsObject; X,Y : integer);
	end;

	PPoint = ^TPoint;

	PEditBox = ^TEditBox;

	{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 PWinView;
		TargetView : array[1..MaxLinks] of PWinView;
		LinkCalculator : pointer;
		ForceInitLink : boolean;
		Next : PInputLinker; {for linker chain in editbox}
		constructor Init(NLinkCalculator : pointer; const EditBox : PEditBox);
		destructor Done; virtual;
{		procedure SetSourceView(const View : PWinView; const svtype : byte);
		procedure SetTargetView(const View : PWinView; const tvtype : byte);
		procedure CalculateLink(const CallingView : PWinView); virtual;{}
	end;

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


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

		constructor Init(Bounds : TRect);
		procedure HandleEvent(var Event : TEvent); virtual;
		procedure InsertEditFields; virtual;
		procedure Draw; virtual;
		procedure SetChanged; {mark as having been changed}
{	end;{}


	PInputELine = ^TInputELine;
	TInputELine = object(TEdit)
		{Automatic link fields}
		Linker : PInputLinker;

		{Flags}
		IgnoreChanges : boolean; {used internally to temporarily switch off link checking, eg for setdata}

		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}

		LinkChanged : boolean; {marked if data has changed since last link}
		OrigChanged : boolean; {marked automatically if field has been edited}

		constructor Init(AParent : PWindowsObject; X,Y,W,H : integer; NFieldLen : integer);
	end;

	PInputNum = ^TInputNum;
	TInputNum = object(TInputELine)
{		Calculator : PCalculator;{}
		DecPlaces : integer; {number of decimal places allowed}
		constructor Init(AParent : PWindowsObject; X,Y,W,H : integer; NFieldLen : integer);
	end;


	PInputLint = ^TInputLint;
	TInputLint = object(TInputNum)
		constructor Init(AParent : PWindowsObject; X,Y,W,H : integer; NFieldLen : integer);
	end;

	PInputInt = ^TInputInt;
	TInputInt = object(TInputLint)
	end;

	PInputWord = ^TInputWord;
	TInputWord = object(TInputLint)
	end;

	PInputByte = ^TInputByte;
	TInputByte = object(TInputLint)
	end;


	PInputReal = ^TInputReal;
	TInputReal = object(TInputNum)
	end;

	PInputSingle = ^TInputSingle;
	TInputSingle = object(TInputNum)
	end;

	PInputBoolean = ^TInputBoolean;          {'twould be nice to make this into a proper check box}
	TInputBoolean = object(TInputELine)
		constructor Init(AParent : PWindowsObject; X,Y,W,H : integer);
	end;

	PInputPStr = ^TInputPStr;
	TInputPStr = object(TInputELine)
	end;

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

		Linker : PInputLinker;

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

		constructor Init(AParent : PWindowsObject; X,Y,W,H : integer; Title: PChar);
	end;


	{for skipping pointers, etc}

	PSkipBytes = ^TSkipBytes;
	TskipBytes = object(TWindowsObject)
		BytesToSkip : word;
		constructor Init(NBytesToSkip : word);
	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(TWindowsObject)
		BytesToStore : word;
		StoreField : PString;
		constructor Init(NBytesToStore : word);
		destructor Done; 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(APArent : PWinView; const X,Y : integer; ATitle : PChar;
											ACommand : word; Abf : word; ADataITem : pointer);
	end;

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


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

		FirstLinker : PInputLinker;
		ChangedIndicator : PChangedIndicator;
		CallingView : PWinView; {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}

		constructor Init(X,Y,W,H : integer; NTitle : PChar; NCallingView : PWinView);
		destructor Done; virtual;
		procedure EndInit; virtual;      {Used after all inserts by descendant to tidy up etc}
		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;

		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(X,Y,W,H : integer; NTitle : PChar; NCallingView : PWinView);
	end;

function InputWarning(const Message : PChar) : word;        {Fields wrong}

IMPLEMENTATION

uses
			winMsgs, {for input error/keypress error warning}
			strings,
			global, minilib, help;


{**********************************
 ***        INPUT WARNING       ***
 **********************************}
 {Field not valid, etc}
function InputWarning(const Message : PChar) : word;
begin
	WrongFldBleep;
	MessageBox(0,'INPUT ERROR', Message, mfWarning + mfOKButton);
end;



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

constructor TChangedIndicator.Init;
begin
	inherited Init(AParent, X,Y,20,10);
	Changed := False;
{	EventMask	:= EventMask or evBroadCast; {so it gets the message...}
{	GrowMode := gfGrowLoY + gfGrowHiY;{}
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;{}
	Next := nil;
	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,cmSetLinker, nil);
		if TargetView[I]<>nil then Message(TargetView[I],evCommand,cmSetLinker, nil);{}
	end;{}
	if Next<>nil then dispose(Next, done);
	inherited Done;
end;


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

constructor TInputELIne.Init;
var B : byte;
begin
	inherited Init(nil, 100, nil, X,Y,W,H, NFieldLen, False);
{	Options := Options or ofValidate;  {Forces validation before exiting}

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

	{clear links}
	Linker := 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;
end;


{************************************************************
 ***                     NUMERIC INPUT                    ***
 ************************************************************}
	constructor TInputNum.Init;
	begin
		inherited Init(Aparent, X,Y,W,H, NFieldLen);
{		Calculator := nil;{}
		DecPlaces := -1;  {any}
	end;


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




{************************************************************
 ***                   NUMERIC INPUT - A WORD             ***
 ************************************************************}
{************************************************************
 ***                   NUMERIC INPUT - A BYTE             ***
 ************************************************************}

{***********************************************************
 ***                   NUMERIC INPUT - INTEGER           ***
 ************************************************************}

{************************************************************
 ***                   NUMERIC INPUT - A SINGLE PREC REAL ***
 ************************************************************}

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


{************************************************************
 ***                   LINE INPUT - FROM A STRING PTR     ***
 ************************************************************}

{****************************************************
 ***             RADIO BUTTONS WITH TARGET LINK   ***
 ****************************************************}
constructor TERadioButtons.Init;
var B : byte;
begin
	inherited Init(AParent, 101, Title, X,Y,W,H, nil);
	{clear linkers}
	Linker := nil;
{	Options := Options or ofValidate; {force checklink on focusrelease}
{	EventMask := EventMask or evBroadcast; {for link messages}
	LinkChanged := False;
	OrigChanged := False;
end;


{************************************************
 ***            STANDARD BUTTON TYPES         ***
 ************************************************}
{Add key trapping}
constructor TOurButton.Init;
var W : integer;
		B : byte;
begin
	W := 10;
	B := length(deltildes(StrPas(ATitle)));
	if B>7 then W := B;
	inherited Init(nil, 105, Atitle, X,Y,W,20, (Abf and bfDefault)<>0);
	kbType := kbNone;
	DataItem := ADataItem;
	OwnerValid := 0; {not tested}
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;
	begin
		inherited Init(nil);
		BytesToSkip := NBytesToSkip;
	end;


	constructor TStoreBytes.Init;
	begin
		inherited Init(nil);
		BytesToStore := NBytesToStore;
		GetMem(StoreField, BytesToStore);
	end;

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


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

{============= INIT ==============================}
constructor TEditBox.Init;
begin
	inherited Init(CallingView, NTitle);
	CallingView := NCallingView;
{	HelpCtx := hcEditBox;{}
	CopyData := nil;
{	EventMask := EventMask or evBroadCAst;
	Options := Options or ofIdle;{}
	FirstLinker := nil;

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

destructor TEditBox.Done;
var L : PInputlinker;
begin
	L := FirstLinker;
	if L<>nil then dispose(L, done); {dispose afterwards in case of checklinks in the focussing involved}
	inherited Done;
	{or not, as it will be disposing of views...?}
end;


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

{=========== Insert Cancel (Abandon) Button ====================}
procedure TEditBox.InsCancelButton;
var OurButton : POurButton;
begin
	OurButton := New(POurButton, init(@Self, 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;
var OurButton : POurButton;
begin
	OurButton := New(PAYSCancelButton, init(@Self, X,Y, '~C~ancel', cmCancel, bfNormal,nil));
{	POurButton(Current)^.kbType := kbESC; {system standard}
end;


procedure TEditBox.EndInit;
begin
{	SelectNext(False);  {Move from last inserted field to first field}
end;


procedure TEditBox.AddLinker(const Linker : PInputLinker);
begin
	Linker^.Next := FirstLinker;
	FirstLinker := Linker;
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;
var SB : PSkipBytes;
begin
	inherited Init(X,Y,W,H, NTitle, NCallingView);
{	New(SB, init(@Self, 2));{}
end;




end.
