{**********************************************************************
 ***                            JIMMY                               ***
 **********************************************************************}
{$I compdirs}  {needed for single/m user}
unit Jimmys;

INTERFACE

uses files,
{$IFDEF WINDOWS}
	win,	{windows}
{$ELSE}
	tuilist,{} tuiedit, views, dialogs,
{$ENDIF}
	lstrings,
	global,
	tasks,
	dattime,
	jimprint,
	devices,
	forms, objects;

const
	{On locked actions}
	laIgnore 			= 0;
	laMsgInUse 		= 1;
	laMsgRefocus 	= 2;
	laAllowView 	= 3;

	{responses}
	rsOK 					= 0; {all clear}
	rsCancel			= 1; {cancel operation}
	rsViewOnly		= 2; {continue but w/o making any changes}

type

	{used by jimmy creators as a standard parameter to a jimmy so it can
	set defaults etc, depending on where it is created}
	PJimmyInitParam = ^TJimmyInitParam;
	TJimmyInitParam = record
		ListView : PListView; {nil if desktop}
		ForWho : longint;  {pointer to who hooked list is connected to, if exists}
		FocusedID : longint; {pointer to focused jimmy if in list}
		FocusedParentID : longint; {pointer to focused jimmy's parent, if available (ie hooked list)}
{  	Command : word; {command used to start jimmy - usually cmNew, but might be cmNewChild, etc}
	end;


	PJimmy = ^TJimmy;
	TJimmy = object(TDataItem)

		GotByIx : byte; 		{set to which ixtype was used to retrieve from index file - eg alias or full - so that it can
													know whether to display with the alias line or the main one}
		Deleted 			: boolean; 	{deleted marker - store in archive if done}
		Tag 					: boolean; 			{tagged marker}
		AllowChanges 	: boolean;  {eg invoices should set this to false when sent}

		InsertTime : TTimer; {used for checking insert time for this jimmy - not stored!}

		procedure JimmyMarker; virtual; {do NOT override - used by IsJimmyDescendant}

		{==== Usually Overridden =========}
		constructor Init;

		{--- Editing ----}
		procedure MakeEditBox(var EditBox : PEditBox; Caller : PView); virtual;
{		function MakeInputGroup(const X,Y : integer) : PInputGroup; virtual;{}
		procedure AddFields(const Group : PGroup; const EditBox : PEditBox); virtual;

		function Blank : boolean; virtual; {returns true if empty}

		{--- Viewing -----}
		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 -
																																					eg selection lines, window headers, etc}
		{--- Printing ----}
		procedure SetFormCodes(const FormCodes: PFormCodeCollection); virtual;

		{--- Database ----}
		function RecSize : word; virtual; {space to be reserved in jimmy file}
		function srType : word; virtual; {descendants set as fixed, so can be
																			used to identify jimmy for file operations, etc}

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

		{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}

		{=== Sometimes Overridden =================}
		procedure CommonInit; virtual; {init procedures common to Init and Load (eg scode logging)}

		{--- Printing ----}
		procedure Print; {asks user, etc}

		procedure PrintPrintType(const PrintType : TJimmyPrintType);

		function GetPrintType(var PrintType : TJimmyPrintType; PrintAs : PSItem; PrintAsLink : pointer) : word; virtual;
		procedure GetDefaultPrintType(var PrintType : TJimmyPrintType; var PrintAs : PSitem; var PrintAsLink : pointer); virtual;

{		function GetFaxNum : string; virtual;{}

		procedure PrintForm(const Device : PDeviceStream; const FormName : FNameStr); virtual;
		procedure PrintFull(const Device : PDeviceStream; const PrintAs : word); virtual;
		procedure PrintSummary(const Device : PDeviceStream; const PrintAs : word); virtual;
		procedure PrintLine(const Device : PDeviceStream); virtual;
		procedure PrintLabel(const Device : PDeviceStream; LabelAs : word); virtual;

		procedure OnPrinting(const PrintType : TJimmyPrintType); virtual;

		procedure DoLabelNow(const FormName : string; const NumCopies,LabelAs : word); {blank for printlabel, o/w does a printform}
		procedure DeferLabel(const FormName : string; const NumCopies,LabelAs : word);

		{--- Database ----}
		function OneHookEntryOnly : boolean; virtual; {only allow one entry on hook lists}
		procedure PreStoreing(const DiskJimmy : PJimmy); virtual;   {pre-hooking, etc}
		procedure OnStoreing(const DiskJImmy : PJimmy); virtual;    {extra storing method, done by storeself}
		function PtrOffset : byte; virtual;{}

		{for automatic doing of things if it's being hooked to - see eg TLivestock}
		function HookingOn(const hkType,htType : byte; const HookingJimmy : PJimmy) : boolean; virtual;
		function ReHooking(const hkType,htType : byte; const Jimmy, OldJimmy : PJimmy) : boolean; virtual;
		function UnHooking(const hkType,htType : byte; const HookingJimmy : PJimmy) : boolean; virtual;

		{legal/security}
		function AllowDeletion : boolean; virtual; {allow deletion from history list, etc - eg invoices should not be}

		{--- Indexing ----}
		function NumixTypes : byte; virtual;
		procedure GetIndex(const ixType : byte; var IdxRec : Plongint; var fiType : byte); virtual;
		function GetIndexKey(const ixType : byte) : string; virtual;
		function GotByAlias(const fiType : byte) : boolean; virtual; {returns whether this is an alias (true)
																												or the main entry (false) in index ixtype}
		{--- Hooking on others -----}
		function NumhkTypes : byte; virtual;
		procedure GetHookOn(const hkType : byte; var HookRec : PLongint); 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;

		{-- Miscellaneous other pointers ---}
		{used for special pointers for special data structures.  Having them
		here and the jimmy look after them means they act as pointers should -
		ie updated/loaded/etc at the right time...  see krally.pas}
		function NumopTypes : byte; virtual;
		procedure Getop(const opType : byte; var opRec : PLongint); virtual;

		{--- for diary, creating a new one for repeaters ---}
		procedure DoRepeater(const NewDate : TDate; const DeltaDays : integer); virtual;

		{=== DON'T OVERRIDE ========================}
		function  Edit(const Caller,AcceptorView : PView) : word;   virtual;  {Caller helps to set position}

		procedure Store(var S : TDataStream);
		procedure StoreSelf; {should not be overriden - see onstoreing method}

		{--- Calculating ----}
{		procedure Price(var Result : TMoney); virtual;
		procedure Calculate; virtual; {for invoices, purchase orders, etc}

		{---- database -----}
		{loading/storing ptrs}
		function LoadPtr(const Posit : word) : longint;
		procedure StorePtr(const Posit : word; var FilePtr : longint);

		{Locks}
		function IncLock(Action : word) : word;
		procedure DecLock;
		procedure SetLock(On : boolean); virtual; {does auto store if changed}
		procedure StoreLock; virtual;
		procedure LoadLock; virtual;

		{---- Indexing --------}
		function IdxPtrPos(const ixType : byte) : byte;{}

		procedure LoadIdxPtr( const ixType : byte);
		procedure StoreIdxPtr(const ixType : byte);

		{shortcuts - should get rid of}
		function GetIdxPtr(const ixtype : byte) : longint;
		procedure SetIdxPtr(const ixType : byte; const NIdxPtr : longint);
		function GetfiType(const ixType : byte) : byte;{}

		{---- "other" pointers}
		procedure Loadop(const opType : byte);
		procedure Storeop(const opType : byte);
		function opPos(const opType : byte) : byte;
		procedure LoadAllop;
		function 	GetopPtr(const optype : byte) : longint;
		procedure SetopPtr(const opType : byte; const NopPtr : longint);


		{---- Hooking ------}
		function GetFirstHookPtr(const hkType : byte) : longint;
		procedure SetFirstHookPtr(const hkType : byte; const NFirstHookPtr : longint);{}

		procedure LoadFirstHookPtr( const hkType : byte);
		procedure StoreFirstHookPtr(const hkType : byte);

		function HookPtrPos(const hkType : byte) : byte;
		procedure LoadAllHooks;

		procedure LoadAllPtrs;
		procedure ClearAllPtrs;

		{obsolete methods, to trap descendants at compile - change to new indexing/hooking/stuff}
{		procedure idxPtr; virtual;
		procedure GetHookToID; virtual;
		procedure Hookptr; virtual;
		procedure GetKeyString; virtual;
		procedure LoadSupplements(W : word); virtual;
		procedure Decode(const Code, Param : TFCodeStr; var LString : TLongString); virtual;
		procedure FormAccessCode(W : word); virtual;{}

	end;


	{=============== REPORTS/FORM CODES =========================}
	{for tying a code to a jimmy.  For a start, just the ID of the jimmy
		is set, so that it is not loaded until it is required.  ie, tie the
		code <TO> to a person, and when it finds <TO.ADD> it will load the jimmy,
		set the form codes for that jimmy, and if it cannot decode the code
		normally it will call the jimmys "decode" method
		use eg:	Insert(New(PJimmyFormCode, init('TO', ToWho)));}
	PJimmyFormCode = ^TJimmyFormCode;
	TJimmyFormCode = object(TFormCode)
		ID : longint;
		Jimmy : PJimmy;  {loads jimmy once required and stores in memory until formcode disposed}
		constructor Init(const NCode : string; const NID : longint);
		destructor Done; virtual;
		function Replace(const SubCode, Param : TFCodeStr; const FormCodes : PFormCodeCollection;
																													var LString : TLongString) : boolean; virtual;
	end;


	{based on data stream, includes botch fix for v1 & 2's more-about list}
{	PJimmyStream = ^TJimmyStream;
	TJimmyStream = Object(TDataStream)
		function  GetAt(RecNo : longint) : PObject; virtual;  {Seek & get combined}
{	end;{}

procedure RegisterJimmy(var RType : TStreamRec; Creator : TCreatorFunc; lsType : word; MenuName : PChar);

{procedure CreateEditJimmy; {for use by desktop/tasks for the desktop new
menu - register this procedure as the task associated with the various
commands on that new menu}

var JimmyStream : PDataStream; {pointer to data stream - saves running through stream() method}
{function JimmyStream : PJimmyStream; {returns current pointer to jimmystream}

{===== SHORTHAND ROUTINES ==============}
{Check for nil jimmys, -1 pointers, logging onto jimmy stream, etc}

{shorthand for direct file storage}
function GetJimmy(const JimmyID : longint) : PJimmy;

 {get with lock check/set}
{function GetLkJimmy(const JimmyID : longint; const lkType : byte; const lkMsg : string; const lkmfType : word) : PJimmy;{}

procedure PutJImmy(Jimmy : PJimmy); {stores over previous point, or at end if new}

{loading codes for output streams & getting names}
procedure SetJimmyIDFormCodes(const JimmyID : longint; FormCodes : PFormCodeCollection);{}
function GetJimmyIDName(const JimmyID : longint; naType, Maxlen : byte) : string;

{to deal with non-modal displays, these routines allow us to get/put
jimmys, automatically checking to see if they're already on screen.}
function FindJimmy(const JimmyID : longint; var EditView : PView) : PJimmy;
procedure UpdateJimmy(const Jimmy : PJimmy); {}

{get view that a given jimmy is being edited in}
function GetJimmyView(const JimmyID : longint; const lsType : word) : PView;
{just sets views' data & redraws}
procedure UpdateJimmyView(const View : PGroup; const Jimmy : PJImmy);


procedure DeleteJimmy(var Jimmy : PJimmy; const Confirm : boolean); {common code to delete from list views
																						tests for lock, allowable, check with user}


{takes all of deljimmy's history and adds to merges, then deletes deljimmy}
procedure MergeJimmys(DelJimmy, MergeJimmy : PJImmy);

{now part of jimmy var
	InsertTime : TTimer; {used to time insertions, eg for jimmy storeself}

IMPLEMENTATION

uses 	tuimsgs,
			{$IFNDEF SingleUser} muser, {$ENDIF}
			setup,
			help,
			memory, {for lowmemory}
			printers, faxes, kamsetup,{$IFDEF kwplink} kwplink, {$ENDIF} {for fax & print button & method}

			editfile, {editing file once printed to file}
			minilib,
			labels,
			dbg,

			autodial, {for faxing}
			jimhooks, jimindxs, {for storing self}
			chains,
			tuijimmy, drivers,
			tuiapp, App; {for validview & desktop}

{For registering jimmys, general creation, etc}
{type
	PJimmyCreatorItem = ^TJimmyCreatorItem;
	TJimmyCreatorItem = object(TChainLink)
		CreatorCommand : word;
		VMTLink : word;
		InitPointer : pointer;
		constructor Init(const NCreatorCommand,NVMTLink : word; const NInitPointer : pointer);
	end;

	constructor TJimmyCreatorItem.Init;
	begin
		inherited Init;
		CreatorCommand := NCreatorCommand;
		VMTLink := NVMTLink;
		InitPointer := NInitPointer;
	end;

const
	CreatorChain : PChain = nil;


{function MakeJimmy(const CreatorItem : TJimmyCreatorItem; Param : pointer) : PObject; assembler;
asm
@@4:    LES     DI,Param
				PUSH    ES
				PUSH    DI
				PUSH    T.VmtLink{}
{				XOR     AX,AX
				PUSH    AX
				PUSH    AX
				CALL    CreatorItem.InitPointer
@@5:
end;

function CreateJimmy(const Command : word; const Param : pointer) : PJimmy;
begin
	CreatorItem := CreatorChain^.FirstLink;
	while (CreatorItem<>nil) and (CreatorItem^.CreatorCommand<>Command) do CreatorItem := CreatorItem^.Next;

	if CreatorItem<>nil then
		CreateJImmy := MakeJimmy(CreatorItem^, Param)
	else
		CreateJimmy := nil;
end;


{********************************************
 ***    JIMMY SHORTCUT HANDLING METHODS   ***
 ********************************************}
procedure RegisterJimmy;
begin
	RegisterType(RType);

{	CreatorCHain^.AddLink(New(PJimmyCreatorItem, init(RType.ObjType, RType.VMTLink, InitPointer)));{}

	RegisterCreator(RType.ObjType, Creator); {assumes srtype is same as creator cmxxx}

	if lsType<>0 then
		RegisterNewWithList(lsType, MenuName, RType.ObjType);
end;


procedure SetJimmyIDFOrmCodes;
var Jimmy : PJimmy;
begin
	Jimmy := GetJimmy(JimmyID);
	if Jimmy<>nil then begin
		Jimmy^.SetFormCodes(FormCOdes);
		dispose(jimmy, done);
	end;
end;{}

function GetJimmyIDName;
var Jimmy : PJimmy;
begin
	GetJimmyIDName := '';
	Jimmy := GetJimmy(JimmyID);
	if Jimmy<>nil then begin
		GetJimmyIDName := Jimmy^.GetName(natype, maxlen);
		dispose(jimmy, done);
	end;
end;

{Just store jimmy on jimmy stream - used by storeself routines before
linking into admin groups}
procedure PutJimmy;
begin
	FileAdmin(fiJimmys)^.LogOn;
	with JimmyStream^ do begin
		if Jimmy^.RecNo = -1 then Jimmy^.RecNo := NoRecs;
		PutAt(Jimmy^.RecNo, Jimmy);
		if Status<>stOk then ErrorMsg('Storing jimmy');
		{$IFNDEF SingleUser} Flush; {$ENDIF}
	end;
	FileAdmin(fiJimmys)^.LogOff;
end;

{======= GET =====================}
function GetJimmy;
var Jimmy : PJimmy;
begin
	Jimmy := nil;
	if JimmyID>-1 then begin
		FileAdmin(fiJimmys)^.LogOn;
		with JimmyStream^ do begin
			{$IFNDEF SingleUser} Flush; {$ENDIF} {clear}
			Jimmy := PJimmy(GetAt(JimmyID));
			if Jimmy=nil then
				if Status<>stOk then ErrorMsg('GetJimmy('+N2Str(JimmyID)+')');
		end;
		FileAdmin(fiJimmys)^.LogOff;
	end;
	GetJimmy := Jimmy;
end;


{====== GET/FIND FROM VIEW =============}
function FindJimmy;
begin
	if JimmyID=-1 then
		FindJimmy := nil
	else begin
		{have a look for editboxes that have it attached}
		EditView := GetJimmyView(JimmyID, 0);
		if EditView<>nil then
			FindJimmy := PJimmyEditBox(EditView)^.Jimmy {DO NOT DISPOSE!}
		else
			FindJimmy := GetJImmy(JimmyID);
	end;
end;

{Checks if should be updating view or file}
procedure UpdateJimmy;
var View : PGroup;
begin
	if Jimmy^.RecNo>-1 then begin
		{check if being edited - if so, update just that view}
		View := PGroup(GetJimmyView(Jimmy^.RecNo,0));
		if View<>nil then begin
			UpdateJimmyView(View, Jimmy);
		end;
	end;
	Jimmy^.StoreSelf; {but always store anyway}
end;{}


{register as the task with all the desktop new menu items}
procedure CreateAndEditJimmy(Command : word); far;
var Jimmy : PJimmy;
		InitParam : TJimmyInitParam;
		Event : TEvent;
begin
	{Check creator list}
	if CreatorExists(Command) then begin {taskcommand is set in tasks.pas}
		if LowMemory then begin
			Kameleon^.OutOfMemory;
			exit;
		end;
		{set up initialisation param}
		InitParam.ListView := nil;
		InitParam.ForWho := -1;
		Event.What := evBroadCast;
		Event.Command := cmGetFocusedJimmy;
		Event.InfoLong := -1;
		Desktop^.HandleEvent(Event);
		InitParam.FocusedID := Event.InfoLong;
		InitParam.FocusedParentID := -1;
		Jimmy := PJimmy(Create(Command, @InitParam));
		if Jimmy<>nil then Jimmy^.Edit(nil,nil);
	end;
end;


{returns pointer to view editing/etc jimmy; pass rec no of jimmy and lstype =0 for edit box, lstype for list, eg history}
function GetJimmyView(const JimmyID : longint; const lsType : word) : PView;
var IsJimmyEditedInfo : TIsJImmyEditedInfo;
begin
	ISJimmyEditedInfo.JimmyID := JImmyID;
	IsJimmyEditedInfo.lsType := lstype;
	GetJimmyView := PJimmyEditBox(Message(Desktop, evBroadCast, cmIsJimmyEdited, @ISJimmyEditedInfo));
end;

{Updates data in editbox of jimmy & redraws}
procedure UpdateJimmyView(const View : PGroup; const Jimmy : PJimmy);
begin
	with View^ do begin
		SetData(Jimmy^);
		Lock;
		Redraw;
		Unlock;
	end;
end;



procedure DeleteJimmy(var Jimmy : PJimmy; const COnfirm : boolean);
var S : string;
		fiType : byte;
		RecNo: longint;
		Control : word;
		EditView : PView;

begin
	{Check if allowed to...}
{$IFNDEF fixit}
	if not JImmy^.AllowDeletion then begin
		PauseMessage('DELETE','Delete not Allowed', hcNoContext);
		exit;
	end;
{$ENDIF}
	FileAdmin(fiJimmys)^.LogOn;

	{Check if locked}
	{$IFNDEF SIngleUser}
	JimmyStream^.Flush;

	Jimmy^.LoadLock;
	if Jimmy^.GetLock<>0 then begin
		Control := LockMessage('Entry',Jimmy^.GetLock,mfCancelOverRetry);
		if Control = cmRetry then begin
			DeleteJimmy(Jimmy,Confirm); {try again}
			exit;
		end;
		if Control = cmCancel then exit;
	end;
	{$ELSE}
	if GetJimmyView(Jimmy^.RecNo,0)<>nil then begin
		ProgramWarning('Cannot delete - is on screen'#13'Close view then try again', hcViewInUseMsg);
		exit;
	end;
	{$ENDIF}

	{Confirm with user}
	if COnfirm then begin
		S := Jimmy^.Getname(naFull,0);
		if (ucase(S) = 'GETNAME NOT DEFINED') or (S='') then S := 'Item' else S := ucase(S);
		S := S + ' will be deleted';
		if Jimmy^.Getfitype(ixArchive)<>0 then S := S + ' and Archived';
	end;

	if not Confirm or
		(MessageBOx('DELETE',S
							+#13#13+'ARE YOU SURE?', mfConfirmation + mfYesNo, hcNoContext) = cmYes) then begin

		Jimmy^.Deleted := True;    {mark as deleted - necessary for archiving}
		Jimmy^.StoreSelf;
	end;

	FileAdmin(fiJimmys)^.LogOff;
end;

{***************************************************
 ***             MERGE JIMMY                     ***
 ***************************************************}
procedure MergeJimmys(DelJimmy, MergeJimmy : PJImmy);
var HookJimmy : PJimmy;
		hkType, temphktype,htType : byte;
		SubHookToID, HookToID: PLongint;
		Key : longint;
		InsBias : boolean;

begin
	{user confirm}
	if MessageBox('MERGE',
							'About to add details of '+DelJimmy^.GetName(naFull,0)+' to '+MergeJimmy^.GetName(naFull,0)+#13+
							'and then remove '+DelJimmy^.GetName(naFull,0), mfConfirmation + mfYesNo,
							hcDirMerge)
			= cmNO then exit;

	{merge hooked items by running through and doing a kind of sethooktoid}
	FileAdmin(fiHooks)^.LogOn;
	for hkType := 1 to MergeJimmy^.NumhkTypes do begin

		DelJimmy^.LoadFirstHookPtr(hkType); {in case storeself below changes}
		HookJimmy := HookFile^.GetFirst(DelJimmy^.GetFirstHookPtr(hkType),0);

		while HookJimmy<>nil do begin

			{now find the bit that has the hooktoID to the DelJimmy and change to
				the mergejimmy}
			for htType := 1 to HookJimmy^.NumHookTo do begin
				HooKJimmy^.GetHookTo(htType, HookToID, SubHookToID, TemphkType, Key, InsBias);

				if HookToID^ = DelJimmy^.RecNO then HookToID^ := MergeJimmy^.RecNo;
			end;

			HookJimmy^.StoreSelf;

			dispose(HookJimmy, done);

			{should now have been removed, so keep looking for first}
			DelJimmy^.LoadFirstHookPtr(hkType);
			HookJimmy := HookFile^.GetFirst(DelJimmy^.GetFirstHookPtr(hkType),0);

		end;
	end;
	FileAdmin(fiHooks)^.LogOff;

	DeleteJimmy(DelJimmy, False);
end;


{************************************************
 ***            OUTPUT/FORM CODES             ***
 ************************************************}
constructor TJimmyFormCode.Init;
begin
	inherited Init(NCode);
	ID := NID;
	Jimmy := nil;
end;

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

function TJimmyFormCode.Replace;
var Found : boolean;
		FullCode : string;
begin
	Found := False;
	if ID=-1 then begin
		{No ID set - eg contact for in letters to person}
		LSClear(LString);
		Found := True;
	end else begin
		if Jimmy = nil then begin
			{if jimmy not loaded then load & check codes again}
			Jimmy := GetJimmy(ID);
			FormCodes^.SetPrefix(Code^);
			Jimmy^.SetFormCodes(FormCodes); {tied code should be set to normal prefix}
			FormCodes^.SetPrefix(''); {clear}
			{try decode again now that more codes are set}
			FullCode := Code^; if subCode<>'' then FullCode := FullCode + '.'+SubCode;
			Found := FormCodes^.Decode(FullCode, Param, LString);
		end;
		{if not found so far, try the jimmys decode method}
{		if not FOund and (Jimmy<>nil) then
			Found := Jimmy^.Decode(SubCode,Param,LString);{no such thing any more}
	end;
	Replace := Found;
end;



{****************************************************************
 ***                                                          ***
 ***                  JIMMY DEFINITION                        ***
 ***                                                          ***
 ****************************************************************}

{All ought to be overriden/added to by descendant...}
constructor TJimmy.Init;
begin
	inherited Init;
	CommonInit;
end;

procedure TJimmy.CommonInit;
begin
	RecNo := -1;
	LockTerminal := 0;
	LockCount := 0;
	GotbyIx := 0;
	Deleted := False;
	Tag := False;
	ClearAllPtrs;
	InsertTime.Clear;
	AllowChanges := True;
end;

{*************************************
 ***      ENQUIRIES                ***
 *************************************}

function TJimmy.Blank;
begin Blank := false; end;

function Tjimmy.AllowDeletion : boolean;
begin AllowDeletion := True; end;

function TJimmy.OneHookEntryOnly : boolean;
begin OneHookEntryOnly := False; end;

{*************************************
 ***    EDITING/DISPLAY            ***
 *************************************}
procedure TJimmy.MakeEditBox;
begin
	{descendants should create a JimmyEditBox onto heap, with all sub
	views required}
	{This can be called to skip the appropriate pre-bytes}
	EditBox^.Insert(New(PSkipBytes, init(Sizeof(TJimmy))));
end;

procedure TJimmy.AddFields;
begin end;

function TJimmy.DisplayLine;
begin DisplayLine := 'Display Line not defined'; end;

function TJimmy.GetName;
begin GetName := ''; end;

{************************************************************
 ***                                                      ***
 ***                 EDIT PROCEDURE                       ***
 ***                                                      ***
 ************************************************************}
{I have a feeling this should be a separate procedure, rather
than a method of the jimmy, but it works, ok?!}

{Handles complete editing function, usually no need to override}
function TJimmy.Edit;
var EditBox : PJimmyEditBox; {Makeeditbox *must* make a tjimmyeditbox}
		Result : word;
		Modal : boolean;

begin
	if LowMemory then begin
		Kameleon^.OutOfMemory;
		exit;
	end;

	{set edit mode - if caller is modal or the acceptor line exists, we
	 must make this one modal, to capture inputs in the former and so that
	the acceptor cannot be closed before this is completed in the latter}
	if ((Caller<>nil) and (Caller^.GetState(sfModal))) or (AcceptorView<>nil) then
		Modal := True
	else
		Modal := False;

	{--- Check lock/in use markers ---}

	{This involves taking care of "modalness":
		1) where both what is already on-screen and what this needs to be are
			non-modal.  Just focus on the one existing.
		2) Where the one on-screen is non-modal, we can assume this one will
			be modal due to the nature of the way as soon as one view becomes
			non-modal, any further boxes have to be modal too.  I can't see a way
			of safely handling this, so it comes up with a message and abandons
		3) where this one needs to be modal and the one on-screen is non-modal.
			Once we know we are modal, there must be a view somewhere dependant on
			another (eg an inputline access), so we cannot focus back on an old
			one as disposing of it/closing it can crash any dependant views}

	if RecNo>-1 then begin
		if not Modal then
			Result := IncLock(laMsgReFocus)
		else
			Result := IncLock(laMsgInUse);

		if Result = rsCancel then begin
			Free;
			exit;
		end;
	end;

	MakeEditBox(PEditBox(EditBox), Caller);

	if Kameleon^.ValidView(EditBox)<>nil then begin

		EditBox^.AcceptorView := AcceptorView;
		EditBox^.ViewOnly := not AllowChanges;

		EditBox^.SetData(Self);

		{runs through linkers, forcing all if this is a new jimmy, or just
			doing the ones with initforcelink set as true if not}
		if RecNo=-1 then
			EditBox^.CheckInitLinks(True)
		else
			EditBox^.CheckInitLinks(False); {runs through linkers looking to see if forceinitlink is set and forceing link if sp}

		{if the caller is modal, then this box should be too.  This is required
		lists created for inputlines have to be modal (you don't want to let the
		inputline be disposed of or the list will have no where to accept to - and
		it's acceptor link will no longer point anywhere sensible).  And if they
		then spawn dialog boxes, these have to be modal in order to receive
		events....}
		if Modal then begin
			Edit := Desktop^.ExecView(EditBox);
			dispose(EditBox, done);
		end else
			Desktop^.Insert(EditBox);
	end;

end;



{************************************************************
 ***                                                      ***
 ***                 FORMS AND CODES                      ***
 ***                                                      ***
 ************************************************************}
{loads supplements.  Note that prefix ought to be set by caller}
procedure TJimmy.SetFormCodes;
begin
{	Loadsupplements;{}
end;

{Used for hooked-on lists to associate a usual code with an attached
*type* of jimmy - eg <NOTE> for attached more-about notes}
{function TJimmy.FormAccessCode;
begin FormAccessCode := ''; end;{not used any more}


{function TJimmy.Decode;
begin Decode := False; end; {not decoded}
{procedure TJimmy.Decode;
begin end;{}

{************************************************************
 ***                                                      ***
 ***                 PRINTING                             ***
 ***                                                      ***
 ************************************************************}

const
	DefaultPrintType : TJimmyPrintType =
		(Editor : edInternal;
		 Target : ptPrint;
		 DeviceName : '';
		 PrintAs : 0;
		 FormName : '';
		 NumCopies : 1;
		 PlusLabel : False);

{=== SET DEFAULT PRINT TYPE ====================}
procedure TJimmy.GetDefaultPrintType(var PrintType : TJimmyPrintType; var PrintAs : PSitem; var PrintAsLink : pointer);
begin
	PrintType := DefaultPrintType;
	PrintAs := nil;
	PrintAsLink := nil;
end;

{============== PRINT ==========================}
{ie get print type, ask user to confirm/modify print type, and print}
procedure TJimmy.Print;
var PrintType : TJimmyPrintType;
		PrintAs : PSItem;
		PrintAsLink : pointer;
		Control : word;

begin
	PrintAs := nil;
	PrintASLink := nil;

	GetDefaultPrintType(PrintType, PrintAs, PrintAsLink);

	Control := GetPrintType(PrintType, PrintAs, PrintAsLink);

	if Control = cmCancel then exit; {break;{}

	PrintPrintType(PrintType);
end;

{function TJimmy.GetFaxNum;
begin GetFaxNum := ''; end;{}


{============== PRINT =============================}
{Print in a particular format/etc according to PrintType}
procedure TJimmy.PrintPrintType(const PrintType : TJimmyPrintType);
var	Device : PDeviceStream;
		Copy : byte;

begin
	ThinkingOn('Printing');

	{set device - see jimprint.pas}
	Device := SetDeviceFrom(PrintType.Editor, PrintType.Target, PrintType.DeviceName);

{	if (PrintType.Target = ptFax) then Device^.FaxToNumber := GetFaxNum;{}

	if Device=nil then begin
		ProgramError('Target not understood - device = nil'#13'JimPrint - SetJimmyPrintDevice',hcInternalErrorMsg);
		exit;
	end;

	with PrintType do begin

		{$IFDEF kwplink}
		if (Editor= edWP51) and (Target=ptFax) then PWPStream(Device)^.SendAsFax := True;
		{$ENDIF}

		{--- Print As ------}

		{PRINT!}
		for Copy := 1 to NumCopies do
			if delspaceR(FOrmName)<>'' then
				PrintForm(Device, FormName)
			else
				PrintFull(Device, PrintType.PrintAs);

		{-- post printing ----}
		case Editor of
			{$IFDEF kwplink}
			edWP51 : begin
				case Target of
{					ptPrint : CallWP(WPStream^.DosFileName, WPSetup.PrintMacro); {auto done}
					ptFax 	: PWPStream(Device)^.SendAsFax := False; {switch offFaxWP(WPStream^.DosFileName);{}
					ptView	: CallWP(WPStream^.DosFileName, '');  {just edit}
				end;
			end;
			{$ENDIF}
			edInternal : begin
				case Target of
					ptView 		: begin
						if Device^.IsOpen then Device^.Close; {so buffer is flushed}
						EditTextFile(Device^.DosFileName, ''); {edit output file}
					end;

					{should happen automatically - see fax printer device - on endprint}
{					ptFax  internal fax driver...?! One day!  Or 3rd party to fax ascii file...}
				end;
			end;
		end;

		{onprinting}
		if Device^.Status=stOK then OnPrinting(PrintType);

		{--- dispose of device ----}
		if not Device^.Permanant then dispose(Device, done);

		{plus label}
		if PlusLabel then DoLabelNow('',NumCopies,0);

	end; {with printtype}

	ThinkingOff;
end;

{******************************************************
 ***            USER-CONFIRM PRINT TYPE             ***
 ******************************************************}
function TJimmy.GetPrintType(var PrintType : TJimmyPrintType; PrintAs : PSItem; PrintAsLink : pointer) : word;
begin
	GetPrintType := GetJimmyPrintType('Print '+GetName(naFull,0),PrintType, PrintAs, PrintAsLink); {see jimprint unit}
end;


{***************************************************
 ***                PRINT FORM                   ***
 ***************************************************}

procedure TJimmy.PrintForm(const Device : PDeviceStream; const FOrmName : FNameStr);
begin
{	LoadSupplements;{}
	Device^.FormCodes^.SetPrefix('');
	SetFormCodes(Device^.FormCodes);
	if Right(FOrmName, 4)='.FRM' then begin
		Device^.PrintForm(FormName);
		if not Device^.FormFound then
			ProgramWarning('Form required to Print'#13#10
										 +'Create '+FormName+' in Maintenance', hcEditForm);
	end else begin
		Device^.StartPrint(FormName, '');
		if not Device^.FormFound then
			ProgramWarning('Header required to Print'#13#10
										 +'Create '+FormName+'.HDR in Maintenance', hcEditForm);
		Device^.FormCodes^.SetPrefix('');
		SetFormCodes(Device^.FormCodes); {reset for footer}
		Device^.EndPrint;
	end;

end;

procedure TJimmy.PrintFull(const Device : PDeviceStream; const PrintAs : word);
begin end; {descendants should do a printform or whatever}

procedure TJimmy.PrintSummary(const Device : PDeviceStream; const PrintAs : word);
begin PrintLine(Device); end;

procedure TJimmy.PrintLine(Const Device : PDeviceStream);
begin
	Device^.writeln(TabOut(DisplayLine(-1,0,0,0), #20#40#60#80, Device^.Paper^.Width));
end;

{some descendants may want to set sentdate, etc}
procedure TJimmy.OnPrinting;
begin end;

{============== LABEL ===================}
procedure TJimmy.PrintLabel;
begin end;

procedure TJimmy.DoLabelNow;
var Device : PDeviceStream;
		I : byte;
begin
	New(Device, init('Label',WorkPath + 'LABEL.NOW'));
	Device^.StartPrint('','');

	for I := 1 to NumCopies do begin
		if delspaceR(FormName)='' then
			PrintLabel(Device,LabelAs)
		else
			PrintForm(Device, FormName+'.LBL');
		Device^.writeln(#12); {end of label marker}
	end;

	Dispose(Device, done);
	PrintLabels(WorkPath+'LABEL.NOW');  {See labels}
end;

procedure TJimmy.DeferLabel;
var Device : PDeviceStream;
		I : byte;
begin
	ThinkingOn('Deferring Label(s)');
	OpenDeferredLabels(Device);

	for I := 1 to NumCopies do begin
		if delspaceR(FormName)='' then
			PrintLabel(Device,LabelAs)
		else
			PrintForm(Device, FormName+'.LBL');
		Device^.writeln(#12); {end of label marker}
	end;

	dispose(Device, done);
	ThinkingOff;
end;


{************************************************************
 ***                                                      ***
 ***                 DATABASE                             ***
 ***                                                      ***
 ************************************************************}

{eg note text}
{procedure TJimmy.Loadsupplements;
begin end;{}

function TJimmy.PtrOffset : byte;
begin PtrOffset := 1+1+2+1; end; {descendant's ver, jimmy's ver, lock markers, delete/tag marker}

{in order to allow extra stuff to be done when storeing a jimmy (see
TDirectoryItem), but which should be done before Notifying views}
procedure TJimmy.OnStoreing(const DiskJimmy : PJimmy);
begin end;

{extra stuff which needs to be done before hooking/indexing - eg timing in krally}
procedure TJimmy.PreStoreing(const DiskJimmy : PJimmy);
begin end;

function TJImmy.RecSize : word;
begin RecSize := 0; end; {gives record size}

function TJimmy.srType : word;
begin ProgramError('No srtype defined',hcInternalErrorMsg); Abstract; end; {for identifying in hooking, etc}

procedure TJimmy.DoRepeater;
begin
	RecNo := -1; {so stores as new}
	ClearAllPtrs;
	{Descendant then deals with NewDate}
end;


{========= GENERAL ===============}

constructor TJimmy.Load;
var	PRec : Plongint;
		fiType : byte;
		B : byte;
		Buffer : array[1..5] of byte;

begin
	CommonInit;

	{lock}
	S.Read(B,1);
	if B<250 then begin
		{pre v4.3a, pre "version"}
		{once all updated, remove this & read in four bytes - ver, locks, markers - at once for speed}
		LockCount 		:= B and $F0; LockCount := LockCount shr 4;
		LockTerminal 	:= B and $0F;

		{markers}
		S.Read(B, 1);
		Deleted := (B and $01)>0;
		Tag := (B and $02)>0;
	end else
		case B of
			255: begin
				S.Read(Buffer,3);
				{lock details - see also LoadLock/StoreLock}
				LockCount 		:= Buffer[1];
				LockTerminal 	:= Buffer[2];
				{markers}
				Deleted := (Buffer[3] and $01)>0;
				Tag := (Buffer[3] and $02)>0;
			end;
		end;

	{don't do via loadptr, etc, as they flush the stream buffer}
	{load index pointers (incl archive, ixtype=0)}
	for B := 0 to NumixTypes do begin
		GetIndex(B, PRec, fiType);
		if PRec<>nil then S.Read(PRec^, 4);
	end;

	{load hook pointers}
	for B := 1 to NumhkTypes do begin
		GetHookOn(B, PRec);
		if PRec<>nil then S.Read(PRec^, 4);
	end;

	{load "other" pointers}
	for B := 1 to NumopTypes do begin
		Getop(B, PRec);
		if PRec<>nil then S.Read(PRec^, 4);
	end;{}
end;

procedure TJimmy.StoreFields;
var	PRec : Plongint;
		fiType, B, BufferSize : byte;
		Buffer : array[1..99] of byte;

begin
	Buffer[1] := 255; {version}

	{lock}
	Buffer[2] := LockCount;
	Buffer[3] := LockTerminal;

	{markers}
	Buffer[4] := 0;
	if Deleted 	then Buffer[4] := Buffer[4] or $01;
	if Tag 			then Buffer[4] := Buffer[4] or $02;

	BufferSize := 4;

	{store index pointers, incl archive ixtype=0}
	for B := 0 to NumIxtypes do begin
		GetIndex(B, PRec, fiType);
		if PRec<>nil then	begin
			Move(PRec^, Buffer[BufferSize+1], 4);
			inc(BufferSize, 4);
		end;
	end;

	{store hook pointers}
	for B := 1 to NumhkTypes do begin
		GetHookOn(B, PRec);
		if PRec<>nil then	begin
			Move(PRec^, Buffer[BufferSize+1], 4);
			inc(BufferSize, 4);
		end;
	end;

	{store "other" pointers}
	for B := 1 to NumopTypes do begin
		Getop(B, PRec);
		if PRec<>nil then	begin
			Move(PRec^, Buffer[BufferSize+1], 4);
			inc(BufferSize, 4);
		end;
	end;

	{and WRITE in ONE BIG LUMP!}
	S.Write(Buffer, BufferSize);

	{store index pointers}
{	for B := 0 to NumIxtypes do begin
		GetIndex(B, PRec, fiType);
		if PRec<>nil then	S.Write(PRec^, 4);
	end;

	{store hook pointers}
{	for B := 1 to NumhkTypes do begin
		GetHookOn(hkType, PRec);
		if PRec<>nil then	S.Write(PRec^, 4);
	end;

	{store "other" pointers}
{	for B := 1 to NumopTypes do begin
		Getop(opType, opRec);
		if PRec<>nil then	S.Write(PRec^, 4);
	end;{}

end;

procedure TJimmy.Store;
var StartPos : longint;

begin
	StartPos := S.GetPos;

	StoreFields(S);

	TopUpRecord(S, RecSize, StartPos);
end;

{***********************************************
 ***         STORESELF                       ***
 ***********************************************}
{Like the .edit method, not sure if it should be
an independant procedure...?}

{indexes & hooks up as necessary}
procedure TJimmy.StoreSelf;
var DiskJimmy : PJimmy;
		hkType, opType, ixType : byte;

	{===== Sends a messages to all views =======}
		{pass general notification message, so that inpjimmy lines, list views,
		etc can update.  }
	procedure NotifyViews;
	var	JimmyStoredInfo : TJImmyStoredInfo;

	begin
		JimmyStoredInfo.Jimmy := @Self;
		JimmyStoredInfo.OldJimmy := DiskJimmy;
		Message(Desktop, evBroadcast, cmJimmyStored, @JimmyStoredInfo);
	end;

begin
	FileAdmin(fiJimmys)^.LogOn; {for speed - open/close once}

	if RecNo=-1 then begin
		{new}
		DiskJimmy := nil;
		PutJimmy(@Self);
		PreStoreing(DiskJimmy);
		HookUpNewJimmy(@Self);
		InsertTime.Start;
		IndexNewJimmy(@Self);
		InsertTime.Stop;
		OnStoreing(DiskJimmy);
		NotifyViews;
	end else begin
		{edited}
		DiskJImmy := GetJimmy(RecNo); {get original one}
		{copy over pointers that may have been changed}
		for ixType := 0 to NumixTypes do SetIdxPtr(ixType, DiskJimmy^.GetIdxPtr(ixtype));
		for hkType := 1 to NumhkTypes do SetFirstHookPtr(hkType, DiskJimmy^.GetFirstHookPtr(hktype));
		for opType := 1 to NumopTypes do SetopPtr(opType, DiskJimmy^.GetopPtr(optype));

		PutJimmy(@Self);
		PreStoreing(DiskJimmy);
		HookUpEditedJimmy(Diskjimmy, @Self);
		InsertTime.Start;
		IndexEditedJimmy(DiskJimmy, @Self);
		InsertTime.Stop;
		OnStoreing(DiskJimmy);
		NotifyViews;
		dispose(DiskJimmy, done);
	end;

	FileAdmin(fiJimmys)^.LogOff;
end;                   {}


{****************************************************
 ***          HOOKING A JIMMY ON                  ***
 ****************************************************}

{==== DESCENDANT OVERRIDERS =========================}
{These two provide all the details that the hooking
procs need to know in order to hook things on to this jimmy}
function TJimmy.NumhkTypes;
begin NumhkTypes := 0; end;

{pass hktype and return a pointer to the longint pointing to the first chain item}
procedure TJimmy.GetHookOn;
begin HookRec := nil; end;

{When hooking something on, can override these to do things}
function TJimmy.HookingOn;
begin HookingOn := False; end;

function TJimmy.UnHooking;
begin UnHooking := False; end;

function TJimmy.ReHooking;
var B : boolean;
begin
	B := UnHooking(hkType, htType, OldJimmy); {unhook old one}
	B := HookingOn(hkType, htType, Jimmy) or B;  {rehook new one}
	ReHooking := B; {B is set if either of above are true}
end;

{==== DISK POINTER METHODS ==========================}

{Load/Store/etc methods for directly accessing just that pointer on-disk}
function TJimmy.GetFirstHookPtr(const hkType : byte) : longint;
var L : Plongint;
begin
	GetHookOn(hkType, L);
	if L=nil then GetFirstHookPtr := -1 else GetFirstHookPtr := L^;
end;

procedure TJimmy.SetFirstHookPtr(const hkType : byte; const NFirstHookPtr : longint);
var L : Plongint;
begin
	GetHookOn(hkType, L);
	if L<>nil then L^ := NFirstHookPtr;
end;
{}
procedure TJimmy.LoadFirstHookPtr( const hkType : byte);
var L : Plongint;
begin
	GetHookOn(hkType, L);
	if L<>nil then L^ := LoadPtr(HookPtrPos(hkType));
{		else ProgramError('No HookPtr defined for hktype='+N2Str(hkType)+#13
												+'Jimmy srtype'+N2Str(SrType)+' .LoadFirstHookPtr');{}
end;

procedure TJimmy.StoreFirstHookPtr(const hkType : byte);
var L : Plongint;
begin
	GetHookOn(hkType, L);
	if L<>nil then StorePtr(HookPtrPos(hktype), L^)
	else ProgramError('No HookPtr defined for hktype='+N2Str(hkType)+#13
												+'Jimmy srtype '+N2Str(SrType)+' TJimmy.StoreFirstHookPtr'#13+
												GetName(naDisplay,0),hcInternalErrorMsg);
end;


{****************************************************
 ***          HOOKING A JIMMY ON                  ***
 ****************************************************}

{===== DESCENDANT OVERRIDERS ========================}
{These methods, for a given htType (Hook-to type), will return
all the information required by hooking on procs (see jimhooks)
to know what to hook to, what indexing key to use, etc
See also indexing below}

function TJimmy.NumHookTo;
begin NumHookto := 0; end;

procedure TJimmy.GetHookTo;
begin
	HookToID := nil;
	SubHookToID := nil;
	Key := 0;
	hkType := 0;
  InsertBias := biEnd;
end;


{****************************************************
 ***               MISCELLANEOUS POINTERS         ***
 ****************************************************}
function Tjimmy.NumopTypes;
begin NumopTypes := 0; end;

procedure TJimmy.Getop;
begin
	opRec := nil;
end;


{****************************************************
 ***               POINTERS TO OTHER JIMMYS       ***
 ****************************************************}
function TJimmy.NumIDs;
begin NumIDs := 0; end;

function TJimmy.GetJImmyID;
begin	GetJimmyID := nil; end;


{****************************************************
 ***              INDEXING                        ***
 ****************************************************}
{a descendant's IdxPtr returns a pointer to each pointer to an index file
(and the pointer to a chain of hooked-on pointers in the case of HookPtr).
Given the arbitrary ixtype (or hktype for hookptr) which has a value of 0
(archive ptr) to 9, it returns details on an index ptr - a pointer to the
actual longint value in the jimmy descendant, and fitype being the index file
it should be connected to.

Note that this routine should *always* return the details, no matter the
circumstances of the particular jimmy; eg it should return details of the
archive ptr even if the jimmy is not deleted.  this is so that the ptr
storeage and updating can work with a consistant record layout.

Some routines need to know where in the file record the pointer is stored, eg
indexing routines, so that the whole jimmy does not have to be gotten &
replaced all the time.  This is calculated by running through the idxptr
and hookptr methods, and adding the value returned by the PtrOffset method, which
is usually a fixed 1+1+1 (ver, lock, delete markers) but may be changed if
there is other information stored before the indexes, ie before the inherited
StoreFields/Load methods are called}
function TJimmy.NumixTypes : byte;
begin NumixTypes := 0; end;

procedure TJimmy.GetIndex;
begin
	IdxRec := nil;
	fiType := 0;
end;

function TJimmy.GetIndexKey;
begin GetIndexKey := ''; end;

function TJimmy.GotByAlias;
begin GotByAlias := False; end;


{==== DISK POINTER METHODS ==========================}

function TJimmy.GetfiType(const ixType : byte) : byte;
var L : Plongint;
		fitype : byte;
begin
	GetIndex(ixType, L, fiType); {get details}
	GetfiType := fitype;
end;{}


function TJimmy.GetIdxPtr(const ixtype : byte) : longint;
var L : Plongint;
		fitype : byte;
begin
	GetIndex(ixType, L, fiType); {get details}
	if L=nil then GetIdxPtr := -1 else GetIdxPtr := L^;
end;

procedure TJimmy.SetIdxPtr(const ixType : byte; const NIdxPtr : longint);
var L : Plongint;
		fiType : byte;
begin
	GetIndex(ixType, L, fiType); {get details}
	if L<>nil then L^ := NIdxPtr;
end;{}

procedure TJimmy.LoadIdxPtr(const ixType : byte);
var L : Plongint;
		fiType : byte;
begin
	GetIndex(ixType, L, fiType); {get details}
	if L<>nil then L^ := LoadPtr(IdxPtrPos(ixType));
end;


procedure TJimmy.StoreIdxPtr(const ixType : byte);
var L : Plongint;
		fiType : byte;
begin
	GetIndex(ixType, L, fiType); {get details}
	if L<>nil then StorePtr(IdxPtrPos(ixType), L^);
end;


{****************************************************
 ***           "OTHER" POINTERS                   ***
 ****************************************************}
{used for miscellaneous data structures that need to
have the same independant updating as indexes/hooks - ie
they may be changed on disk while a jimmy is being edited}
procedure TJimmy.Loadop;
var L : Plongint;
begin
	Getop(opType, L);
	if L<>nil then L^ := LoadPtr(opPos(opType));
end;


procedure TJimmy.Storeop;
var L : PLongint;
begin
	Getop(opType, L);
	if L<>nil then StorePtr(opPos(opType), L^);
end;

function TJimmy.GetopPtr(const optype : byte) : longint;
var L : Plongint;
begin
	Getop(opType, L); {get details}
	if L=nil then GetopPtr := -1 else GetopPtr := L^;
end;

procedure TJimmy.SetopPtr(const opType : byte; const NopPtr : longint);
var L : Plongint;
begin
	Getop(opType, L); {get details}
	if L<>nil then L^ := NopPtr;
end;{}

{****************************************************
 ***           POINTER METHODS                    ***
 ****************************************************}

{============== PTR LOADING/STORING ========}
{These are useful functions for the descendants of the above ones.  Pass
Posit as the offset of the pointer from the beginning of the stored
object (*not* including the 2 byte srtype)}
{They both assume the jimmy is located on the jimmystream}
function TJimmy.LoadPtr;
var FilePtr : longint;
begin
	if RecNo=-1 then
		ProgramError('Trying to load a pointer for a Jimmy with RecNo=-1'+CRLF
										+'srtype='+N2Str(srType)+' Posit='+N2Str(Posit),hcInternalErrorMsg)
	else begin
		FileAdmin(fiJimmys)^.LogOn;
		with JimmyStream^ do begin
			{$IFNDEF SingleUser} Flush; {$ENDIF}
			Seek(RecNo+2+Posit); {recno points to exact byte location, skip 2 bytes ID & move to position}
			Read(FilePtr, 4);
		end;
		FileAdmin(fiJimmys)^.LogOff;
		LoadPtr := FilePtr;
	end;
end;

procedure TJimmy.StorePtr;
begin
	if RecNo=-1 then
		ProgramError('Trying to store a pointer for a Jimmy with RecNo=-1'+CRLF
										+'srtype='+N2Str(srType)+' Posit='+N2Str(Posit),hcInternalErrorMsg)
	else begin
		FileAdmin(fiJimmys)^.LogOn;
		with JimmyStream^ do begin
			{$IFNDEF SingleUser} Flush; {$ENDIF}
			JimmyStream^.Seek(RecNo+2+Posit); {recno points to exact byte location}
			JimmyStream^.Write(FilePtr, 4);
			{$IFNDEF SingleUser} Flush; {$ENDIF}
		end;
		FileAdmin(fiJimmys)^.LogOff;
	end;
end;

function Tjimmy.IdxPtrPos(const ixType : byte) : byte;
var i : integer;
		P : byte;
		L : Plongint;
		fiType : byte;

begin
	P := PtrOffset;
	for i:=0 to ixtype-1 do begin
		GetIndex(i, L, fiType);
		if L<>nil then inc(P, 4);
	end;
	IdxPtrPos := P;
end;

function Tjimmy.HookPtrPos(const hkType : byte) : byte;
var i : byte;
		P : byte;
		L : Plongint;

begin
	P := IdxPtrPos(NumixTypes+1);
	for i:=1 to hktype-1 do begin
		GetHookOn(i, L);
		if L<>nil then inc(P, 4);
	end;
	HookPtrPos := P;
end;

function TJimmy.opPos(const opType : byte) : byte;
var P : byte;
		L : PLongint;
		i : byte;
begin
	P := HookPtrPos(NumhkTypes+1);
	for i:=1 to optype-1 do begin
		Getop(i, L);
		if L<>nil then inc(P, 4);
	end;
	opPos := P;
end;


{======= LOCKING =====================}
{check lock, & set/increment if possible}
function Tjimmy.IncLock(Action : word) : word;
var Command : word;
		View : PView;
begin
	{terminals >$F are considered view-only - eg press rally}
	if LockTerminal>$0F then exit;

	LoadLock;
	IncLock := rsOK;

	if LockTerminal = TerminalNo then
		{Locked by self}
		case Action of
			laMsgInUse,laMsgReFocus : begin
				View := GetJimmyView(RecNo, 0);
				if View=nil then begin
					{there isn't another view editing it}
					Command := MessageBox('LOCK',
																'Entry in use by another process',
																mfCancelOverRetry or mfWarning, hcLockMsg);
					case COmmand of
						cmRetry : begin
							IncLock := IncLock(Action);
							exit;
						end;
						cmCancel : begin
							IncLock := rsCancel;
							exit;
						end;
						cmOverride : begin
							LockCount := 0; {terminal set below}
						end;
					end; {case command}
				end else begin
					{there is a view already editing this jimmy}
					if Action = laMsgInUse then begin
						MessageBox('LOCK',
												'Entry already being edited'#13'Close views to reach it',
												mfCancelButton, hcViewInUseMsg);
						IncLock := rsCancel;
						exit;
					end else begin
						View^.MakeFirst;
						View^.Focus;
						IncLock := rsCancel;
						exit;
					end;
				end; {if}
			end;
			laAllowView : begin
				IncLock := rsViewOnly;
				exit;
			end;

		end {case action}
{$IFNDEF SingleUser}
	else
		if LockTerminal<>0 then begin
			{Locked by someone else}
			case Action of
				laMsgInUse, laMsgRefocus : begin
					Command := LockMessage('Entry ',LockTerminal, mfCancelOverRetry);
					case Command of
						cmRetry : begin
							IncLock := IncLock(Action);
							exit;
						end;
						cmCancel : begin
							IncLock := rsCancel;
							exit;
						end;
					end;
				end;
				laAllowView : begin
					IncLock := rsViewOnly;
					exit;
				end;
			end;
		end;
{$ELSE}
	;
{$ENDIF}

	LockTerminal := TerminalNo;
	inc(LockCount,1);

	StoreLock;
{don't bother	if LockCount=1 then Message(Desktop, evBroadcast, cmJimmyLocked, @Self);{}
end;

{decrement lock count}
procedure TJimmy.DecLock;
begin
	{terminals >$F are considered view-only - eg press rally}
	if LockTerminal>$0F then exit;

	LoadLock;
	if LockCount>0 then dec(LockCount,1);
	if LockCount=0 then
		SetLock(False)
	else
		StoreLock;
end;


{does auto store}
procedure TJimmy.SetLock(On : boolean);
var OldLock : byte;
begin
	{terminals >$F are considered view-only - eg press rally}
	if LockTerminal>$0F then exit;

	OldLock := GetLock;
	inherited SetLock(On);
	if (GetLock<>OldLock) and (RecNo>-1) then begin
		StoreLock;
{don't bother displaying
need to do it as OK button draws with lock marker...{}
		Message(Desktop, evBroadcast, cmJimmyLocked, @Self);{}
	end;
end;


{assumes first byte after srtype & ver - override if not}
procedure TJimmy.StoreLock;
begin
	{terminals >$F are considered view-only - eg press rally}
	if LockTerminal>$0F then exit;

	if RecNo>-1 then begin
		FileAdmin(fiJimmys)^.LogOn;
		with JimmyStream^ do begin
			{$IFNDEF SingleUser} Flush; {$ENDIF}
			Seek(RecNo+ 2 + PtrOffset-3); {skip srtype etc, & back past markers}
			Write(LockCOunt, 1);
			Write(LockTerminal, 1);
			{$IFNDEF SIngleUser} Flush; {$ENDIF}
		end;
		FileAdmin(fiJimmys)^.LogOff;
	end;
end;

procedure TJimmy.LoadLock;
begin
	{terminals >$F are considered view-only - eg press rally}
	if LockTerminal>$0F then exit;

	if RecNo>-1 then begin
		FileAdmin(fiJimmys)^.LogOn;
		with JimmyStream^ do begin
			{$IFNDEF SingleUser} Flush; {$ENDIF}
			Seek(RecNo + 2 + PtrOffset-3); {skip srtype & ver}
			Read(LockCount, 1);
			Read(LockTerminal, 1);
		end;
		FileAdmin(fiJimmys)^.LogOff;
	end;
end;

procedure TJimmy.LoadAllHooks;
var hkType : byte;
begin
	for hkType := 1 to NumhkTypes do LoadFirstHookPtr(hktype);
end;

procedure TJimmy.LoadAllop;
var opType : byte;
begin
	for opType := 1 to NumopTypes do Loadop(opType);
end;

procedure TJimmy.LoadAllPtrs;
var ixType : byte;
begin
	for ixtype := 0 to NumIxtypes do LoadIdxPtr(ixType);
	LoadAllHooks;
	LoadAllop;
end;

procedure TJimmy.ClearAllPtrs;
var ixType,hktype,opType : byte;
		L : Plongint;

begin
	for ixtype := 0 to NumIxtypes do SetIdxPtr(ixType, -1);
	for hkType := 1 to NumhkTypes do SetFirstHookPtr(hkType, -1);
	{we could clear the hookto's as well I suppose...}

	{miscellaneous pointers}
	for opType := 1 to NumopTypes do begin
		Getop(opType, L);
		if L<>nil then L^ := -1;
	end;
end;


procedure TJimmy.JimmyMarker;
begin end;


{		procedure TJimmy.idxPtr; begin end;
		procedure TJimmy.GetHookToID; begin end;
		procedure TJimmy.Hookptr; begin end;
		procedure TJimmy.GetKeyString; begin end;
		procedure TJimmy.LoadSupplements; begin end;
		procedure TJimmy.Decode; begin end;
		procedure TJimmy.FormAccessCode; begin end;{}

{*****************************************************************
 ***                                                           ***
 ***                      JIMMY STREAM                         ***
 ***                                                           ***
 *****************************************************************}
{probably not needed as a separate mnethod any more - here from old days.
Useful for the -1 check though
place to set recno though}
{all done in tdataastream now function  TJimmyStream.GetAt(RecNo : longint) : PObject;  {Seek & get combined}
{var P : PObject;

begin
	if RecNo = -1 then {allows easier code in places}
{		GetAt := nil
	else begin
		P := inherited GetAt(RecNo);
		GetAt := P;

		{done in tdatastream now}
{		if P<>nil then Pjimmy(P)^.RecNo := RecNo; {better not store anything other than jimmys on the jimmystream...}
{	end;
{
{	if Status<>stOk then ErrorMsg('Getting At '+L2Str(recNo));{Callers should do this sort of check}
{end;

{procedure TJimmyStream.PutAt(RecNo : longint; P : PObject); {Seek and put combined}
{begin
	inherited PutAt(RecNo, P);
	PJimmy(P)^.RecNo := RecNo; {not sure if this is a good idea, but it ought to stand good.}
{	if Status<>stOk then ErrorMsg('Putting At '+N2Str(recNo));
end;{}

function NewJimmyStream : PStream; far;
begin
	JimmyStream := New(PDataStream, init('JIMMYS.DAT',1, StreamBufSize));
	NewJImmyStream := JimmyStream;
end;

{shorthand to jimmy stream}
{function JimmyStream : PJimmyStream;
begin JimmyStream := PJimmyStream(FileAdmin(fiJimmys)^.FilePtr); end;{}

begin
	{$IFDEF FIXIT} writeln('Jimmys unit...'); {$ENDIF}
	NewFileAdmin(fiJimmys, 'Jimmy Stream', NewJimmyStream);
	JimmyStream := nil;

	RegisterRangeTask(DesktopTasks, 1000, 1999, @CreateAndEditJimmy);
end.
