{****************************************************************************
 ***                                                                      ***
 *** New Fabbo Singing Dancing OOP                                        ***
 ***                            LETTER PARENT                             ***
 ***                                                                      ***
 *** M Hill                                                      Jan 1993 ***
 ****************************************************************************}
{Object for handling both editor & wp/whatever-linked letters}

{$I compdirs}  {Compiler directives}

unit KLetter;

INTERFACE
uses  jimmys, {letter object derivitives}
{$IFDEF WINDOWS}
	wui,	{windows}
{$ELSE}
	views, dialogs, {text}
{$ENDIF}
			tuiedit,
			lstrings,
			tuijimmy,
			objects,
			jimprint,
			notes,
			global, files, dattime,
			devices, forms;

{**************************************************************
 ***               DEFINE LETTER OBJECT                     ***
 **************************************************************}
const

	{Letter status}
	leNone      = $00;  {No need to send yet}
	leDeferred  = $02;  {Been deferred - waiting to be printed}
	leSent      = $04;  {Sent/Printed}
	leEdited    = $10;	{Been edited - needs to be sent again}

	TLetterIndexSize = 40;


	{BEWARE that TReport, a descendant of this, might not have ToWho as a
	TDIrectoryItem}
type
	PLetter = ^TLetter;
	TLetter = object(TJimmy)

		Ptr2OutIdx : longint;  {out tray index pointer}

		ToWho     : longint;               {Pointer to who it is to}
		ToCoy		 : longint;								{at which org}
		ToAdd		 : longint;								{& which address}
		ByWho     : longint;               {Who typed it}
		ReWho     : longint;

	 Date      : TDate;                 {Date of writing}
	 DateSent	: TDate;									{non-editable - date of printing}

	 Codes     : string[12];            {Description codes}
	 Subject   : PFreeTextData;						{A brief description/subject box}
	 Ref       : string[6];             {Any old reference string}

	 Header    	: string[8];             {Header form name}
	 StdForm		: string[8];							{Standard form for standard letter headers}

	 EditorType : word; 									{Mark editor type - needs word for radiobutton}

	 CCList		 	: longint;								{copies to...  pointer to hook chain}

	 Status    	: byte;									{non-editable - as per lexxx above}

	 TextBody  	: longint;								{pointer to letter data}
	 TextLoaded : boolean;							{marks whether it's been loaded}

	 {-- Methods --}
	 constructor Init(Param : PJimmyInitParam);
	 destructor Done; virtual;
	 procedure CommonInit; virtual;

	 {Display}
		function DisplayLine(ListForWho : longint; lstype : byte; Maxlen : integer; View : word) : string; virtual;
		function GetName(naType : byte; Maxlen : integer) : string; virtual; {used for various displays/prints -

	 {DataBase}
	 constructor Load(var S : TDataStream);
	 procedure   StoreFields(var S : TDataSTream); virtual;

		function RecSize : word; virtual;
		function srType : word; virtual;

		{for fixit, etc}
		function NumIDs : byte; virtual; {the number of jimmy ID ptrs in the data}
		function GetJimmyID(const jiType : byte) : PLongint; virtual; {each jimmy ID}

		{--- Indexing ----}
		function NumixTypes : byte; virtual;
		procedure GetIndex(const ixType : byte; var IdxRec : Plongint; var fiType : byte); virtual;
		function GetIndexKey(const ixType : byte) : string; virtual;

		{-- Hooking to others -----}
		function NumHookTo : byte; virtual;
		procedure GetHookTo(const htType : byte; var HookToID,SubHookToID : PLongint;
												var hkType : byte; var Key : longint; var InsertBias : boolean); virtual;

	 procedure MakeEditBox(var EditBox : PEditBox; Caller : PView); virtual;

	 procedure SetFormCodes(const FormCodes : PFormCodeCollection);   virtual;
{		function Decode(const Code, Param : TFCodeStr; var LString : TLongString) : boolean; virtual;{}

{		function GetFaxNum : string; virtual;{}

		procedure GetDefaultPrintType(var PrintType : TJimmyPrintType; var PrintAs : PSItem; var PrintAsLink : pointer); virtual;
	 procedure PrintFull(const Device : PDevicestream; const PrintAs : word); virtual;
	 procedure PrintSummary(const Device : PDeviceStream; const PrintAs : word); virtual;
		procedure PrintLabel(const Device : PDeviceStream; LabelAs : word); virtual;
	 procedure OnPrinting(const PrintType : TJimmyPrintType); virtual;

{	 procedure OnStoreing(const DiskJimmy : PJimmy); virtual;{}

		{body/text editing funcs}
		function editBody : word; {returns cmOK or cmCancel}
		procedure CreateBody(const Device : PDeviceStream; const ForEdit : boolean; var Width : integer);
		procedure StoreBody;

 end;

	PEditBodyButton = ^TEditBodyButton;
	TEditBodyButton = object(TJimmyStoreButton)
		procedure Press;  virtual;
	end;


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


IMPLEMENTATION

uses inpdnt, inpfname,
			menus,
			address,
			drivers,
			dos,
			tasks,
			kdirctry, {Directory implementation, for to who and by who}
			editfile,
{$IFDEF kwplink}
			kwplink, {for editwpfile for inpfname in letter header line}
{$ENDIF}
			kcompany, kperson, jimhooks, {for types when setting coy, etc}
			inpjimmy,
			trays, tuiapp, jimindxs, app,
			{$IFDEF kusers} kusers, {$ENDIF}
			{$IFNDEF SIngleUser} muser, {$ENDIF}
			printers,
			tui, tuimsgs,
			messtext,
			dosutils, {for making backup of letter}
			faxes,{} email,
			help,
			kamsetup,
			minilib, scodes;


const
	InternalCommentString = '<BEGIN>'+EndParaChar+#13#10;

{**************************************************************************
 ***                                                                    ***
 ***                         LETTER OBJECT DEFINED                      ***
 ***                                                                    ***
 **************************************************************************}
{--- Inititalise - set ptrs to SC ---}
constructor TLetter.Init;
var Coy,Contact : PDirectoryItem;
begin
	inherited Init;

	ToWho := -1;
	ToCoy := -1;
	ToAdd := -1;
	ByWho := -1;
	ReWho := -1;      {Who the letter is about}
	CCList := -1;					{copies to... hook pointer}


	{To who - default to owner of history list (if present)}
	{ToCoy, ToADD, etc, will be set by the linker in the editbox}
	if (Param<>nil) then ToWho := Param^.ForWho else ToWho := -1;

	{By who - default to current user if set}
{$IFDEF kusers} if CurrentUser<>nil then ByWho := CurrentUser^.RecNo; {$ENDIF}

	Date.SetToToday;  {Date written}
	DateSent.Clear;   {Date sent/printed}

	{should be set by LetterSetup}
	Header := 'LETTER';  {Default to Letter header - should be settable in }
	StdForm := '';				{No standard body}
	EditorType := edInternal;{default}
{$IFDEF kwplink} EditorType := edWP51; {$ENDIF}

	Status    := leNone;

	TextBody  := -1;
	TextLoaded := False; {nothing to load}

	Ptr2OutIdx := -1;
end;

{Used by constructors Init and Load}
procedure TLetter.CommonInit;
begin
	inherited CommonInit;
	SCodeCollection[scLetters]^.LogOn;
	FileAdmin(fiJimmys)^.LogOn;
	New(Subject, init);
end;


destructor TLetter.Done;
begin
	dispose(Subject, done);
	SCodeCollection[scLetters]^.LogOff;
	FileAdmin(fiJimmys)^.LogOff;
	inherited Done;
end;


{==== CREATE DISPLAY-LINE-FORMAT STRING ============}
function TLetter.DisplayLine;
var S,SS : string;

begin
	{First line}
	S := DateSent.Digit8+' '+header; if StdForm<>'' then S:=S+' ('+StdForm+')';

{	if Copies>1 then S := S +' x'+L2Str(Copies);{}

	SS := ExpandSCode(scLetters, Codes);
	if SS<>'' then S := S +' ('+SS+')';

	{To who}
	if ToWho=-1 then
		SS := 'Sirs @'+GetJimmyIDName(ToCoy, naRef, 15)
	else
		SS := GetJimmyIDName(ToWho, naRef, 15);
	if Xpos(S+SS)+4>Maxlen then S := S + CRLF+space(27); {xpos is #chars since last CRLF}
	S := S + ' To '+SS;

	if (ReWho>-1) and (ReWho<>ToWho) then begin {not strictly necessary, but may help with speed}
		SS := GetJimmyIDName(ReWho, naRef, 15);
		if Xpos(S+SS)+3>Maxlen then S := S + CRLF+space(27) else S := S +', ';
		S := S + 'Re '+SS;
	end;

	if ByWho>-1 then begin
		SS := GetJimmyIDName(ByWho, naRef, 15);
		if XPos(S+SS)+3>Maxlen then S := S + CRLF+space(27) else S := S +', ';
		S := S + 'By '+SS;
	end;{}

	DisplayLine := delspaceR(S);
end;


function TLetter.GetName;
begin
	GetName := 'Letter To '+GetJimmyIDName(ToWho,naRef,0);
end;

{*****************************************
 ***        SCREEN INPUT BOX           ***
 *****************************************}
	procedure TEditBodyButton.Press;
	var Control : word;
			V : boolean;
	begin
		DrawState(True);

		{don't want to do store, etc yet. Just update links & getdata}
		if (Owner^.Current^.EventMask and evBroadCast)<>0 then
			Message(Owner^.Current, evBroadCast, cmCheckLink, nil); {tell current view to checklink}

		V := Owner^.Valid(cmOK); {check all is OK}

		if V then begin

			Owner^.GetData(DataItem^);  {Read data from box into data}

			Control := PLetter(DataItem)^.EditBody;

			if Control = cmOK then begin
				Command := cmOK;
				inherited press; {sends OK message, stores}
			end;
		end else
			DrawState(False);
	end;

const
	svToWhoLine = 2; {swap round so that initial link does towho second, thus}
	svToCoyLine = 1; {making the address the persons default address}
	tvToAddLine = 3;

procedure LinkToWhoCoyAdd(const Linker : PInputLinker; const CallingView : PView); far;
var ID : longint;
		ToWhoLine : PINputDirectory;
		ToCoyLine : PInputDirectory;
		ToAddLine : PInputHookedJimmy;
		Caller : PInputDirectory; {as callingview is a constant, and we may want to change.. naughty}

	procedure SetID(InputLine : PInputJimmy; NewID : longint);
	begin
		InputLine^.SetData(NewID);
		InputLine^.DrawView;
	end;

begin
	ToWhoLine := PInputDirectory(Linker^.SourceView[svToWhoLine]);
	ToCoyLine := PInputDirectory(Linker^.SourceView[svToCoyLine]);
	ToAddLine := PInputHookedJimmy(Linker^.TargetView[tvToAddLine]);

	if (PView(ToWhoLine) = CallingView) and (ToWhoLine^.GetJimmy<>nil) then begin
		{to line changed}
		case ToWhoLine^.GetJimmy^.srType of
			srCompany : begin
				{company entered in to-who line}
				SetID(ToCoyLine, ToWhoLine^.GetJimmy^.RecNo); {move to coy line}
				{get first contact for towho line}
				FileAdmin(fiHooks)^.LogOn;
				SetID(ToWhoLine, HookFile^.FindFirst(PDirectoryItem(ToCoyLine^.GetJimmy)^.Ptr2More, srPerson));
				FileAdmin(fiHooks)^.LogOff;
			end;
			srPerson : if PPerson(ToWhoLine^.GetJimmy)^.ContactFor<>-1 then
										SetID(ToCoyLine, PPerson(ToWhoLine^.GetJimmy)^.ContactFor);

		end;
	end;

	Caller := PInputDirectory(CallingView);

	{In the initial (forceinitlink) call, the callingview is the towho line, but
	it may be that that is empty, so we need to get the address from the tocoy line}
	if (ToWhoLine = Caller) and (ToWhoLine^.GetJimmy=nil) then begin
		Caller := ToCoyLine; {cheating a bit...}
	end;

	{set address from whichever - person/coy}
	if PInputJimmy(Caller)^.GetJimmy<>nil then
		if PDirectoryItem(CAller^.GetJimmy)^.AddressID<>-1 then
			SetID(ToAddLine, PDirectoryItem(Caller^.GetJimmy)^.AddressID);

	if ToWhoLine^.GetJimmy=nil then begin
		ToAddLine^.SetParent(ToCoyLine^.ID);
		ToAddLine^.SetSuperParent(-1);
	end else begin
		ToAddLine^.SetParent(ToWhoLine^.ID); {address line should give towho's addresses, not companies}
		ToAddLine^.SetSuperParent(ToCoyLine^.ID);
	end;
end;



procedure TLetter.MakeEditBox;
var	R: TRect;
		SItem : PSItem;
		L : byte;
		ByLine, DescLine, FormLine, HeaderLine : PView; {for auto-focusing}
		ToLinker : PInputLinker;
		FormLinker : PFormedTypeLinker;

begin
	{===== LETTER DETAILS ===========}
	R.Assign(0, 0, 65,14);
	CentreOnView(R, Caller);
	EditBox  := New(PJimmyEditBox, Init(R, 'Letter',Caller, @Self));

	inherited MakeEditBox(EditBox, Caller);

	with EditBox^ do begin

		Insert(New(PSkipBytes, init(4))); {skip out tray indx ptr}


		New(ToLinker, init(@LinkToWhoCoyAdd, EditBox));
		ToLinker^.ForceInitLink := True;{}

		InsTitledField(9,  1,25, 1,  '~T~o', New(PInputDirectory, Init(R,25, fiFullDirIdx, lsDirectory, '')));
{		PInputELine(Current)^.MustInputToClose := True;  {Must be set to something or it may crash later}

		if srtype <> srReport then begin {horrible botch - for reports don't want these two...}
			ToLinker^.SetSourceView(Current, svToWhoLine);

			InsTitledField(45, 1,16, 1,  'At',   New(PInputDirectory, init(R, 20, fiFullDirIdx, lsDirectory, '')));
			ToLinker^.SetSourceView(Current, svToCoyLine);

			InsTitledField(9, 2, 25, 1,  '~A~ddress',New(PInputHookedJimmy, init(R, 25, hkAddress, lsAddresses, 0, -1)));
			ToLinker^.SetTargetView(Current, tvToAddLine);
			PInputELine(Current)^.MustInputToClose := True;
		end else begin
			Insert(New(PSkipBytes, init(8))); {skip past them}
		end;

{$IFDEF kusers}
		ByLine := InsTitledField(9,  4,25, 1,  '~B~y', New(PInputDirectory, Init(R,25, fiUserIdx, lsUsers, '')));
{$ELSE}
		ByLine := InsTitledField(9,  4,25, 1,  '~B~y', New(PInputDirectory, Init(R,25, fiFullDirIdx, lsDirectory, '')));
{$ENDIF}
		InsTitledField(9,  5,25, 1,  '~R~e', New(PInputDirectory, Init(R,25, fiFullDirIdx, lsDirectory, '')));

		InsTitledField(45, 4,10, 1,'Writte~n~', New(PinputDate, Init(R)));
		InsTitledField(45, 5,10, 1,'Sent',	New(PInputDate, init(R)));
{$IFNDEF fixit}		Current^.SetState(sfDisabled, True); {$ENDIF}

		DescLine := InsTitledField(9,  7,25, 1,'~D~esc', New(PInputSCLine, Init(R,12,scLetters)));
		InsTitledField(9,  8,25, 1,'~S~ubject', New(PInputString, init(R)));{}
		InsTitledField(9,  9, 6, 1, 'Re~f~', New(PInputELine,  Init(R, 6)));

		{default to editor}
		InsTitledField(45, 7, 8, 1,'~H~eader', New(PInputFName,  Init(R, 8, 'HDR HD1', FormsPath, True, EditTextFile)));
		HeaderLine := Current;
		PInputELine(Current)^.MustInput := True;

		{Standard form/letter}
		FormLine := InsTitledField(45, 8, 8, 1,'For~m~', New(PInputFName,  Init(R, 8, 'FRM STL', FormsPath, True, EditTextFile)));
		{only allowed if the letter hasn't been edited}
		if TextBody<>-1 then Current^.SetState(sfDisabled, True);

		{Editor to be used}
		InsTitledField( 9,11, 15, 2, 'Editor', New(PERadioButtons, init(R,
																									NewSItem('~I~nternal',
																									NewSItem('~W~ordPerfect',nil)))));

{$IFNDEF Fixit}
		{can't switch editor types after first edit (except from internal) so
		make others non-selectable}
		if (EditorType<>edInternal) and (TextBody<>-1) then
			PCLuster(Current)^.EnableMask := Exp2(EditorType); {make only that one enabled}
{$ENDIF}

		{switch off wp marker if wplink not available}
		{$IFNDEF kwplink} PCluster(Current)^.EnableMask := PCLuster(Current)^.EnableMask and not Exp2(edWP51); {$ENDIF}


		New(FormLinker, init(Current, FormLine, EditBox));
		FormLinker^.SetTargetView(HeaderLine, 2);

		Insert(New(PSkipBytes, init(4))); {skip CC - button later for that}

		{-- Buttons --}
		{edit or OK}
		{don't use cmedit for the edit button 1) no point and 2) puts a cmedit
		into pending event}
		Insert(New(PEditBodyButton, Init(30,	11, '~E~dit', cmNone, bfDefault, @Self)));
		Insert(New(PJimmyOKButton, init(40,11, @Self)));
		POurButton(Current)^.amDefault := False;

		Insert(New(PJImmyCancelButton, init(50,11, @Self)));

		EndInit;

		{--- Now focus on best line ---}
		if ToWho<>-1 then
			if ByWho<>-1 then
				DescLine^.Focus
			else
				ByLine^.Focus;
	end;

{	EditBox^.EnableCommands([cmEdit, cmOK, cmCancel]);  {cmEdit may be disabled from list...}
end;


{*****************************************
 ***     STREAMING DEFINITIONS         ***
 *****************************************}

const
	{--- Required for Stream ----}
	RLetter : TStreamRec = (
		ObjType : srLetter;
		VmtLink : Ofs(TypeOf(TLetter)^);
		Load : @TLetter.Load;
		Store : @TLetter.Store
	);

function TLetter.RecSize;
begin RecSize := 100; end;

function TLetter.srType;
begin srType := srLetter; end;

constructor TLetter.Load(var S : TDataStream);
var Ver : byte;
begin
	S.Read(Ver, 1);

	case ver of
		2 : begin

			{added out tray index}
			inherited Load(S); {common init, lock, etc}

			S.Read(ToWho, 4);
			S.Read(ToCoy, 4);{now done by TJimmy}
			S.Read(ToAdd, 4);
			S.Read(ByWho, 4);

			Date.Load(S);
			DateSent.Load(S);

			S.Read(reWho, 4); {now done by TJimmy}
			S.Read(CCList, 4);

			Codes := S.ReadStr; Delspace(Codes);
			Subject^.Load(S); Subject^.LoadText;

			Ref := S.ReadStr;

			Header := S.ReadStr;
			StdForm := S.ReadStr;

			S.Read(EditorType, 2);
if EditorType = 2 then EditorType := edWP51; {don't know why, my conversion to 4.1 left wp letters with a value of 2...}
			S.Read(Status, 1);

			S.Read(TextBody,4);
			TextLoaded := False;
		end;
		1 : begin
			Ptr2OutIdx := -1;

			CommonInit;

			{converted from inherited for letteroutidx}
			S.Read(LockTerminal, 1);
			S.Read(Deleted, 1);
{			S.Read(ToWho, 4);
			S.Read(ToCoy, 4);
			S.Read(Rewho, 4);{}

{			inherited Load(S); {common init, lock, etc}

			S.Read(ToWho, 4);
			S.Read(ToCoy, 4);
			S.Read(ToAdd, 4);
			S.Read(ByWho, 4);

			Date.Load(S);
			DateSent.Load(S);

			S.Read(reWho, 4);
			S.Read(CCList, 4);

			Codes := S.ReadStr; Delspace(Codes);
			Subject^.Load(S); Subject^.LoadText;

			Ref := S.ReadStr;

			Header := S.ReadStr;
			StdForm := S.ReadStr;

			S.Read(EditorType, 2);
if EditorType = 2 then EditorType := edWP51; {don't know why, my conversion to 4.1 left wp letters with a value of 2...}
			S.Read(Status, 1);

			S.Read(TextBody,4);
			TextLoaded := False;
		end
	else
		DBaseMessage(@S, 'Version '+N2Str(Ver)+' not understood'#13#10'Letter.Load',mfError,hcInternalErrorMsg);
		fail;
	end;

end;

procedure TLetter.StoreFields(var S : TDataStream);
var ver : byte;

begin
	Ver := 2; S.Write(Ver, 1);

	inherited StoreFields(S);

	S.Write(ToWho, 4);
	S.Write(ToCoy, 4);{now stored by TJimmy}
	S.Write(ToAdd, 4);
	S.Write(ByWho, 4);

	Date.Store(S);
	DateSent.Store(S);

	S.Write(ReWho, 4); {now stored by TJimmy}
	S.Write(CCList, 4);

	S.WriteStr(@Codes);
	Subject^.Store(S);
	S.WriteStr(@Ref);

	S.WriteStr(@Header);
	S.WriteStr(@StdForm);

	S.Write(EditorType, 2);
	S.Write(Status, 1);

	S.Write(TextBody, 4);
end;


{============== POINTERS TO OTHER JIMMYS===================}
function TLetter.NumIDs;
begin NumIDs := 5; end;

function TLetter.GetJImmyID;
begin
	case jiType of
		1 : GetJImmyID := @ToWho;
		2 : GetJimmyID := @ToCoy;
		3 : GetJImmyID := @ToAdd;
		4 : GetJimmyID := @ByWho;
		5 : GetJimmyID := @ReWho;
	else
		GetJimmyID := nil;
	end;
end;



function TLetter.NumixTypes;
begin Numixtypes := 1; end;

{Get items the letter is supposed to hook up to when storing}
procedure TLetter.GetIndex;
begin
	inherited GetIndex(ixType, Idxrec, fiType);
	case ixType of
		1 : begin
			IdxRec := @Ptr2OutIdx;
			fiType := fiLettersOutIdx;
		end;
	end;
end;

function TLetter.GetIndexKey;
begin
	GetIndexKey := '';
	case ixType of
		1 :	if not Deleted and ((Status and leEdited)>0) then GetIndexKey := Date.AsKey;
	end;
end;

function TLetter.NumHookTo;
begin NumHookTo := 3; end;

{Get items the letter is supposed to hook up to when storing}
procedure TLetter.GetHookTo;
begin
	inherited GetHookTo(htType, HookToId,SubHookToID, hkType, Key, InsertBias);

	hkType := hkHistory;
	if DateSent.Blank then Key := SortKeyStart    {Make sure appears at beginning}
	else Key := -DateSent.Days;  									{Reverse Sort on date}
	InsertBias := biStart;

	case htType of
		1 : begin HookToID := @ToWho; end;
		2 : begin	HookToID := @ToCoy;	end;

		3 : begin HookToID := @ReWho; end;
	end;
end;


{***********************************
 ***     SET CODE                ***
 ***********************************}
{function TLetter.Decode(const Code, Param : TFCodeStr; var LString : TLongString) : boolean;
begin
	Decode := False;
end;{}


procedure TLetter.SetFormCodes;
var S : string;
begin
	inherited SetFormCodes(FormCodes);

	with FormCodes^ do begin
		{Set up codes}
		SetDate('DT', Date);        {Letter date, not today}
		SetDate('SDT', DateSent);        {Letter sent date}
		SetStr('REF', Ref);
		SetStr('DSC', ExpandSCode(scLetters, Codes));  {Expand description}
		Insert(New(PFreeTextFormCode, init('SBJ', Subject^)));

		{To whom, address et}
		if ToWho<>-1 then
			Insert(New(PJimmyFormCode, init('TO', ToWho)))
		else
			Insert(New(PJimmyFormCode, init('TO', ToCoy)));

		{override towho's address to specify date of letter and apCorrespondence}
{		Insert(New(PAddressFOrmCode, init('ADD', ToWho, apCorrespondence, Date)));{}
{		Insert(New(PAddressFormCode, init('ADD', ToAdd)));{}

		SetJimmyIDFormCodes(ToAdd, FormCodes); {put in address stuff direct}

		Insert(New(PJimmyFormCode, init('COY', ToCoy)));

		{complete address}
		S := '<TO.NAME>'+CRLF;
		if (ToCoy<>-1) and (ToWho<>-1) then begin
			if QDecode('TO.JOB')<>'' then S := S + '<TO.JOB>'+CRLF;
			if QDecode('TO.DPT')<>'' then S := S + '<TO.DPT>'+CRLF;
			S := S + '<COY.NAME>'+CRLF;
		end;
		SetStr('FULLADD',S+'<ADD>');

		{Re whom, address et}
		Insert(New(PJimmyFormCode, init('RE', ReWho)));

		{By}
		if ByWho>-1 then
			Insert(New(PJimmyFormCode, init('BY',ByWho)))
		else begin
			SetStr('BY.NAME',''); {for below}
			SetStr('BY.JOB',''); {for below}
		end;

		SetStr('YOURS', '<TO.YOURS>'+CRLF+CRLF+CRLF+CRLF+CRLF+'<BY.NAME>'+CRLF+'<BY.JOB>');

		S := Prefix; SetPrefix('');
		SetStr('FAXTO.NAME','<'+S+'TO.NAME>');
		SetStr('FAXTO.NUM', '<'+S+'TO.FAX>');
		SetStr('FAXTO.CNTRY', '<'+S+'TO.CNTRY>');
		SetPrefix(S);
	end;
end;

{**************************************
 ***          PRINT                 ***
 **************************************}
const
	LetterPrintType : TJimmyPrintType =
		(Editor : edInternal;
		 Target : ptPrint;
		 DeviceName : '';
		 PrintAs : 0;
		 FormName : '';
		 NumCopies : 1;
		 PlusLabel : True);

procedure TLetter.GetDefaultPrintType(var PrintType : TJimmyPrintType; var PrintAs : PSItem; var PrintAsLink : pointer);
begin
	PrintType := LetterPrintType;
	PrintType.Editor := EditorType;

	if (Copy(Header,1,3)='FAX') and (delspaceR(FaxName)<>'') 	then PrintType.Target := ptFax;
	if (Copy(Header,1,5)='EMAIL') and (EmailSetup.ViaFormat<>vfNone) then PrintType.Target := ptemail;

	if ToAdd = -1 then PrintType.PlusLabel := False; {eg reports}

	PrintAs := nil; {NewSItem('~N~ormal', nil);{}
	PrintAsLink := nil;

	{and set no. copies for cc}
end;

procedure TLetter.PrintSummary;
var	DirectoryItem : PDirectoryItem;
		Width : integer;
		Add : PAddress;
		S : string;
begin
	{Date & Header}
	S := Date.Digit8+' '+ucase(header);
	if Delspace(Codes)<>'' then S := S+' ('+ExpandScode(scLetters, Codes)+')';
	if not Subject^.Loaded then Subject^.LoadText;
	Device^.Writeln(S+' '+LSGetLine(Subject^.Text, 1));

	Device^.Writeln('        To: '+GetJimmyIDName(ToWho, naReport, 0));
	Device^.Writeln('        By: '+GetJimmyIDName(ByWho, naReport, 0));

	if Delspace(Ref)<>'' then Device^.Writeln('         Ref: '+Ref);
end;

procedure TLetter.PrintLabel;
var Add : PAddress;
		Person : PPerson;
begin
	if ToWho<>-1 then begin
		Person := PPerson(GetJimmy(ToWho));
		Device^.writeln(Person^.Getname(naAddress,0));
		if Person^.srType=srPerson then begin
			if delspaceR(Person^.JobTitle)<>'' then Device^.writeln(Person^.JobTitle);
			if delspaceR(Person^.Dept)<>'' then Device^.writeln(Person^.Dept);
		end;
		dispose(Person, done);
	end;

	if ToCoy<>-1 then Device^.writeln(GetJimmyIDName(ToCoy, naAddress,0));

	if ToAdd<>-1 then begin
		Add := PAddress(GetJimmy(ToAdd));
		{should do for each person on cc list}
		Add^.PrintLabel(Device, 0);
		dispose(Add, done);
	end;
end;

procedure TLetter.PrintFull;
var Width : integer;
begin
	CreateBody(Device, False, Width);
{	Device^.FeedPage; {switched off in createbody }
end;

{function TLetter.GetFaxNum;
var TW : PDirectoryItem;
begin
	GetFaxNum := '';
	if ToWho<>-1 then begin
		TW := PDirectoryItem(GetJimmy(ToWHo));
		if TW<>nil then begin
			GetFaxNum := TW^.GetFaxNum;
			dispose(TW, done);
		end;
	end;
end;{}


procedure TLetter.OnPrinting;
begin
	if (DateSent.Blank) then DateSent.SetToToday;
	Status := Status and not leEdited; {switch off been edited w/o printing marker}
	Status := Status or leSent; {been sent}
	if RecNo<>-1 then StoreSelf; {don't store if new - eg mailshots}
end;

{procedure TLetter.OnStoreing;
var IdxRec : longint;
begin
	if ((Status and leEdited)>0) and ((PLetter(Diskjimmy)^.Status and leEdited)=0) then begin
		{insert into letter out tray}
{		FileAdmin(fiLettersOutIdx)^.LogOn;

		New(IndexItem, init);
		IndexItem^.KeyString := Date.AsKey;
		IndexItem^.Ptr2Dat := RecNo;

		IndexStream(fiLettersOutIdx)^.Insert(fiLettersOutIdx, IndexItem, IdxRec);

		dispose(IndexItem, done);
		FileAdmin(fiLettersOutIdx)^.LogOff;
	end;

	if (DiskJimmy<>nil) and ((Status and leEdited)=0) and ((PLetter(Diskjimmy)^.Status and leEdited)<>0) then begin
		{remove from out tray}
{		FileAdmin(fiLettersOutIdx)^.LogOn;

		IndexStream(fiLettersOutIdx)^.Delete...

		dispose(IndexItem, done);
	end;



{*********************************************************************
 ***                                                               ***
 ***              BODY EDITING ROUTINES                            ***
 ***                                                               ***
 ***                                                               ***
{*********************************************************************}

function TLetter.EditBody;
var Width : integer;
		Control : word;
		FName : FNameStr;
		WPFile : text;
		Attr : word;
		Device : PDeviceStream;

begin
	FName := Header;
	if TerminalNo<>0 then FName := FName + '.'+N2Str(TerminalNo); {avoids conflicts if sharing directory, eg WP}

	case EditorType of
		edInternal 	: begin FName := WorkPath+FName; 			New(Device, init('Letter Body',FName)); end;
{$IFDEF kwplink}
		edWP51 			: begin FName := WPSetup.LocalPath+Fname; 	Device := New(PWPStream, init(FName));	end;
{$ENDIF}
	else
		Device := nil;
	end;

	CreateBody(Device, True, Width);

	dispose(Device, done);

	{========== EDIT FILE ==================}
	Control := cmCancel;
	case EditorType of
		edInternal : begin
			Control := FileEditor(FName, Width, InternalCommentString, '', StdBufferSize, hcEditLetter);
		end;
{$IFDEF kwplink}
		edWP51 : begin
			CallWP(FName, WPSetup.EditMacro);{}

			{test file to see if it's been changed}
			Assign(WPFile, FName);
			GetFAttr(WPFile, Attr);
			if ((Attr and Archive)>0) then Control := cmOK;
		end;
{$ENDIF}
	end;

	if Control = cmOK then begin
		Date.SetToToday;
{Test done by checking TextBody    StdForm := ''; {it's been edited & accepted - in future pick up
		the actual text not the standard form}

		Status := Status or leEdited;

		{========== REMOVE HEADER/EXTRACT BODY ==========}
		StoreBody;

		StoreSelf;
	end;

	EditBody := Control;
end;

{Convert letter body file into chain & store}
{This routine does not assume editor - works for WP, etc as it reads block
by block}
procedure TLetter.StoreBody;
const
	BlockSize = 200;

type
	TBlock = array[1..BlockSize] of char;

var
		CommentString : string;

    {file containing letter body (input)}
    BodyFileName : string;
		BodyFile : file of TBlock;
		BodyFileSize : longint;
		SizeFile : file of byte;
		InBlock : TBlock;
		InString : string;

		{for output/storing text chain}
		TextBlock : string[TLetterItemSize-4];
		TextItem : PTextItem;
		TextFile : PTextStream;
		TextRec : longint;
		Ch : char;
		I : integer;
		NoBlocks : longint;
		S : string;
		LastIOResult : integer;
		More : boolean;
		DeleteFirstBytes : integer;

begin

	case EditorType of
		edInternal : begin BodyFileName := WorkPath+Header;  CommentString := InternalCommentString; end;
{$IFDEF kwplink}
		edWP51   : begin BodyFileName := WPSetup.LocalPath+Header; CommentString := WPSetup.CommentString; end;
{$ENDIF}
	end;
{BodyFileName := 'LETTER.JIC';{}
	if TerminalNo<>0 then BodyFileName := BodyFileName + '.'+N2Str(TerminalNo); {avoids conflicts if sharing directory, eg WP}

	{Create backup}
	if PRogramSetup.GetBoolean(siCreateLetterBackup,True) then begin
		ThinkingOn('Creating backup');
		{copies text file to same name with .BK? where ? is the terminal number}
		CopyFile(BodyFileName, GetPath(BodyFileName)+'\'+GetJustFileName(BodyFileName)+'.BK'+N2Str(TerminalNo));
		ThinkingOff;
	end;

	ThinkingOn('Storing Text');
	{Find out how big it is in bytes}
	{$I-}
	Assign(SizeFile, BodyFileName);{}
	Reset(SizeFile);
	LastIOresult := IOResult;
	if LastIOREsult=0 then begin
		BodyFIleSize := FileSize(SizeFile);
		LastIOResult := IOResult;
		if LastIOREsult = 0 then Close(SizeFile);
	end;
	{$I+}
	if LastIOResult<>0 then begin
		ProgramError('Could not size letter'#13#10
									+IOError(LastIOResult)+#13#10
									+'Storing abandoned',
									hcIOErrorMsg);
		exit;
	end;

	{Open block file}
	Assign(BodyFile,BodyFileName);{}
	Reset(BodyFile);

	New(TextItem, init);

	if eof(BodyFile) then
		TextBody := -1
	else
		{Check lock status of text data file}
		{$IFNDEF SingleUser}
		if CheckFileLock(fiLetterText,'Text Data File'#13#10'Storing Text')>0 then  {Check lock on file, returns >0 if cancel}
			{cancel - clear it all}
			TextBody := -1
		else
		{$ENDIF}
		begin

			{$IFNDEF SingleUser}
			SetFileLock(fiLetterText, True);
			{$ENDIF}

			{==================== SKIP HEADER =======================}

			{---Read until BEGIN encountered---}
			{Do this by reading in a block of BlockSize chars (above) at a time.  As
			we have to allow for the BEGIN marker being on a block boundary, we FIFO
			it through the string S, adding it on the end and removing the first block,
			always two blocks present, and doing the check on S.
			When it's found, it needs to be deleted, and the codes surrounding it. Again,
			the codes surrounding it (esp the codes after it) may be accross a block
			boundary.  So we clear S up to and including the comment, and delete the
			rest on the first clear line we read}

			S := space(length(CommentString));   {Leave gap so that when copy() done below, it allows <BEGIN> to be
													found if split across block boundaries}
			{S remains the temporary interface string througout}
{$I-}                  {Switch off IO checking o/w tends to fall over read past end of file}
			ThinkingOn('Discarding Header');

			NoBlocks := 0;
			repeat
				Read(BodyFile, InBlock);
				NoBlocks := NoBlocks +1;
				LastIOResult := IOResult;

				{If read partly past end of file, chop off surplus}
				S := S + InBlock;
				if BodyFileSize<(NoBlocks * BlockSize) then
					S := Copy(S,1,(BodyFileSize mod BlockSize)+length(CommentString));
								{chop surplus, allowing for spaces added above}

				{look for begin marker}
				I := Pos(CommentString,S);
				if I>0 then
					{found! chop off up to end}
					S := copy(S,I+length(CommentString),length(S))  {Skip char #10 at end of line as well}
				else
					{not found - chop out first block}
					S := copy(S,BlockSize+1,length(S));  {chop off block at start of string, ready for next at end}


			until (I>0) or eof(BodyFile);    {Found begin or end of file}

			if I=0 then begin
				ProgramWarning('Could not find "'+Copy(CommentString,1,15)+'..." marker'+#13#10+
											'NOT STORING! You will need to recover the letter"',
											hcBeginNotFoundMsg);
				Close(BodyFile);
				ThinkingOff; ThinkingOff;
				exit;
			end;

			ThinkingOff;

			DeleteFirstBytes := 0;
{$IFDEF kwplink}
			if EditorType = edWP51 then
				DeleteFirstBytes := WPSetup.NumBytesAfterComment; {set for deleting post-search codes - see above comment}
{$ENDIF}

			{=============== COPY BODY TO CHAIN FILE ==================}
			FileAdmin(fiLetterText)^.LogOn;
			TextFile := PTextStream(Stream(fiLetterText));

			if TExtBody = -1 then	TextBody := TextFile^.NoRecs; {if a new one, set new pointer}

			TextRec := TextBody;

			while not eof(BodyFile) do begin
{				for I := 1 to BlockSize do InBlock[I] := #0;  {Clear array}
				Read(BodyFile, InBlock);
				NoBlocks := NoBlocks + 1;
				LastIOResult := IOResult;

				InString := InBlock;
				if (NoBlocks * BlockSize)>BodyFileSize then InString := Copy(Instring,1,BodyFileSize mod BlockSize);
																																		{Chop off rubbish at end}

				if (DeleteFirstBytes>0) and (length(S)>DeleteFirstBytes) then begin {wait until string holds all the bytes}
					S := Copy(S,DeleteFirstBytes+1,length(S)); {remove from string}
					DeleteFirstBytes := 0; {do it only once}
				end;

				{TLetterItemSize is 256, and no string can be longer than this. Thus
				the somewhat roundabout string processing...}
				if length(S+InString)>(TLetterItemSize-4) then begin
					TextItem^.Data := S+InString;
					S := Copy(InString,TLetterItemSize-4-length(S)+1,length(InString));
					more := true; if eof(BodyFile) and (length(S)=0) then More := false;  {Is there any more to store}
					TextFile^.PutNext(TextRec, TextItem, More);
				end else
					S := S + InString;
			end;

			if length(S)>0 then begin
				if (DeleteFirstBytes>0) then {it may be that above while is never run if very small letter}
					S := Copy(S,DeleteFirstBytes+1,length(S)); {remove from string - no need to reset deletefirstbytes}

				TextItem^.Data := S;
				TextFile^.PutNext(TextRec, TextItem, false);  {Put at TextRec, the textitem, no more}
			end;

			FileAdmin(fiLetterText)^.LogOff;

			{AND UNLOCK FILE}
			{$IFNDEF SingleUser}
			SetFileLock(fiLetterText, False);
			{$ENDIF}
		end;


{$I+}   {IO checking back on}

	close(BodyFile);
	Dispose(TextItem, done);
	ThinkingOff;
end;


procedure TLetter.CreateBody(const Device : PDeviceStream; const ForEdit : boolean; var Width : integer);
{Boolean marks whether to decode codes (for printing) or not (for editing)}
var
	TextFile : PTextStream;
	TextRec : longint;
	TextItem : PTextItem;
	OutString : TLongString;
	Append : boolean; {marker for whether to append to stream rather do startprint/endprint}
	S,ASCIIOutString : string; {for formatting internal editor stuff}

	procedure FormatEditorLine(Line : String);
	begin
		{format - check for eoparagraph markers}
		if (Line[length(Line)]=EndParaChar) then begin
			dec(ASCIIOutString[0]); {remove marker}
			if (Device^.JustMode = juFull) then
				{special case - full justified, end of paragraph, treat as left justfied}
				JustifyLine(ASCIIOutString, Device^.Paper^.Width, juLeft)
			else
				JustifyLine(ASCIIOutString, Device^.Paper^.Width, Device^.JustMode);
		end else
			JustifyLine(ASCIIOutString, Device^.Paper^.Width, Device^.JustMode);
	end;



begin
	ThinkingOn('Extracting Letter');
	Width := 0;
	SetFormCodes(Device^.FormCodes);
	Append := Device^.IsOpen;

	{========= CREATE HEADER ================}
	case EditorType of
		edInternal : begin
			if ForEdit then begin
				{reset paper to default paper type}
				Device^.SetPaperTo(printersPath+Printer^.DefPaper+'.'+PaperExt);
				Device^.Paper^.LeftMargin := 0; {Switch off Left Margin - o/w end up with spaces in funny places}
				Device^.FormCodes^.SetStr('LM',''); {switch off left margin change codes}
				Device^.FormCodes^.SetStr('JR',''); {switch off justifications}
				Device^.FormCodes^.SetStr('JF','');
				Device^.FormCodes^.SetStr('JC','');
				Device^.FormOptions := Device^.FOrmOptions or foEndPAraMarker; {force crlfs to add endparamarker so rewrap works OK}
			end;

			if Append then begin
				Device^.HEaderName1 := Header;
				Device^.StartPage;
			end else
				Device^.StartPrint('',Header);                 {Output standard form to file TEMP}

			Device^.FormOptions := Device^.FOrmOptions and not foEndPAraMarker; {switch off}
			if ForEdit then Device^.WriteStr(InternalCommentString);   {Mark end of header}

			Width := Device^.Paper^.Width; {set width once set in header/paper}
		end;

{$IFDEF kwplink}
		edWP51 : begin
			if Append then begin
				Device^.HeaderName1 := Header;
				Device^.StartPage;
			end else
				Device^.StartPrint('',Header);                 {Output standard form to file TEMP}
			if ForEdit then begin
				Device^.WriteStr(WP51BeginMarker);   {Mark end of header}
				Device^.AutoSend := False;
			end;
		end;
{$ENDIF}
	else
		ProgramWarning('Editor not available',hcLetters);
		ThinkingOff;
		exit;
	end;

	if ForEdit then Device^.PageCheck := False;

	{============ ADD LETTER/BODY TEXT ============}
	if (TextBody = -1) and (StdForm <> '') then begin
		{Standard letter}
		if ForEdit then Device^.CLearCodes; {meant for edit, so clear codes}
		Device^.PrintFOrm(StdForm+'.FRM');
		if not Device^.FormFound then Device^.PrintFOrm(StdForm+'.STL');
	end else begin
		{Extract text body}
		FileAdmin(fiLetterText)^.LogOn;	TextFile := PTextStream(Stream(fiLetterText));
		LSNew(OutString); {for decoding each line below}
		ASCIIOutString := '';
		TextRec := TextBody;
		{Read each text record item}
		while TextRec > -1 do begin
			TextItem := PTextItem(TextFile^.GetNext(TextRec));
			{Check it is valid}
			if TextItem = nil then begin
				DBaseMessage(Textfile,'Could not retrieve Text Item for letter'#13#10
															+'FirstRec #'+N2Str(TextBody),mfError,hcInternalErrorMsg);
				TextRec := -1;
			end else begin
				{Write out to bodyfile}
				if TextRec = -1 then TextItem^.Data := DelNulR(TextItem^.Data);  {Delete last nuls if last one}

				if ForEdit then
					{straightforward no decoding, no formatting}
					Device^.writeStr(TextItem^.Data)
				else begin
					{if not, need to decode any codes...}

					{need to treat editor differently
						- get rid of end of para markers, format/justify}
					if EditorType = edInternal then begin
						while NumLines(TextItem^.Data)>1 do begin
							{write out each line}
							S := GetLine(TextItem^.Data,1);
							ASCIIOutString := ASCIIOutString + S; {append to existing}
							Delete(TextItem^.Data, 1, length(S)+1); {remove from data, incl CR}
							if TextItem^.Data[1] = #10 then Delete(TextItem^.Data,1,1); {remove LF if present}
							{remove LF if carried over from prev textitem.data}
							if AsciiOutString[1] = #10 then ASCIIOutString := Copy(ASCIIOutString,2,255);

							{format - check for eoparagraph markers}
							FormatEditorLine(ASCIIOutString);
							Device^.writeCodedStr(ASCIIOutString+CRLF);
							ASCIIOutString := '';
						end;
						ASCIIOutString := ASCIIOutString + TextItem^.Data; {append remainder}

					end else
						Device^.WriteCodedBlock(OutString, TextItem^.Data); {writes & decodes all but last 50 chars}
				end;

				Dispose(TextItem, done);
			end; {text item valid}
		end; {while}
		FileAdmin(fiLetterText)^.LogOff;

		{print remainder}
		if LSLen(Outstring)>0 then Device^.writeLStr(OutString); {won't do anything if oustring not used above}
		if length(ASCIIOutstring)>0 then begin
			{remove LF if carried over from prev textitem.data}
			if AsciiOutString[1] = #10 then ASCIIOutString := Copy(ASCIIOutString,2,255);
			FormatEditorLine(ASCIIOutString);
			Device^.WriteCodedStr(ASCIIOutString+CRLF);
		end;
	end;

	if ForEdit then begin Device^.HeaderName1 := ''; Device^.HeaderName2 := ''; end;
	Device^.EndPrint;

	TextLoaded := True;
	THinkingOff;

end;

{**************************************
 ***       INITIALISER              ***
 **************************************}
function CreateLetter(P : pointer) : pointer; far;
begin CreateLetter := New(PLetter, init(P)); end;

procedure StartLettersOutTray; far;
var Bounds : TRect;
		List : POutTrayListView;
begin
	Bounds.Assign(0,0,60,15);
	CentreOnView(Bounds, Desktop);

	List := New(POutTrayListView, Init(Bounds, lsLettersOut, fiLettersOutIdx,''));

	Kameleon^.InsertWindow(New(PIndexedJimmyListWindow, init(Bounds, 'Letters Out', List)));
end;

function NewLettersOutIndex : PStream; far;
begin NewLettersOutIndex := New(PIndexedJimmyStream, init('OUTLTRS.IDX',TLetterIndexSize)); end;


begin
{$IFDEF fixit} writeln('KLetter...'); {$ENDIF}
	RegisterSCodeType(scLetters, 'KLetters.SC',   'Letter Types', StdScodeCreator);

	{$IFDEF kletter}
		RegisterType(RLetter);

		RegisterCreator(cmNewLetter, CreateLetter);

		{Register with directory history}
		RegisterNewWithList(lsHistory, '~L~etter', cmNewLetter);

		{Register with desktop}
		RegisterNewWithList(lsDesktop, '~L~etter', cmNewLetter);{}
{	RegisterTask(DesktopTasks, cmNewLetter, @CreateEditJimmy); autodone}

		RegisterTask(DesktopTasks, cmStartLettersOutTray, @StartLettersOutTray);
		NewFileAdmin(fiLettersOutIdx, 'Letters Out Index',NewLettersOutIndex);

		RegisterWithList(lsLettersOut, '~P~rint',
			NewLine(
			NewItem('~L~abel', 		 ksLabel, kbLabel, cmPrintLabel, hcNoContext,
			NewItem('Defer Label', ksDeferLabel, kbDeferLabel, cmDeferLabel, hcNoContext,
			nil))),
		nil);

{$ENDIF}
end.
