{****************************************************************************
 ***                                                                      ***
 ***               MULTI LINE LIST VIEWER                                 ***
 ***                                                                      ***
 ****************************************************************************}
{$I compflgs}
{Originally designed to replace TListViewer which could not cope with
multiple-line entries (eg history list) or hidden entries (eg holed
indexes)

It also has a buffered display, so that information once displayed does
not need to be continuously re-read from the disk every time the screen
is redrawn, and scroll up/down requires only one get.

The buffering is in the form of a PDisplayItem chain, each chain item
corresponding to a list item, with an ID of that list item and a string
pointer to the text to be drawn.

Note that this list view is a parent for other kinds of lists - eg indexed
files or chain files.  Therefore we cannot assume that each item number is
sequential to the previous.

This is mostly done through the GetNextItemNo and GetPrevItemNo, which for
chains gets the next record number pointed to by the item, and for indexes
gets the next non-hole.  These two methods assume that a parameter of -1 means
start from the beginning/end, and return -1 once the beginning/end has been
passed.

A final general routine is InRange(ItemNo) which returns true if itemno
is between firstitem and lastitem (or whatever, as appropriate)

Also, the list does not assume zero base, but starts from FirstItem

We hit a snag with some descendants (eg diary) that you cannot do a setrange
or redraw until it has initialised it's own variables.  So the setrange
and redraw is done by DoStartUp, from either the handleevent or draw
methods, which will be run after the .init has been done}



unit TUILIST;

INTERFACE

uses objects,
{$IFDEF WINDOWS}
	OWindows, odialogs,	{windows}
{$ELSE}
	tui,views,menus,{text}
{$ENDIF}
			drivers,
			tasks,
			setup; {for list setup}

const
	{force link on...}
	flOnTagging = $01;
	flOnMoving 	= $02;

type
	PListSetup = ^TListSetup;
	TListSetup = object(TSetup)
		MultiLine : boolean;
		View : word;
	end;

	PDisplayItem = ^TDisplayItem;
	TDisplayItem = object(TObject)
		LineText : PString;
		ItemNo : longint;
		Next : PDisplayItem;
		constructor Init;
		destructor Done; virtual;
	end;


	PListView = ^TListView;
	TListView = object(TView)

			lsType : word; {a bit like help context, this constant tells the
											system what kind of list this is.  The init routine
											uses it to work out any menu additions, etc by
											looking through the command collection and building
											up the menus required}
			{Draw buffer}
			FirstDisplayItem : PDisplayItem;
			LastDisplayItem : PDisplayItem;
			ScrollY : byte; {offset in lines from beginning of displayitems chain}

			VScrollBar : PScrollBar;
			ViewOnly   : Boolean;    {Mark as viewing only - eg locked scodes, sent invoice, etc}

			LinkChanged : boolean; {any changes made since last link update?}
			Changed    : boolean;    {A marker used to say whether any changes have been made}

			{Various "record number" type things}
			TopItem    : longint;  {Top record number on display}
			Focused    : longint;  {Focused Record No/Array pos}
			FirstItem  : longint;	 {First item in list - allows list to access only part of file}
			LastItem   : longint;  {}
			PgStep     : integer;  {remember that some lists are not in consecutive order - eg chains}

			{Search parameters}
			Search     : string;
			SearchCol  : byte; {column to search on}
			AllowSearchScreen : boolean; {marks whether OK to search screen lines first - only OK if display lines are in sort order}

			{Drawing/Partial drawn fields}
			ReDrawn      : boolean;				{finished redrawnext for screen}
			NextLineToDraw  : integer;   {No. Lines drawn so far this update}
			DrawnFocused : boolean;    {Marker to say if focused item has been drawn}

			DoneStartUp : boolean;
			DoneNew   : boolean;    {Used for automatic new, so that auto new only comes on at start and if range BECOMES 0}

			{New}
			MenuOfNew : PMenu;
			TaskList : PTaskItem; {for external procedures to be run from this list}

			ListSetup : PListSetup;   {Used for alternative views of same info}

			AcceptorLink : PView;
			LInkers : PCollection; {for linking peer views}
			ForceLinkOn : byte; {set of flags saying whether to force link on
														moving or tagging (or whatever else in future)}

			kbFocusKey : word; {shortcut key to access this list - useful if this
											list is in a dialog box, press this button and it
											will focus here}

			Tabs : string[19]; {up to 19 tabs}
			ColHeader : string; {Column header - leave blank if not wanted}

		{-- Top level calls --}
		constructor Init(var Bounds : TRect; NlsType : word);
		procedure DoStartUp; virtual;
		destructor Done; virtual;

		{window/display routines}
		procedure Draw; virtual;
		procedure Redraw; virtual;
		procedure RedrawNext; virtual;
		procedure RedrawItem(ItemNo : longint);
		procedure DrawCompletely; {does redraw & redrawnextline until drawn}
		procedure DrawDisplayItem(const DisplayItem : PDisplayItem; var Line : integer);

		function 	GetPalette : PPalette; virtual;
		procedure SetState(AState : word; Enable : boolean); virtual;

		function  GetText(const ItemNo: longint) : string; virtual; {for display}
		function  GetIndexText(const ItemNo : longint) : string; virtual; {override for dosearch stuff}
		procedure	SetTabs; virtual; {sets tabs for this view setup}
		function 	GetDisplayText(Const ItemNo : longint) : string; {override gettext not this, which does TabOut, etc}
		procedure ChangeBounds(var Bounds : TRect); virtual; {redraw & redo scrollbar}

		{View linking}
		procedure SetChanged; {set changed marker}
		procedure CheckLink; virtual;
		procedure ForceLink;
		procedure SetLinker(NewLinker : pointer);
		procedure ClearLinker(Linker : pointer);

		{--- List functions ----}
		procedure SetFocused(ItemNo : longint);
		procedure FocusItem(ItemNo : longint);
		procedure FocusText(Text : string); virtual;

		function LineOfItem(const ItemNo : longint) : integer;
		function ItemOfLine(const Line : integer) : longint;
		function DisplayPOfItem(ItemNo : longint) : PDisplayItem;

		procedure SetFocusKey(kb : word); {sets a focusing key & eventmask to trap it}

		{--- Search functions ----}
		function SearchScreen(Text : string) : longint;
		procedure AddToSearch( C : char); virtual;
		procedure ClearSearch; virtual;
		function GetSearch4Index : string; virtual;  {converts search typed to search to search on}
		function GetSearch4Display : string; virtual;  {converts search typed to search to display}

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

		{Actions}
		procedure Edit(ItemNo : LongInt); virtual;
		procedure Del(ItemNo : LongInt); virtual;
		procedure EditNew(ItemNo : longint; Command : word); virtual;


		{-- routines for above --}
		procedure SendAcceptMessage(var Event : TEvent; const Command : word; const Info : pointer);
		procedure TidyDisplayItems;

		procedure InsertItem(ItemNo : longint; const Top : boolean);

		{Movement}
		procedure PageUp; virtual;
		procedure PageDown; virtual;
		procedure GoHome;
		procedure GoEnd; virtual;
		procedure OneUp;
		procedure OneDown;

		{Ranging}
		procedure GetNextItemNo(var ItemNo : longint); virtual;
		procedure GetPrevItemNo(var ItemNo : longint); virtual;
		procedure StepItemNo(var ItemNo : longint; const Step : integer); virtual;
		procedure FindOKItemNo(var ItemNo : longint); virtual;

		procedure SetRange; virtual;
		function InRange(const ItemNo : longint) : boolean; virtual;
		function ListEmpty : boolean; virtual; {returns true if no items in list}

		{printing}
		procedure PrintList(const TaggedOnly : boolean; const UpToItemNo,NumPages : longint); virtual;
		procedure PrintEach(const TaggedOnly : boolean); virtual; {full print}

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

	{===== LIST WINDOW ==========}
	{For independant lists to be inserted into desktop etc}
	PListWindow = ^TListWindow;
	TListWindow = object(TWindow)

		MenuBar   : PDlgMenuBar;    {Extended Menu Bar for this list}
		List      : PListView;

		Precascade : TPoint;

		constructor Init(var Bounds: TRect; WTitle : string; AList : PListView);
		procedure InitMenuBar; virtual;
		procedure HandleEVent(var Event : TEvent); virtual;

		procedure Cascade;
		procedure UnCascade;

	end;


	{Freebie collection viewer, based on above}
	PCollectionViewer = ^TCollectionViewer;
	TCollectionViewer = object(TListView)
		Collection : PCollection;

		destructor Done; virtual;
		procedure SetRange; virtual;

		procedure Edit(ItemNo : LongInt); virtual;
		procedure Del(ItemNo : LongInt); virtual;
		procedure EditNew(ItemNo : longint; Command : word); virtual;

		function EditItem(P : PObject) : word; virtual; {override, takes
		 pointer to item to be edited and returns cmOK/cmCancel}
		function CreateNewItem : PObject; virtual;
	end;

IMPLEMENTATION

uses
		memory, {for low memory warning}
		tuiApp, {setup}
		tuiMsgs,{}
		tuiedit, {for inputlinker stuff}
		printers, {for printlist}
		kamsetup, {for auto new}
		global, minilib, app, help;

const
	EndOfList = '--- END ---';


constructor TDisplayItem.Init;
begin
	LineText := nil;
	ItemNo := -1;
	Next := nil;
end;

destructor TDisplayItem.Done;
begin
	if LineText<>nil then disposeStr(LineText);
	if Next<>nil then dispose(Next, done);
	inherited Done;
end;

{*************************************************************************
 ***                                                                   ***
 ***                    LIST VIEWER                                    ***
 ***                                                                   ***
{*************************************************************************}


{--- Initialisation -------------------------------------}
constructor TListView.Init;
var R : tRect;
		B : byte;

begin
	inherited Init(Bounds);

	if LowMemory then begin
		RunAllTasks(LowMemoryTasks); {unload sentence codes, etc}
		if LowMemory then begin
			ProgramWarning('OUT OF MEMORY!'+CRLF+CRLF+'Cannot start list'+CRLF+'Please close some views',hcMemoryLowMsg);
			fail;
		end else
			ProgramWarning('Memory Running Low!'+CRLF+CRLF+'Close some views', hcMemoryLowMsg);
	end;

	ForceLinkOn := 0;

	EventMask := $FFFF and not evMouseMove; {catch all except mouse *movements* - still pick up clicks}

	{switch on act on first click (ofSelectable & ofFirstClick) and switches off moving to top on click - interferes with tab}
	Options := (Options or ofSelectable or ofFirstClick or ofIdle)
							and not ofTopSelect;

{	HelpCtx := ListHelpCtx[NlsType and $FF];{}

	{grow exactly relative to window frame}
	GrowMode := gfGrowHiX + gfGrowHiY;

	ListSetup := nil;

	{Make up scroll bar ready to be insertd into owner, just to right of self}
	R.Assign(Bounds.B.X, Bounds.A.Y, Bounds.B.X+1, Bounds.B.Y);
	New(VScrollBar, init(R));

	TopItem :=-1; {BotItem := -1;{}
	FirstItem := -1; LastItem := -1;
	if ProgramSetup.GetBoolean(siAutoNew, True) then DoneNew := False else DoneNew := True;{}

	Search := '';
	SearchCol := 0;  {For matching search with screen text}
	AllowSearchScreen := True;

	PgStep := Size.Y;
	Focused := -1;          {Not yet focused}
	ViewOnly := False;
	Changed := False;

	ScrollY := 0;

	FirstDisplayItem := nil;
	LastDisplayItem := nil;

{	Redraw;                 {Mark as needing drawn}
{leave to descendants as some need to set particular things before doing redraw}

	AcceptorLink := nil;

	Linkers := nil;

	kbFocusKey := kbNone;

	{Build menus from lsType}
	lsType := NlsType;
	MenuOfNew := nil;
	TaskList := nil;

	AddCommandsToListNewMenu(lsType, MenuOfNew);

	DoneStartup := false;
	Redrawn := False;

	Tabs := '';
	SetTabs;
	ColHeader := '';
end;

procedure TListView.DoStartUp;
begin
	DoneStartUp := True;
	SetRange;
	Redraw;
end;

destructor TListView.Done;
begin
{	EnableCommands([cmNew, cmEdit, cmDel, cmAccept, cmPrint, cmDeferPrint, cmCopyJimmy]); {re-enable all -
																										may have been disabled while drawing}
	if ListSetup<>nil then dispose(ListSetup, done);

	IF MenuOfNew<>nil then disposeMenu(MenuOfNew);

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

	if FirstDisplayItem<>nil then dispose(FirstDisplayItem, done);
	inherited Done;
end;


{--- Change bounds (& page stepping) --------------------}
procedure TListView.ChangeBounds;
var R : TREct;
begin
	Inherited ChangeBounds(Bounds);
	SetTabs;
	if DoneStartUp then ReDraw;{causes double loading of lists when doing initial setbounds as part of window, etc}
	TidyDisplayItems; {tidies up any surplus items off bottom}

	{Relocate Scroll Bar too}
	if VScrollBar<>nil then begin
		R.Assign(Bounds.B.X, Bounds.A.Y, Bounds.B.X+1, Bounds.B.Y);
		VScrollBar^.Locate(R);
	end;{}
end;

{------ Set State -----------------------}
procedure TListView.SetState;
begin
	inherited SetState(AState, Enable);
	if (AState = sfSelected) or (AState = sfActive) then begin{}
		Draw; {draw view so that highlighted line changes colour}
		if (Astate = sfSelected) and Enable and (Options and ofTopSelect<>0) and (VScrollBar<>nil) and (VSCrollBar^.Owner<>nil) then
			{particularly so that scrollbar gets moved to top of other scroll bars
			in tabbed views}
			VScrollBar^.MakeFirst;
	end;

end;

procedure TListView.SetFocusKey;
begin
	kbFocusKey := kb;
	Options := Options or ofPreProcess; {trapper}
end;



{--- FOCUS ON ITEM -------------------------------------}
procedure TListView.FocusItem(ItemNo : longint);
begin
	if InRange(ItemNo) then begin

		SetFocused(ItemNo);

		{check to see if on screen}
		if DisplayPOfItem(ItemNo) <> nil then begin
			{on screen}
			DrawView; {just draw}
		end else begin
			TopItem := Focused;{}
			Redraw;
		end;

	end else
		SetFocused(ItemNo); {always set to unfocused if no items present}
end;


{---- set focussed, update scroll bar ------}
procedure TListView.SetFocused(ItemNo : longint);
begin
	if Focused<>ItemNo then begin
		Focused := ItemNo;
		if (ForceLinkOn and flOnMoving)>0 then ForceLink;
	end;

	{Update scroll bar}
	if VScrollBar<>nil then begin
		{Don't call Set Value as it returns a command}
		if Focused>-1 then VScrollBar^.Value := Focused else VSCrollBar^.Value := 0;
		VScrollBar^.DrawView;
	end;
end;

{--- For use by focus-on-text descendants ----}
{returns item number to focus on}
function TListView.SearchScreen(Text : string) : longint;
var PrevDisplayItem, DisplayItem : PDIsplayItem;
		S : string;

	function DisplayStr(DisplayItem : PDIsplayItem) : string; {returns portion of display string to test}
	begin
		DisplayStr := ucase(Copy(DisplayItem^.LineText^, SearchCol+1,length(Text)));
	end;


begin
	Text := ucase(Text);

	DisplayItem := FirstDisplayItem;
	PrevDisplayItem := nil;
	SearchScreen := -1;

	{look through display buffer}
	while (DisplayItem<>nil) and (DisplayStr(DisplayItem)<Text) do begin
		PrevDisplayItem := DisplayItem;
		DisplayItem := DisplayItem^.Next;
	end;

	if (DisplayItem<>nil) and (DisplayItem<>FirstDisplayItem) then begin
		{if it's not the first one (there may be matches before), and some match
			was found, then we can set a match.  Work out whether it's the current
			one (past the search point) or the previous one}
		SearchScreen := DisplayItem^.ItemNo;

		{check if prev}
		if (DisplayStr(DisplayItem)>Text) and (PrevDisplayItem<>nil) then
			{near match, not exact, is the prev one a closer match than the current?}
			if BestMatchLength(DisplayStr(PrevDisplayItem), DisplayStr(DisplayItem), Text) = -1 then
				SearchScreen := PrevDisplayItem^.ItemNo; {set to prev one}
	end;
end;


{Defaults to getting ordinary text; descendants can override to speed up file
get's etc.  This is what the searchtext is compared with}
function TListView.GetIndexText;
begin
	GetIndexText := GetText(ItemNo);
end;

{sends message to changedindicator (if it exists) to mark a change}
procedure TListView.SetChanged;
begin
	LinkChanged := True; CheckLink; {so that link updates}
	if not Changed then begin
		Changed := True;
		Message(Owner, evBroadCast, cmSetChangedIndicator, @Self);
	end;
end;

procedure TListView.CheckLink;
var B : byte;
begin
	if LinkChanged then begin
		LinkChanged := False;
		ForceLink;
	end;
end;

procedure TListView.ForceLink;

	procedure DoLink(InputLinker : PInputLinker); far;
	begin
		InputLinker^.CalculateLink(@Self);
	end;

begin
	if (Linkers<>nil) and Valid(cmForceLink) then
		Linkers^.ForEach(@DoLink);
end;

{--- FOCUS ON TEXT ------------------------------}
{Simple procedure, meant to be overwritten, that looks through all items
until it finds a match.  Sorted descendants should perhaps do a chop, etc}
procedure TListView.FocusText;
var	ItemNo, OItemNo : longint;
		GotText,GotText2 : string;

	function SearchText(ItemNo : longint) : string;
	begin
		SearchText := ucase(Copy(GetIndexText(ItemNo),SearchCol+1,length(Text)))
	end;

begin
{	Text := ucase(Text);{}

	{See if match already on-screen}
	if AllowSearchScreen then ItemNo := SearchScreen(Text) else ItemNo := -1;

	if ItemNo <> -1 then begin
		SetFocused(ItemNo);
		DrawView;
	end else begin

		{itemno is -1, so will start at beginning}
		repeat
			GetNextItemNo(ItemNo);
			if ItemNo=-1 then GotText := '' else GotText := SearchText(ItemNo);
		until (ItemNo=-1) or (GotText>=Text);

		if GotText<>Text then begin
			{not an exact match - let's see what's best.  So far, above
			search will have found the one just *after* match, if not exact match}
			{so lets see if the previous item is any better...}
			OItemNo := ItemNo;
			GetPrevItemNo(ItemNo);
			if ItemNo>-1 then begin
				GotText2 := SearchText(ItemNo);  {match only up to search length}

				{Check to see which best matches - if GotText does, revert to that}
				if BestMatchLength(GotText, GotText2, Text) <1 then ItemNo := OItemNo;
			end;
		end;

		if ItemNo<>-1 then FocusItem(ItemNo);
	end;

end;



{--- Get Text ------------------------------------------}
function TListView.GetText(const ItemNo: longint) : string;
begin
	GetText := 'Parent Get Text, ListView';
end; {should be overriden by descendant}



procedure TListView.SetTabs;
begin
	{by default, *don't* set to '', that way descendants can set tabs in the
	.init proc and not get overwritten}
{	Tabs := '';{}
end;


{--------- Search methods -----------}
procedure TListView.AddToSearch;  {descend to do actual search}
var Event : TEvent;
begin
	if C = #8 then Search := Copy(Search,1,length(Search)-1) {Backspace}
						else Search := Search + C;  {Normal Add-on}

	{Check to see if more being typed in - if so, don't go anywhere yet}
{Problem - when there is no search match, it doesn't move, so if someone types
in SMIK it goes to S, then does a complete search on SMIK, doesn't find anyone
and stays on S instead of moving to SMI or whatever...}
	GetEvent(Event);

	if (Event.What = evKeyDown) and {if it's a keypress}
		((Event.CharCode =#8) or		{and it's a delete}
		((Event.CharCode>=#32) and (Event.CharCode<=#127))) then begin {or it's in range}

			AddToSearch(Event.CharCode)   {go round again}

	end else begin

		PutEvent(Event); {not a search keypress - replace for dealing with normally}

		if Search = '' then GoHome									{perform search}
									 else FocusText(GetSearch4Index);
	end;

end;

procedure TListView.ClearSearch;
begin Search := ''; end;


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

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


procedure TListView.Idle;
begin
	if not ReDrawn then
		RedrawNext;   {Check if screen is drawn}
end;

{======== TAGGING ================}
procedure TListView.TagItem(const ItemNo : longint);
begin end;

procedure TListView.TagAll(TagType : byte); {0 - tag, 1 - untag, 3 - invert tag}
begin end;

procedure TListView.PrintEach(const TaggedOnly : boolean);
begin end;{}

{=== LINKERS ============}
procedure TListView.SetLinker(NewLinker : pointer);
begin
	if Linkers = nil then New(Linkers, init(1,1));
	Linkers^.Insert(NewLinker); {add to start}
	EventMask	:= EventMask or evBroadCast; {so it gets linker events}
end;

procedure TlistView.ClearLinker(Linker : pointer);
begin
	if Linkers<>nil then
		Linkers^.Delete(Linker);
end;

{*******************************************************
****               HANDLE EVENT                      ***
********************************************************}

procedure TListView.HandleEvent(var Event : TEvent);
var ItemNo : longint;{}
		MousePos : TPoint;
		Event2 : TEVent;

procedure StartCommand(const cmtype : word);
{var Event : TEvent; {use handleevent's event so that it gets cleared}
begin
	if CommandEnabled(cmType) then begin
		Event.What := evCOmmand;
		Event.Command := cmType;
		Event.InfoPtr := @Self;
		Desktop^.PutEvent(Event);
	end;
	ClearEvent(Event);
end;

procedure DoneEvent;
begin
	ClearSearch;
	ClearEvent(Event);
end;


begin
	if not DoneStartUp then DoStartup; {see comment at beginning}

	{Automatic new if list becomes (or starts) empty, and is focused}
	if (not DoneNew) and ReDrawn and (not DrawnFocused) {nothing in list & not done new}
		and GetState(sfFocused) and (not IsEvent) then begin {and become focused and nothing else yet to do}
		if not ViewOnly then begin
			Event2.What := evKeydown;
			Event2.KeyCode := kbNew;
			Event2.InfoPtr := @Self;
			QueueEvent(Event2);
		end;
		DoneNew := True;
	end;

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

		case Event.Command of

			cmReceivedFocus, cmReleasedFocus : DrawView; {so attr changes for focused line}

			{scroll bar been changed by user - eg dragging marker}
			{need to check it is for this scroll bar in case of multiple list views
			in an editbox}
			cmScrollBarChanged : if Event.InfoPtr=VScrollBar then begin
				DoneEvent;
				ItemNo := VSCrollBar^.Value;    {new focus value}
				FindOKItemNo(ItemNo);
				FocusItem(ItemNo);
			end;                {}

			cmRedraw : Redraw;

			cmForceLink : ForceLink;

			cmCompareLoc : if (Origin.X = PPoint(Event.InfoPtr)^.X) and (Origin.Y = PPOint(Event.InfoPtr)^.Y) then
				ClearEvent(Event);
		end;
	end;

	{========== COMMANDS ======================}
	if Event.What = evCommand then begin

		{run task list}
		if RunListTask(TaskList, Event.Command, @Self) then ClearEvent(Event);

		case Event.Command of
			{--- Standard user generated commands ---}
			{descendants should override accept by trapping before calling this method}
			cmAccept,
			cmEdit 		  : if DrawnFocused then begin
											{if not ViewOnly then{} Edit(Focused); {edit routine must check how to do viewonly}
											DoneEvent;
										end else begin
											if not Redrawn then begin
												ReDrawNext; {make sure it draws a line - prevent looping}
												PutEvent(Event); {replace - postpone}
											end else
												ClearEvent(Event); {empty list}
										end;

			cmCut      	: begin
											Del(Focused);
											DoneEvent;
										end;

			cmEditSetup	: if ListSetup<>nil then begin
											ListSetup^.Edit;
											SetTabs;
											Redraw;
											DoneEvent;
										end;

			{---- Link updates/handling ----------------}
			cmUpdateFromLink 	: begin CheckLink; 									ClearEvent(Event);	end;
			cmSetLinker 			: begin SetLinker(Event.InfoPtr); 	ClearEvent(Event); end;
			cmClearLinker 		: begin ClearLinker(Event.InfoPtr);	ClearEvent(Event); end;

			{tagging/untagging}
			cmTagItem : begin
				TagItem(Focused);
				if (ForceLinkOn and flOnTagging)>0 then ForceLink;
				StartEvent(evKeyDown, kbDown);
				ClearEvent(Event);
			end;

			cmTagAll 			: TagAll(0);
			cmUntagAll 		: TagAll(1);
			cmInvertTags 	: TagAll(2);

			{--- Printing ---}
			cmPrintTagged, cmPrintAll : begin
				PrintEach(Event.Command = cmPrintTagged);
				ClearEvent(Event);
			end;

			cmPrintList,cmPrintTaggedList : begin
				PrintList(Event.Command=cmPrintTaggedList, 0,0);
				ClearEvent(Event);
			end;
			cmPrintLastPage : begin
				PrintList(False, 0, 1); {print last page only}
				ClearEvent(Event);
			end;

		end;
	end;

	{============= MOUSE =====================}
	{left single - focus
	 left double - accept/edit
	 right single - tag
	 right double - new}
	if (Event.What = evMouseDown) and MouseInView(Event.Where) then begin {Button clicked}

		Focus; {focus on list if not already}

		if (Event.Buttons = mbRightButton) and Event.Double then
			{double right - new}
			StartEvent(evKeyDown, kbNew)
		else begin
			{move}
			MakeLocal(Event.Where, MousePos);	{Find where in list}
			if ColHeader='' then inc(MousePos.Y);
			ItemNo := ItemOfLine(MousePos.Y);
			if ItemNo<>-1 then begin
				SetFocused(ItemNo);
				DrawView;
				if Event.Buttons = mbRightButton then
					StartCommand(cmTagItem)
				else
					if Event.Double then StartCommand(cmAccept); {Double click means accept}
			end;{}
		end;

		DoneEvent;
	end;


	{========== CONVERT KEYPRESSES ======================}
	{For those keys with no menu equivelent, and for those lists with no
	menus (so that the event is generated)}
	if (Event.What = evKeyDown) and GetState(sfFocused) then begin {if it's focused - o/w ignore such events}

		if not ReDrawn then RedrawNext; {handy for cursor moving etc, if we force an extra
		redraw while doing things, as idle may never be called if there's a
		series of keypresses stacked up}

		{--- any-time events ---}
		case Event.KeyCode of
			kbNew 			: if (Event.InfoPtr = @Self) or (Event.InfoPtr = nil) then
						{this check is to make sure that the automatically generated new
						 via kbIns if the list is empty, only picks up new if it is for this list}
							StartCommand(cmNew);
			kbPaste 		: StartCommand(cmListPaste);
			kbPrintList : begin
{				if Message(Owner, evBroadCast, cmHaveYouaMenu, nil)<>nil then{}
					StartCommand(cmPrintList);
					ClearSearch;
{				end else begin
					{no print menu, so display pop up menu}
{					Event.What := evCommand;
					Event.Command := DoPopUpMenu(hmmmMenuOfNew, Owner);	{Build and execute a menu box - autoselect if 1 option}
{				end;{}
			end;

			{List Navigation keys}
			kbPgDn : begin 	DoneEvent; PageDown; end;
			kbPgUp : begin 	DoneEvent; PageUp;   end;
			kbHome : begin 	DoneEvent; GoHome;   end;
			kbEnd  : begin 	DoneEvent; GoEnd;    end;
		end;

		{--- actions on focused ----}
		if DrawnFocused then
			case Event.KeyCode of

				kbEnter 		: begin StartCOmmand(cmAccept); ClearSearch; end;
				kbChange 		: begin StartCommand(cmEdit); ClearSearch; end;
				kbShiftDel 	: begin
					StartCommand(cmListCut); ClearSearch; end;

				{one up/down movement dependant on focused}
				kbUp   			: begin DoneEvent; OneUp; end;
				kbDown 			: begin	DoneEvent; OneDown; end;

				kbAsterisk,kbTagItem : begin
					StartCommand(cmTagItem);
					Event.What := evKeyDown;
					Event.KeyCode := kbDown;
					QueueEvent(Event);
					ClearEvent(Event);
				end;
			end;

		{On-the-fly Search}
		if (Event.What<>evNothing) then
			if (Event.CharCode = #8) or
				((Event.CharCode>=#32) and (Event.CharCode<=#127)) then begin
					AddToSearch(Event.CharCode);
					ClearEVent(event);
			end;
	end;

	inherited HandleEvent(Event);

	{A focus key facility is provided - if it is set (use SetFocusKey method)
	then pressing the button will focus on this view and zoom in - useful for
	when lists appear in dialog boxes. }
	if (Event.What=evKeyDown) and (Event.KeyCode=kbFocusKey) then begin
		Focus;
		StartEvent(evKeyDown, kbZoom);	{zoom in - if available}
		DoneEvent;
	end;

	if GetSTate(sfFocused) and not ViewOnly then begin {only do if focused}

		{----------- If new not handled, no menu, so put box on screen ------------}
		if (Event.What = evCommand) and (Event.Command=cmNew) then begin
			if MenuOfNew = nil then begin
				EditNew(Focused, cmNew); {nothing there so do standard new}
				DoneEvent;
			end else begin
				Event.What := evCommand;
				Event.Command := DoPopUpMenu(MenuOfNew, Owner);	{Build and execute a menu box - autoselect if 1 option}
			end; {Menuofnew nil}
		end;

		{Check registered new commands}
		if Event.What = evCommand then
			if CommandinMenu(MenuOfNew, Event.Command) and CreatorExists(Event.Command) then begin
				EditNew(Focused, Event.Command);
				ClearEvent(Event);
			end;

	end;

end;

{------- EDIT ------------------------------------------}
procedure TListView.Edit(ItemNo : LongInt);
begin end;
procedure TListView.EditNew(ItemNo : LongInt; Command : word);
begin end;

{------- DELETE ------------------------------------------}
procedure TListView.Del(ItemNo : LongInt);
begin end;




{================ DRAW SCREEN ==============================}
{Various ways of drawing lines and screens, from buffer or calling
 application (which may get info from file; thus the buffer}

function TListView.GetPalette: PPalette;
const
	P: String[Length(CListViewer)] = CListViewer;
begin
	GetPalette := @P;
end;{}

{--- Draw Screen (from buffer) --------------------------}
procedure TListView.Draw;
var Line : integer; {line}
		DisplayItem : PDisplayItem;
		DrawBuffer : TDrawBuffer;
begin
{	inherited Draw; {blanks background}
	if not DoneStartup then DoStartup;

	DisplayItem := FirstDisplayItem; Line := -ScrollY;

	if ColHeader<>'' then begin
		WriteStr(0,0,TabOut(ColHeader,Tabs,Size.X),5);
		inc(Line);
	end;

	while (DisplayItem<>nil) and (Line<Size.Y) do begin

		{check if first one focused and off screen}
		if (DisplayItem^.ItemNo = Focused) and (Line<0) then begin
			ScrollY := 0;
			Line := 0;
		end;

		DrawDisplayItem(DisplayItem, Line);
		DisplayItem := DisplayItem^.Next;
	end;

	NextLineToDraw := Line;

	if Line>=(Size.Y) then
		Redrawn := True
	else begin
		{blank rest of screen}
		MoveChar(DrawBuffer,#32,GetColor(1),Size.X);
		WriteLine(0, Line, Size.X, Size.Y-Line, DrawBuffer);
		if Redrawn then WriteStr(Size.X div 2 - 6,LIne,EndofList,5); {end of list marker, colour #5}
	end;

	{If tekky mode, put id no on top right}
	{$IFDEF fixit} WriteStr(Size.X-5-length(N2Str(Focused)), 0, 'Item '+N2Str(Focused), 2); {$ENDIF}
end;


procedure TListView.DrawDisplayItem(const DisplayItem : PDisplayItem; var Line : integer);
var Attr,SMatch,B : byte;
		DrawBuffer : TDrawBuffer;
begin
	if DisplayItem = nil then exit;
	if DisplayItem^.LineText = nil then
		ProgramError('DisplayItem.LineText=nil'#13'TListView.DrawDisplayItem', hcInternalErrorMsg);

	{work out attribute}
	if (DisplayItem^.ItemNo=Focused) and (Focused<>-1) then begin
		if {GetState(sfSelected) and GetState(sfActive)} GetState(sfFocused) then Attr := 3 else Attr := 4;
		DrawnFocused := True;   {Will have now drawn focused}
		SMatch := MatchLength(ucase(copy(DisplayItem^.LineText^,SearchCol+1,99)), ucase(GetSearch4Display)); {mark part searched}
	end else begin
		Attr := 1;
		SMatch := 0;
	end;

	{write first line}
	MoveChar(DrawBuffer,#32,GetColor(Attr),Size.X); {clear}
	MoveStr(DrawBuffer, GetLine(DisplayItem^.LineText^,1), GetColor(Attr));
	{mark searched bit}
	if SMatch>0 then MoveStr(DrawBuffer[SearchCol], ucase(Copy(GetSearch4Display,1,SMatch)), GetColor(4));
	WriteBuf(0, Line, Size.X, 1, DrawBuffer);
	inc(Line);

	{write rest of lines if multiline}
{$IFNDEF fixit} {or in fixit mode, so tekky info displayed}
	if (Listsetup=nil) or (ListSetup^.MultiLine) then
{$ENDIF}
		for B := 2 to NumLines(DisplayItem^.LineText^) do begin
{			MoveChar(DrawBuffer,#32,GetColor(Attr),Size.X);
			MoveStr(DrawBuffer, GetLine(DisplayItem^.LineText^,B), 0);{}
			WriteStr(0, Line, SetLength(GetLine(DisplayItem^.Linetext^, B),Size.X), Attr);
			inc(Line);
		end;
end;

procedure TListView.DrawCompletely;
begin
	DoStartup; {includes Redraw;}
	repeat RedrawNext until ReDrawn;
end;

{--- Draw next line --------}
{To allow keyboard processing while getting data off disk for displaying,
the drawing process is split into three parts - a redraw method, which simply
clears the display buffer in order to redraw the complete view from disk, and
the following redrawnextLine which gets the info for one line at a time while
idling, and the normal draw method which draws the got information onto the
screen}
{Stop calling when Redrawn is set}

procedure TListView.ReDrawNext;
var Line : integer;
		ItemNo : longint;
		DisplayItem : PDisplayItem;

begin
	{set item no. needing displayed}
	if LastDisplayItem<>nil then begin
		ItemNo := LastDisplayItem^.ItemNo;
		GetNextItemNo(ItemNo); {and move on}
	end else
		{nothing displayed yet, so}
		ItemNo := TopItem;

	if not InRange(ItemNo) then begin
		{nothing to draw or end of list}
		Redrawn := True; {nothing to draw or end of list}
		DrawView;
	end else begin
		InsertItem(ItemNo, False); {insert at bottom}
		DrawDisplayItem(LastDisplayItem, NextLineToDraw);

		If NextLineToDraw>=Size.Y then begin
			Redrawn := True;
			{work out page step}
			PgStep := LastDisplayItem^.ItemNo - TopItem;
			if PgStep<2 then PgStep := 2; {In case the window is well shrunk...}
		end;
	end;
end;

{--- Redraw item only --------------------------------}
{Used for when one item has been changed/edited}
procedure TListView.RedrawItem(ItemNo : longint);
var DisplayItem : PDisplayItem;

begin
	DisplayItem := DisplayPofItem(ItemNo);
	if DisplayItem<>nil then begin {safety}
		Disposestr(DisplayItem^.LineText);
		DisplayItem^.LineText := NewStr(GetDisplayText(ItemNo));
		Draw;
	end else
		ProgramError('TListView.RedrawItem('+N2Str(ItemNo)+')'#13'Not on screen... no focused item?',hcInternalErrorMsg);
end;

function TListView.GetDisplayText(const ItemNo : longint) : string;
var S : string;
begin
	S := GetText(ItemNo);
	if S='' then S := 'BLANK ITEM';
	{$IFNDEF fixit} {need to have extra lines for fixit mode}
		if (ListSetup<>nil) and (not ListSetup^.MultiLine) then S := GetLine(S,1);
	{$ENDIF}
	GetDisplayText := TabOut(S, Tabs, Size.X);
end;

{--- REDRAW SCREEN (from file) --------------------------}
procedure TListView.Redraw;
begin
	{Set this in slow-drawing descendants}
{	TopItem := Focused;  {Move to focused item so that user can move while drawing}
	if not DoneStartup then begin DoStartup; exit; end; {redraw called in dostartup}

	if Focused=-1 then Focused := FirstItem;

	DrawnFocused := False; {Has not yet drawn focused}
	ReDrawn := False;      {Mark as not drawn}
	if not InRange(TopItem) then TopItem := FirstItem;
	NextLineToDraw := -ScrollY;

	if FirstDisplayItem<>nil then dispose(FirstDisplayItem, done);
	FirstDisplayItem := nil;
	LastDisplayItem := nil;

	if ListEmpty then Redrawn := True; {nothing to draw}

	DrawView; {clear view}

	{don't do this as redraw is done in init and fitype/collection/etc will
	not be set}
{  RedrawNextLine; {draw the first line anyway}
end;


function TListView.LineOfItem(const ItemNo : longint) : integer;
var Line : integer;
		DisplayItem : PDisplayItem;
begin
	Line := 0; if ColHeader<>'' then inc(Line);
	DisplayItem := FirstDisplayItem;

	while (DisplayItem<>nil) and (DisplayItem^.ItemNo<>ItemNo) do begin
		inc(Line, NumLines(DisplayItem^.LineText^));
		DisplayItem := DisplayItem^.Next;
	end;

	if (DisplayItem<>nil) and (DisplayItem^.ItemNo=ItemNo) then
		LineOfItem := Line
	else
		LineOfItem := -1;
end;

function TListView.ItemOfLine(const Line : integer) : longint;
var WorkLine : integer;
		DisplayItem : PDisplayItem;
begin
	WorkLine := 0;
	DisplayItem := FirstDisplayItem;

	while (DisplayItem<>nil) and (WorkLine+NumLines(DisplayItem^.LineText^)<Line) do begin
		inc(WorkLine, NumLines(DisplayItem^.LineText^));
		DisplayItem := DisplayItem^.Next;
	end;

	if DisplayItem<>nil then
		ItemOfLine := DisplayItem^.ItemNo
	else
		ItemOfLine := -1;
end;


{returns pointer to display item of itemno}
function TListView.DisplayPOfItem(ItemNo : longint) : PDisplayItem;
var	DisplayItem : PDisplayItem;
begin
	DisplayItem := FirstDisplayItem;

	while (DisplayItem<>nil) and (DisplayItem^.ItemNo<>ItemNo) do
		DisplayItem := DisplayItem^.Next;

	DisplayPofItem := DisplayItem
end;


{--- Set Line ------------------------------------------}
procedure TListView.InsertItem(ItemNo : longint; const Top : boolean);
var	LineNum : integer;
		DisplayItem : PDisplayItem;

begin
	if not InRange(itemNo) then exit;

	if LowMemory then begin
		Kameleon^.OutOfMemory;
		ReDrawn := True; {don't carry on drawing}
		StartEvent(evCommand, cmClose); {close view}
		exit;
	end;

	{make up display item}
	New(DisplayItem, init);
	DisplayItem^.LineText := NewStr(GetDisplayText(ItemNo));
	DisplayItem^.ItemNo := ItemNo;

	if Top then begin
		{insert at top}
		DisplayItem^.Next := FirstDisplayItem;
		FirstDisplayItem := DisplayItem;
		if LastDisplayItem = nil then LastDisplayItem := DisplayItem;
		if ItemNo=focused then ScrollY := 0;
	end else begin
		{bottom}
		if LastDisplayItem = nil then
			FirstDisplayItem := DisplayItem {set first}
		else
			LastDisplayItem^.Next := DisplayItem;
		LastDisplayItem := DisplayItem;

		if ItemNo=Focused then begin
			{make sure it wil fit on screen OK}
			LineNum := NextLineToDraw + NumLines(lastDisplayItem^.LineText^);
			if LineNum>Size.Y then ScrollY := ScrollY + LineNUm-Size.Y;
		end;
	end;

	TidyDisplayItems;{}
end;



{========== TIDY DISPLAY BUFFER ============}
{Removes any extra lines above and below window, that are not to do with
any items partially on window, resets scrolly offset, topitem and botitem}
procedure TListView.TidyDisplayItems;
var Line : integer;
		DisplayItem : PDisplayItem;
begin
	{remove complete displayitems before scrollY}
	if (FirstDisplayItem=nil) then exit; {nothing to do}

	if ScrollY>0 then
		while  {(FirstDisplayItem<>nil) and{} (FirstDisplayItem^.Next<>nil) and
				((FirstDisplayItem^.Linetext=nil) or (Numlines(FirstDisplayItem^.linetext^)<=ScrollY)) do begin
			if FirstDisplayItem^.Linetext<>nil then ScrollY := ScrollY-Numlines(FirstDisplayItem^.linetext^);
			DisplayItem := FirstDisplayItem^.Next;
			FirstDisplayItem^.Next := nil; {so it doesn't dispose of the others}
			dispose(FirstDisplayItem, done);
			FirstDisplayItem := DisplayItem;
		end;

	{remove complete displayitems beyond end of screen}
	if NextLineToDraw>=Size.Y then
		while LineOfItem(LastDisplayItem^.ItemNo)>=(Size.Y+ScrollY) do begin
			{find one previous to last}
			DisplayItem := FirstDisplayItem;
			while (DisplayItem^.Next<>LastDisplayItem) do DisplayItem := DisplayItem^.Next;
			DisplayItem^.Next := nil;
			dispose(LastDisplayItem, done);
			LastDisplayItem := DisplayItem;
		end;

	TopItem := FirstDisplayItem^.ItemNo;{}
{	BotItem := LastDisplayItem^.ItemNo;{}
end;


{================== MOVEMENT ======================================}
{Routines for handling movement of the "cursor" bar about, including
scrolling the list when moving off the top and bottom}
{To implement scroll bar stuff, see p220 of the manual}

{--- Page Down -------------------------------------------}
procedure TListView.PageDown;
var ItemNo : longint;
begin
	if ListEmpty then exit;

	ItemNo := TopItem;

	if ReDrawn then
		ItemNo := LastDisplayItem^.ItemNo {new top item is bottom item}
	else
		StepItemNo(ItemNo, PgStep);

	if not InRange(ItemNo) then
		ItemNo := LastItem;

	TopItem := ItemNo;   {Set top of page}
	ScrollY := 0;
	SetFocused(ItemNo);
	Redraw;            {Force redraw from new position}
end;

{--- Page Up -------------------------------------------}
procedure TListView.PageUp;
var ItemNo : longint;
begin
	if (TopItem=FirstItem) or ListEmpty then exit; {no point - already at start}

	ItemNo := TopItem;
	StepItemNo(ItemNo, -PgStep);

	if not InRange(ItemNo) then ItemNo := FirstItem;

	TopItem := ItemNo;
	ScrollY := 0;
	SetFocused(ItemNo);
	Redraw;
end;


{--- Home ---------------------------------------------}
procedure TListView.GoHome;
var ItemNo : longint;
begin
	ItemNo := FirstItem;
	FocusItem(ItemNo);
end;

{--- End ---------------------------------------------}
procedure TListView.GoEnd;
var ItemNo : longint;
begin
	ItemNo := LastItem;
	FocusItem(ItemNo);
end;

{--- One Up -------------------------------------------}
procedure TListView.OneUp;
var ItemNo : longint;
		DisplayItem : PDisplayItem;

begin
	if Focused=-1 then exit;                {No focus, nothing displayed}

	if TopItem = Focused then begin
		{First on screen}
		ItemNo := TopItem;
		GetPrevItemNo(ItemNo);
		if ItemNo>-1 then	begin
			SetFocused(ItemNo);
			InsertItem(ItemNo, True);		{Insert previous ItemNo}
		end;
	end else begin
		{find previous display item before currently focused one}
		DisplayItem := FirstDisplayItem;
		while (DisplayItem^.Next<>nil) and (DisplayItem^.Next^.ItemNo<>Focused) do
			DisplayItem := DisplayItem^.Next;

		if DisplayItem^.Next<>nil then SetFocused(DisplayItem^.ItemNo);

		{If it's now reached the first item on the screen, show it properly}
		if (DisplayItem = FirstDisplayItem) then ScrollY := 0;
	end;
	DrawView;
end;

{--- One Down -------------------------------------------}
procedure TListView.OneDown;
var ItemNo : longint;
		L : byte;
		DisplayItem : PDisplayItem;
begin
	if Focused = -1 then exit;             {No focus - no display}


	if LastDisplayItem^.ItemNo = Focused then begin
		{currently last on screen - insert new}
		ItemNO := Focused;
		GetNextItemNo(ItemNo);
		if ItemNo>-1 then begin
			SetFocused(ItemNo); {so insertion knows it'll be focused}
			InsertItem(ItemNo, False); {insert next at bottom}
		end;
	end else begin
		DisplayItem := DisplayPOfItem(Focused);
		if (DIsplayItem<>nil) and (DisplayItem^.Next<>nil) then
			SetFocused(DisplayItem^.Next^.ItemNo)
		else begin
			ItemNo := Focused;
			GetNextItemNo(ItemNo);
			if ItemNo<>-1 then SetFocused(itemNo);
		end;

		if LastDisplayItem^.ItemNo = Focused then begin
			{now last on screen - make sure fully displayed}
			L := LineOfItem(Focused)+NumLines(LastDisplayItem^.LineText^);
			if L>Size.Y then ScrollY := L-Size.Y;
		end;
	end;

	DrawView;
end;

{===== Get NEXT/PREV ====================================}
{Used to find next item number from the current one, skipping holes or
moving down chain as nec.  Default just increments by one}
procedure TListView.GetNextItemNo(var ItemNo : longint);
begin
	if ItemNo=-1 then
		ItemNo := FirstItem
	else
		inc(ITemNo);
	if ItemNo>LastItem then ItemNo := -1; {reset}
end;

procedure TListView.GetPrevItemNo(var ItemNo : longint);
begin
	if ItemNo = -1 then
		ItemNo := LastItem
	else
		dec(ItemNo);
	if ItemNo<FirstItem then ItemNo := -1;
end;

{for changing in big chunks - just increments, etc as nec, override
if required}
procedure TListView.StepItemNo(var ItemNo : longint; const Step : integer);
begin
	if ItemNo = -1 then
		if Step<0 then ItemNo := LastItem else ItemNo := FirstItem
	else begin
		ItemNo := ItemNo + Step;
	end;
	if not inRange(ItemNo) then itemno := -1;
end;

{used to find nearest item no to ItemNo that is valid for this list.  eg
when changing scroll bar in indexes, it may try to focus on a hole}
procedure TListView.FindOKItemNo(var ItemNo : longint);
begin end; {by default does nothing}


{--- Set Range -----------------------------------------}
procedure TListView.SetRange;
begin
	{should override but call inherited}
	if VScrollBar <> nil then begin
		if (FirstItem<-32765) or (LastItem>32766) then begin
			dispose(VScrollBar, done);
			VScrollBar := nil;
		end else begin
			EventMask := EventMask and not evBroadCast; {so that scrollbar doesn't send messages to self}
			VScrollBar^.SetRange(FirstItem,LastItem);
			EventMask := EventMask or evBroadCast;
		end;
	end;
end;


function TListView.InRange(const ItemNo : longint) : boolean;
begin
	if (ItemNo>=FirstItem) and (ItemNo<=LastItem) and (ItemNo<>-1) then InRange := True else InRange := false;
end;

function TListView.ListEmpty : boolean;
begin
	if (LastItem<FirstItem) or (LastItem<0) then ListEmpty := True else ListEmpty := False;
end;


{returns event as cleared if accepted}
procedure TListView.SendAcceptMessage(var Event : TEvent; const Command : word; const Info : pointer);
var P : pointer;
begin
	{if set and still exists}
	if (AcceptorLink<>nil) and (IsView(AcceptorLink)) then begin

		P := Message(AcceptorLink, evBroadCast, Command, Info);
{  else
		P := Message(Desktop, evBroadCast, Command, Info);{}
			{Send to top level to be sent down again...}

	 {Close list if message handled}
		if P <> nil then begin {ie event has been dealt with}
			StartEvent(evCommand, cmClose); {ask to close the list}
			ClearEvent(Event);
		end;
	end;
end;

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

{default print - ie print what would be on screen}
procedure TListView.PrintList(const TaggedOnly : boolean; const UpToItemNo,NumPages : longint);
var Item : longint;
		S : string;
		EndItem : longint;
begin
	THinkingOn('Printing List');
	Printer^.FormCodes^.Clear;
	Printer^.FormCodes^.SetStr('RTITLE',PWindow(Owner)^.GetTitle(0));

	Printer^.StartPrint('REPORT','');
	Item := FirstItem;
	if UpToItemNo>0 then EndItem := UpToItemNo else EndItem := LastItem;
	while (Item<=EndItem) and (Item>-1) do begin
		S := GetText(Item);
		S := TabOut(S, Tabs, Printer^.Paper^.Width);
		Printer^.writeln(S);
		if (NumPages>0) and (Printer^.Page=NumPages) and (Printer^.ReadyForEndOfPage(5)) then
			Item := -1
		else
			GetNextItemNo(Item);
	end;
	Printer^.EndPrint;{}
	THinkingOff;
end;



{***********************************************************
 ***                                                     ***
 ***           MODIFIED LIST WINDOW                      ***
 ***                                                     ***
 ***********************************************************}
{Sets up a view for multi line lists within a window that can
exist on its own - such as the person list - with space for
menu bar, etc}

{=== SET UP WINDOW =============================}

constructor TListWindow.Init;

begin
{	if AList = nil then fail;   {Just in case list does not form for some reason}

	{Automatic zoom - expand to fill screen}
{	if DesktopSetup.AutoZoom then Desktop^.GetExtent(Bounds);{}

	inherited Init(Bounds, WTitle, wnNoNumber);

{	CallingView := NCallingView;{}

	EventMask := $FFFF and not evMouseMove; {catch all except mouse *movements* - still pick up clicks}

	Options := Options or ofTileable or ofIdle;

	{--- Insert List ----}
	List := AList;
	if List<>nil then begin
		GetExtent(Bounds);
		Bounds.Grow(-1,-1);           {Come off frame}
		Bounds.A.Y := Bounds.A.Y +1;  {Make room for menu}

		List := AList;
		Insert(List);              									 {Insert & make current}
		List^.Locate(Bounds);       {Set correct boundaries}

		if List^.VScrollBar<>nil then Insert(List^.VSCrollBar);{}
	end;

	InitMenuBar;
	if List<>nil then AddCommandsToListMenu(List^.lsType, MenuBar^.Menu, List^.TaskList);

	{add changed indicator}
	Insert(New(PChangedIndicator, init(2,Size.Y-1)));
end;

{=== DONE WINDOW =============================}
{destructor TListWindow.Done;
begin
	inherited Done;
end;


{=== MENU BAR ===============================}
procedure TListWindow.InitMenuBar;
var R: TRect;
		MenuItem : PMenuItem;

begin
	{--- Set up Standard Menu Bar ----}
	GetExtent(R);
	R.Grow(-1,-1); {Come off frame}
	R.B.Y := R.A.Y + 1; {one line}

	{Although the "New" menu item is normally a submenu rather than a
	selection item, because they are essentially the same thing, we can
	insert it here as an item so that it has the kbNew, and the default
	cmNew, and the AddCommandsToList below will then build on the submenu
	as necessary}
	New(MenuBar, init(R,
		NewMenu(
			NewSubMenu(mnEdit, kbNone, hcNoContext,
				NewMenu(
					NewItem('~N~ew',		ksNew,			kbNew, 			cmNew, 		hcNew,
					NewItem('~E~dit',   ksChange,   kbChange,   cmEdit,  	hcEdit,
					NewItem('Del/Cut',	ksDelItem,  kbShiftDel, cmCut,   	hcDel,
					NewItem('~P~aste',	ksPaste,		kbPaste,		cmPaste, 	hcPaste,
					NewItem('~A~ccept', ksAccept,   kbEnter,    cmAccept,	hcAccept,
				nil)))))),
			nil)
		)
	));

	{adds "new" creator-type options and all special ones}
{now done above, so that all specials get appended to lists,  o/w descendants
of this one - eg indexed lists - append to the specials.  Makes the menus
look a bit untidy if they keep changing at the top...
	if List<>nil then AddCommandsToListMenu(List^.lsType, MenuBar^.Menu, List^.TaskList);

	{slight tidy up - make new the immediate action if only one new menu
	option}
	{assume edit is first in bar and new is first in edit menu}
	MenuItem := MenuBar^.Menu^.Items^.SubMenu^.Items;

	if MenuItem^.Name^='~N~ew' then {safety}
		if (MenuItem^.SubMenu <> nil) and (MenuItem^.SubMenu^.Items <> nil) then
			if MenuItem^.SubMenu^.Items^.Next = nil then begin
				{only one menu item - move to new}
				DisposeStr(MenuItem^.Name);
				MenuItem^.Name := NewStr('~N~ew '+MenuItem^.SubMenu^.Items^.Name^); {eg "New Animal"}
				MenuItem^.Command := MenuItem^.SubMenu^.Items^.Command;
				MenuItem^.HelpCtx := MenuItem^.SubMenu^.Items^.HelpCtx;
				disposeMenu(MenuItem^.SubMenu);
				MenuItem^.SubMenu := nil;
			end;


	Insert(MenuBar); {}
end;


{====== HANDLE EVENT ==================}
procedure TListWindow.HandleEvent;
begin
	if (Event.What = evBroadcast) and (Event.Command = cmHaveYouaMenu)
		and (MenuBar<>nil) then begin ClearEvent(Event); Event.InfoPtr := MenuBar; end; {Yes we do have a menu}

	{======= CASCADING ===============}
{	if (Event.What = evCommand) and DesktopSetup.AutoCascade then begin
		case Event.Command of
			cmCascadeView : Cascade;
			cmUncascadeView : Uncascade;
		end;
	end;{}

	if (Event.What = evKeyDown) then
		case Event.KeyCode of
			kbESC, kbF10 : begin
				Event.What := evCommand;
				Event.Command := cmClose;
				Event.InfoPtr := @Self;
			end;
		end;

	inherited HandleEVent(Event);
end;

{====== Cascade to calling view ================}
procedure TListWindow.Cascade;
var	Point : TPoint;
		R : TRect;
begin
	PreCascade := Origin; {precascadeposition}
{	if CallingView<>nil then begin
		Point := CallingView^.Origin;
{  	CallingView^.MakeGlobal(CallingView^.Origin, Point); {convert to global}
{    MakeLocal(Point, Point); {then back to this object's local}
{		MoveTo(Point.X+1, Point.Y+1);{}
{		GrowTo(Size.X, Size.Y+PreCascade.Y-Point.Y-1);

{  	Draw;{}
{	end;{}
end;

procedure TListWindow.UnCascade;
begin
	MoveTo(PreCascade.X, Precascade.Y);
end;


{*************************************************
 ***           COLLECTION VIEWER               ***
 *************************************************}
destructor TCollectionViewer.Done;
begin
	if Collection<>nil then dispose(Collection, done);
	inherited Done;
end;

procedure TCollectionViewer.SetRange;
begin
	FirstItem := 0;
	if Collection<>nil then LastItem := Collection^.Count-1 else LastItem := -1;
	inherited SetRange;
end;

{===== EDIT COLLECTION ITEM ===============}
procedure TCollectionViewer.Edit(ItemNo : longint);
var	Item : PObject;
		Control : word;

begin
	if not InRange(ItemNo) then exit; {JIC check}

	Owner^.SetState(sfActive, False); {switch off highlighted frame, etc}

	{ -- Set up & edit ---}
	Item := Collection^.At(ItemNo); {Saves doing At's all the time}

	{--- Edit & Validate ----}
	Control := EditItem(Item);

	if Control<>cmCancel then begin

		{If edit, delete old item first}
		Collection^.AtDelete(ItemNo);
		{An attempt to delete by pointer (ie Item) will fail as it does not look for
		the pointer to match but the search criteria - which may have changed in the edit above}

		Collection^.Insert(Item);

		{-- Focus list ---}
		FocusItem(Collection^.IndexOf(Item));
		Redraw;
		SetChanged; {notify list there's been a change}
	end;

	Owner^.SetState(sfActive, True); {switch back on highlighted frame, etc}

end;

{====== NEW ==========================}
procedure TCollectionViewer.EditNew;
var Item : PObject;
		Control : word;

begin
	{ -- Set up & edit ---}
	Item := PObject(CreateNewItem);

	{--- Edit & Validate ----}
	Control := EditItem(Item);

	if Control<>cmCancel then begin

		Collection^.Insert(Item);

		SetRange;

		{-- Focus list ---}
		FocusItem(Collection^.IndexOf(Item));
		Redraw;

		SetChanged; {notify list there's been a change}
	end else
		{Cancel}
		Dispose(Item,Done);

end;


{==== OVERRIDER FUNCS FOR ABOVE ===============}
function TCollectionViewer.EditItem(P : PObject) : word;
begin EditItem := cmCancel; end;

function TCollectionViewer.CreateNewItem : PObject;
begin CreateNewItem := nil; end;

{==== DELETE LIST ITEM ========================}
procedure TCollectionViewer.Del;
var P : pointer;

begin
	if InRange(ItemNo) then begin
		P := Collection^.At(ItemNo);
		Collection^.Free(P);                   {Removes from collection & disposes}
		Collection^.Pack;
		SetRange;
		SetChanged;

		{--Refocus--}
		if not InRange(ItemNo) then dec(ItemNo); {may have deleted last in list}
		FocusItem(ItemNo);
		Redraw;
	end;
end;




end.
