{**************************************************************
 ***                                                        ***
 ***                     HOOK CHAIN VIEWER & FILES          ***
 ***                                                        ***
 **************************************************************}
{Extensions to the Linked List TTreeNode objects, providing routines, etc
for handling a jimmy attached to a tree node}

{$I compflgs}

unit JimHooks;

INTERFACE

uses 	linklist,
{$IFDEF WINDOWS}
	wui,	{windows}
{$ELSE}
	tuilist, tui,views,menus, tuijimmy, {text}
{$ENDIF}
			objects, drivers,
			devices, forms,
			jimmys;

{******************************************
 *** HOOK ITEM                          ***
 ******************************************}
type
 PHook = ^THook;
 THook = object(TNode)
	 SortKey : longint;   {a sortkey, from Jimmy^.getkey}
	 OwnerID : longint;  {Pointer back to jimmy these are hooks onto of... grammer?!}
	 srType : word;      {Type of object this Hook points to - use JImmy^.srType}
	 JimmyID : longint;   {Pointer to attached jimmy}

	 {-- Methods --}
	 constructor Init;
	 constructor Load(var S : TStream);
	 procedure   Store(var S : TSTream);
	 function    GetKey : longint; virtual;
{$IFDEF fixit}	 function		 Edit(Caller : PView) : word; virtual; {for debug} {$ENDIF}

 end;


{*****************************************
 ***     PURE CHAIN STREAM             ***
 *****************************************}
{provides methods for getting/searching/putting etc jimmys to a node stream}
type
	PHookStream = ^THookStream;
	THookStream = object(TNodeStream)

		FindRec : longint;
		FindsrType : word; {for find routines below}

		{--Methods used by external--}
		constructor Init(NFileName : FNameStr);
		destructor Done; virtual;

		function GetJimmyAtHook(RecNo : LongInt) : PJimmy;

		{---- searching & finding, etc ------}
		function GetHookForID(const FirstHookID, JimmyID : longint) : PHook;
		{finds chain item - useful for checking keys, see livestock}
		function GetFirstHook(FirstHookID : longint; srType : word) : PHook;
		function GetHookFrom(Forwards : boolean) : PHook;

		{returns ID of jimmy}
		function FindFirst(FirstHookID : longint; srType : word) : longint;
		function FindLast(FirstHookID : longint; srType : word) : longint; {looks for last srtype match}
		function FindFrom(Forwards : boolean) : longint;
		function FindNext : longint;
		function FindPrev : longint;

		{returns jimmy}
		function GetFirst(FirstHookID : longint; srType : word) : PJImmy; {looks for first srtype match}
		function GetLast(FirstHookID : longint; srType : word) : PJImmy; {looks for last srtype match}
		function GetFrom(Forwards : boolean) : PJimmy; {serves both next two methods}
		function GetNextJimmy : PJImmy; {looks for next srtype match in chain}
		function GetPrevJimmy : PJImmy; {looks for previous srtype match in chain}

		{variations}
		function IsEarlier(FirstHookID : longint; srType : word; Key : longint) : boolean; {checks if Key is latest}

	end;

{*****************************************
 ***     PURE CHAIN VIEWER             ***
 *****************************************}
{Pass pointer to Jimmy owner in memory (if retreived)
and hook type (hkType)}
const
	{for use with insmode}
	Child = True;
	Peer = False;

type
	PHookViewer   = ^THookViewer;          {Interior}
	THookViewer    = object(TNodeView)

		DummyParentJimmy : PJimmy; {Jimmy that owns the chain - empty, used to update pointers}
		hkType : word; {type of hook - eg history, more-about, see global hkxxx}
		srRestrictor : word;

		ClippedID : longint; {pointer to clipped jimmy}
		ClippedhtType : byte; {httype of clipped jimmy}
		ClippedChildID : longint;

		InsMode : boolean; {childmode inserts as child to focused view - o/w inserts as peer}

		constructor Init(var Bounds: TRect;
											NlsType : word;
											NsrRestrictor : word;
											NhkType : word;
											NParentJImmy : PJimmy);

		destructor Done; virtual;

		procedure Draw; virtual; {adding childmode marker}
		procedure Redraw; virtual;
		procedure HandleEvent(var Event : TEvent); virtual;

		function  GetText(const ItemNo : longint)  : string; virtual;
		procedure Edit(ItemNo : LongInt); virtual;
		procedure EditNew(ItemNo : LongInt; Command : word); virtual;

		procedure FocusOnJimmy(RecNo : longint);

		procedure PrintList(const TaggedOnly : boolean; const UpToItemNo,NumPages : longint); virtual; {straight print to printer}

		procedure DelJimmy(ItemNo : longint); virtual;

		procedure RedrawJimmy(const JimmyID : longint);

		procedure JimmyStored(const JimmyStoredInfo : PJimmyStoredInfo);

		procedure SetOwnersRoot; virtual; {Set's Node owner's pointer to first in Node}
		function GetOwnersRoot : longint; virtual;

		procedure PasteFromClipBoard(ItemNo : longint); virtual;
		procedure CutToClipBoard(ItemNo : longint); virtual;

	end;

	PDlgHookView = ^TDlgHookView;
	TDlgHookView = object(THookViewer)
		EditBox : PJimmyEditBox; {we cannot assume that owner^ is the editbox,
			as this view might be a subview of an inputgroup.  Therefore pass editbox
			as parameter}
		constructor Init(var Bounds: TRect;
											NlsType : word;
											NsrRestrictor : word;
											NhkType : word;
											NParentJImmy : PJimmy;
											NEditBox : PJimmyEditBox);

		procedure HandleEvent(var Event : TEvent); virtual;
	end;

	function NewJImmyHookWindow(Bounds : TRect; lsType : byte; srRestrictor, hkType : word; ParentJimmy : PJimmy;
																HookView : PHookViewer) : PListWindow;
	{Stand-alone window}
	{sets up menu, etc}
{	PJimmyHookWindow = ^TJimmyHookWindow;
	TJimmyHookWindow = object(TListWindow)
		constructor Init(Bounds : TRect;
											NlsType : word;
											NCallingView : PView;
											NSrRestrictor : word;
											NhkType : byte;
											NParentJimmy : PJimmy;
											HookView : PHookViewer);
	end;

	{Displays attached hook list - eg more about list/history}
type
	PHookListButton = ^THookListButton;
	THookListButton = object(TJimmyStoreButton)
		hkType : word;
		lsType : word;
		srRestrictor : word;
		constructor Init(X,Y : byte; ATitle: TTitleStr; ACommand: Word;
										AFlags: Word; NTiedObject : PJimmy; NhkType : word;
										NlsType : word);
		procedure Press; virtual;
	end;


	{===== FORM CODES ======================}
{	procedure PrintHooksFormCode(const Device : PDeviceStream;
																const Param : TFCodeStr;
																const Form : FNameStr;
																const Jimmy : PJimmy; const hkType : word);{}

	{for putting hook lists into forms.  Jimmy is disposed of when this is}
	PHookListFormCode = ^THookListFormCode;
	THookListFormCode = object(TFlushFormCode)
		hkType : word;
		FormName : FNameStr;
		srRestrictor : word;
		constructor Init(NCode : string; Jimmy : PJimmy; NhkType : word; NFormName : FNameStr; NsrRestrictor : word);
		procedure DoFunc(const Device : PDeviceStream; const SubCode, Param : TFCodeStr); virtual;
		procedure DoSpecial(var Tree : PTree; Param : TFCodeStr); virtual;
		procedure DoPrint(Device : PDeviceStream; Jimmy : PJimmy; Param : string); virtual;
	end;

	{for putting a particular type of item from a hook list}
{	PHookItemFormCode = ^THookFormCode;
	THookFormCode = object(TFlushFormCode)
		hkType : word;
		FormName : FNameStr;
		constructor Init(NCode : string; Jimmy : PJimmy; NhkType : word; NFormName : FNameStr);
		procedure DoFunc(const Device : PDeviceStream; const SubCode, Param : TFCodeStr); virtual;
		procedure DoSpecial(var Tree : PTree; Param : TFCodeStr); virtual;
	end;


	{**************************************
	 ***  global access point to stream ***
	 **************************************}

var HookFile : PHookStream;

	{***************************************
	 ***      HOOKING METHODS            ***
	 **************************************}

{Hooks up a jimmy using its getHookToID for all the other jimmys it ought
to hook to}
procedure HookUpEditedJimmy(var DiskJimmy : PJimmy; const JImmy : PJimmy);
procedure HookUpNewJimmy(const JImmy : PJimmy);


{Does one hook from a given jimmy to another given jimmy
	Used by cascading hooks}
procedure HookToID(		 const Jimmy : PJimmy; const ParentID, SubParentID : longint; const htType : word);
procedure UnHookFromID(const Jimmy : PJimmy; const ParentID : longint; const htType : word);


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

IMPLEMENTATION

uses 	tuimsgs,
			global,
			files,
			kamsetup,
			dbg,
			printers,
			{$IFNDEF singleuser} muser, {$ENDIF} {for cutting lockout message}
			faxes,
			help,
			messtext,
			status,
			tasks,
			tuiedit,
			app, minilib;


{********************************************
 ***             HOOK ITEM                ***
 ********************************************}
const
 {--- Required for Stream ----}

 RHook : TStreamRec = (
		 ObjType : srHook;
		 VmtLink : Ofs(TypeOf(THook)^);
		 Load : @THook.Load;
		 Store : @THook.Store
 );


{---- INITIALISATION -------}
constructor THook.Init;
begin
	 inherited Init;
	 {Minus one marks as being illegal - shows up errors}
	 OwnerID := -1;
	 NextID := -1;
	 PrevID := -1;
	 srType := 0;
	 JimmyID := -1;
 end;



{****************************
 ***    STREAMING         ***
 ****************************}
const
	 THookSize = 30; {THIS IS SET TO BE #BYTES USED BY STORE & LOAD BELOW}

{-------- LOAD -------------}
constructor THook.Load;
var Ver : byte;
		I : longint;
begin
	S.Read(Ver, 1);
	CommonInit; {clear heap pointers}
{	Expanded := False; {not allowed yet}
	case Ver of
		1 : begin
			S.Read(SortKey,  sizeof(Sortkey));
			S.Read(I,  4);
			S.Read(OwnerID, sizeof(OwnerID));
			S.Read(NextID,     sizeof(NextID));
			S.Read(PrevID,     sizeof(PrevID));
			S.Read(srType,  sizeof(srType));
			S.Read(JimmyID,  sizeof(JimmyID));
			ChildID := -1;
			Expanded := False;
		end;
		2 : begin
			S.Read(SortKey,  sizeof(Sortkey));
			S.Read(OwnerID, sizeof(OwnerID));
			S.Read(NextID,   4);
			S.Read(PrevID,   4);
			S.Read(ChildID,  4);
			S.Read(srType,  sizeof(srType));
			S.Read(JimmyID,  sizeof(JimmyID));
			Expanded := False;
		end;
		3 : begin
			{pre v4.3a}
			S.Read(SortKey,  4);
			S.Read(OwnerID, 4);
			S.Read(NextID,   4);
			S.Read(PrevID,   4);
			S.Read(ChildID,  4);
			S.Read(srType,  2);
			S.Read(JimmyID,  4);
			S.Read(Expanded, 1);
		end;
	else
		DBaseMessage(@S,'THook.Load, Unknown ver '+N2Str(Ver), mfError, hcInternalErrorMsg);
		fail;
	end;
end;

{-------- STORE ----------}
procedure THook.Store;
var StartPos : longint;
	 Ver : byte;

begin
	StartPos := S.GetPos;
	Ver := 3; S.Write(Ver, 1);
	S.Write(Sortkey,  4);
	S.Write(OwnerID, 4);
	S.Write(NextID,  4);
	S.Write(PrevID,  4);
	S.Write(ChildID, 4);
	S.Write(srType,  2);
	S.Write(JimmyID, 4);
	S.Write(Expanded, 1);

	TopUpRecord(S, THookSize, StartPos); {not really needed as usually stored in a special hook file}
end;


function THook.GetKey;
begin
	GetKey := Sortkey;
end;



{$IFDEF fixit}
{=== EDIT =======================}
{Edit Item - for debug purposes}
function THook.Edit;
var EditBox : PEditBox;
		R : TRect;

begin
	R.Assign(0,0,36,9);
	CentreOnView(R, Caller);
	New(EditBox, init(R, 'Hook Fix',Caller));

	with EditBox^ do begin

		Insert(New(PSkipBytes, init(sizeof(TDataItem))));
		Insert(New(PSkipBytes, init(sizeof(PNode)*4))); {skip next, prev, child, parent}

		InsTitledField(8, 2, 6, 1, 'Next', New(PInputLInt, init(R, 6)));
		InsTitledField(8, 3, 6, 1, 'Prev', New(PInputLInt, init(R, 6)));
		InsTitledField(8, 4, 6, 1, 'Child', New(PInputLInt, init(R, 6)));

		Insert(New(PSkipBytes, init(sizeof(Boolean)))); {skip expanded}

		InsTitledField(26, 2, 6, 1, 'SortKey', New(PInputLInt, init(R, 6)));
		InsTitledField(26, 3, 9, 1, 'OwnerID', New(PInputLInt, init(R, 9)));
		InsTitledField(26, 4, 6, 1, 'srType', New(PInputWord, init(R, 6)));
		InsTitledField(26, 5, 9, 1, 'JimmyID', New(PInputLInt, init(R, 9)));

		InsOKButton(5, 6, @Self);
		InsCancelButton(16, 6);

		EndInit;

		SetData(Self);
	end;

	Edit := Desktop^.ExecView(EditBox);

end;
{$ENDIF}


{function THook.GetsrType;
begin
	GetsrType := srType;
end;

{**************************************************************************
 ***                Hook STREAM DEFINITION                         ***
 **************************************************************************}
{A Data stream modified to form Hooks, such as history}

{======= INIT ================}
constructor THookStream.Init;
begin
	inherited Init(NFileName+'.CHA', THookSize, THookSize);       {Adds item size}

{	New(DataFile, Init(NFileName+'.DAT',1));{}
	FileAdmin(fiJimmys)^.LogOn;
end;

destructor THookStream.Done;
begin
{	Dispose(DataFile, done);{}
	FileAdmin(fiJimmys)^.LogOff;
	inherited Done;
end;


{===== FILE GET THRU CHAIN ============}
function THookStream.GetJimmyAtHook;
var Hook : PHook;
		Jimmy : PJimmy;

begin
	Jimmy := nil;

	if RecNo >-1 then begin
		Hook := PHook(GetAt(RecNo));  {So access through collection}
		if Status<>stOK then ErrorMsg('Pre-GetDataItem');

		if Hook<>nil then begin
			{Should now get the data item (jimmy!), load into DataItem}
			if Hook^.JimmyID<>-1 then begin
				Jimmy := PJimmy(JimmyStream^.GetAt(Hook^.JimmyID));

				if Jimmy = nil then begin
					{don't give a warning - just leave to be displayed on list, o/w with
					wrong flavour kameleons can end up with lots of errors trying to
					display history list with uncompiled jimmy types, and don't
					reset so that callers can check error}
					RecordError('DBASE ERROR',
											'Could not retrieve Jimmy @'+N2Str(Hook^.JimmyID),
											'from Hook @'+N2Str(RecNo)+' sr'+N2Str(Hook^.srType));
				end;
			end;
			dispose(Hook, done);
		end;
	end;

	GetJimmyAtHook := JImmy;

	{Descendants of this can add special details, such as setting person files, etc}
end;


{===== EDITED JIMMY, CHAIN PUT ============}
{Pass first-in-chain pointer (for chain inserts), rec no (if edited/replacing),
and new edited key}
{use when chain item exists but just re-storing chain item, not jimmy
basically just checks key and re-sorts if necessary}
{returns new FirstLinkID and new recno}
{procedure THookStream.UpdateHook(var FirstLinkID : longint; var RecNo : LongInt; const NewSortKey : longint);
var	Hook : PHook;
begin
	{normal put edited - pass recno as chain item position, remove
	and re-insert if required o/w leave}
{	Hook := PHook(GetAt(RecNo));

	{safety}
{	if Hook=nil then begin
		DBaseMessage(@Self,'Failed to load Chain Item'#13#10'THookStream.UpdateHook, Rec '+N2Str(RecNo), mfError);
		exit;
	end;

	{Check if date has changed}
{	if Hook^.Sortkey<>NewSortKey then begin
		{Re-sort}
{		Delete(RecNo, FirstLinkID);  {delete old position}
{		Hook^.Sortkey := NewSortKey;   {set to new index}
{		Insert(FirstLinkID, Hook, RecNo);  {Insert into new position}
{	end;{ else
		LatestInserted := False; {unknown/no change}

{	dispose(Hook, done);
end;

{===== EXISTING JIMMY, NEW CHAIN ITEM ===============}
{Note Jimmy must have been already stored so that it's RecNo is set}
{procedure THookStream.StoreNewHook(var FirstLinkID : longint; var RecNo : LongInt;
																												const Jimmy : PJimmy; const OwnerID : longint;
																												const hkType : byte);
var Hook : PHook;
begin
	New(Hook, init);
	Hook^.OwnerID := OwnerID;
	Hook^.srType := Jimmy^.srType;
	Hook^.SortKey := Jimmy^.GetKey(hkType);
	Hook^.JimmyID := JImmy^.RecNo;

	Insert(FirstLinkID, Hook, RecNo);

	dispose(Hook, done);
end;


{************************************************************
 ***              SEARCHING/GETTING                       ***
 ************************************************************}
{returns hook pointing to given jimmyID}
function THookStream.GetHookForID(const FirstHookID, JimmyID : longint) : PHook;

	function TestHookID(Node : PNode) : boolean; far;
	begin
		TestHookID := (PHook(Node)^.JimmyID = JimmyID);
	end;

begin
	GetHookForID := PHook(FirstThat(FirstHookID, @TestHookID));
end;


{gets first hook of srtype}
function THookStream.GetFirstHook(FirstHookID : longint; srType : word) : PHook;
begin
	FindRec := FirstHookID;
	FindsrType := srType;
	GetFirstHook := GetHookFrom(True);
end;


function THookStream.GetHookFrom(Forwards : boolean) : PHook;
var ChainRec : longint;
		Hook : PHook;
begin
	ChainRec := FindRec;
	GetHookFrom := nil;
	FindRec := -1;

	while Chainrec<>-1 do begin
		Hook := PHook(GetAT(ChainRec));
		if (Hook^.srType = FindsrType) or (FindsrType = 0) then begin
			GetHookFrom := Hook;
			if Forwards then FindRec := Hook^.NextID else FindRec := Hook^.PrevID; {set for next findnext}
			Chainrec := -1;
		end else begin
			if Forwards then ChainRec := Hook^.NExtID else ChainRec := Hook^.PrevID;
			dispose(Hook, done);
		end;
	end;
end;


{========== LOCATE BY ID ====================}
function THookStream.FindFirst(FirstHookID : longint; srType : word) : longint;
begin
	FindRec := FirstHookID;
	FindsrType := srType;  {set for findnext}

	FindFirst := FindFrom(True);
end;

function THookStream.FindFrom(Forwards : boolean) : longint;
var ChainRec : longint;
		Hook :PHook;
begin
	Hook := GetHookFrom(Forwards);
	if Hook = nil then
		FindFrom := -1
	else begin
		FindFrom := Hook^.JimmyID;
		dispose(Hook, done);
	end;
end;

function THookStream.FindLast(FirstHookID : longint; srType : word) : longint;
var ChainRec : longint;
		Hook : PHook;
		JimmyID : longint;
begin
	FindsrType := srType;  {set for findnext}

	{Go to end of chain, looking for last match}
	ChainRec := FirstHookID;
	FindRec := -1;
	JimmyID := -1;
	while ChainRec<>-1 do begin
		Hook := PHook(GetAt(ChainRec));
		if (Hook^.srType = FindsrType) or (FindsrType=0) then begin
			FindRec := Hook^.PrevID; {keep setting findrec to last one found...}
			JimmyID := Hook^.JimmyID;
		end;
		ChainRec := Hook^.NextID;
		dispose(Hook, done);
	end;

	{At this point findrec is set to -1 if no matches in chain, or the chain
	record of the last match}
	FindLast := JimmyID;
end;

function THookStream.FindNext : longint;
begin FindNext := FindFrom(True); end;

function THookStream.FindPrev : longint;
begin FindPrev := FindFrom(False); end;


{=========== FINDS FIRST JIMMY OF A PARTICULAR TYPE ===========}
function THookStream.GetFirst(FirstHookID : longint; srType : word) : PJimmy;
begin
	FindRec := FirstHookID;
	FindsrType := srType;  {set for findnext}

	GetFirst := GetNextJimmy;
end;

{=========== FINDS LAST JIMMY OF A PARTICULAR TYPE ===========}
function THookStream.GetLast(FirstHookID : longint; srType : word) : PJimmy;
begin GetLast := PJimmy(JimmyStream^.GetAt(FindLast(FirstHookID, srType))); end;


{=========== FINDS NEXT/PREV JIMMY OF A PARTICULAR TYPE ===========}
function THookStream.GetFrom(Forwards : boolean) : PJimmy;
begin GetFrom := PJImmy(JImmyStream^.GetAt(FindFrom(Forwards))); end;

{shorthand functions to access above}
function THookStream.GetNextJimmy : PJimmy;
begin GetNextJimmy := GetFrom(True); end;

function THookStream.GetPrevJimmy : PJimmy;
begin GetPrevJimmy := GetFrom(False); end;


{======= VARIATIONS =================}
function THookStream.IsEarlier(FirstHookID : longint; srType : word; Key : longint) : boolean; {checks if Key is latest}
var Hook : PHook;
begin
	IsEarlier := False;
	Hook := GetFirstHook(FirstHookID, srType);
	if Hook<>nil then begin
		if Hook^.GetKey<Key then IsEarlier := True;
		dispose(Hook, done);
	end;
end;




{*******************************************************************
 ***                        HOOK VIEWER                          ***
 *******************************************************************}
constructor THookViewer.Init;
begin
	if (NParentJimmy = nil) then begin
		ProgramError('Hook Viewer'#13'No Parent Jimmy', hcInternalErrorMsg);
		fail;
	end;

	{--Set up view --}
	Inherited Init(Bounds, NlsType);  {Initialise Chain List}

	Tree^.fiType := fiHooks;

	srRestrictor := NsrRestrictor;

	{set up dummy parent jimmy.  It doesn't contain any info, but it allows
	us to store/update the correct pointers on the disk, without worrying about
	altering data in use elsewhere in memory (ie nparentjimmy) and besidees
	which we don't need any other info}
	DummyParentJimmy := PJimmy(Create(NParentJimmy^.srtype, nil));
	DummyParentJimmy^.RecNo := NParentJimmy^.RecNo;

	hkType := NhkType;

	FileAdmin(fiJimmys)^.LogOn;
	FileAdmin(fiNoteText)^.LogOn; {save opening and closing a lot}

	ClippedID := -1;
	ClippedhtType := 0;
	ClippedChildID := -1;

	InsMode := Peer;{}
end;

destructor THookViewer.Done;
begin
	dispose(DummyParentJimmy, done);
	FileAdmin(fiJimmys)^.LogOff;
	FileAdmin(fiNoteText)^.LogOff;
	inherited Done;
end;

procedure THookViewer.Draw;
begin
	{add child/parent marker... botch, should be part of indicator}
	inherited Draw;
	if InsMode = Child then
		WriteStr(Size.X-5,Size.Y-1,'[+]',1);{}
end;


procedure THookViewer.Redraw;
begin
	TopItem := Focused;  {Move current item to top of list, so user can move while displaying}
	inherited Redraw;
end;

{Redraws just the line with given recno jimmy}
procedure THookViewer.RedrawJimmy(const JimmyID : longint);
var DIsplayItem : PDisplayItem;
		Hook : PHook;

begin
	{run through those on screen and see if need redrawing}
	DisplayItem := FirstDIsplayItem;
	while DisplayItem<>nil do begin
		Hook := PHook(Tree^.NodeAt(DisplayItem^.ItemNo));
		if (Hook<>nil) and (Hook^.JimmyID = JimmyID) then begin
			RedrawItem(DisplayItem^.ItemNo);
			DisplayItem := nil;
		end else
			DisplayItem := DisplayItem^.next;
	end;
end;

procedure THookViewer.HandleEvent;
var
	jimmy : PJimmy;
	Hook : PHook;

begin
	{=== BROADCAST EVENTS =========================}
	if (Event.What = evBroadCast) then begin

		case Event.Command of
			cmRedraw : begin
				{if it gets a redraw instruction by message then reload the chain index
				and focus at the start again}
				LoadTree; {and redraw}
				FocusItem(FirstItem); {focus on the first item}
				{and carry on to do redraw}
			end;

			cmIsJimmyEdited :
				{check by buttons, etc to see if history/etc lists already exist}
				if (PISJimmyEditedInfo(Event.InfoPtr)^.lsType=lstype) and
						(PIsJimmyEditedInfo(Event.InfoPtr)^.JimmyID = DummyParentJimmy^.REcNo) and
						(DummyParentJImmy^.RecNo<>-1) then
							ClearEvent(Event); {this sets infoptr to self}

			{Update if the jimmy stored has been hooked into this list}
			cmJimmyStored : JimmyStored(PJimmyStoredInfo(Event.InfoPtr));

			cmJimmyLocked :	RedrawJimmy(PJimmy(Event.InfoPtr)^.RecNo);

			cmFocusJimmy	:
				FocusOnJimmy(PJimmy(Event.InfoPtr)^.RecNo);

			{returns currently selected jimmy if view focused}
			cmGetFocusedJimmy : begin
				if GetState(sfFocused) then begin
					ClearEvent(Event);
					Event.InfoLong := -1
					;
					If Focused<>-1 then begin
						Hook := PHook(Tree^.NodeAt(Focused));
						if Hook<>nil then begin
							Event.InfoLong := Hook^.JimmyID;
						end;
					end;
				end;
			end;

		end; {case}
	end;

	{===== LOCAL EVENTS ===================================}

	if GetState(sfFocused) then begin
		if Event.What = evKeyDown then begin

			{$IFDEF fixit}
			case Event.KeyCode of
				kbCtrlEnter : begin
					Hook := PHook(Tree^.NodeAt(Focused));
					if Hook^.Edit(@Self) = cmOK then begin
						FileADmin(fiHooks)^.LogOn;
						HookFile^.PutAt(Hook^.RecNo, Hook);
						FileADmin(fiHooks)^.LogOff;
					end;
					ClearEvent(Event);
				end;
			end; {case}
			{$ENDIF}
		end; {event keydown}

		if	(Event.What = evCommand) then begin

			case Event.Command of
				cmChildNew : begin
					InsMode := Child;
					StartEvent(evCommand, cmNew);
					ClearEvent(Event);
				end;
				cmPeerNew : begin
					InsMode := Child;
					StartEvent(evCommand, cmNew);
					ClearEvent(Event);
				end;

				cmListPaste : begin
					{-- Paste ---}
					PasteFromClipBoard(Focused); {pastes, using focused as peer/parent}
					ClearEvent(Event);
				end;

				cmCut, cmListCut : if DrawnFocused then begin
					{-- Cut ---}
					{note that this is trapped before cmdel below, which then never gets run}
					CutToClipBoard(Focused);
					ClearEvent(Event);
				end;

				{Do before inherited so it doesn't attempt a creator}
				cmCopyJimmy : if DrawnFocused then begin
					EditNew(Focused, cmCopyJimmy);
					ClearEvent(Event);
				end;

				{---- Accept Item & send to input line -----}
				cmAccept    : if DrawnFocused then begin
					if AcceptorLink<>nil then begin {too general to be just passed up down the tree to see who wants it first.
																						Specify acceptorlink before using if need be.}
						{Get *DataItem* file position}
						Hook := PHook(Tree^.NodeAt(Focused));

						{Send to level above to be sent down again, create close...}
						SendAcceptMessage(Event, cmAcceptJimmy, @Hook^.JimmyID);

						ClearEvent(Event); {clear anyway - if acceptor link rejects selection}
					end;
				end;

				{--- Various Prints ---------}
				{Descendant decides if available (ie on menu) but method of printing is standard}
				cmPrint, cmPrintBox, cmFax, cmPrintLabel, cmDeferLabel : if DrawnFocused then begin

					Hook := PHook(Tree^.NodeAt(Focused));
					Jimmy := GetJimmy(Hook^.JimmyID);
					case Event.Command of
						cmPrint : 		Jimmy^.PrintFull(Printer, 0);{}
						cmPrintBox :	Jimmy^.Print;
						cmFax   : 		Jimmy^.PrintFull(Fax, 0);{}
						cmPrintLabel :Jimmy^.DoLabelNow('',1,0);{}
{						cmDeferLabel :Jimmy^.Print(prDefer+prLabel, nil);{}
					end;

					{Some prints, eg invoices, change information}
					{but should all happen automatically with storeself}

					Dispose(Jimmy, done);
					ClearEvent(Event);
				end; {print commands}
			end; {case}
		end; {command}

	end; {focused & current drawn}

	inherited HandleEvent(Event);
end;


	procedure THookViewer.JimmyStored(const JimmyStoredInfo : PJimmyStoredInfo);
	var DiskKey, Key : longint;
			htType : byte;
			DiskJimmy : PJimmy;
			TemphkType : byte;
			TargetID, SubTargetID, DiskTargetID, SubDiskTargetID : PLongint;
			DoUpdate,DoReLoad : boolean;
			Jimmy : PJimmy;
			InsBias : boolean;

	begin
		{Jimmy has been stored - see if this list needs updating}
		Jimmy := JimmyStoredInfo^.Jimmy;           {shorthand}
		DiskJimmy := JimmyStoredInfo^.OldJimmy;

		DoUpdate := False; htType := 1;
		while not DoUpdate and (htType<=Jimmy^.NumHookTo) do begin
			{check if it's been hooked to this one}
			Jimmy^.GetHookTo(htType, TargetID, SubTargetID, TemphkType, Key, InsBias);
			if TargetID<>nil then
				DoUpdate := (TargetID^ = DummyParentJImmy^.RecNo) and (TemphkType=hkType);

			{check if it's been unhooked from the disk one}
			if (DiskJimmy<>nil) then begin
				DiskJimmy^.GetHookTo(htType, DiskTargetID, SubDiskTargetID, TemphkType, DiskKey, InsBias);
				if (DiskTargetID<>nil) and (DiskTargetID^ = DummyParentJimmy^.RecNo) and (TemphkType=hkType) then DoUpdate := True;
			end;

			inc(htType);
		end;

		dec(htType); {now htType is set to the hookto context relevant to the jimmy in this list
		 (as are PID, DiskPID, etc)}

		if DoUpdate then begin
			{At this stage, we know that the jimmy has been hooked on/off this list,
			but it may not have actually moved position.  So now we test for that,
			but we also test to make sure it hasn't been added/taken off this list,
			ie that the targetid for both disk and memory jimmy are the same}

			{see also HookEditedJImmy, similar logic to decide whether to re-sort}
			if DiskJimmy=nil then
				DoReLoad := True
			else begin
				DoReLoad := (Key<>DIskKey); {key changed?}
				{changed from list to list?}
				if not DoReLoad then
					DoReLoad := (DiskTargetID<>nil) and (DiskTargetID^<>TargetID^);
				{changed subtarget in list?}
				if not DoReLoad then
					DoReLoad := (SubTargetID<>nil) and (SubDiskTargetID<>nil) and (SubTargetID^<>SubDiskTargetID^);
				{subtarget added/removed}
{				if not DoReLoad then should always return *a* value even if -1
					DoReLoad := ((SubTargetID<>nil) and (SubDiskTargetID=nil)) or
										 ((SubTargetID=nil) and (SubDiskTargetID<>nil));  {}
			end;


			if DoReLoad then begin
				LoadTree; {and redraw}
				FocusOnJimmy(Jimmy^.RecNo);
			end else
				RedrawJimmy(Jimmy^.RecNo); {just redraw jimmy}

			SetChanged; {set changed marker}
		end;
	end;

{*********************************************
 ***            CLIPBOARD                  ***
 *********************************************}
{pastes as child of itemno's hook}
{Remember that ItemNo may be invalid if the list is empty}
procedure THookViewer.PasteFromClipBoard(ItemNo : longint);
var HookToID,SubHookToID : PLongint;
		ThishkType : byte;
		Key : longint;
		Jimmy : PJimmy;
		Hook : PHook;
		InsBias : boolean;

begin
	if ClippedID = -1 then exit; {nothing to paste}

	Jimmy := PJimmy(JImmyStream^.GetAt(ClippedID));
	if InRange(ItemNo) then Hook := PHook(Tree^.NodeAt(ItemNo))
		else Hook := nil;

	{set hookto and subhooktoid}
	Jimmy^.GetHookTo(ClippedhtType, HookToID, SUbHookToID, ThishkType, Key, InsBias);
	HookToID^ 		:= DummyParentJimmy^.RecNo;

	{now check to see if the focused one is valid, and whether we're going
	to insert as a child on it or a a peer}
	if SubHookToID<>nil then
		if Hook = nil then
			SubHookToID^ := -1 {root peer}
		else
			if InsMode=Child then
				SubHookToID^ := Hook^.JimmyID
			else
				if Hook^.Parent<>nil then
					SubHookToID^ := PHook(Hook^.Parent)^.JimmyID
				else
					SubHookToID^ := -1; {root peer}

	Jimmy^.StoreSelf;
	dispose(Jimmy, done);

	{set childID of hook - storeself will have set focused to the pasted jimmy}
	if ClippedChildID<>-1 then begin
		Hook := PHook(Tree^.NodeAt(Focused));
		Hook^.ChildID := ClippedCHildID;
		FileADmin(fiHooks)^.LogOn;
		Stream(fiHooks)^.PutAt(Hook^.RecNo, Hook);
		FileAdmin(fiHooks)^.LogOff;

		{update view - for the moment just reload tree}
		LoadTree; {and redraw}
	end;
end;

procedure THookViewer.CutToClipBoard(ItemNo : longint);
var HookToID,SubHookToID : PLongint;
		ThishkType : byte;
		htType : byte;
		Key : longint;
		Jimmy : PJimmy;
		Hook : PHook;
		Control : word;
		InsBias : boolean;

begin
	{sets clipped to refer to jimmy that's being clipped, sets
	appropriate subhooktoid	and hooktoid to -1, and stores again}
	Hook := PHook(Tree^.NodeAt(ItemNo));
	ClippedID := Hook^.JimmyID;
	ClippedhtType := 0;
	ClippedChildID := Hook^.ChildID;

	Jimmy := Pjimmy(JimmyStream^.GetAt(ClippedID));

	if Jimmy^.GetLock<>0 then begin
		{$IFNDEF SingleUser}
			LockMessage('Cannot Cut'#13'Entry ', Jimmy^.LockTerminal, mfWarning + mfCancelButton);
		{$ELSE}
			ProgramWarning('Cannot Cut'#13'Entry in Use - Close its view', hcLockMsg);
		{$ENDIF}
		exit;
	end;

	for htType := 1 to Jimmy^.NumHookTo do begin
		if ClippedhtType=0 then begin {don't do once clipped}
			Jimmy^.GetHookTo(htType, HookToID, SUbHookToID, ThishkType, Key, InsBias);
			if (ThishkType=hkType) and (HookToID<>nil) and (HookToID^=DummyParentJimmy^.RecNo) then begin
				{check subhook}
				if (SubHookToID=nil) or (Hook^.Parent=nil) or (SubHookToID^ = PHook(Hook^.Parent)^.JimmyID) then begin
					ClippedhtType := htType;
					HookToID^ := -1;
					if SubHookToID<>nil then SubHookToID^ := -1;
					Jimmy^.StoreSelf; {remove from list}
				end;
			end;
		end;
	end;

	if ClippedhtType=0 then begin
		{Could not find hook-to type, so will not be able to re-insert here}
		Control := MessageBox('CUT FROM LIST','Will not be able to re-insert',mfOKCancel + mfWarning, hcNoReinsertMsg);
		if Control <> cmCancel then
			Del(ItemNo); {remove from hooks anyway}
		ClippedID := -1;
	end;

	dispose(Jimmy, done);
end;



{*******************************
 ***     GET TEXT            ***
 *******************************}
function THookViewer.GetText;
var
	Jimmy : PJimmy;
	TL,S : string;
	Hook : PHook;

begin
	GetText := '';

	{Load & set Hook, Load & set actual object}
	Hook := PHook(Tree^.NodeAt(ItemNo));
	if Hook=nil then begin
		S := 'CANNOT GET HOOK '+N2Str(ItemNo); {this should not happen!}
	end else begin

		Jimmy := GetJimmy(Hook^.JimmyID);

		if Jimmy = nil then begin
			S := 'CANNOT READ JIMMY '+JimmyStream^.StatusText
													+' sr'+N2Str(Hook^.srType);
			JimmyStream^.Reset;
		end else begin
			TL := TreeLines(Hook);
			S := Jimmy^.DisplayLine(DummyParentJimmy^.RecNo, lsType, Size.X-length(TL), 0);
			S := AddTreeLines(Hook, S);			{add tree drawing lines}
			if Jimmy^.GetLock<>0 then
				{add lock marker}
				S := SetLength(GetLine(S,1),Size.X-1)+char(Jimmy^.GetLock)+Copy(S,length(GetLine(S,1)),256);
			dispose(Jimmy, done);
		end;

{		dispose(Hook, done); not when it's part of the memory chain}

		{Add tekky line}
		{$IFDEF fixit}
			with Hook^ do begin
				S := S + inherited GetText(ItemNo) {get node tekky details}
									+' Dat'+N2Str(JimmyID)
									+' Ow'+N2Str(OwnerID)
									+' sr'+N2Str(srType);
			end;
		{$ENDIF}

	end;


	GetText := S;
end;

{*************************************
 ***    EDIT DATA ITEM             ***
 *************************************}
procedure THookViewer.Edit(itemNo : longint);
var Jimmy : PJimmy;
		Control : word;
		Hook : PHook;

begin
	if ItemNo>-1 then begin
		Hook := PHook(Tree^.NodeAt(ItemNo));
		Jimmy := GetJimmy(Hook^.JimmyID);

		{Edit Jimmy}
		if Jimmy<>nil then begin
			if ViewOnly then Jimmy^.AllowChanges := False;
			Jimmy^.Edit(Self.Owner, nil);      {Make caller then owning window}
		end;
		ClearSearch;
	end;
end;

{========== NEW =======================}
procedure THookViewer.EditNew(ItemNo : longint; Command : word);
var	ChainRec : longint;
		Jimmy : PJimmy;
		Hook : PHook;
		InitParam : TJimmyInitParam;

begin
	Jimmy := nil;

	Hook := PHook(Tree^.NodeAt(ItemNo));
	{Read/Clear/Copy}
{	if Command =cmCopyJimmy then begin
		{Create new from focused jimmy}
{		Hook := PHook(PHookStream(Stream(fiType))^.GetAt(ItemNo));

		if Hook<>nil then begin
			InitParam.ForWho := Hook^.JimmyID;
			InitParam.LIstView := @Self;
			Jimmy := PJimmy(Create(cmCopyJimmy, @InitParam));
			dispose(Hook, done);
		end;

		if Jimmy=nil then exit; {nothing created}
{	end else{} begin
		InitParam.ListView := @Self;
		InitParam.ForWho := DummyParentJimmy^.REcNo;
		if Hook<>nil then begin
			InitParam.FocusedID := Hook^.JimmyID;

			{set parentid}
			if InsMOde=Child then
				{insert as child of focused - ie focused is parent}
				InitParam.FocusedParentID := Hook^.JimmyID
			else
				{insert as peer of focused - ie parent of focused is parent}
				if Hook^.Parent<>nil then
					InitParam.FocusedParentID := PHook(Hook^.Parent)^.JimmyID
				else
					InitParam.FocusedParentID := -1

		end else begin
			InitParam.FocusedID := -1;
			InitParam.FocusedParentID := -1;
		end;

		Jimmy := PJimmy(Create(Command, @InitParam));

		if Jimmy = nil then begin
			{might be a forced fail - eg koptions, does not allow marking sheet
			if no criteria set}
{			ProgramError('JIMHOOKS unit, EditNew: No new method defined for '+N2Str(Command)+#13#10'Incorrect/missing Jimmy');{}
			exit;
		end;
	end;

	{Edit}
	Jimmy^.Edit(Self.Owner, nil); {pass owner as focusing/positioning/cascading view}
end;


procedure THookViewer.FocusOnJimmy;
var
	Index : word;

	function IsJimmy(Node : PNode) : boolean; far;
	begin
		inc(Index);
		IsjImmy := (PHook(Node)^.JimmyID = RecNo);
	end;

begin
	{first checks if already focused item, then runs through chain}
	if (Focused=-1) or (Tree^.NodeAt(Focused)=nil) or (PHook(Tree^.NodeAt(Focused))^.JimmyID<>RecNo) then begin
		Index := 0;
		if Tree^.FirstThat(@IsJimmy)<>nil then
			FocusItem(Index-1)
		else
			GOHome;
	end;
end;

{********************************************
 ***       HANDLING CHAIN OWNER           ***
 ********************************************}

procedure THookViewer.SetOwnersRoot;
var JimmyStoredInfo : TJimmyStoredInfo;
begin
	DummyParentJimmy^.SetFirstHookPtr(hkType, Tree^.RootNodeID);
	{store *just* new hook pointer - as indexes if any may have changed}
	DummyParentJImmy^.StoreFirstHookPtr(hkType);

	{now ought to pass message saying "update!" to the owning jimmy if it is
	being edited.  This will be automatic anyway if it's a hooked jimmy being
	stored, but in cases of delete, etc in lists where the jimmy does *not*
	self-store on that list, then the owner needs to be told if on-screen}
	JimmyStoredInfo.Jimmy := DummyParentJimmy;
	JimmyStoredInfo.OldJimmy := nil;
	Message(Desktop, evBroadcast, cmJimmyStored, @JImmyStoredInfo);{}
end;

function THookViewer.GetOwnersRoot;
begin
	if DummyParentJimmy=nil then {on first redraw, dummyparent not set yet}
		GetOwnersRoot := -1
	else begin
		if DummyParentJimmy^.RecNo>-1 then {may be a new entry while displaying}
			DummyParentJimmy^.LoadFirstHookPtr(hkType);
		GetOwnersRoot := DummyParentJImmy^.GetFirstHookPtr(hkType);
	end;
end;



{******************************
 ***      PRINT LIST        ***
 ******************************}
procedure THookViewer.PrintList;
var	Jimmy : PJimmy;
		Hook : PHook;

	procedure PrintJimmy(Node : PNode); far;
	begin
		if (NumPages=0) or (Printer^.Page<NumPages) or not Printer^.ReadyForEndOfPage(5) then begin
			Jimmy := GetJimmy(PHook(Node)^.JimmyID);
			if JImmy<>nil then begin
				Jimmy^.PrintSummary(Printer,0);
				Printer^.writeln('');
				dispose(Jimmy, done);
			end;
		end;
	end;

begin
	if FirstItem = -1 then
		PauseMessage('Print List','No List to Print!',hcNoContext)
	else begin
		ThinkingOn('Printing');

		{Set up list}
		Printer^.ClearCodes;

		Printer^.FormCodes^.Insert(New(PJimmyFormCode, init('P', DummyParentJImmy^.RecNo)));

		Printer^.FormCodes^.SetStr('RTITLE',ucase(PWindow(Owner)^.Title^));
		Printer^.StartPrint('HOOKLIST','REPORT');

		{Print each item}
		Tree^.ForEach(@PrintJimmy);

		Printer^.EndPrint;
		ThinkingOff;
	end;
end;

{Assumes most jimmys will expect an initial parameter corresponding to the
parent (at least if they are history/etc jimmys.  Descendant viewer types
can easily override}
{function THookViewer.CreateItem(Command : word) : pointer;
var InitParam : TJimmyInitParam;
begin
	InitParam.ListView := @Self;
	InitParam.ForWho := ParentJimmy^.RecNo;
	CreateItem := Create(Command, @InitParam);
end;{}

{*******************************************
 ***      DELETE ITEM                    ***
 *******************************************}
{Shift delete does ordinary delete - ie removes from chain only
(could really do with deleting back pointer/whatever it was that
tied it to the chain in the first place).

Ctrl delete does a delete jimmy completely - ie this one:}
procedure THookViewer.DelJimmy(ItemNo : longint);
var	Jimmy : PJimmy;
		M : boolean;
		TemphkType,B : byte;
		Hook : PHook;

begin
	Hook := PHook(Tree^.NodeAt(ItemNo));
	Jimmy := GetJimmy(Hook^.JimmyID);

	{pass httype as 1 - parentid overrides}
	UnHookFromID(Jimmy, DummyParentJimmy^.RecNo, 1);

	LoadTree;
	FindOKItemNo(ItemNo);
	FocusItem(ItemNo);

	dispose(Jimmy, done);

	{First, check it's deleteable}
{	Hook := PHook(Tree^.NodeAt(ItemNo));
	Jimmy := GetJimmy(Hook^.JimmyID);

	if Jimmy <> nil then begin

		M := False;
		for B := 0 to 9 do if Jimmy^.GetHookToID(B,TemphkType)=DummyParentJImmy^.REcNo then M := True;

		DeleteJimmy(Jimmy, True); {mark jimmy as deleted}

{		if Jimmy^.Deleted then begin
			{if successfully done, ie it was allowed & not cancelled}
{			if not M then Del(ItemNo); {there was no backpointer - so remove from hook chain too}

{			LoadTree;
			FindOKItemNo(ItemNo);            {Find new OK record}
{			FocusItem(ItemNo);               {Focus on it}
{		end;

		dispose(JImmy, done);
	end;

{	Message(Desktop, evBroadCast, cmCheckFileVer, nil); {get all views to check their file links}
end;



{*************************************************************
 ***                                                       ***
 ***               HOOKING UP AND UNHOOKING                ***
 ***                                                       ***
 *************************************************************}
const
	dtHook = 1;
	dtUnHook = 2;
	dtReHook = 3;
	dtNoHook = 4;

{== HOOK ON/OFF/RE =============}
{Pass dotask as
	dtHook 		: hooks on jimmy
	dtUnHook 	: unhooks jimmy
	dtReHook 	: removes hook to jimmy and replaces
	dtNoHook  : does nothing except inform parentjimmy about rehook{}

{Jimmy is jimmy to be hooked, ParentID is the ID of the jimmy to be hooked onto.
SubHookID is the hook that jimmy is to be hooked via, eg in the case of invoice
items where the tree structure of the hooks are in use. htType is the hookingto
context

DiskJimmy is required only if doing a rehook, for the purpose of informing the
parent}

procedure DoHook(const Jimmy,DiskJimmy : PJimmy; ParentID,SubParentID : longint; const htType,DoTask : word);
var Parent : PJimmy;
		EditView : PView;
		WorkRec, ChainRec : longint;
		Hook : PHook;
		FirstLinkID, OldFirst : longint;
		hkType : byte;
		TargetID, SubTargetID : PLongint;
		SortKEy : longint;
		InsertBias : boolean;
		B : boolean;
		Msg : string[20];

	procedure InsertHook;
	var SUbHook : PHook;
			ChildID : longint;
	begin
		if SubParentID = -1 then
			{straightforward chain-like insert}
			HookFile^.Insert(FirstLinkID, Hook, InsertBias)
		else begin
			{nodal insert - attach to hook which points to SubParentID}
			SubHook := HookFile^.GetHookForID(FirstLinkID, SubParentID);
			if SubHook=nil then begin
				{Probably caused by selecting a subparentID that doesn't appear in
				the hooklist}
				{What about when cutting & pasting??}
				ProgramError('Trying to hook jimmy '+N2Str(Jimmy^.RecNo)+' to nil subhook '+N2Str(SubParentID)+#13
											+'JimHooks.pas, DoHook, InsertHook',hcInternalErrorMsg);
				HookFile^.Insert(FirstLinkID, Hook, InsertBias);
			end else begin
				ChildID := SubHook^.ChildID;
				HookFile^.Insert(SubHook^.ChildID, Hook, InsertBias);  {insert in subchain}
				if ChildID<>SubHook^.ChildID then
					HookFile^.PutAt(SubHook^.RecNo, SubHook);
				dispose(SubHook, done);
			end;
		end;
	end;

	procedure DeleteHook;
	var SUbHook : PHook;
			ChildID : longint;
	begin
		if HookFile^.LastParentID=-1 then
			{straightforward chain-like insert}
			HookFile^.DeleteLink(FirstLinkID, Hook^.RecNo)
		else begin
			{nodal delete - uses HookFile^.LastParentID from GetHookToID below}
			SubHook := PHook(HookFile^.GetAt(HookFile^.LastParentID));

			ChildID := SubHook^.ChildID;
			HookFile^.DeleteLink(SubHook^.ChildID, Hook^.RecNo);  {insert in subchain}
			if ChildID<>SubHook^.ChildID then
				HookFile^.PutAt(SubHook^.RecNo, SubHook);
			dispose(SubHook, done);
		end;
	end;

begin
	Jimmy^.GetHookTo(htType, TargetID, SubTargetID, hkType, SortKey, InsertBias);

	if hktype=0 then exit; {not to be hooked}

	{parameters that override above}
	if ParentID=-1 then
		if TargetID = nil then exit
		else ParentID := TargetID^;

	if (SubParentID=-1) and (SubTargetID<>nil) then SubParentID := SubTargetID^;


	Parent := FindJimmy(ParentID, EditView);
	if Parent = nil then exit;

	FileAdmin(fiHooks)^.LogOn;

	FirstLinkID := Parent^.GetFirstHookPtr(hkType);

	case DoTask of
		{---- Add Hook -----}
		dtHook : begin
			New(Hook, init);
			Hook^.OwnerID := Parent^.RecNo;
			Hook^.srType := Jimmy^.srType;
			Hook^.SortKey := Sortkey;
			Hook^.JimmyID := Jimmy^.RecNo;

			{insert into file}
			InsertHook; {see above}

			dispose(Hook, done);
		end;

		{--- Remove Hook -----}
		dtUnHook : begin
			Hook := HookFile^.GetHookForID(FirstLinkID, Jimmy^.RecNo);

			if Hook<>nil then begin
				{found position - remove}
				DeleteHook;
				dispose(Hook, done);
			end;
		end;

		{---- Change Hook Pos -----}
		dtReHook : begin
			Hook := HookFile^.GetHookForID(FirstLinkID, Jimmy^.RecNo);
			if Hook=nil then begin
				{this should *not* happen!}
				{$IFNDEF Update}
				Debug.Writeln('WARNING! *RE*hooking #'+N2Str(Jimmy^.RecNo)+' srtype='+N2Str(Jimmy^.srType)
										+' to #'+N2Str(ParentID)+#13
										+' No old item to unhook.  Ignoring...');
				{$ENDIF}
				New(Hook, init);
				Hook^.OwnerID := Parent^.RecNo;
				Hook^.srType := Jimmy^.srType;
				Hook^.JimmyID := Jimmy^.RecNo;
			end else
				DeleteHook;

			Hook^.SortKey := SortKey;
			InsertHook;

			dispose(Hook, done);
		end;
	end;

	{set changes & store if any}
	if FirstLinkID<>Parent^.GetFirstHookPtr(hkType) then begin
		Parent^.SetFirstHookPtr(hkType, FirstLinkID);
		Parent^.StoreFirstHookPtr(hkType);
	end;

	{notify the parent jimmy that this one is being hooked on, in case it
	wants to do anything about it - eg cascade}
	case doTask of
		dtHook 		: begin B := Parent^.HookingOn(hkType, htType, Jimmy); Msg := 'Hooking On'; end;
		dtUnHook 	: begin B := Parent^.UnHooking(hkType, htType, Jimmy); Msg := 'UnHooking'; end;
		dtReHook,dtNoHook :
								begin B := Parent^.ReHooking(hkType, htType, Jimmy, DiskJimmy); Msg := 'ReHooking'; end;
	else
		ProgramError('ReHookWithJimmyID - No DoTask defined', hcInternalErrorMsg);
		B := False;
	end;

	if B then begin
		{Things changed needing saving}
		UpdateJimmy(Parent); {stores to editbox or file as nec}
		if (Parent^.GetLock<>0) and (Parent^.GetLock<>TerminalNo) then
			ProgramWarning(Parent^.GetName(naDisplay,0)+' ID'+N2Str(Parent^.RecNo)
										+' used by Terminal '+N2Str(Parent^.GetLock)+
{$IFDEF kusers}			+' '+GetJimmyIDName(ProgramStatus.GetWhoAtTerminal, naDisplay,0) {$ENDIF}
										+#13'Updating but may be overwritten'#13
										+Msg+' '+Jimmy^.GetName(naDisplay,0),
										hcDubiousUpdateMsg);
	end;

	FileAdmin(fiHooks)^.LogOff;

	if EditView=nil then Dispose(Parent, done);
end;

{shortcuts to above}

{Purely hooks on jimmy}
procedure HookToID(const Jimmy : PJimmy; const ParentID, SubParentID : longint; const htType : word);
begin
	DoHook(Jimmy, nil, ParentID, SubParentID, htType, dtHook);
end;

{purely unhooks jimmy}
procedure UnHookFromID(const Jimmy : PJimmy; const ParentID : longint; const htType : word);
begin
	DoHook(Jimmy, nil, ParentID, -1, htType, dtUnHook);
end;

{purely rehooks jimmy *on same chain*}
{procedure ReHookToID(const Jimmy : PJimmy; ParentID : longint; const SubParentID : longint; const htType : word);
begin
	DoHook(Jimmy, DiskJimmy, ParentID, SubParentID, htType, dtReHook);
end;



{PutJImmy *has* to be done by jimmys storeself method, to avoid overwriting
jimmys stored on the disk by the one on the heap in this method or the
storenewindexed one}

{Hooks up edited jimmy.  Disk jimmy is passed as the "old" jimmy on disk
before the new one was stored.  Decides whether to unhook/rehook or leave}
procedure HookUpEditedJimmy(var DiskJImmy : PJimmy; const JImmy : PJimmy);
var htType : byte;
		hkType, DiskhkType : byte;
		TargetID, SubTargetID, DiskTargetID, SubDiskTargetID : Plongint;
		ReHook : boolean;
		SortKey, DiskSortKey : longint;
		InsBias, DiskInsBias : boolean;

begin
	ThinkingOn('Hooking');

	if (Jimmy^.Deleted) then begin
		if (not Diskjimmy^.deleted) then begin
			{just been deleted - unhook}
			for htType:=1 to Jimmy^.NumHookTo do
				DoHook(DiskJimmy, nil, -1, -1, htType, dtUnHook);

		end

	end else

		{not deleted}

		for htType := 1 to Jimmy^.NumHookTo do begin

			Jimmy^.GetHookTo(htType, TargetID, SubTargetID, hkType, SortKey, InsBias);
			DiskJimmy^.GetHookTo(htType, DiskTargetID, SubDiskTargetID, DiskhkType, DiskSortKey, DiskInsBias);

			if (TargetID<>nil) and (DiskTargetID<>nil) and (TargetID^<>DiskTargetID^) then begin
				{changed chain}
				DoHook(DiskJimmy, nil, -1, -1, htType, dtUnHook);
				DoHook(Jimmy, 		nil, -1, -1, htType, dtHook);
			end else begin
				{same chain.  Now check for key changed or subtarget changed, could
				make this all one statement but it seems to get a bit complex so do
				it this way:}
				{$IFDEF Fixit}
				ReHook := True;
				{$ELSE}
				ReHook := (SortKey<>DiskSortKey);{}
				{$ENDIF}
				{subtarget changed}
				if not ReHook then
					ReHook := (SubTargetID<>nil) and (SubDiskTargetID<>nil) and (SubTargetID^<>SubDiskTargetID^); {subtarget changed}

				{HookTo should always return subtarget as non-nil if the ptr exists,
				even if it's -1, so the following should never occur}
{				if not ReHook then
					ReHook := ((SubTargetID<>nil) and (SubDiskTargetID=nil)) or
										((SubTargetID=nil) and (SubDiskTargetID<>nil)); {subtarget added/removed}

				{same chain - check for key changed or subtarget changed}
				if ReHook then
					DoHook(Jimmy, DiskJimmy, -1, -1, htType, dtReHook)	{moved}
				else
					DoHook(Jimmy, DiskJimmy, -1, -1, htType, dtNoHook); {no move, still need to inform parent}
			end;

		end;

	ThinkingOff;
end;

{Use this routine if the jimmy is new.  By this stage, the storeself
method will have actually stored the jimmy so we can't check for recno=-1}
procedure HookUpNewJimmy(const JImmy : PJimmy);
var htType : byte;
begin
	ThinkingOn('Hooking');

	FileAdmin(fiHooks)^.LogOn; {to save opening/closing}

	for htType := 1 to Jimmy^.NumHookTo do
		DoHook(Jimmy, nil, -1, -1, htType, dtHook); {hook on}

	FileAdmin(fiHooks)^.LogOff; {to save opening/closing}

	ThinkingOff;
end;


{**************************************************************
 ***                                                        ***
 ***                 HOOK LIST BUTTON                       ***
 ***                                                        ***
 **************************************************************}
{For giving press button access to a hook chain attached to a
jimmy - eg more-about button, history button}

constructor THookListButton.Init;
begin
	inherited Init(X,Y, ATitle, ACommand, AFlags or bfGetData, NTiedObject);
	hkType := NhkType; {hook type of tied object}
	lsType := NlsType; {list type}
	srRestrictor := 0;
end;


procedure THookListButton.Press;
var Bounds : TRect;
		ChainView : PView;
		ChainWindow : PView;

begin
{	PJimmy(DataItem)^.Lock := TerminalNo; {make sure it's locked}

	inherited Press; {Stores chain owning item}

	if OwnerValid=-1 then exit; {no store, etc}

{	DrawState(True);   {Draw as pressed}

	Message(Owner, evCommand, cmCascadeView, nil);

	{See if history list already being edited on screen}
	CHainView := getJimmyView(PJimmy(DataItem)^.RecNo, lstype);

	{see notes on modalness in jimmys.pas, TJimmy.Edit}
	if (ChainView<>nil) then begin {returns the list view itself}
		if not ChainView^.GetState(sfModal) and not Owner^.GetState(sfModal) then begin
			ChainView^.Owner^.MakeFirst;
			ChainView^.Owner^.FOcus;
		end else
			ProgramWarning('List already in use'#13#13
											+'Close views until it appears', hcViewInUseMsg);
		exit;
	end;

	{Set bounds for window}
	if Desktop^.Size.Y>25 then Bounds.Assign(0,0,65,19)
	else Bounds.Assign(0,0,65,13);

	CentreOnView(Bounds, @Self);

	{Execute list}
	ChainWindow := NewJimmyHookWindow(	bounds,
																			lsType,
																			srRestrictor, {restrictions}
																			hkType,
																			PJimmy(DataItem),
																		nil);

	{if owner is modal, make this one}
	if Owner^.GetState(sfModal) then begin
		Desktop^.ExecView(ChainWindow);
		dispose(ChainWindow, done);
	end else
		Desktop^.Insert(ChainWindow);

	DrawState(False);

end;

{********************************************************
 ***           IN-DIALOG HOOK VIEW                    ***
 ********************************************************}
{Slight mods required for putting hook views direct into the
owners dialog box - eg more about view in directory edit box}
constructor TDlgHookView.Init(var Bounds: TRect;
											NlsType : word;
											NsrRestrictor : word;
											NhkType : word;
											NParentJImmy : PJimmy;
											NEditBox : PJimmyEditBox);
begin
	inherited Init(Bounds, NlsType, NsrRestrictor, NhkType, nParentJimmy);
	Editbox := NEditbox;
end;


procedure TDlgHookView.HandleEvent;
var HookWindow : PListWindow;{}
		Bounds : TRect;
begin

	{We have to make sure that the parentjimmy is stored before accessing the
	list, so that the list has something to hook to}

	{Need to store parentjimmy when focused.  Unfortunately, can't do a test
	on cmreceivedfocus, as then when a more-about item ends its edit, just
	after having stored self and hooked on, this goes and overwrites the
	pointer.  So instead we *must* make sure that anything *done* by the
	user - ie a new or a change - stores away the parentjimmy.  This applies
	whether new or not, so that inputdirectory lines etc of the more-about
	items are accurate if someones changed a name}
{	if GetState(sfFocused) and (Event.What = evKeyDown) and
			((Event.KeyCode = kbChange) or (Event.KeyCOde = kbAccept) or (Event.KeyCode = kbNew) or (Event.KeyCOde = kbZoom)) then begin
		Owner^.GetData(PJimmyEditBox(Owner)^.Jimmy^);  {owning box should always be a jimmy edit box}
{		PJImmy(PJImmyEditBox(Owner)^.Jimmy)^.StoreSelf;
		DummyParentJimmy^.RecNo := PJImmyEditBox(Owner)^.Jimmy^.RecNo;
	end;{}

	{I think now,  with v4.1, the ptr2first is picked up from disk just before
	storing (allowing for the new semi-independant lists, etc) so we ought to
	be able to do a check on cmreceivedfocus.  HOWEVER, we don't actually want
	to store the owner if it's a new one and we're just tabbing through, so
	revert to above...}
	if GetState(sfFocused) and (Event.What = evKeyDown) and (DummyParentJimmy^.RecNo = -1) then
		case Event.KeyCode of
			kbChange, kbAccept, kbNew, kbZoom : with EditBox^ do begin
				if Valid(cmOK) then begin
					GetData(EditBox^.Jimmy^);
					EventMask := EventMask and not evBroadcast; {no point in picking up it's stored message}
					Jimmy^.StoreSelf;
					EventMask := EventMask or evBroadcast;
					DummyParentJimmy^.RecNo := Jimmy^.RecNo;
				end;
			end;
		end;

	inherited HandleEvent(Event);

	{Zoom pressed - expand/make new window}
	if GetState(sfFocused) and (Event.What = evKeyDown) and (Event.KeyCode=kbZoom) then begin
		{if it's not been trapped so far, then this must be in a dialog box
		rather than a window.  So store the pointers, and start up a bigger
		version of self}
		EditBox^.SetState(sfActive, false);{deactivate editor box}
		Message(EditBox, evCommand, cmCascadeView, nil);

		{Set bounds for window}
		if Desktop^.Size.Y>25 then
			Bounds.Assign(0,0,60,20)
		else
			Bounds.Assign(0,0,60,12);

		CentreOnView(Bounds, @Self);

		{Execute list}
{		HookWindow := New(PJimmyHookWindow,	Init(bounds,
																					lsType,
																					 Owner,
																						srRestrictor,
																						hkType,
																						DummyParentJimmy,
																						nil));{}
		HookWindow := NewJimmyHookWindow(	bounds,
																			lsType,
																			srRestrictor,
																			hkType,
																			DummyParentJimmy,
																			nil);{}
		HookWindow^.List^.DoneNew := True; {bit of a botch fix - this view will already have queued a kbIns event}
		Desktop^.ExecView(HookWindow);
		dispose(HookWindow, done);

		Message(EditBox, evCommand, cmUnCascadeView, nil);
		Owner^.SetState(sfActive, true);{}

		{no need to check on owners root - cos we're sharing the same
		jimmy pointer, ie the memory image is up to date.  Do need to reload
		index though & redraw}
		Redraw;
		ClearEvent(Event);
		DoneNew := True; {so that no auto-new is done returning to list}
	end;


	{Need to convert some keypresses direct to commands, eg kbPrint}
	if (State and sfFocused<>0) and (Event.What = evKeyDown) then
		case Event.KeyCode of
			kbPrint : begin
				Message(@Self, evCommand, cmPrintBox, @Self);
				ClearEvent(Event);
			end;
			kbChildNew 	: begin StartEvent(evCommand, cmChildNew); end;
			kbPeerNew 	: begin StartEvent(evCommand, cmPeerNew); end;
		end;


end;

{********************************************
 ***           PRINT HOOKS LIST           ***
 ********************************************{}
{Info points to parent object}
{procedure PrintHooksFormCode(const Device : PDeviceStream;
															const Param : TFCodeStr;
															const Form  : FNameStr;
															const Jimmy : PJimmy;
															const hkType : word);
var
	BreakLines : word;
	Tree : PTree;

	procedure PrintHook(Hook : PHook); far;
	var HookedJim : PJimmy;
			I : word;
	begin
		HookedJim := GetJimmy(Hook^.JimmyID);
		if HookedJim<>nil then begin
			HookedJim^.PrintForm(Device, Form);
			if (BreakLines<>0) and (Hook^.Next<>nil) and (Hook^.Parent<>nil) then
				{do breaklines if at end of chain and not top chain}
{				for I := 1 to BreakLines do Device^.writeln('');
			dispose(HookedJim, done);
		end;
	end;

begin
	New(Tree, init);
	Tree^.fiType := fiHooks;
	Tree^.RootNodeID := Jimmy^.GetFirstHookPtr(HkType);
	Tree^.LoadTree;

	{---check parameters---}
	{break lines - ie at end of child list}
{	if IsParam(Param, '/BL') then
		BreakLines := S2Num(Copy(GetParam(Param, '/BL'),3,99))
	else
		BreakLines := 1; {default to 1 line}

	{reverse order}
{	if IsParam(Param, '/R') then Tree^.ReverseOrder;

	{print}
{	Tree^.ForEach(@PrintHook);
end;


{used in forms for printing hook lists}
constructor THookListFormCode.INit;
begin
	inherited Init(NCode, nil, Jimmy);
	hkType := NhkType;
	FormName := NFormName;
	srRestrictor := NsrRestrictor;
end;

procedure THookListFormCode.DoPrint(Device : PDeviceStream; Jimmy : PJimmy; Param : string);
var Form : FNameStr;
		Prefix : string;
begin
	{form name override}
	if IsParam(Param, '/FORM') then
		Form := Copy(GetParam(Param, '/FORM'),7,99) {skip also = :, etc, ie /FORM=xxxx}
	else
		Form := FormName;

	{code prefix override - so it doesn't interfere with other codes}
	if IsParam(Param, '/PRE') then
		Prefix := Copy(GetParam(Param, '/PRE'),6,99) {skip also = :, etc, ie /PRE=xxxx}
	else
		Prefix := '';

	if Form='' then
		Jimmy^.PrintLine(Device)
	else begin
		if pos('.',Form)=0 then Form := Form+'.FRM';
		if Prefix='' then
			Jimmy^.PrintForm(Device, Form)
		else begin
			Device^.FormCodes^.SetPrefix(Prefix);
			Jimmy^.SetFormCodes(Device^.FormCodes);
			Device^.PrintForm(Form);
		end;

		if not Device^.FormFound then Jimmy^.PrintLine(Device);
	end;
end;



procedure THookListFormCode.DoFunc;
var
	BreakLines : word;
	Tree : PTree;
	Num : longint;

	procedure PrintHook(Hook : PHook); far;
	var HookedJim : PJimmy;
			I : word;
	begin
		if (srRestrictor=0) or (Hook^.srType = srRestrictor) then begin
			HookedJim := GetJimmy(Hook^.JimmyID);
			if HookedJim<>nil then begin
				inc(Num);
				Device^.FormCodes^.SetStr('POS',N2Str(Num));

				DoPrint(Device, HookedJim, Param);

				if (BreakLines<>0) and (Hook^.Next=nil) and (Hook^.Parent<>nil) then
					{do breaklines if at end of chain and not top chain}
					for I := 1 to BreakLines do Device^.writeln('');

				dispose(HookedJim, done);
			end;
		end;
	end;

begin
	if PJImmy(Info)^.GetFirstHookPtr(hkType)=-1 then exit; {nothing to do - shortcut out}

	New(Tree, init);
	Tree^.fiType := fiHooks;
	Tree^.RootNodeID := PJimmy(Info)^.GetFirstHookPtr(HkType);
	Tree^.LoadTree;

	{---check parameters---}
	{break lines - ie at end of child list}
	if IsParam(Param, '/BL') then
		BreakLines := S2Num(Copy(GetParam(Param, '/BL'),3,99))
	else
		BreakLines := 1; {default to 1 line}

	{so descendants can re-order/whatever}
	DoSpecial(Tree, Param);

	{reverse order}
	if IsParam(Param, '/R') then Tree^.ReverseOrder;

	{print}
	Num := 0;
	Tree^.ForEach(@PrintHook);

	Dispose(Tree, done);
end;

procedure THookListFormCode.DoSpecial;
begin end;



{*****************************
 ***      TASKS            ***
 *****************************}
function NewJImmyHookWindow(Bounds : TRect; lsType : byte; srRestrictor, hkType : word; ParentJimmy : PJimmy;
																	HookView : PHookViewer) : PListWindow;
var ListWindow : PListWindow;
begin
	{usual hookview - but allow callers to override}
	if HookView = nil then HookView := New(PHookViewer, init(Bounds, lsType, srRestrictor, hkType, ParentJimmy));{}

	New(ListWindow, Init(Bounds,
									GetMessage(msListTitles, lsType)+' '+ParentJimmy^.GetName(naDisplay, 0), HookView));

	AddItemSubMenu(ListWindow^.MenuBar^.Menu, '~P~rint',
		NewItem('~P~rint',      ksPrint, kbPrint,cmPrintBox,    hcNoContext,
		NewItem('~F~ax',        ksFax, 	kbFax,	cmFax,			hcNoContext,
		NewItem('~L~abel', 			ksLabel, kbLabel, cmPrintLabel, hcNoContext,
		NewLine(
{		NewItem('~D~efer Print', ksDefer, kbDefer, cmDeferPrint,    hcNoContext,{}{}
		NewItem('Defer L~a~bel', ksDeferLabel, kbDeferLabel, cmDeferLabel, hcNoContext,
		newLine(
		NewItem('Print L~i~st',  ksPrintList, kbPrintList,cmPrintList,hcNoContext,
		NewItem('Las~t~ Page of List',  ksLastPage, kbLastPage,cmPrintLastPage,hcNoContext,
	nil)))))))));

	AddItemSubMenu(ListWindow^.MenuBar^.Menu, '~E~dit',
		NewLine(
		NewItem('New ~C~hild',		ksChildNew, kbChildNew, cmChildNew, hcNoContext,
		NewItem('New ~P~eer',			ksPeerNew,  kbPeerNew,  cmPeerNew, 	hcNoContext,
{		NewLine(
		NewItem('~C~reate from...', ksCopyJimmy, kbCopyJimmy, cmCopyJimmy, hcNoContext,{}
	nil))));

	{restrictor overrider - eg invoice only list}
	if srRestrictor<>0 then
		ReplaceItem(ListWindow^.MenuBar^.Menu^.Items^.SubMenu, {first item in menu bar is edit menu}
			'~N~ew',		{old option}
			NewItem('~N~ew', ksNew, kbIns, srRestrictor, hcNoContext,
		nil));

	NewJimmyHookWindow := ListWIndow;
end;

{globally available hook file}
{function HookFile : PHookStream;
begin HookFile := PHookStream(Stream(fiHooks)); end;{}

function CreateHookFile : PStream; far;
begin
	HookFile := New(PHookStream, init('HOOKS'));
	CreateHookFile := HookFile;
end;


{*****************************
 ***      INITIALISATION   ***
 *****************************}

begin
	{$IFDEF fixit} writeln('Jimhooks unit'); {$ENDIF}
	NewFileAdmin(fiHooks, 'Hook Stream',CreateHookFile);
	RegisterType(RHook);
	HookFile := nil;
end.

