{********************************************************
 ***                 INDEX & JIMMY LINKING            ***
 ********************************************************}
{$I compflgs}
unit Jimindxs;

INTERFACE

uses
{$IFDEF WINDOWS}
	wui,	{windows}
{$ELSE}
	menus, views,  tuimsgs, tuilist, {text}
{$ENDIF}
		inplist,
		dattime,
		jimmys, objects, indexes, drivers, files;

type
	{--- LIST VIEW ASSUMING JIMMY DATA ITEMS ---}
	PIndexedJimmyListView   = ^TIndexedJimmyListView;          {Interior}
	TIndexedJimmyListView   = object(TIndexListView)

		SubIndexString : string[20]; {used for subindexes - eg directory subcategory lists}

		constructor Init(var Bounds: TRect; const NlsType, NfiType : word; NSubIndexString : string);
		destructor Done; virtual;
		function GetText(const RecNo: longint) : string; virtual;  {}

		function GetSearch4Index : string; virtual; {allow for subindexstring}

		procedure HandleEvent(var Event : TEvent); virtual;

		procedure JimmyStored(InfoPtr : pointer);
		procedure Edit(RecNo : LongInt); virtual;
		procedure EditNew(ItemNo : longint; Command : word); virtual;

		procedure RedrawJimmy(const Jimmy : PJimmy);
		procedure FocusJimmy(Const Jimmy : PJimmy);

		procedure Del(RecNo : LongInt); virtual; {}

		procedure Setrange; virtual;

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

		{tagging}
		procedure TagItem(const ItemNo : longint); virtual;
		procedure TagAll(TagType : byte); {0 - tag, 1 - untag, 3 - invert tag} virtual;
	end;

	{adds extra jimmy functions - ie print, and print list}
	PIndexedJimmyListWindow = ^TIndexedJimmyListWindow;
	TIndexedJimmyListWindow = object(TListWindow)

		procedure InitMenuBar; virtual;
	end;

	PIndexedJimmyStream = ^TIndexedJimmyStream;
	TIndexedJimmyStream = object(TIndexStream)

		function GetJimmyAtIdx(IdxRec : longint) : PJimmy; {gets data item it points to}

		procedure SetDat2Idx(const IndexItem : PIndexItem; const NewIdxPtr : longint); virtual; {for data-associated descendants}

		procedure TagAll(const TagType, fiType : byte; const FIrstRec,LastRec : longint); virtual; {0 tag, 1 untag, 3 invert}
	end;


{==== INDEXED JIMMY STORE RELATED ROUTINES ===============}
procedure SetJimmyIdxPtr(const ixType : byte; const JimmyRec, NewIdxRec : longint);

procedure IndexNewJimmy(const Jimmy : PJimmy);
procedure IndexEditedJimmy(var DiskJimmy : PJimmy; const Jimmy : PJimmy);

procedure CheckToDoHoles(const Jimmy : PJimmy);


IMPLEMENTATION

uses  muser, {check locks}
			tui, {menu oeprations}
			tuiedit, labels, jimprint, {print tagged}
			devices,
			printers,
			app,
			help,
			tuijimmy, dialogs,
			tasks,
			minilib,
			indxutil,
			global;

{==== FOR IMPORTING/MASS INSERTING/ETC ===============}
{Checks if it took "too long" to insert jimmy, and if so,
	insert holes}
procedure CheckToDoHoles;
var lastfiType,ixType, fiType : byte;
		PL : PLongint;

begin
	{add holes?}
	if Jimmy^.InsertTime.Secs>=2 then begin
		fitype := 0;
		for ixType := 0 to Jimmy^.NumixTypes do begin
			lastfitype := fitype; {so it doesn't keep doing same one over and over}
			Jimmy^.GetIndex(ixType, PL, fiType);
			if (fiType<>0) and (fitype<>lastfitype) then begin
				FileAdmin(fiType)^.LogOn;
				MakeHoles(PIndexStream(Stream(fiType)), 2);
				FileAdmin(fiType)^.logoff;
			end;
		end;
	end;
end;

{**************************************************************************
 ***                                                                    ***
 ***                        VIEW ON INDEX LIST                          ***
 ***                                                                    ***
 **************************************************************************}

{=== INITIALISE ==========================================}
constructor TIndexedJimmyListView.Init;
begin
	FileAdmin(fiJimmys)^.LogOn;	{log on to jimmy stream - do before init so that setrange works}
	Inherited Init(Bounds, NlsType, NfiType);

	SubIndexString := NSubIndexString;
	AllowSearchScreen := False; {search on screen doesn't work with subindexstring set}

end;

{=== DONE ==================================================}
destructor TIndexedJimmyListView.Done;
begin
	FileAdmin(fiJimmys)^.LogOff;
	inherited Done;                                                                          {Close list view}
end;

{====== TAGGING ===============================}
procedure TIndexedJimmyListView.TagItem;
var Jimmy : PJimmy;
begin
	{Tagging - TIndexListView only does for indexitem}
	Jimmy := PJimmy(PIndexedJimmyStream(Stream(fiType))^.GetJimmyatIdx(ItemNo));
	if Jimmy<>nil then begin
		if Jimmy^.GetLock=0 then begin
			Jimmy^.Tag := not Jimmy^.Tag;
			PutJimmy(Jimmy);
			RedrawJimmy(Jimmy);
		end else
			ProgramWarning('Cannot tag/untag locked Entry',hcNoContext);
		dispose(Jimmy, done);
	end;
end;

procedure TIndexedJimmyListView.TagAll;
begin
	PIndexedJimmyStream(Stream(fiType))^.TagAll(TagType, fiType, FirstItem, LastItem);
	Redraw;
end;


{===== HANDLE EVENT ===========================}
procedure TIndexedJimmyListView.HandleEvent;
var IndexItem : PIndexItem;
		Jimmy : PJimmy;
		OldLastItem, IdxRec,ItemNo : longint;
		Control, ixtype : word;
		Tempfitype : byte;
		IdxRecP : PLongint;

begin
	if (Event.What and evCommand)>0 then begin

		case Event.COmmand of

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

			cmAccept : if AcceptorLink<>nil then
				if DrawnFocused then begin
					IndexItem := PIndexItem(Stream(fiType)^.GetAt(Focused));
					SendAcceptMessage(Event, cmAcceptJimmy, @IndexItem^.Idx2Dat);
					Dispose(IndexItem, Done); IndexItem := nil;
					ClearEvent(Event); {Clear event, so it's not converted to an edit}
				end else begin
					{if it's not drawn yet, this ends up turning
				 into an edit (in inherited handleevent), and as the inherited method
					also does an idle, this can result in it being drawn and then edited,
					rather than accepted.  So if an accept is required, wait until it's been
					drawn...}
					RedrawNext; {make sure we keep drawing another line to prevent looping}
					PutEvent(Event);
					ClearEvent(Event);
				end;

			cmPrint, cmPrintBox, cmPrintLabel, cmDeferLabel : begin
				Jimmy := PJimmy(PIndexedJimmyStream(Stream(fiType))^.GetJimmyatIdx(Focused));
				if Jimmy<>nil then begin
					case Event.COmmand of
						cmPrint  : Jimmy^.PrintFull(Printer, 0);
						cmPrintLabel 	: Jimmy^.DoLabelNow('',1,0);
						cmDeferLabel  : Jimmy^.DeferLabel('',1,0);
					else
						Jimmy^.Print; {do box if cmprint or cmprintbox}
					end;
					dispose(Jimmy, done);
				end;
				ClearEvent(Event);
			end;

{done in tuilist			cmPrintList : begin
				PrintList;
				ClearEvent(Event);
			end;{}

{done in tuilist			cmPrintTagged : begin
				PrintTagged;
				ClearEvent(Event);
			end;{}

{done in tuilist			cmPrintTaggedList : begin
				PrintTaggedList;
				ClearEvent(Event);
			end;{}
		end;

	end; {command}

{$IFDEF fixit}
	if (Event.What=evKeyDown) and (Event.KeyCOde = kbCtrlEnter) then begin
		IdxRec := Focused;
		IndexItem := PindexItem(Stream(fiType)^.GetAt(IdxRec));
		Control := IndexItem^.Edit(@Self);
		if Control=cmOK then begin
			PIndexStream(Stream(fiType))^.Delete(Focused, lkIgnore);
			PIndexStream(Stream(fiType))^.Insert(fiType, IndexItem, IdxRec);
			FocusItem(IdxRec);
			Redraw;
		end;
		ClearEvent(Event);
	end;
{$ENDIF}

	if ((Event.What and evBroadCast)>0) then begin

		{broadcast events}
		case Event.Command of

			{a jimmy has been stored - see if this file index has changed}
			cmJimmyStored :	JimmyStored(Event.InfoPtr);

			cmFocusJimmy	: FocusJimmy(Event.InfoPtr);

			{a jimmy has been locked - redraw with/without lock marker}
			cmJimmyLocked :	begin
				for ixtype := 0 to PJimmy(Event.InfoPtr)^.Numixtypes do begin
					PJimmy(Event.InfOptr)^.GetIndex(ixType, IdxRecP, tempfiType);
					if (IdxrecP<>nil) and (DisplayPOfItem(IdxRecP^)<>nil) then	RedrawItem(IdxRecP^);{}
				end;
			end;

			{returns currently selected jimmy if view focused}
			cmGetFocusedJimmy :
				if GetState(sfFocused) then begin
					ClearEvent(Event);
					Event.InfoLong := -1;
					If Focused<>-1 then begin
						IndexItem := PIndexItem(Stream(fiType)^.GetAt(Focused));
						if IndexItem<>nil then begin
							Event.InfoLong := IndexItem^.Idx2Dat;
							dispose(IndexItem, done);
						end;
					end;
				end;

		end;


	end; {broadcast}


	inherited HandleEvent(event);
end;


{--- Get Text -------------------------------------------}
{For each line}
function TIndexedJimmyListView.GetText(const RecNo : longint) : string;
var Jimmy : PJimmy;
		View : word;
		S : string;
{$IFDEF fixit}		IndexItem : PIndexItem; {for tekky mode} {$ENDIF}

begin
	if not InRange(RecNo) then begin GetText := ''; exit; end;

	Jimmy := PJimmy(PIndexedJimmyStream(Stream(fiType))^.GetJimmyAtIdx(RecNo));  {No need to lock}

	if ListSetup = nil then View := 0 else View := ListSetup^.View;

	if Jimmy=nil then
		{not loaded so mark as hole}
		S := 'nil Jimmy'
	else begin
		{get text}
		S := Jimmy^.DisplayLine(-1, lsType, Size.X, View); {}
		if Jimmy^.Tag then
			{add tagged marker to first line}
			S := GetLine(S,1)+RightSet+'*'+Copy(S,length(GetLine(S,1))+1,255);

		if Jimmy^.GetLock<>0 then
			{add lock marker}
			S := GetLine(S,1)+RightSet+char(Jimmy^.GetLock)+Copy(S,length(GetLine(S,1))+1,255);

		Dispose(Jimmy, Done);
	end;

	{ --- Technical aid ----}
	{$IFDEF fixit}
		{add index detail}
		IndexItem := PIndexItem(IndexStream(fiType)^.GetAt(RecNo));
		S := S +#13	+'   IDX:'+PadSpaceR(N2Str(RecNo),6)+'Dat'+PadSpaceR(N2Str(IndexItem^.Idx2Dat),8);
		if IndexItem^.Hole then S := S + 'Hole ';
		S := S + 'ix'+PadSpaceR(N2Str(IndexItem^.ixType),2)+'KEY:'+IndexItem^.GetKey+':';
		dispose(IndexItem, done);
	{$ENDIF}

	GetText := S;
end;

function TIndexedJimmyListView.GetSearch4Index;
begin
	GetSearch4Index := SubIndexString + ucase(Search);
end; {default - descendants may want to search on a
																															processed search string}


procedure TIndexedJimmyListView.SetRange;
var ItemNo : longint;
		HoleRec, ID : longint;
begin
	if SubIndexString='' then
		inherited SetRange
	else begin

		if ListEmpty then begin
			{for example, when initialising}
			IndexStream(fiType)^.FindFirst(SubIndexString, FirstItem, HoleRec, ID);
			IndexStream(fiType)^.FindFirst(SubIndexString+#255, LastItem, HoleRec, ID);
		end else begin
			{otherwise, findfrom which should? be quicker}
			IndexStream(fiType)^.FindFrom(SubIndexString, FirstItem, HoleRec, ID);
			IndexStream(fiType)^.FindFrom(SubIndexString+#255, LastItem, HoleRec, ID);
		end;
		{now firstitem points to first index entry of that category, and lastitem
		points to the one just after}

		if GetIndexText(FirstItem)=HoleMark then GetNextItemNo(FirstItem);
		GetPrevItemNo(LastItem);

		if (LastItem<FirstItem) or (LastItem=-1) or (FirstItem=-1) then begin
			LastItem := -1;
			FirstItem := -1;
		end;

		TListView.SetRange; {updates scroll bar}
	end;

end;


{==== Handle cmjimmystored event ===============}
{several layers of checks.  The first is to see if the stream has actually
been changed, which is done above in the handleevent.  So we can assume that
the stream has been changed, (but not nec by this jimmy - other actions may
have changed the stream) and the jimmy may have moved onto or off the
screen}

procedure TIndexedJimmyListView.JImmyStored(InfoPtr : pointer);
var Jimmy,OldjImmy : Pjimmy;
		OldLastItem :longint;
		DoRedraw : boolean;
		Relevant : boolean; {is this jimmy relevant to this list?}
		ixType : byte;
		OldIdxRec, IdxREc : Plongint;
		FocusRec : longint;
		TempfiType : byte;

	function InScreenRange(const IdxPtr : longint) : boolean;
	begin
		InScreenRange := (IdxPtr>= TopItem) and
				(LastDisplayItem<>nil) and (IdxPtr<= LastDisplayItem^.ItemNo);
	end;


begin
	{shorthand access}
	jimmy := PJimmy(PJimmyStoredInfo(InfoPtr)^.Jimmy);
	OldJimmy := PJimmy(PjimmyStoredInfo(InfoPtr)^.OldJimmy);

	OldLastItem := LastItem;
	DoRedraw := False;
	Relevant := False;

	{first lets see if this is relevant to this list at all}
	{eg it might be a hookjimmy being stored}
	ixType := 1;
	while not Relevant and (ixType<=Jimmy^.Numixtypes) do begin
		Jimmy^.GetIndex(ixType, IdxRec, TempfiType);
		Relevant := (TempfiType=fiType);
		if not Relevant and (OldJimmy<>nil) then begin
			{check old jimmy}
			OldJimmy^.GetIndex(ixType, OldIdxRec, TempfiType);
			Relevant := TempfiType=fitype;
		end;
		inc(ixType);
	end;

	if not Relevant then exit;

	EventMask := EventMask and not evBroadcast; {so it refuses to acknowledge any
	messages from the scrollbar}
	SetRange;

	EventMask := EventMask or evBroadcast;

	{Now see whether it needs completely redrawing or just a bit}

	{check focused is in range}
	if not inRange(Focused) then begin
		{not in range, so need to do complete redraw with findokitemno}
		DoRedraw := True;
	end else begin

		{we'll need to do a complete redraw if lastitem has changed and is
			on screen, as there will have been something inserted/deleted beforehand.
			No need to check for firstitem as that will be the top one, and changes
			prev to it in the file will not be on view}
		if (OldLastItem<>LastItem) and (DisplayPOfItem(OldLastItem)<>nil) then
			DoRedraw := True
		else begin

			for ixType := 1 to Jimmy^.Numixtypes do begin
				Jimmy^.GetIndex(ixType, IdxRec, TempfiType);
				if TempfiType=fitype then begin
					{if it's a new item, then see if the stored jimmy is on screen, or the oldjimmy was}
					{can't just check for *new* item, as it may be newly into the list but
					actually an edited item...}
					if OldJimmy<>nil then OldJimmy^.GetIndex(ixType, OldIdxRec, tempfiType);

					if InScreenRange(IdxRec^) then begin
						{was on screen and still in same position, so just draw item}
						if (OldJimmy<>nil) and
							(DisplayPOfItem(OldIdxRec^)<>nil) and (IdxRec^=OldIdxRec^) then
							RedrawItem(IdxRec^)
						else
							DoRedraw := True;
					end else
						if (OldJimmy<>nil) and InscreenRange(OldIdxRec^) then
							DoRedraw := True; {been deleted from list}

				end; {if}
			end; {for}
		end;
	end;

	{re-focusing}

	{now ought to check and see if this jimmy was focused - if so, refocus
	in case it has moved, etc (or new - focus on new ones)}
	if OldJimmy<>nil then OldJimmy^.GetIndex(Jimmy^.Gotbyix, OldIdxRec, tempfiType);

	if (OldJimmy=nil) or (OldIdxRec=nil) or (Focused = OldIdxRec^) then begin
		FocusJimmy(Jimmy);
{		Jimmy^.GetIndex(Jimmy^.Gotbyix, IdxRec, tempfiType);
		if IdxRec=nil then FocusRec := -1 else FocusRec := IdxRec^;

		if (FocusRec=-1) or not InRange(FocusRec) then
			{no good any more - see if we can focus on any other ixtype}
{			for ixType := 1 to Jimmy^.NumixTypes do begin
				Jimmy^.GetIndex(ixtype, IdxRec, tempfiType);
				if (Tempfitype=fitype) and (FocusRec=-1) and (IdxRec<>nil) then
					FocusRec := IdxRec^; {if for this file and not one found}
{				if not InRange(FocusRec) then FocusRec := -1; {eg change of category}
{			end;

		if FocusRec>-1 then begin
			FindOKItemNo(FocusRec);		{check not hole, etc, in case not entered in this list}
{			FocusItem(FocusRec);
{		end;{}
	end;

	if DoRedraw then begin
		FindOKItemNo(Focused);
		Redraw;
	end;

	ClearSearch;
	DrawView;
end;

procedure TIndexedJimmyListView.FocusJimmy(const Jimmy : PJimmy);
var IdxRec : PLongint;
		ixType, tempfiType : byte;
		FocusRec : longint;
begin
	Jimmy^.GetIndex(Jimmy^.Gotbyix, IdxRec, tempfiType);
	if IdxRec=nil then FocusRec := -1 else FocusRec := IdxRec^;

	if (FocusRec=-1) or not InRange(FocusRec) then
		{no good any more - see if we can focus on any other ixtype}
		for ixType := 1 to Jimmy^.NumixTypes do begin
			Jimmy^.GetIndex(ixtype, IdxRec, tempfiType);
			if (Tempfitype=fitype) and (FocusRec=-1) and (IdxRec<>nil) then
				FocusRec := IdxRec^; {if for this file and not one found}
			if not InRange(FocusRec) then FocusRec := -1; {eg change of category}
		end;

	if FocusRec>-1 then begin
		FindOKItemNo(FocusRec);		{check not hole, etc, in case not entered in this list}
		FocusItem(FocusRec);
	end;
end;

{redraws all entries of a particular jimmy on screen}
{similar to above code for redrawing for changes}
procedure TIndexedJimmyListView.RedrawJimmy(const Jimmy : PJimmy);
var
	tempfitype,ixType : byte;
	IdxRec : PLongint;

	function InScreenRange(const IdxPtr : longint) : boolean;
	begin
		InScreenRange := (IdxPtr>= TopItem) and
				(LastDisplayItem<>nil) and (IdxPtr<= LastDisplayItem^.ItemNo);
	end;


begin
	for ixType := 1 to Jimmy^.Numixtypes do begin
		Jimmy^.GetIndex(ixType, IdxRec, TempfiType);

		if TempfiType=fitype then
			if InScreenRange(IdxRec^) then
				RedrawItem(IdxRec^);
	end;
end;




{***********************************************
 ***        EDIT ITEM                        ***
 ***********************************************}
procedure TIndexedJimmyListView.Edit(RecNo : longint);
var	Jimmy : PJimmy;
		Control : word;
		IdxRec : longint;
		fitemp, ixtype : byte;

begin
	if not InRange(RecNo) then exit;

	{Get existing data}
	Jimmy := PJimmy(PIndexedJimmyStream(Stream(fiType))^.GetJimmyAtIdx(RecNo));  {No need to lock}

	{Edit Jimmy}
	if Jimmy<>nil then
		Jimmy^.Edit(Self.Owner, nil);      {Make caller then owning window}
	ClearSearch;
end;


{***********************************************
 ***        EDIT/CREATE NEW ITEM             ***
 ***********************************************}
procedure TIndexedJimmyListView.EditNew;
var	Jimmy : PJimmy;
		IndexItem : PIndexItem;
		InitParam : TJimmyInitParam;

begin
	{convert general new to ins --> specific new}
	if Command = cmNew then begin
{		StartEvent(evKeyDown, kbIns);{}
		{maybe lists with no "new" available - eg archive - so don't loop}
		exit;
	end;{}

	if ItemNo<>-1 then IndexItem := PIndexItem(IndexStream(fitype)^.GetAt(ItemNo)) else IndexItem := nil;

	if Command = cmCopyJImmy then begin
		{Create from focused jimmy}
		Jimmy := PJimmy(Create(cmCopyJimmy, @IndexItem^.Idx2Dat));

		dispose(IndexItem, done);

		if JImmy=nil then exit;
	end else begin
		{Create new from passed command}
		InitParam.ListView := @Self;
		InitParam.ForWho := -1;
		InitParam.FocusedParentID := -1;
		if IndexItem<>nil then InitParam.FocusedID := IndexItem^.Idx2Dat else InitParam.FocusedID := -1;

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

		if Jimmy = nil then begin
			ProgramError('JIMINDXS unit, EditNew: No new method defined for '+N2Str(Command)+#13#10
										+'Incorrect/missing Jimmy',hcInternalErrorMsg);
			exit;
		end;
	end;

	if IndexItem<>nil then dispose(IndexItem, done);

	Jimmy^.Edit(Owner, nil);				{edit, store, unlock, etc}
	ClearSearch;
end;


{***********************************************
 ***         DELETE Jimmy - ARCHIVE         ***
 ***********************************************}
procedure TIndexedJimmyListView.Del(RecNo : longint);
var
	Jimmy : PJimmy;
	IndexItem : PIndexItem;
	IdxRec : PLongint;
	TempfiType : byte;

begin
	{Two kinds of deletion - complete deletion of jimmy to the archive index,
	which occurs if this is a main jimmy index which the jimmy points to.  Other
	indexes with no pointer can be taken as temporary/etc, and the entry will
	be deleted from the index but the jimmy will not be marked}
	if InRange(RecNo) then begin
		{Get main data item}
		Jimmy := PJimmy(PIndexedJimmyStream(Stream(fitype))^.GetJimmyAtIdx(RecNo));

		IndexItem := PIndexItem(IndexStream(fitype)^.GetAt(recNo)); {just to get ixtype}

		Jimmy^.GetIndex(IndexItem^.ixType, IdxRec, Tempfitype);

		if IdxRec^=RecNo then begin
			{there is a back pointer - mark jimmy for deletion}
			DeleteJimmy(Jimmy, True); {delete with confirm}
		end else begin
			{just remove from list}
			IndexStream(fitype)^.Delete(RecNo, LkCheck);        {Delete}
		end;

		dispose(JImmy, done);
		dispose(IndexItem, done);

		FindOKItemNo(RecNo);            {Find new OK record}
		FocusItem(RecNo);               {Focus on it}
		ReDraw;                         {Re draw screen}
	end;

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


{******************************
 *** PRINTING LISTS         ***
 ******************************}
{Pass taggedonly as true if only tagged to be printed, and upto as the
number of items to print, or 0 if all are to be printed}
procedure TIndexedJimmyListView.PrintList(const TaggedOnly : boolean; const UpToItemNo,NumPages : longint);
var RecNo : longint;
		srType : word;
		Jimmy : PJimmy;
		ProBox : PProgressBox;
		NumPrinted : longint;
		S : string;
		View : word;

begin
	if ListEmpty then
		PauseMessage('Print List','No List to Print!', hcNoContext)
	else begin
		ProBox := NewProgressBox('Printing '+ucase(PWindow(Owner)^.Title^),'',mfCancelButton,hcNoContext);

		{Set up list}
		Printer^.ClearCodes;
		Printer^.FormCodes^.SetStr('RTITLE',ucase(PWindow(Owner)^.Title^));
		Printer^.StartPrint('REPORT','');

		if ListSetup = nil then View := 0 else View := ListSetup^.View;

		{Print each item}
		RecNo := FirstItem;
		while (RecNo<=LastItem) and (ProBox^.Command<>cmCancel) and
					((UpToItemNo=0) or (NumPrinted<UpToItemNo)) and
					((NumPages=0) or (Printer^.Page<NumPages) or not Printer^.ReadyForEndOfPage(5)) do begin

			if ((RecNo-FirstItem) mod 10) = 0 then
				if TaggedOnly then
					ProBox^.Update('Printing Tagged '+N2Str(NumPrinted)+#13#10+
												'Searched ',RecNo-FirstItem, LastItem-FirstItem)
				else
					ProBox^.Update('Printing List...',RecNo-FirstItem, LastItem-FirstItem);

			Jimmy := PJimmy(PIndexedJimmyStream(Stream(fitype))^.GetJimmyAtIdx(RecNo));
			if Jimmy<>nil then begin
				if not TaggedOnly or Jimmy^.Tag then begin
					S := Jimmy^.DisplayLine(-1, lsType, Printer^.Paper^.Width, View); {}
					S := TabOut(S, Tabs, Printer^.Paper^.Width);
					Printer^.writeln(S);
					inc(NumPrinted);
				end;
				dispose(Jimmy, done);
			end else
				JimmyStream^.Reset;
			inc(RecNo);
		end;

		if ProBox^.Command = cmCancel then Printer^.Writeln('...cancelled');

		Printer^.EndPrint;
		dispose(ProBox, done);
	end;
end;

{******************************
 *** PRINTS ALL TAGGED      ***
 ******************************}
procedure TIndexedJimmyListView.PrintEach;
const
	paLabel = 0;
	paFull = 1;

var RecNo,NumTagged : longint;
		srType : word;
		Jimmy : PJimmy;
		ProBox : PProgressBox;
		Bounds : TRect;
		EditBox : PEditBox;
		Control : word;
		PrintOptions : record
			Editor : word;
			PrintAs : word;
		end;
		PrintType : TJimmyPrintType;
		PrintAs : PSItem;
		PrintAsLink : pointer;
		Device : PDeviceStream;

begin
	if ListEmpty then begin
		PauseMessage('Print List','No List to Print!',hcNoContext);
		exit;
	end;

	{Ask for type of print}
	Bounds.Assign(0,0,30,11);
	New(EditBox, init(Bounds, 'Print All Tagged', @Self));

	with EditBox^ do begin
		Options := Options or ofCentered;

		InsTitledField( 3,1, 15,2, '', New(PERadioButtons, init(Bounds,
																						NewSItem('I~n~ternal',
																						NewSItem('~W~ordPerfect',nil)))));

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

		InsTitledField( 3,4, 15,2, '', New(PERadioButtons, init(Bounds,
																						NewSItem('~L~abel',
																						NewSItem('~F~ull',nil)))));

		InsOKButton(8, Size.Y-3, @PrintOptions);
		InsCancelButton(18, Size.Y-3);

		EndInit;
	end;

	Control := Desktop^.ExecView(EditBox);

	dispose(EditBox, done);

	if Control = cmCancel then exit;

	{=================== PRINT =========================}
	NumTagged := 0;
	case PrintOptions.PrintAs of
		paLabel : begin
			{labels}
			New(Device, init('Label',WorkPath + 'LABEL.NOW'));
			Device^.StartPrint('','');
		end;
	else
		Device := nil;
	end;


	ProBox := NewProgressBox('Printing '+ucase(PWindow(Owner)^.Title^),'',mfCancelButton, hcNoContext);

	{Print each item}
	RecNo := FirstItem;
	while (RecNo<=LastItem) and (ProBox^.Command<>cmCancel) do begin

		if ((RecNo-FirstItem) mod 10) = 0 then ProBox^.Update('Printing Tagged '+N2Str(NumTagged)+#13#10+
							'Searching ',RecNo-FirstItem, LastItem-FirstItem);

		Jimmy := PJimmy(PIndexedJimmyStream(Stream(fitype))^.GetJimmyAtIdx(RecNo));
		if Jimmy<>nil then begin
			if not TaggedOnly or Jimmy^.Tag then begin
				case PrintOptions.PrintAs of
					paLabel : begin
						{label}
						Jimmy^.PrintLabel(Device,0);  {print default label}
						Device^.writeln(#12); {end of label marker}
					end;
					paFull : begin
						Jimmy^.GetDefaultPrintType(PrintType, PrintAs, PrintAsLink);
						Jimmy^.PrintPrintType(PrintType);
					end;
				end;
				inc(NumTagged);
			end;
			dispose(Jimmy, done);
		end else
			JimmyStream^.Reset;
		inc(RecNo);
	end;

	dispose(ProBox, done);

	case PrintOptions.PrintAs of
		paLabel : begin
			{labels}
			Dispose(Device, done);
			PrintLabels(WorkPath+'LABEL.NOW');  {See labels}
		end;
	end;

end;

{*******************************************************
 ***             WINDOW HOUSING                      ***
 *******************************************************}

procedure TIndexedJimmyListWindow.InitMenuBar;
begin
	inherited InitMenuBar;

	AddItemSubMenu(MenuBar^.Menu, mnPrint,
		NewItem('~I~tem',      ksPrint, kbPrint,cmPrintBox,    hcPrintBox,
{		NewItem('~L~abel', 		 ksLabel, kbLabel, cmPrintLabel, hcNoContext,
		NewItem('Defer Label', ksDeferLabel, kbDeferLabel, cmDeferLabel, hcNoContext,
		NewLine({not always appropriate}
		NewItem('~T~agged (Full)',		'', kbNone, cmPrintTagged, 	hcPrintTagged,
		NewItem('~A~ll',		 					'', kbNone, cmPrintAll, 		hcPrintAll,
		newLine(
		NewItem('Ta~g~ged (List)', 		'', kbNone,cmPrintTaggedList,hcPrintTaggedList,
		NewItem('Li~s~t', ksPrintList, kbPrintList,cmPrintList,	hcPrintList,
	nil)))))));

	AddItemSubMenu(MenuBar^.Menu, mnEdit,
		NewItem('~C~reate from...', ksCopyJimmy, kbCopyJimmy, cmCopyJimmy, hcNoContext,
	nil));

	AddItemSubMenu(MenuBar^.Menu, mnTag,
		NewItem('Tag/Untag ~I~tem',				ksTagItem, kbTagItem, cmTagItem, hcTagItem,
		NewItem('Tag ~A~ll', 				'', kbNone, cmTagAll, hcTagAll,
		NewItem('~U~ntag All',       '', kbNone, cmUntagAll, hcUntagAll,
		NewItem('In~v~ert Tags',    '', kbNone, cmInvertTags, hcInvertTags,{}
	nil)))));
end;

{************************************************************************
 ***                                                                  ***
 ***                       THE INDEX JIMMY STREAM                     ***
 ***                                                                  ***
 ************************************************************************}

function TIndexedJimmyStream.GetJimmyAtIdx;
var IndexItem : PIndexItem;
		Jimmy : PJimmy;
begin
	GetJimmyAtIdx := nil;
	if IdxRec = -1 then exit;

	IndexItem := PIndexItem(GetAt(IdxRec));

	if IndexItem <> nil then begin
		if not IndexItem^.Hole then begin
			Jimmy := PJimmy(JimmyStream^.GetAt(IndexItem^.Idx2Dat));
			if Jimmy = nil then begin
				with JimmyStream^ do if Status<>stOk then ErrorMsg('GetJimmyAtIdx '+N2Str(IdxRec)+' ID'+N2Str(IndexItem^.Idx2Dat))
			end else begin
				Jimmy^.GotByIx := IndexItem^.ixType;
{				Jimmy^.Tag := IndexItem^.Tag; now done on the jimmy directly}
			end;
			GetJimmyAtIdx := Jimmy;
		end;
		dispose(IndexItem, done);
	end
end;

{required for shuffling in insert routine}
procedure TIndexedJimmyStream.SetDat2Idx(const IndexItem : PIndexItem; const NewIdxPtr : longint);
begin
	SetJimmyIdxPtr(IndexItem^.ixType, IndexItem^.Idx2Dat, NewIdxPtr);
end;

procedure TIndexedJimmyStream.TagAll(const TagType,fiType : byte; const FirstRec,LastRec : longint);
var IdxRec : longint;
		ProBox : PProgressBox;
		S : String;
		Tag : boolean;
		Jimmy : PJimmy;

begin
	case TagType of
		0 : S := 'Tagging All';
		1 : S := 'Clearing all tags';
		2 : S := 'Inverting all tags';
	end;

	ProBox := NewProgressBox('TAGGING', S+space(20),mfCancelButton, hcNoContext);
	IdxRec := FirstRec;

	{run through all items}
	while (IdxRec<=LastRec) and (ProBox^.Command<>cmCancel) do begin

		if (IdxRec-FirstRec) mod 10 = 0 then
			ProBox^.Update(S,IdxRec-FirstRec, LastRec-FirstRec);

		Jimmy := GetJimmyatIdx(IdxRec);

		if Jimmy<>nil then begin
			Tag := Jimmy^.Tag;

			case TagType of
				0 : Jimmy^.Tag := True;
				1 : Jimmy^.Tag := False;
				2 : if not Jimmy^.GotByAlias(fiType) then Jimmy^.Tag := not Jimmy^.Tag;
			end;

			if Tag<>Jimmy^.Tag then	PutJimmy(Jimmy);

			dispose(Jimmy, done);
		end;

		inc(IdxRec);
	end;

	dispose(ProBox, done);
end;


{***********************************************************************
 ***                                                                 ***
 ***            INDEXED JIMMMY STORE-RELATED ROUTINES                ***
 ***                                                                 ***
 ***********************************************************************}

{***************************************
 ***       STORE INDEXED JIMMY       ***
 ***************************************}


{See jimhooks for notes on allowing both hooked & indexed jimmys, and the
jimmys.pas unit, TJimmy.StoreSelf for the usual storeage method}

{Note that the jimmy *must* be stored before calling this routine, to
reserve space in the file in the case of a new one, but also as the indexing
works off the disk jimmy not the one in the heap (to allow for shuffling
etc) so we must do the same here or we end up with mismatchng pointers}

{Also DiskJImmy is the old jimmy before storing the new one (for checking
if keys have changed etc) so that too has to be gotten beforehand}

{=== RE-INDEX AN EDITED JIMMY ===========}
procedure IndexEditedJimmy;
var ixType : byte;
		IndexItem : PIndexitem;
		fiType, DiskfiType : byte;
		IdxRec, DiskIdxRec : Plongint;
		Key, 		DiskKey : string;

begin
	if Jimmy^.Deleted then begin
		{--- deleted jimmy -----}
		{Store in special archive index, remove from all others}

		{remove from existing indexes}
		for ixType := 1 to Jimmy^.NumixTypes do begin
			DiskJimmy^.GetIndex(ixType, DiskIdxRec, DiskfiType);
			if (DiskfiType>0) and (DiskIdxRec^>-1) then begin
				FileAdmin(DiskfiType)^.LogOn;
				IndexStream(DiskfiType)^.Delete(DiskIdxRec^, lkCheck); {delete index item}
				FileAdmin(DiskfiType)^.LogOff;
				DiskIdxRec^ := -1; {clear pointer}
				DiskJimmy^.StoreIdxPtr(ixtype);
			end;
		end;

		{insert into archive index}
		FileAdmin(fiArchiveIdx)^.LogOn;

		Jimmy^.GetIndex(ixArchive, 		IdxRec, fiType);       		Key 		:= Jimmy^.GetIndexKey(ixArchive);
		DiskJimmy^.GetIndex(ixArchive, DiskIdxRec, DiskfiType);	DiskKey := DiskJimmy^.GetIndexKey(ixArchive);

		if Key<>DiskKey then IndexStream(fiArchiveIdx)^.Delete(DiskIdxRec^, lkCheck);

		if ((not DiskJimmy^.Deleted) or (Key<>DiskKey)) and (Key<>'') then begin
			{new index insertion}
			New(IndexItem, init);
			IndexItem^.Idx2Dat := Jimmy^.RecNo;
			IndexItem^.ixType := ixArchive;
			IndexItem^.KeyString := Key;
			INdexStream(fiArchiveIdx)^.Insert(fiArchiveIdx, IndexItem, IdxRec^);
			{because above line uses IdxRec^, it has automatically changed  jimmy's idxptr}
{			Jimmy^.SetIdxPtr(ixArchive, IdxRec); {heap item}
			Dispose(IndexItem, done);
		end;
		{update pointer on disk}
		Jimmy^.StoreIdxPtr(ixArchive);       {and update work item on disk}
		FileAdmin(FiArchiveIdx)^.LogOff;

		exit; {done}
	end;


	{--- Not deleted.... -----}
	{Log on to index files, etc}
	for ixType := 1 to Jimmy^.NumixTypes do
		if Jimmy^.Getfitype(ixType)<>0 then FileAdmin(Jimmy^.GetfiType(ixtype))^.LogOn;

	FileAdmin(fiJimmys)^.LogOn;

	{=== EDITED =========}
	{First need to delete old index items.  This has to be done after re-reading
	old item, so that 1) any changed pointers due to shuffling are re-read, but
	also 2) that any changed FIELDS that affect pointers (such as the catagory
	field of directory items) are also re-read for the deleting}

	{Lock using srIndexItem: this stops *any* indexed sorting being done???
	NEEDS SOME EXAMINATION!!}
	if CheckFileLock(srIndexItem,'Storing Edited Item'#13#10'Index File')<>0 then exit;

	{I really want to lock all the index streams while doing this - as it is,
	the insert routine will lock the appropriate stream while inserting, and
	I guess the deleting doesn't take long...}

	{set up index item ready for storing}
	New(IndexItem, init);
	IndexItem^.Idx2Dat := Jimmy^.RecNo;

	{Check for changes of indexes}

	{Pass 1 - delete all indexes that have changed - ie convert to hole}
	{Because this pass does not involve any shuffling, we can assume that
	throughout the operation the pointers in memory will continue to match
	the real disk positions}
	for ixType := 1 to Jimmy^.NumixTypes do begin

		Jimmy^.GetIndex(ixType,	IdxRec, fiType); Key := Jimmy^.GetIndexKey(ixType);

		if Key<>DiskJimmy^.GetIndexKey(ixType) then begin
			IndexStream(fiType)^.Delete(DiskJimmy^.GetIdxPtr(ixType), lkCheck); {delete index item}
			Jimmy^.SetIdxPtr(ixType, -1); {clear pointer}
		end else
			{not changed, so copy disk (DiskJimmy) pointer - may have changed due to shuffle while editing}
			if DiskJimmy^.GetIdxPtr(ixType)<>-1 then {for directory items, so that
					blank category pointers	don't overwrite the non-blank one}
				Jimmy^.SetIdxPtr(ixType, DiskJimmy^.GetIdxPtr(ixType));

	end;

	{Pass 2 - insert all indexes that have changed (or clear pointers for
	those that have been cleared).  This pass *does* involve shuffling}
	for ixType :=1 to Jimmy^.Numixtypes do begin

		Jimmy^.GetIndex(ixType, 		IdxRec, fiType); Key := Jimmy^.GetIndexKey(ixType);

		if Key<>DiskJimmy^.GetIndexKey(ixType) then begin

			if Key<>'' then begin
				{if there is anything to insert, make up index item}
				IndexItem^.ixType := ixType;
				IndexItem^.KeyString := Key;

				PIndexStream(Stream(fiType))^.Insert(fiType, IndexItem, IdxRec^); {insert new one}
{			Jimmy^.SetIdxPtr(ixType, IdxRec); {already set with idxrec^ }
			end;
			{update pointer on disk}
			Jimmy^.StoreIdxPtr(ixType);       {and update work item on disk}
		end;

	end;

	dispose(IndexItem, done);

	{--pass 3 & tidy up ----}
	{Load up all indexes as they may have changed due to shuffling aliases, etc
	so that memory image matches disk}
	{Also log off index files, etc}
	for ixType := 1 to Jimmy^.NumixTypes do begin
		Jimmy^.LoadIdxPtr(ixType);
		if Jimmy^.GetfiType(ixType)>0 then FileAdmin(Jimmy^.GetfiType(ixtype))^.LogOff;
	end;
	FileAdmin(fiJimmys)^.LogOff;
end;


{===== INDEX-UP A NEWLY STORED JIMMY =============}
procedure IndexNewJimmy;
var fiType,ixType : byte;
		IndexItem : PIndexitem;
		IdxRec : Plongint;
		Key : string;

begin
	{Log on to index files, etc}
	FileAdmin(fiJimmys)^.LogOn;
	for ixType := 1 to Jimmy^.NumixTypes do
		if Jimmy^.Getfitype(ixType)>0 then FileAdmin(Jimmy^.GetfiType(ixType))^.LogOn;

	{Store Index Items}
	{no point in creating new one on heap each time}
	New(IndexItem, init);
	IndexItem^.Idx2Dat := Jimmy^.RecNo;

	for ixType := 1 to Jimmy^.NumixTypes do begin

		Jimmy^.GetIndex(ixType, IdxRec, fiType); Key := Jimmy^.GetIndexKey(ixType);{get indexing details}

		if Key <> '' then begin {inserting not wanted for this index}

			IndexItem^.ixType := ixType;
			IndexItem^.KeyString := Key;

			{Insert & store index item}
			IndexStream(fiType)^.Insert(fiType, IndexItem, IdxRec^);
			{remember that if two index items will be using the same file (eg
				an alias of a person) then the disk IndexedJimmy item's pointers may have
				changed due to shuffle, whereas the heap item's may not
				That's OK because all the index items are deleted at once, then
				shuffled in - so the pointer at the moment is -1 and won't be
				changed by anyone else}


			{Jimmy^.SetIdxPtr(ixType, IdxRec); {heap item}
			{Store pointer - idxrec above points to jimmys index, so .insert
			routine above sets it}
			Jimmy^.StoreIdxPtr(ixType);
		end;
	end;

	dispose(IndexItem, done);

	{--pass 3 & tidy up ----}
	{Load up all indexes as they may have changed due to shuffling aliases, etc
	so that memory image matches disk}
	{Also log off index files, etc}
	for ixType := 1 to Jimmy^.Numixtypes do begin
		Jimmy^.LoadIdxPtr(ixType);
		if Jimmy^.Getfitype(ixType)>0 then FileAdmin(Jimmy^.GetfiType(ixType))^.LogOff;
	end;

	FileAdmin(fiJimmys)^.LogOff;
end;


{**********************************
 ***      UPDATE POINTER        ***
 **********************************}
{ideally only read in pointer but no way of knowing where it is
 in the record - eg can't do a new(jimmy) as the TJimmy object has a
 blank loaddat2idx method...}
{The below seems to work - basically the same way as the turbovision get
and put - read the first two bytes, then create using the tasks.pas
creator list, so all we need to do is read two bytes not the whole
caboodle.  The creator *must* exist though}
procedure SetJimmyIdxPtr(const ixType : byte; const JimmyRec, NewIdxRec : longint);
var Jimmy : PJimmy;
		srType : word;
begin
	FileAdmin(fiJimmys)^.LogOn;

	JimmyStream^.SeekRec(JimmyRec);
	JimmyStream^.read(srType, 2);
	Jimmy := PJImmy(Create(srType, nil));
	if Jimmy<>nil then begin
		Jimmy^.RecNo := JimmyRec; {so storeidxptr knows where to store}
		Jimmy^.SetIdxPtr(ixType, NewIdxRec);  {change back pointer}
		Jimmy^.StoreIdxPtr(ixType); {store just pointer}
		dispose(Jimmy, done);
	end else
		ProgramWarning('Could not create jimmy srtype '+N2Str(srtype)+#13#10
										+'SetJimmyIdxPtr('+N2Str(ixType)+','+N2Str(JimmyRec)+','+N2Str(NewIdxRec)
										+') jimindxs.pas',hcInternalErrorMsg);

	FileAdmin(fiJimmys)^.LogOff;
end;

type
	PArchiveListView = ^TArchiveListView;
	TArchiveListView = object(TIndexedJImmyListView)
		procedure Del(ItemNo : longint); virtual;
	end;

{remove delete option!}
procedure TArchiveListView.Del;
var
	DummyJimmy,Jimmy : PJimmy;
	Command : word;

begin
	Command := MessageBox('UNDELETE','This entry will be re-inserted into main lists',
									mfCOnfirmation + mfYesNo, hcArchiveUndelete);
	if COmmand=cmYes then begin
		Jimmy := PJimmy(PIndexedJimmyStream(Stream(fitype))^.GetJimmyAtIdx(ItemNo));

		Jimmy^.Deleted := false;

		{clear item on disk so that keys are marked as changed}
		DummyJimmy := Create(Jimmy^.srtype, nil);
		DummyJimmy^.RecNo := Jimmy^.RecNo;
		PutJimmy(DummyJimmy);
		dispose(DummyJimmy, done);

		Jimmy^.SetIdxPtr(ixArchive, -1);
		IndexStream(fitype)^.Delete(ItemNo, LkCheck);        {Delete}

		Jimmy^.StoreSelf;

		FindOKItemNo(ItemNo);				{find nearest ok record to focus on}
		FocusItem(ItemNo);               {Focus on it}
		ReDraw;                         {Re draw screen}
	end;

	dispose(Jimmy, done);
end;


{**********************************************
 ***     JIMMY DATA & ARCHIVE INDEX FILES   ***
 **********************************************}
procedure StartArchiveList; far;
var Bounds : TRect;
		List : PArchiveListView;
begin
	Desktop^.GetExtent(Bounds);

	New(List, Init(Bounds, lsArchive, fiarchiveidx,''));
	List^.HelpCtx := hcArchive;

	Desktop^.Insert(New(PIndexedJimmyListWindow, init(Bounds, 'Archived Items', List)));
end;

function NewArchiveIndex : PStream; far;
begin NewArchiveIndex := New(PIndexedJimmyStream, init('ARCHIVE.IDX', 100)); end;


{procedure UnDelete(P : PView); far;
var List : PListVIew;
begin
	List := PAr
	DiaryDrivers(PDiaryView(P), False);
end;{}


begin
	NewFileAdmin(fiArchiveIdx, 'Archive Index',NewArchiveIndex);
	RegisterTask(DesktopTasks, cmArchiveList, @StartArchiveList);

	RegisterWithList(lsArchive, '~E~dit', NewItem('Undelete', '', kbNone, cmDel, hcArchiveUndelete, nil), nil);

end.

