{****************************************************************************
 ***                                                                      ***
 *** New Fabbo Singing Dancing OOP                                        ***
 ***                          R E P O R T       R O U T I N E S           ***
 ***                                                                      ***
 *** M Hill                                                      Mar 1996 ***
 ****************************************************************************}
{$I compflgs} {Compiler directives}
{provides a report/analyse root object, and various methods for analysing
data}

{The report object provides methods for asking the user:
	1) Search base - ie which file to look through
	2) Search criteria - which items to pick out/match
	3) Output format - how to deal with the information

For the jimmy report:
	Override SearchAll to set basefitype (or do in .init)
	Override EnterCriteria
	Override MatchItem
	Override OutputItem

Storing the report will require also overriding the Load & Store methods - see the
	GeneralAnalysis below

It runs through the search base, comparing the search criteria given, to
produce an indexed file pointing to matched items.

Assumes data items are jimmys


FLOW CONTROL:

			 DoSearch (*until cancel)
		 _____Ý______________________________________________________
		 Ý                   Ý                  Ý                    Ý
	Enter Search      Search &    (o)    Search & (o)           Search & (o)
	Criteria          Add to Graph       Store                  Print
																			___Ý________              _Ý_____________
																		 Ý            Ý            Ý               Ý
																	SearchAll    DoOutput(*) Enter Output    SearchAll
																													 Criteria

{}
unit REPORTS;

INTERFACE

uses
	views, {text}
	forms,
	files,
	tuigrphs,
	notes,
	tuiedit,
  objects, tuimsgs, devices;


const
	eaSearch = 01;
	eaOutput = 02;
	eaOutputNow = 03;
	eaGraph = 04;
	eaAdmin = 05;

	ReportExt = 'RPT';

	{print alias options - not fully implemented?  no user query...}
	aoPrintFull = 1; {print on finding alias as if it were the main entry}
	aoPrintSee = 2; {print alias name then "see " and main entry name}
	aoNoPrint = 3; {do not print}

	{Report stage - not fully implemented}
	rsIdle = 0;
	rsQuery = 1;
	rsSearching = 2;
	rsPrinting = 3;


type
	{Root report type}
	PReport = ^TReport;
	TReport = object(TObject)

		FileName : string[8]; {for storing self}

		Title, SearchMsg, SortMsg, OutputMsg,DescMsg : string[40];

		OutputDevice : PDeviceStream;
		Header1, Header2 : FNameStr;

		StartRec, CurrentREc, EndRec : longint;
		NumMatched : longint;
		ProBox : PProgressBox;              {needs to be set by user if wanted}

		FoundFile : file of longint;

		GraphWindow: PGraphWindow;

		WorkFormCodes : PFormCodeCollection; {for analysing statements}

		Stage : word; {gives stage of report (see above)}

		{these flags control the user messages/box interactions}
		AskForSearch : boolean; {if 0, asks for search criteria, o/w assumes command = askforsearch}
		AskForOutput : boolean; {as above, for print criteria}
		DoDone : boolean; {marks whether to put up 'Done!' message at end of search}
		OnceOnly : boolean; {if false (default) returns to input search once output is done, o/w ends}
		OnSearchOKdo : word; {convert OK to this from the search box}
													{used to set immediate print/search then print}


		constructor Init(NTitle, NSearchMsg, NSortMsg, NOutputMsg,NDescMsg : string);
		procedure CommonInit; virtual;
		destructor Done; virtual;

		function IsSortKey : boolean; virtual; {is there a sort key entered}

		procedure SetHeaderCodes(FormCodes : PFormCodeCollection); virtual;

		function GetGraphYLabel : string; virtual;
		function GetGraphXLabel : string; virtual;

		{user processing/routing}
		procedure DoSearch;
		procedure DoOutput;
		function EnterCriteria(eaType : word) : word; virtual;
		function EnterOutput(eaType : word) : word;

		function Edit : word; virtual;
		constructor Load(var S : TDataStream);
		procedure Store(var S : TDataStream); virtual;

		procedure StoreSelf; {does a putobjtofile}

		{searching}
		procedure SetSearchRange; virtual;
		procedure PreSearch(Command : word);	virtual;
		procedure SearchAll(Command : word);	virtual;
		procedure PostSearch(Command : word); virtual;

		{printing}
		procedure GraphAll;    virtual;
		procedure SortAll;										virtual;
		procedure PreOutput;  		virtual;
		procedure OutputAll;			virtual;
		procedure PostOutput;			virtual;

		{getting item info - for overriding}
		function GetItem(ItemNum : longint) : PObject;  virtual;
		function MatchItem(Item : PObject) : boolean;      virtual;
		procedure StoreItem(Item : PObject);        virtual;
		procedure OutputItem(item : PObject; ItemNo : longint); virtual;
		function GetItemKey(Item : PObject) : string; virtual;
		procedure GraphItem(Item :PObject); virtual;

		function GetFoundItem(ItemNum : longint) : PObject; virtual;

	 end;

	{Root report on jimmys - descendants add search criteria, output type, etc}
	PJimmyReport =^TJimmyReport;
	TJimmyReport = object(TReport)

		AliasOptions : byte;
		BasefiType : word; {which index/or hook file to search by}

		constructor Init(NTitle, NDescMsg : string);
		procedure CommonInit; virtual;
		destructor Done; virtual;

		procedure PreSearch(Command : word); virtual;
		procedure PostSearch(Command : word); virtual;
		procedure SetSearchRange; virtual;
		procedure OutputAll;			virtual;
		procedure SortAll; virtual;
		procedure GraphAll; virtual;

		function GetItem(ItemNum : longint) : PObject; virtual;
		function MatchItem(Item : PObject) : boolean;  virtual; {checks validity/alias}
	end;

	{General high-powered analysis... by any criteria}
	PGeneralAnalysis =^TGeneralAnalysis;
	TGeneralAnalysis = object(TJimmyReport)

		Search : record
			UserfiEntry : word;
			Match : PFreeTextData;
		end;

		Output : record
			SortKey : string[20];
			ItemFormFileName : string[8];
			ItemForm : PFreeTextData;
		end;

		GraphOut : record
			XLabel, YLabel : string[30];
			XCode, YCode : string[50];
		end;

		procedure CommonInit; virtual;
		destructor Done; virtual;

		function IsSortKey : boolean; virtual; {is there a sort key entered}

		function GetGraphYLabel : string; virtual;
		function GetGraphXLabel : string; virtual;


		function EnterCriteria(eaType : word) : word; virtual;

		procedure PreSearch(Command : word); virtual;

		function MatchItem(Item : PObject) : boolean;      virtual;
		procedure OutputItem(item : PObject; ItemNo : longint); virtual;
		function GetItemKey(Item : PObject) : string; virtual;
		procedure GraphItem(Item :PObject); virtual;

		constructor Load(var S : TDataStream);
		procedure Store(var S : TDataStream); virtual;

	end;

type
	PInputFormCodeLine = ^TInputFormCodeLine;
	TInputFormCodeLine = object(TInputELine)
		constructor Init(Bounds : TREct; NFieldLen : byte);
		function Valid(Command : word) : boolean; virtual;
	end;

var
	SyntaxError : boolean;

	CalculatorFormCodes : PFormCodeCollection; {for calculator part of statement analasis}

{Some standard codes for output types}
const
	{Targets}
{	TrNone = 0;{}
	TrPrinter = 0;
	TrScreen = 1;
	TrFile = 2;
	TrWP = 3;

	{Format}
{	FoNone = 0;{}
	FoLabel = 0;      {Produce Labels}
	FoList = 1;				{Produce list}
	FoStdLtr  = 2;    {Produce Standard letters}



{*************************************************************
 ***              IMPLEMENTATION                           ***
 *************************************************************}
IMPLEMENTATION

uses  minilib, app, dialogs, global,
			lstrings,
			dattime,
			dosUtils,
			tui,
			inpfname,
			indxutil,
			editfile,
			editor,
			tuiboxes, stddlg,
			drivers,
			help,
			jimhooks,
			kamsetup, tasks,
			printers, indexes, jimindxs, jimmys;


{***********************************************************************
 ***                                                                 ***
 ***                        TOOLS                                    ***
 ***                                                                 ***
 ***********************************************************************}
{}
{Terminology: Condition is xxx>yyy, etc, and statement is
		condition and condition or condition, etc}

procedure SyntaxErrorMsg(Msg : TMessageStr);
begin
	SyntaxError := True;
	MessageBox('SYNTAX ERROR!',Msg, mfWarning,hcGASyntaxError);
end;

{adds brackets to a string, ie AGE CALF.NAME becomes <AGE> <CALF.NAME>
if brackets not already there, ready for processing by QDecodeStr}
function AddBrackets(S : string) : string;
var BS,BW : string;
begin
	S := ucase(delspace(S)); BS := '';
	while S<>'' do begin
		BW := SplitBy(S,' ');
		if (BW[1] <> '<') and (BW[1]<>'[') then BW := '<'+BW+'>';
		if (BW[1] = '[') and (BW[length(BW)]<>']') then begin
			{find next bracket}
			BW := BW + ' '+copy(S,1,pos(']',S));
			Delete(S,1,pos(']',S));
		end;
		BS := BS + BW +' ';
	end;

	AddBrackets := BS;
end;


	constructor TInputFormCodeLine.Init;
	begin
		Inherited Init(Bounds, NFieldLen);
		UpperCase := True;
	end;

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

		if V and (Command<>cmCancel) and (Command<>cmValid) and (Command<>cmClose) then begin

			Data^ := Copy(AddBrackets(Data^),1,MaxLen);
			Draw;

			if (Count('<',Data^)<>Count('>',Data^)) or
					(Count('[',Data^)<>Count(']',Data^)) then begin
				WrongFldBleep;
				InputWarning('Brackets Incorrect', HelpCtx);
				V := False;
			end;

		end;
		Valid := V;
	end;


{************************************
 ***         SPLIT CONDITION      ***
 ************************************}
{splits by and, or, +, -, *, /, >, <, etc}
procedure SplitStatement(S : string; var Left, Right, Operand : string);
var	BCount : byte; {bracket count}
		P : byte;
		Quotes : boolean; {marker for being in quotes mode}
		C : char;

begin
	P := 1; Quotes := False; BCount := 0;
	Left:= ''; Right := ''; Operand := '';

	{remove outside (superfluous) brackets}
	S := delspace(S);

	while (P<=Length(S)) and (Operand = '') do begin

		C := S[P]; {shorthand}

		{-- check if reached operand ---}
		if not Quotes and (BCount=0) then begin {take quotes as literal and split with brackets}

			{mathematical and comparison}
			case C of
				'+','-','/','*' : Operand := C;
				'>','<','=' : begin
					Operand := C;
					if (P<length(S)) then
						if (S[P+1]='=') or ((Operand='<') and (S[P+1]='>')) then
							Operand := Operand + S[P+1];
				end;
			end;

			{logical}
			if ucase(Copy(S,P,5))=' AND ' then Operand := ' AND ';
			if ucase(Copy(S,P,4))=' OR '  then Operand := ' OR ';
		end;
		{ought to check also for double quotes? ie "HI""THERE"}

		{build split parts}
		if Operand = '' then Left := Left + C;

		{check "mode"}
		case C of
			'"' : Quotes := not Quotes;
			'(' : inc(BCount);
			')' : if Bcount=0 then begin
							SyntaxErrorMsg('Brackets inconsistent'#13+S);
							exit;
						end else
							dec(BCount);
		end;

		inc(P);
	end;

	if Operand = '' then
		Right := ''
	else
		Right := Copy(S,P-1+length(Operand), length(S));

	{delete surplus spaces and one outer brackets  - cannot take  off more than
		one pair because of eg ((A=B) AND (C=D))   -->  A=B) AND (C=D}
	Left := delspace(Left); if (Left[1]='(') and (Left[length(Left)]=')') then Left := Copy(Left,2,length(Left)-2);
	Right := delspace(Right); if (Right[1]='(') and (Right[length(Right)]=')') then Right := Copy(Right,2,length(Right)-2);
	Operand := delspace(Operand);

	if (Operand = '') then begin
		{see if surrounding bracket, and remove}
		if (S[1]='(') and (S[length(S)]=')') then begin
			S := Copy(S,2,length(S)-2);
			SplitStatement(S,Left,Right,Operand);
		end
	end else
		{there is an operand}
		if (Left='') or (Right='') then SyntaxErrorMsg('Operand but missing parameter'#13+S);

end;

{************************************
 ***   ARITHMETIC CALCULATOR      ***
 ************************************}
function IsNumeric(var S : string) : boolean; {dequotes and tests for numeric}
var Date : TDate;
begin
	IsNumeric := True;

	{quotes}
	if S[1]='"' then
		if S[length(S)]<>'"' then
			SyntaxErrorMsg('No closing quotes'#13+S)
		else begin
			S := Copy(S, 2, length(S)-2);
			IsNumeric := False;
		end;

	{dates}
	{BEWARE - if someone enters 20.1.96<2003.96 it will give funny answers}
	if S<>'' then begin
		Date.SetToStr(S);
		if (DatErr=0) then begin
			{valid date - change to numeric in days}
			IsNumeric := True;
			S := N2Str(Date.Days);
		end;
	end;{}

	if ((S2Num(S)=0) and (S[1]<>'0')) then IsNumeric := False;
end;


{pass string:
	if numeric will calculate and return true,
	if code, decodes and redoes calculation
	if neither, returns in upper case for comparison}
function Calculate(var S : string) : boolean;
var Left, Right,Operand,CodeS : string;
		LNumeric,RNumeric : boolean;

begin
	if SyntaxError then exit;

	Calculate := False;

	SplitStatement(S, Left, Right, Operand);

	if (Operand='') then begin
		{no calculation to be done}

		{decode}
		if CalculatorFormCodes <>nil then
			CodeS := CalculatorFormCodes^.QDecode(Left)
		else
			CodeS := Left;

		{assume that codes are not going to contain any sub-calculations,
			otherwise we have trouble with dates (ie it returns 01-01-95 which it
			then tries to calculate...}
		Calculate := IsNumeric(CodeS);

		S := CodeS;
		exit;
	end;

	LNumeric := Calculate(Left);  {recalculate to decode and in case of 224-34+26}
	RNumeric := Calculate(Right);
	if SyntaxError then exit;

	if LNUmeric and RNumeric then begin
		{both are numeric}
		Calculate := True;
		case Operand[1] of
			'+' : S := N2Str( S2Num(Left) + S2Num(Right));
			'/' : S := N2Str( S2Num(Left) div S2Num(Right));
			'*' : S := N2Str( S2Num(Left) * S2Num(Right));
			'-' : S := N2Str( S2Num(Left) - S2Num(Right));
		else
			SyntaxErrorMsg('Incorrent Operand in calculation'#13+S);
		end;

	end else begin
		{one or other is not numeric but an operand found}
		SyntaxErrorMsg('Not numeric'+#13+S);
	end;
end;



function IsComparisonTrue(Left, Right,Operand : string) : boolean;
var S : string;
		Numeric : boolean;

begin
	IsComparisonTrue := False;

	if Operand[1] = '>' then begin
		{swap}
		Operand[1] := '<';
		S := left;
		left := Right;
		Right := S;
	end;

	Numeric := Calculate(Left);
	if not Calculate(Right) then Numeric  := False; {if right is not numeric, set flag to false}

	if SyntaxError then exit;


	if not Numeric then begin left := UCase(left); Right := Ucase(Right); end;

	{TEST!}
	if Operand[1] = '=' then begin
		if NUmeric then begin
			if S2Num(left)=S2Num(Right) then IsComparisonTrue := True;
		end else begin
			if Operand = '==' then begin
				{match word}
				if (pos(' '+left+' ', ' '+Right+' ')>0) or (Pos(' '+Right+' ', ' '+left+' ')>0) or (left=Right)
						then IsComparisonTrue := True;
			end else begin
				{partial pos match - use pos and also direct check as pos('','') returns 1}
				if (pos(left, Right)>0) or (Pos(Right, left)>0) or (left=Right)
						then IsComparisonTrue := True;
			end;
		end;
	end;

	if Operand= '<>' then begin
		if Numeric then begin
			if S2Num(left)<>S2Num(Right) then IsComparisonTrue := True;
		end else begin
			if left<>Right then IsComparisonTrue := True;
		end;
	end;

	if Operand='<=' then begin
		if Numeric then begin
			if S2Num(left)<=S2Num(Right) then IsComparisonTrue := True;
		end else begin
			if left<=Right then IsComparisonTrue := True;
		end;
	end;

	if Operand='<' then begin
		if Numeric then begin
			if S2Num(left)<S2Num(Right) then IsComparisonTrue := True;
		end else begin
			if left<Right then IsComparisonTrue := True;
		end;
	end;
end;



{============= SYNTAX ANALYSIS ==================}
function IsConditionTrue(S : string) : boolean;
var	Left, Right, Operand : string; {for conditions - true/false}

begin
	IsConditionTrue := False; {by default}

	if SyntaxError then exit; {for un-recursing when a syntax error is found}

	if S = '' then begin IsConditionTrue := True; exit; end;

	SplitStatement(S, Left, Right, Operand); if SyntaxError then exit;

	if (Operand = 'AND') then
		{anding other true/false conditions}
		IsConditionTrue := IsConditionTrue(Left) and IsConditionTrue(Right)
	else
		if (Operand = 'OR') then
			{oring other true/false conditions}
			IsConditionTrue := IsConditionTrue(Left) or IsConditionTrue(Right)
		else
			if (Operand='>=') or (Operand='<=') or (Operand='==') or
				 (Operand='>') or (Operand='<') or (Operand='=') or
				 (Operand='<>') then
				{comparing values/strings}
				IsConditionTrue := IsComparisonTrue(Left, Right, Operand)
			else
				SyntaxErrorMsg('Not a condition'#13+S);

end;


{***********************************************************************
 ***                                                                 ***
 ***                          REPORT ROOT                            ***
 ***                                                                 ***
 ***********************************************************************}

{================= OBJECT ADMIN =========================}
constructor TReport.Init;
begin
	inherited Init;

	Title := NTitle;
	SearchMsg := NSearchMsg;
	SortMsg   := NSortMsg;
	OutputMsg := NOutputMsg;
	DescMsg := NDescMsg;

	CommonInit;

	ProBox := NewProgressBox(Title,'',mfCancelButton,hcNoContext); {needs to be available throughout for testing cancel}
	ProBox^.Hide;
end;

procedure TReport.CommonInit;
begin
	FileName := '';

	StartRec := -1; EndRec := -1; CurrentRec := -1;
	NumMatched := 0;

	SyntaxError := False;

	New(WorkFormCodes, init);

	Assign(FoundFile, WorkPath + 'FOUND.$$$');

	GraphWindow := nil;

	OutputDevice := Printer; {by default}
	Header1 := '';
	Header2 := 'REPORT';

	{This needs some careful documentation as to what each type of setting does,
	considering the various combineations}
	AskForSearch := False;
	AskForOutput := False;
	OnceOnly := True;  {if the above two are false, should go round once only...}
	OnSearchOKdo := cmSearch; {normally do a search}

	DoDone := True;
	Stage := rsIdle;
end;

destructor TReport.Done;
begin
	dispose(WorkFormCodes, done);
	dispose(ProBox, done);
	inherited Done;
end;

procedure TReport.SetHeaderCodes;
begin
	with FormCodes^ do begin
		SetStr('MATCHED', N2Str(NumMatched));
		SetStr('RTITLE', Title);
		SetStr('RPTDESC', DescMsg);
		SetStr('LISTHDR', '');
	end;
end;


{=========== REPORT CHARACTERISTICS ====================}
{function TReport.ImmediatePrint;
begin ImmediatePrint := True; end;{}

function TReport.IsSortKey;
begin IsSortKey := False; end;

function TReport.GetGraphYLabel : string;
begin GetGraphYLabel := 'Y Axis'; end;

function TReport.GetGraphXLabel : string;
begin GetGraphXLabel := 'X Axis'; end;


{**************************************************************
 ***                 USER PROCESSING & ROUTING              ***
 **************************************************************}
procedure TReport.DoSearch;
var COntrol,Control2 : word;
begin
	repeat
		ProBox^.Command := cmOK; {reset}

		if AskForSearch then Control := EnterCriteria(eaSearch) else Control := cmOK;

		if Control = cmOK then begin
			if IsSortKey then Control := cmSearch else Control := cmPrint;
			if OnSearchOKdo<>0 then Control := OnSearchOKdo;
		end;

		case Control of
			cmGraph : begin
				Control2 := EnterOutput(eaGraph);
				if Control2 <> cmCancel then SearchAll(Control2); {immediate print}
			end;

			cmSearch : begin
				SearchAll(cmSearch);
				if ProBox^.Command<>cmCancel then DoOutput;
			end;

			cmPrint : begin
				Control2 := EnterOutput(eaOutputNow);
				if Control2 <> cmCancel then SearchAll(Control2); {immediate print}
			end;
			cmAdmin : Edit;
		end;

{		if ProBox^.Command = cmCancel then
			PauseMessage(TItle, 'Cancelled');{}

	until (Control = cmCancel) or OnceOnly or (not AskForSearch and (Control2=cmCancel));
end;

{returns cancel, cmgraph or cmPrint}
function TReport.EnterOutput(eaType : word) : word;
var Control : word;
begin
	if AskForOutput then begin
		Control := cmGraph;
		while (Control=cmGraph) or (Control = cmPrint) do begin
			Control := EnterCriteria(eaType); {returns cmgraph or cmPrint if in wrong box}

			case Control of
				cmGraph : eaType := eaGraph;
				cmPrint : eaType := eaOutput;
			end;
		end;
	end else
		Control := cmOK;

	if Control = cmOK then
		if eaType = eaGraph then EnterOutput := cmGraph else EnterOutput := cmPrint
	else
		EnterOutput := cmCancel;
end;


{=============== POST FOUND OUTPUT =======================}
procedure TReport.DoOutput;
var Control : word;
		eaType : word;
begin
	if NumMatched=0 then begin
		PauseMessage(Title, 'None Found',hcNoContext);
	end else begin
		{---- output ----}
		TitledThinkingOn('Found',N2Str(NumMatched));

		{--- Get output format and keep doing untilled cancelled}
		eaType := eaOutput;

		repeat
			Control := EnterOutput(eaType);

			ProBox^.Command := cmOK;

			if Control = cmGraph then begin GraphAll; eaType := eaGraph; end;
			if Control = cmPrint then begin
				if IsSortKey then SortAll;
				if ProBox^.Command<>cmCancel then OutputAll;
				eaType := eaOutput;
			end;
		until (Control = cmCancel) or (not AskForOutput);

		ThinkingOff;
	end;
end;

{=================== USER ENTRY ================================}
function TReport.EnterCriteria(eaType : word) : word;
var EditBox : PObjectEditBox;
		Bounds,R : TRect;
		Control : word;

begin
	case eaType of
		eaAdmin : begin
			Bounds.Assign(0,0,40, 7);
			New(EditBox, init(Bounds,'REPORT '+FileName+' EDIT',Desktop));

			with EditBox^ do begin
				Insert(New(PSkipBytes, init(sizeof(FIleName))));

				InsTitledField(8, 2, 29, 1, 'Title', New(PInputELine, Init(R, 40)));

				{if FileName<>'' then{} Insert(New(POurButton, Init(5,4, '~G~o', cmSearch, bfNormal+bfGetData, @Self)));

				InsOKButton(17,Size.Y-3, @Self);
				InsCancelButton(EditBox^.Size.X-12, EditBox^.Size.Y-3);

				Options := Options or ofCentered;

				EndInit;

				SetData(Self);
			end;
		end;
	end;

	Control := Desktop^.ExecView(EditBox);

	dispose(EditBox, done);

	EnterCriteria := Control;
end;

{********************************************************
 ***             TASK PROCESSING                      ***
 ********************************************************}
procedure TReport.PreSearch;
var Bounds : TRect;
begin
	{---- PRE SEARCH ---------------}
	case Command of
		cmSearch : 	begin
			Rewrite(FoundFile);
			if IOResult<>0 then begin
				ProgramError('Could not reset FoundFile'#13#10+WorkPath+'FOUND.$$$',hcInternalErrorMsg);
				exit;
			end;
		end;

		cmPrint : PreOutput;

		cmGraph : begin
			Desktop^.GetExtent(Bounds);
			New(GraphWindow, init(Bounds, Title, GetGraphXLabel, GetGraphYLabel));
			Desktop^.Insert(GraphWindow);
			ProBox^.MakeFIrst;
		end;
	end;
end;

procedure TReport.PostSearch;
begin
	{--- POST SEARCH --------------------}
	case Command of
		cmPrint : PostOutput;

		cmSearch : begin
			Close(FoundFIle);
		end;

		cmGraph : if ProBox^.Command=cmCancel then begin
				dispose(GraphWindow, done);
				GraphWindow := nil;
			end else
				GraphWindow^.Redraw;
	end;

	{Done-it message}
	if ProBox^.Command=cmCancel then
		PauseMessage(Title, 'Cancelled Search', hcNoContext)
	else
		if DoDone then
			if NumMatched>0 then
				PauseMessage(Title, 'Done!', hcNoContext)
			else
				PauseMessage(Title, 'None Found', hcNoCOntext);
end;

procedure TReport.SetSearchRange;
begin StartRec := -1; EndRec := -1; end; {should mark as abstract...}

{=========== SEARCH FILE ====================}
procedure TReport.SearchAll(Command : word);
var	Item : PObject;

begin
	Stage := rsSearching;

	ProBox^.Show;
	ProbOx^.ResetTime;
	NumMatched := 0;

	PreSearch(Command);

	SetSearchRange; {sets startrec & endrec}

	{---- SEARCH --------------------}
	CurrentRec := StartRec;

	while (CurrentRec<=EndRec) and (ProBox^.command<>cmCancel) do begin

		{update display}
		if ((CurrentRec-StartRec) mod 10) = 0 then
			ProBox^.Update(SearchMsg+' Found '+N2Str(NumMatched)+' Searched ',
											CurrentRec-StartRec,EndRec-StartRec);

		{Get item}
		Item := GetItem(CurrentRec);

		{Check match & display/output}
		if Item<>nil then begin
			if MatchItem(Item) then begin
				{item matched, so...}
				inc(NumMatched);
				case Command of
					cmGraph : GraphItem(Item);
					cmSearch : StoreItem(Item);
					cmPrint : OutputItem(Item, CurrentRec);
				end;
			end;
			dispose(Item, done);
		end;

		inc(CurrentRec);

	end;

	PostSearch(Command);

	ProBox^.Hide;
end;


{===== PRODUCE GRAPH FROM FOUND INFORMATION ================}
procedure TReport.GraphAll;
var	FoundRec : longint;
		ItemRec : longint;
		Item : PObject;
		Bounds : TRect;

begin
	{prepare graph}
	Desktop^.GetExtent(Bounds);
	New(GraphWindow, init(Bounds, Title, GetGraphXLabel, GetGraphYLabel));
	Desktop^.Insert(GraphWindow);

	ProBox^.Show;
	ProBox^.ResetTime;

	Reset(FoundFIle);

	{add data}
	FoundRec := 0;
	while FoundRec<filesize(FoundFile) do begin
		Seek(FoundFile, FoundRec);
		Read(FoundFile, ItemRec);
		Item := GetItem(ItemRec);

		GraphItem(Item);

		dispose(Item, done);

		inc(FoundRec);
	end;

	GraphWindow^.ReDraw;

	close(FoundFIle);

	ProBox^.Hide;
end;


{============= SORT FOUND ITEMS =======================}
procedure TReport.SortAll;
var FoundFileREc, ItemRec : longint;
		IndexItem :PIndexItem;
		Item : PObject;

begin
	{build index}
	ThinkingOn('Bulding Index');
	FoundFileRec := 0;
	FileAdmin(fiFOundIdx)^.LogOn;
	Stream(fiFoundIdx)^.Seek(0);
	Stream(fiFoundIdx)^.Truncate; {clear index}
	Reset(FoundFIle);

	ProBox^.Show;
	ProBox^.ResetTime;

	New(IndexItem, init);

	while FoundFileRec<filesize(FoundFile) do begin
		Seek(FoundFile, FoundFileRec);
		Read(FoundFile, ItemRec);
		Item := GetItem(ItemRec);

		IndexItem^.Idx2Dat := ItemRec;
		IndexItem^.KeyString := GetItemKey(Item);

		Stream(fiFoundIdx)^.Put(IndexItem);

		dispose(Item, done);

		inc(FoundFileREc);
	end;

	Dispose(IndexItem, done);

	ProBox^.HIde;

	ThinkingOff;

	{sort}
	QuickSort(IndexStream(fiFoundIdx), False); {sort w/o updating back ptrs}
	FileAdmin(fiFoundIdx)^.LogOff;
	Close(FoundFile);
end;

{=================== OUTPUT ===========================}
procedure TReport.OutputAll;
var ItemNo : longint;{}
		Item : PObject;

begin
	Stage := rsPrinting;

	FileAdmin(fiFoundIdx)^.LogOn;
	Reset(FoundFIle);

	PreOutput;

	ProBOx^.Show;
	ProBox^.ResetTime;

	ItemNo := 0;
	while (ItemNo<NumMatched) and (ProBox^.Command <> cmCancel) and (ProBox^.Command<>cmSkip) do begin

		{update display}
{		if ((ItemNo mod 10) = 0) or (NumMatched<30) then{}
						ProBox^.Update(OutputMsg+' Done ',ItemNo, NumMatched);

		{get item & output}
		Item := GetFoundItem(ItemNo);
		if Item<>nil then begin
			OutputItem(Item, ItemNo);
			dispose(Item, done);
		end else
			ProgramError('Could not retrieve Found Item '+N2Str(ItemNo),hcInternalErrorMsg);

		inc(ItemNo);
	end;

	ProBox^.Hide;

	PostOutput;

	FileAdmin(fiFoundIDx)^.LogOff;
	Close(FoundFile);
end;

procedure TReport.PreOutput;
begin
	if OutputDevice<>nil then begin
		SetHeaderCodes(OutputDevice^.FormCodes);
		OutputDevice^.StartPrint(Header1, Header2);
		if OutputDevice^.Status<>stOK then ProBox^.Command := cmCancel;
	end;
end;

procedure TReport.PostOutput;
begin
	if OutputDevice<>nil then begin
		if ProBox^.COmmand=cmCancel then
			OutputDevice^.writeln('...cancelled');

		SetHeaderCodes(OutputDevice^.FormCodes);
		OutputDevice^.EndPrint;  {even if cancelled need to endprint properly}
	end;
end;


{*************************************
 ***       OVERRIDERS              ***
 *************************************}
procedure TReport.GraphItem(Item :PObject);
begin end;



{========= ITEM DETAIL METHODS  ==========================}
procedure TReport.StoreItem;
begin
	{add to list}
	Seek(FoundFile, filesize(FoundFile));
	Write(FoundFile, CurrentRec);
end;


function TReport.GetFoundItem;
var IndexItem : PIndexItem;
		ItemRec : longint;
begin
	if IsSortKey then begin
		{there is a sort key so there will be an index made}
		IndexItem := PIndexItem(Stream(fiFoundIdx)^.GetAt(ItemNum));
		if IndexItem= nil then
			GetFoundItem := nil
		else begin
			GetFoundItem := GetItem(IndexItem^.Idx2Dat);
			Dispose(IndexItem, done);
		end;
	end else begin
		{no sort key, so read straight from foundfile}
		Seek(FoundFile, ItemNum);
		Read(FoundFIle, ItemRec);
		GetFoundItem := GetItem(ItemRec);
	end;
end;


{------- overriders -------------}
function TReport.GetItem;
begin GetItem := nil; end;

function TReport.MatchItem;
begin MatchItem := True; end;

function TReport.GetItemKey;
begin GetItemKey := ''; end;


procedure TReport.OutputItem;
begin end;


{*************************************
 ***      FILE STOREAGE            ***
 *************************************}
constructor TReport.Load;
var Ver : byte;
begin
	CommonInit;
	S.Read(Ver,1);
	case Ver of
		1 : begin
			Title := S.ReadStr;
			SearchMsg := S.ReadStr;
			SortMsg := S.ReadStr;
			OutputMsg := S.ReadStr;
			DescMsg := S.ReadStr;
		end;
	end;

	ProBox := NewProgressBox(Title,'',mfCancelButton, hcNoContext); {needs to be available throughout for testing cancel}
	ProBox^.Hide;
end;

procedure TReport.Store;
var Ver : byte;
begin
	Ver := 1; S.Write(Ver, 1);
	S.WriteStr(@Title);
	S.WriteStr(@SearchMsg);
	S.WriteStr(@SortMsg);
	S.WriteStr(@OutputMsg);
	S.WriteStr(@DescMsg);
end;


{=============== EDIT =====================}
function TReport.Edit : word;
var Control : word;
begin
	Control := EnterCriteria(eaAdmin);

	if Control = cmSearch then DoSearch;

	Edit := Control;
end;

procedure TReport.StoreSelf;
var FullFIleName : FNameStr;
		Control : word;
begin
	if FileName = '' then begin

		FullFileName := FormsPath+FileName+'.'+ReportExt;
		Control := SaveAsBox(FullFileName, ReportExt);
		if Control<>cmCancel then
			FileName := GetJustFileName(FullFileName);
	end;

	if FileName<>'' then begin
		FullFileName := FormsPath+FileName+'.'+ReportExt;
		PutObjToFile(FullFileName, @Self);
	end;
end;


{***********************************************************************
 ***                                                                 ***
 ***                    JIMMY REPORT                                 ***
 ***                                                                 ***
 ***********************************************************************}
constructor TJImmyReport.Init(NTitle, NDescMsg : string);
begin
	inherited Init(NTitle, 'Searching', 'Sorting', 'Outputting', NDescMsg);
end;

procedure TJimmyREport.CommonInit;
begin
	inherited COmmonInit;
	BasefiType := 0;
	AliasOptions := aoNoPrint;
	FileAdmin(fiJimmys)^.LogOn;
end;


destructor TJImmyReport.Done;
begin
	FileAdmin(fiJimmys)^.LogOff;

	inherited Done;
end;


{**************************************************************
 ***                 TASK PROCESSING                        ***
 **************************************************************}
procedure TJimmyReport.SetSearchRange;
begin
	StartRec := 0;
	EndREc := Stream(BasefiType)^.NoRecs-1;
end;

procedure TJimmyReport.PreSearch;
begin
	FileAdmin(BasefiType)^.LogOn;	{log on}
	inherited PreSearch(Command);
end;

procedure TJimmyReport.PostSearch;
begin
	inherited PostSearch(Command);
	FileAdmin(BasefiType)^.LogOff; {log off}
end;

procedure TJimmyReport.SortAll;
begin
	FileAdmin(BasefiType)^.LogOn;
	inherited SortAll;
	FileAdmin(BasefiType)^.LogOff;
end;

procedure TJimmyREport.OutputAll;
begin
	FileAdmin(BasefiType)^.LogOn;
	inherited OutputAll;
	FileAdmin(BasefiType)^.LogOff;
end;

procedure TJimmyREport.GraphAll;
begin
	FileAdmin(BasefiType)^.LogOn;
	inherited GraphAll;
	FileAdmin(BasefiType)^.LogOff;
end;


{**************************************************************
 ***                 ITEM OVERRIDERS                        ***
 **************************************************************}

function TJImmyReport.GetItem(ItemNum : longint) : PObject;
var Jimmy : PJimmy;
begin
	if BasefiType=fiHooks then
		Jimmy := HookFile^.GetJimmyAtHook(ItemNum)
	else
		Jimmy := PIndexedJimmyStream(Stream(BasefiType))^.GetJimmyAtIdx(ItemNum);

	if Jimmy=nil then
		JimmyStream^.Reset; {probably some error - ignore for reports}

	{allow only one ixtype per file - set baseixtype}
{now replaced by aliasoptions and .GotByAlias
	if (Jimmy<>nil) and (BaseixType<>0) and (Jimmy^.Gotbyix <> BaseixType) then begin dispose(Jimmy,done); Jimmy := nil; end;{}

	GetItem := Jimmy;
end;


function TJimmyReport.MatchItem;
begin
	MatchItem := (AliasOptions <> aoNoPrint) or not PJimmy(Item)^.GotByAlias(BasefiType);
end;



{***********************************************************************
 ***                                                                 ***
 ***                    GENERAL JIMMY REPORT                         ***
 ***                                                                 ***
 ***********************************************************************}
procedure TGeneralAnalysis.CommonInit;
begin
	inherited COmmonInit;
	New(Search.Match, init);
	New(Output.ItemForm, init);
	FileAdmin(fiJimmys)^.LogOn;

	AskForSearch := True;
	AskForOUtput := True;
end;


destructor TGeneralAnalysis.Done;
begin
	Dispose(Search.Match, done);
	Dispose(Output.ItemForm, done);
	FileAdmin(fiJimmys)^.LogOff;

	inherited Done;
end;

function TGeneralAnalysis.IsSortKey : boolean;
begin IsSortKey := (Output.SortKey <> ''); end;

function TGeneralAnalysis.GetGraphYLabel : string;
begin GetGraphYLabel := GraphOut.YLabel; end;

function TGeneralAnalysis.GetGraphXLabel : string;
begin GetGraphXLabel := GraphOut.XLabel; end;



{**************************************************************
 ***                 USER PROCESSING & ROUTING              ***
 **************************************************************}
type
	PSaveButton = ^TSaveButton;
	TSaveButton = object(TOurButton)
		Report : PReport;
		constructor Init(X,Y : integer; NDataItem : PObject; NReport : PReport);
		procedure Press; virtual;
	end;

	constructor TSaveButton.Init;
	begin
		inherited Init(X,Y, '~S~ave', cmSave, bfNormal or bfGetData or bfClose, NDataItem);
		Report := NReport;
	end;

	procedure TSaveButton.Press;
	var Control : word;
			FullFileName : FNameStr;
	begin
		inherited press;  {get data}

		{store}
		if OwnerValid=1 then begin
			FullFileName := FormsPath+Report^.FileName+'.'+ReportExt;
			Control := SaveAsBox(FullFileName, ReportExt);
			if Control<>cmCancel then begin
				Report^.FileName := GetJustFileName(FullFileName);
				Report^.StoreSelf;
			end;
		end;

		DrawState(False);
	end;

procedure FormLink(Const Linker : PInputLinker; const CallingView : PView); far;
var FileNameLine : PINputFName;
		FreeTExt : PInputFreeText;
		FileName : string[8];
		Line : string;
		FOrmFile : text;

begin
	FileNameLine := PInputFName(Linker^.SourceView[1]);
	FreeText := PINputFreeText(Linker^.TargetView[1]);

	{load form into free text line}
	FileNameLine^.GetData(FileName);
	if delspace(FileName)<>'' then begin
		Assign(FormFile, FormsPath + FileName + '.FRM');
		Reset(FormFile);
		FreeText^.SetBufLen(0);
		while not eof(FormFile) do begin
			Readln(FormFile, Line);
			if not eof(FormFile) then Line := Line +CRLF;
			FreeText^.InsertAt(FreeText^.BufLen, Line);
		end;
		CLose(FormFile);
		FreeText^.DrawView;
	end;
end;



function TGeneralAnalysis.EnterCriteria;
var EditBox : PEditBox;
		Bounds,R : TRect;
		Control : word;
		Indicator : PIndicator;
		FormLinker : PINputLinker;
		FormFile : text;
		L : byte;
		DataP : pointer;

begin
	EditBox := nil;

	case eaType of
		eaAdmin : Control := inherited EnterCriteria(eaAdmin);

		eaSearch : begin
			DataP := @Search;

			Bounds.Assign(0,0,50,17);
			New(EditBox, init(Bounds,ucase(Title)+' SEARCH',Desktop));

			EditBox^.HelpCtx := hcReports;

			{Ask for search parameters, ie edit box}
			with EditBox^ do begin
				{Base index search type}
				InsTitledField(8, 1, 38, 4, 'Search',
						New(PERadioButtons, init(R, NewSItem('~D~irectory',
																				NewSItem('~L~ive stock',
                                        NewSItem('Diar~y~',
																				NewSItem('~C~ontainers',
																				NewSItem('Cont''rs @ ~R~ongai',
																				NewSItem('~H~istories', nil)))))))));

				InsTitledField(8, 7, 38, 4, '~M~atch', New(PInputFreeText, init(Bounds, 250, 0, nil)));

				Insert(New(POurButton, init(Size.X-31, Size.Y-5, 'Setup', cmAdmin, bfNormal+bfGetData+bfClose, DataP)));
{ok button to do				Insert(New(PCloseButton, init(Size.X-10, Size.Y-6, '~S~earch', cmSearch, bfNormal, DataP)));{}
				Insert(New(POurButton, init(Size.X-21, Size.Y-5, '~P~rint', cmPrint, bfNormal+bfGetData+bfClose, DataP)));
				Insert(New(POurButton, init(Size.X-11, Size.Y-5, '~G~raph', cmGraph, bfNormal+bfGetData+bfClose, DataP)));

				SetData(Search);
			end;
		end;

		eaOutput,eaOutputNow : begin
			DataP := @Output;

			Bounds.Assign(0,0,50,15);
			New(EditBox, init(Bounds,Title+' OUTPUT FORMAT',Desktop));

			New(FormLinker, init(@FOrmLink, EditBox));

			{Ask for search parameters, ie edit box}
			with EditBox^ do begin


				{sort key 20 chars}
				if eaType = eaOutputNow then
					Insert(New(PSkipBytes, init(sizeof(Output.SortKey))))
				else begin
					InsTitledField(9, 1, 20, 1, 'Sort by', New(PInputFormCodeLine, Init(R, 20)));
					PINputELine(Current)^.UpperCase := True;
				end;

				{Item Form 8 chars}
				InsTitledField(9, 3,  8, 1, 'Form', New(PInputFName, init(R, 8, 'FRM', FormsPath, True, EditTextFile)));
				FormLinker^.SetSourceView(Current, 1);

				{form itself}
				R.Assign(2,Size.Y-1,13,Size.Y); New(Indicator, init(R));  {Row, Col, modified indicator}
				Insert(Indicator);
				InsTitledField(9, 4, 28, 4, '', New(PInputFreeText, init(R, 1000, 0, Indicator)));
				FormLinker^.SetTargetView(Current, 1);

				Insert(New(POurButton, init(Size.X-11, Size.Y-5, '~G~raph', cmGraph, bfNormal+bfGetData+bfClose, DataP)));

				SetData(Output);
			end;
		end;

		eaGraph : begin
			DataP := @GraphOut;

			Bounds.Assign(0,0,50,15);
			New(EditBox, init(Bounds,Title+' GRAPH',Desktop));

			{Ask for search parameters, ie edit box}
			with EditBox^ do begin

				InsTitledField(9, 1, 20, 1, 'X Axis', New(PInputELine, Init(R, 30)));
				InsTitledField(9, 2, 20, 1, 'Y Axis', New(PInputELine, Init(R, 30)));

				InsTitledField(9, 4, 20, 1, 'X Data', New(PInputELine, Init(R, 50)));
				InsTitledField(9, 5, 20, 1, 'Y Data', New(PInputELine, Init(R, 50)));

				Insert(New(POurButton, init(Size.X-11, Size.Y-5, '~P~rint', cmPrint, bfNormal+bfGetData+bfClose, DataP)));
				SetData(GraphOut);
			end;
		end;
	end;

	if EditBox<>nil then with EditBox^ do begin
		Insert(New(PSaveButton, init(Size.X-31,Size.Y-3, DataP, @Self)));
{		Insert(New(POurBUtton, init(Size.X-21,Size.Y-3, 'O~K~', cmOK, bfDefault+bfGetData+bfClose, DataP)));
		POurButton(Current)^.kbType := kbFinish;{}
		InsOKButton(Size.X-21, Size.Y-3, DataP);
		InsCancelButton(Size.X-11, Size.Y-3);

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

		EditBox^.EndInit;

		Control := Desktop^.ExecView(EditBox);

		dispose(EditBox, done);
	end;

	if ((eaType = eaOutput) or (eaType = eaOutputNow)) and (Control = cmOK) then begin
		{make up file form for formatting jimmy to print}
		Assign(FormFile, FormsPath + 'RPTFRM.$$$');
		Rewrite(FormFile);
		for L := 1 to LSNumLines(Output.ItemForm^.Text) do
			Writeln(FormFIle, LSGetLine(Output.ItemForm^.Text,L));
		Close(FormFile);
	end;

	EnterCriteria := Control;

end;


{**************************************************************
 ***                 TASK PROCESSING                        ***
 **************************************************************}

procedure TGeneralAnalysis.PreSearch;
begin
	{convert user entry to fitype}
	case Search.UserfiEntry of
		0 : begin BasefiType := fiFullDirIdx; {BaseixType := 1;{} end;
		1 : begin BasefiType := fiLivestockIdx;{ BaseixType := 2;{} end;
		2 : begin BasefiType := fiDiaryIdx; end;
		3 : begin BasefiType := fiContainerIdx; end;
		4 : begin BasefiType := fiRongaiContainerIdx; end;
		5 : begin BasefiType := fiHooks;  end;
	end;

	inherited PreSearch(Command);
end;


{***********************************************
 ***            MATCH ITEM                   ***
 ***********************************************}
function TGeneralAnalysis.MatchItem(Item : PObject) : boolean;
var S : string;
begin
	if (AliasOptions = aoNoPrint) and PJimmy(Item)^.GotByAlias(Basefitype) then begin
		MatchItem := False;
	end else begin
		{Decode match line}
		WorkFormCodes^.Clear;

		PJimmy(Item)^.SetFormCodes(WorkFormCodes);

		S := LS2String(Search.Match^.Text);
    {remove carriage returns/line feeds}
		while pos(CRLF,S)>0 do S := Copy(S,1,pos(CRLF,S)-1)+' '+Copy(S,pos(CRLF,S)+2,255);
		while pos(EndParaChar,S)>0 do delete(S, pos(EndParaChar,S),1);

		CalculatorFormCodes := WorkFormCodes;
		SyntaxError := False;
 		MatchItem := IsConditionTrue(S);

		if SyntaxError then ProBox^.Command := cmCancel; {cancel report}
	end;
end;


function TGeneralAnalysis.GetItemKey(Item : PObject) : string;
begin
	WorkFormCodes^.Clear;
	PJimmy(Item)^.SetFormCodes(WorkFormCodes);

	GetItemKey := WorkFormCodes^.QDecodeStr(Output.Sortkey);
end;

procedure TGeneralAnalysis.GraphItem(Item : PObject);
var X,Y : integer;

	function CalcData(S : string) : integer;
	begin
		if Calculate(S) then
			CalcData := S2Num(S)
		else begin
			SyntaxErrorMsg('Graph Data not numerical'#13+S);
			ProBox^.Command := cmCancel;
		end;
	end;

begin
	WorkFormCodes^.Clear;
	PJimmy(Item)^.SetFormCodes(WorkFormCOdes);

	X := CalcData(GraphOut.XCode);
	Y := CalcData(GraphOut.YCode);

	if not SyntaxError then with GraphWindow^.Graph^ do begin
		AddPoint(X,Y, '*');
		SetScale;
		if AxisChanged then
			Draw
		else
			DrawPoints;
	end;
end;

procedure TGeneralAnalysis.OutputItem(item : PObject; ItemNo : longint);
begin
	if not PJimmy(Item)^.GotByAlias(BasefiType) or (AliasOptions = aoPrintFull) then begin
		OutputDevice^.ClearCodes;
		PJimmy(Item)^.SetFormCodes(OutputDevice^.FormCodes);
		OutputDevice^.PrintForm('RPTFRM.$$$');
	end; {else
		{got by alias}
{		if AliasOptions = aoPrintSee then
			OutputDevice^.Print{}
	if OutputDevice^.Status=stAbandoned then ProBox^.Command := cmCancel;

end;

{*************************************
 ***      FILE STOREAGE            ***
 *************************************}
constructor TGeneralAnalysis.Load;
var Ver : byte;
begin
	S.Read(Ver,1);
	case Ver of
		1 : begin
			inherited Load(S);

			with Search do begin
				S.Read(UserfiEntry,2);
				Match^.Load(S); 		Match^.LoadText;
			end;

			with OUtput do begin
				SortKey := S.ReadStr;
				ItemFormFileName := S.ReadStr;
				ItemForm^.Load(S); ItemForm^.LoadText;
			end;

			with GraphOut do begin
				XLabel := S.REadStr;
				YLabel := S.ReadStr;
				XCode := S.REadStr;
				YCode := S.REadStr;
			end;

		end;
	end;
end;

procedure TGeneralAnalysis.Store;
var Ver : byte;
begin
	Ver := 1; S.Write(Ver, 1);
	inherited Store(S);
	with Search do begin
		S.Write(UserfiEntry,2);
		Match^.Store(S);
	end;

	with OUtput do begin
		S.WriteStr(@SortKey);
		S.WriteStr(@ItemFormFileName);
		ItemForm^.Store(S);
	end;

	with GraphOut do begin
		S.WriteStr(@XLabel);
		S.WriteStr(@Ylabel);
		S.WriteStr(@XCode);
		S.WriteStr(@Ycode);
	end;

end;




{******************************************
 ***          REGISTRATION, ETC         ***
 ******************************************}
const
	{--- Required for Stream ----}
	RGeneralAnalysis : TStreamRec = (
		ObjType : srGeneralAnalysis;
		VmtLink : Ofs(TypeOf(TGeneralAnalysis)^);
		Load : @TGeneralAnalysis.Load;
		Store : @TGeneralAnalysis.Store
	);

function GetREport(FileName : Fnamestr) : PReport;{}
var Report : PReport;
begin
	if (FileName='') or (FileName[1] = '.') then begin GetREport := nil; exit; end;
	if pos('.', FileName)=0 then FileName := FileName+'.'+ReportExt;
	if pos('\',FileName)=0 then FileName := FormsPath + FileName;

	Report := PReport(GetObjFromFile(FIleName));
	if Report<>nil then Report^.FileName := Copy(GetFileNAme(FileName),1,pos('.',GetFileName(FileName)+'.')-1);
	GetReport := Report;
end;


{===== INPUT FNAME COMPATIBLE EDITOR =============}
{function EditReport(var FullFileName : FNameStr; const Ext : string) : word; far;
var	Control : word;
		Report : PReport;
		Name : string;

begin
	EditREport := cmCancel;

	{Open file, retrieve details, warn if new, edit and save}
{	Name := GetFileName(FullFileName);

	if (Name = '') or (Name[1] = '.') then begin
		{no name given - new}
{		Report := New(PGeneralAnalysis, init('', ''));
	end else begin
		Report := GetReport(FullFileName);

		if Report = nil then begin
			ProgramWarning('Could not find/load Report '+Name);
			exit;
		end;
	end;

	Control := Report^.Edit;  {Edit}

{	if (Control = cmOK) and ((Name = '') or (Name[1]='.')) then
		Control := SaveAsBox(FullFileName,GetExt(FullFileName));

	if COntrol = cmOK then
		PutObjToFile(FullFileName, Report);

	dispose(Report, done);

	EditReport := Control;
end;

{***********************************
 ***       CHOOSE REPORT         ***
 ***********************************}
var LastReportFName : FnameStr;

procedure DoGeneralReport; far;
var JimmyReport : PReport;
		Control : word;
		Event : TEvent;
begin
	Control := FileSelectBox('RUN GENERAL REPORT', 'Run',
															LastReportFName,ReportExt, FormsPath,
															fdOKButton+fdNewButton+fdEditButton+fdPickOnly, hcNoContext);

	JimmyReport := nil;

	case Control of
		cmEdit, cmOK : begin
			if LastReportFName<>'' then begin
				JimmyReport := GetReport(LastReportFName);
				if JimmyReport=nil then begin
					ProgramWarning('Could not load report '+LastReportFName, hcFileNotFoundMsg);
					DoGeneralReport;
				end;
			end else begin
				ProgramWarning('No report selected!', hcNoContext);
				DoGeneralReport;
			end;
		end;
	end;

	case Control of
		cmEdit : 	if JimmyReport<>nil then begin
			JimmyReport^.Edit;
			dispose(JimmyReport, done);
		end;
		cmOK : if JimmyReport<>nil then begin
			JimmyReport^.DoSearch;
			dispose(JimmyReport, done);
		end;
		cmNew : begin
			JimmyReport := New(PGeneralAnalysis, init('', ''));
			repeat
				Control := JimmyReport^.Edit;
				if Control<>cmCancel then begin
					LastReportFName := FormsPath;
					Control := SaveAsBox(LastReportFName,ReportExt);
					if COntrol = cmOK then begin
						JimmyReport^.FileName := GetJustFileName(LastReportFName);
						PutObjToFile(LastReportFName, JimmyReport);
					end else
						Control := cmOK; {so that it goes back to being edited}
				end;
			until Control = cmCancel;
			dispose(JimmyReport, done);
		end;
	end;
end;


function NewFoundIdxStream : PStream; far;
begin	NewFoundIdxStream := New(PIndexStream, init(WorkPath+'RPRTIDX.$$$',100)); end;

begin
	REgisterType(RGeneralAnalysis);

	RegisterTask(DesktopTasks, cmGeneralAnalysis, @DoGeneralReport);
	NewFileAdmin(fiFoundIdx, 'Found Items',NewFoundIdxStream);

	LastReportFName := '';
end.
