/*
**	Find - AmigaDOS 2.04 commodities utility
**
**	Copyright © 1991-1992 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

#include "FindGlobal.h"

	/* DisableWindow(BYTE IncludeStart):
	 *
	 *	Disable the gadgets in the window, include `Start' gadget
	 *	if desired.
	 */

VOID __regargs
DisableWindow(BYTE IncludeStart)
{
		/* Block the menu bar. */

	Window -> Flags |= WFLG_RMBTRAP;

		/* Set the wait pointer. */

	SetWait(Window);

		/* Disable the remaining gadgets. */

	GT_SetGadgetAttrs(GadgetArray[GAD_SEARCHAREA],Window,NULL,
		GA_Disabled,	TRUE,
	TAG_DONE);

	GT_SetGadgetAttrs(GadgetArray[GAD_SEARCHFOR],Window,NULL,
		GA_Disabled,	TRUE,
	TAG_DONE);

	GT_SetGadgetAttrs(GadgetArray[GAD_WILDCARDS],Window,NULL,
		GA_Disabled,	TRUE,
	TAG_DONE);

	GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
		GA_Disabled,	TRUE,
	TAG_DONE);

		/* Are we to disable the `Start' gadget? */

	if(IncludeStart)
	{
		GT_SetGadgetAttrs(GadgetArray[GAD_START],Window,NULL,
			GA_Disabled,	TRUE,
		TAG_DONE);
	}
}

	/* EnableWindow():
	 *
	 *	Reenable the gadgets disabled by DisableWindow() and
	 *	restore menu and mouse pointer.
	 */

VOID
EnableWindow()
{
		/* Enable the gadgets. */

	GT_SetGadgetAttrs(GadgetArray[GAD_SEARCHAREA],Window,NULL,
		GA_Disabled,	FALSE,
	TAG_DONE);

	GT_SetGadgetAttrs(GadgetArray[GAD_SEARCHFOR],Window,NULL,
		GA_Disabled,	FALSE,
	TAG_DONE);

	GT_SetGadgetAttrs(GadgetArray[GAD_WILDCARDS],Window,NULL,
		GA_Disabled,	FALSE,
	TAG_DONE);

	GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
		GA_Disabled,	FALSE,
	TAG_DONE);

	GT_SetGadgetAttrs(GadgetArray[GAD_START],Window,NULL,
		GA_Disabled,	FALSE,
	TAG_DONE);

		/* Restore the mouse pointer. */

	ClearPointer(Window);

		/* Enable the menu. */

	Window -> Flags &= ~WFLG_RMBTRAP;
}

	/* EditRoutine():
	 *
	 *	String gadget editing hook routine, called for each key pressed while
	 *	a string gadget is active.
	 */

VOID __saveds __asm
EditRoutine(register __a0 struct Hook *Hook,register __a1 ULONG *Msg,register __a2 struct SGWork *Work)
{
		/* Did we receive a keystroke? */

	if(Msg[0] == SGH_KEY)
	{
			/* Was the right Amiga key pressed? */

		if(Work -> IEvent -> ie_Qualifier & IEQUALIFIER_RCOMMAND)
		{
				/* Did we get a `C' (copy gadget contents to clipboard)? */

			if(ToUpper(Work -> Code) == 'C' && Work -> PrevBuffer[0])
			{
					/* Don't put this keystroke into the buffer. */

				Work -> Actions &= ~SGA_USE;

					/* Is the clipboard server running? */

				if(ClipPort)
				{
					struct MsgPort *ReplyPort;

						/* Create a replyport. */

					if(ReplyPort = CreateMsgPort())
					{
						struct Message ClipMessage;

							/* Initialize the transport message. */

						ClipMessage . mn_Node . ln_Name	= Work -> PrevBuffer;
						ClipMessage . mn_ReplyPort	= ReplyPort;
						ClipMessage . mn_Length		= sizeof(struct Message);

							/* Tell the clipboard server to save the contents. */

						PutMsg(ClipPort,&ClipMessage);

							/* Wait for reply... */

						WaitPort(ReplyPort);

							/* Remove message from ReplyPort. */

						GetMsg(ReplyPort);

							/* Delete the ReplyPort. */

						DeleteMsgPort(ReplyPort);
					}
				}
				else
					Work -> Actions |= SGA_BEEP;
			}

				/* Did we get a `V' (insert clipboard contents)? */

			if(ToUpper(Work -> Code) == 'V')
			{
					/* Don't put this keystroke into the buffer. */

				Work -> Actions &= ~SGA_USE;

					/* Is the clipboard server running? */

				if(ClipPort)
				{
					struct MsgPort *ReplyPort;

						/* Create a replyport. */

					if(ReplyPort = CreateMsgPort())
					{
						STATIC UBYTE	Buffer[257];
						struct Message	ClipMessage;

							/* Mark the buffer as empty. */

						Buffer[0] = 0;

							/* Initialize the transport message. */

						ClipMessage . mn_Node . ln_Name	= &Buffer[0];
						ClipMessage . mn_ReplyPort	= ReplyPort;
						ClipMessage . mn_Length		= sizeof(struct Message);

							/* Tell the clipboard server to send us some data. */

						PutMsg(ClipPort,&ClipMessage);

							/* Wait for reply... */

						WaitPort(ReplyPort);

							/* Remove message from ReplyPort. */

						GetMsg(ReplyPort);

							/* Delete the ReplyPort. */

						DeleteMsgPort(ReplyPort);

							/* Did we get any text? */

						if(Buffer[0])
						{
							WORD Len = strlen(Buffer);

								/* Determine how many bytes the gadget
								 * will be able to accept from the buffer.
								 */

							while(Len > 0 && Work -> NumChars + Len > Work -> StringInfo -> MaxChars)
								Len--;

								/* Are we able to insert any text? */

							if(Len > 0)
							{
								STATIC UBYTE OtherBuffer[257];

									/* Provide null-termination for
									 * the (shrunken?) buffer.
									 */

								Buffer[Len] = 0;

									/* Copy the current string buffer
									 * contents to the undo buffer.
									 */

								strcpy(Work -> StringInfo -> UndoBuffer,Work -> PrevBuffer);

									/* Adjust the undo cursor position. */

								Work -> StringInfo -> UndoPos = --Work -> BufferPos;

									/* Copy the first bytes of the string
									 * buffer contents to our private buffer.
									 */

								if(Work -> BufferPos)
									CopyMem(Work -> PrevBuffer,OtherBuffer,Work -> BufferPos);

									/* Provide null-termination. */

								OtherBuffer[Work -> BufferPos] = 0;

									/* Append the new data. */

								strcat(OtherBuffer,Buffer);

									/* Append the data to follow the cursor. */

								strcat(OtherBuffer,&Work -> PrevBuffer[Work -> BufferPos]);

									/* Install the new buffer contents. */

								strcpy(Work -> WorkBuffer,OtherBuffer);

									/* Adjust contents data accordingly... */

								Work -> BufferPos	+= Len;
								Work -> NumChars	+= Len;

									/* Tell Intuition what to do with the buffer. */

								Work -> Actions	|= SGA_USE;
								Work -> EditOp	 = EO_BIGCHANGE;
							}
							else
								Work -> Actions |= SGA_BEEP;
						}
					}
				}
				else
					Work -> Actions |= SGA_BEEP;
			}
		}
	}
}

	/* ClipServer():
	 *
	 *	The clipboard server process, waits for incoming load or save
	 *	requests and processes them.
	 */

VOID __saveds
ClipServer()
{
		/* Try to create a MsgPort other processes will use to talk to us. */

	if(ClipPort = CreateMsgPort())
	{
		ULONG		 Mask;
		struct Message	*ClipMsg;
		UBYTE		*Buffer;
		BYTE		 Terminated = FALSE;

			/* Flag the main process to continue. */

		Signal(MainProcess,SIG_REPLY);

			/* Go into loop waiting for messages... */

		while(!Terminated)
		{
				/* Wait for message or termination signal. */

			Mask = Wait(SIG_KILL | SIG_CLIP);

				/* Are we to remove ourselves? */

			if(Mask & SIG_KILL)
				Terminated = TRUE;

				/* Are we to process a clipboard request? */

			if(Mask & SIG_CLIP)
			{
					/* Process all messages. */

				while(ClipMsg = GetMsg(ClipPort))
				{
						/* Pick up the buffer to work on. */

					Buffer = ClipMsg -> mn_Node . ln_Name;

						/* If there is data to be
						 * dealt with, save the buffer.
						 */

					if(Buffer[0])
						SaveClip(Buffer,strlen(Buffer));
					else
					{
						WORD Len = LoadClip(Buffer,255);

							/* In any other case,
							 * try to fill the buffer.
							 */

						Buffer[Len] = 0;
					}

						/* Reply the message. */

					ReplyMsg(ClipMsg);
				}
			}
		}

			/* Delete the ReplyPort, at the time this is happening
			 * the control panel has already been closed.
			 */

		DeleteMsgPort(ClipPort);
	}

		/* Don't quit before we are done. */

	Forbid();

		/* Ring back & fall through. */

	Signal(MainProcess,SIG_REPLY);
}

	/* SaveClip(UBYTE *Buffer,LONG Size):
	 *
	 *	Save text data to the clipboard.
	 */

BYTE __regargs
SaveClip(UBYTE *Buffer,LONG Size)
{
	struct IFFHandle	*Handle;
	BYTE			 Success = FALSE;

		/* Allocate an IFFHandle... */

	if(Handle = AllocIFF())
	{
			/* Make it operate on the clipboard. */

		if(Handle -> iff_Stream = (ULONG)OpenClipboard(PRIMARY_CLIP))
		{
				/* Tell iffparse.library that it is to deal
				 * with the clipboard, not with an AmigaDOS file.
				 */

			InitIFFasClip(Handle);

				/* Open the handle for writing. */

			if(!OpenIFF(Handle,IFFF_WRITE))
			{
					/* Say it's a formatted text form. */

				if(!PushChunk(Handle,'FTXT','FORM',IFFSIZE_UNKNOWN))
				{
						/* Create the text chunk. */

					if(!PushChunk(Handle,0,'CHRS',Size))
					{
							/* Write the actual data. */

						if(WriteChunkBytes(Handle,Buffer,Size) == Size)
						{
								/* Did the chunk get written correctly? */

							if(!PopChunk(Handle))
								Success = TRUE;
						}
					}
				}

					/* Did our previous actions succeed? */

				if(Success)
				{
						/* Did the chunk get written correctly? */

					if(PopChunk(Handle))
						Success = FALSE;
				}

					/* Close the iff stream. */

				CloseIFF(Handle);
			}

				/* Flush the data out to the clipboard. */

			CloseClipboard((struct ClipboardHandle *)Handle -> iff_Stream);
		}

			/* Free the IFFHandle data. */

		FreeIFF(Handle);
	}

		/* Return whether our actions were successful or not. */

	return(Success);
}

	/* LoadClip(UBYTE *Buffer,LONG Size):
	 *
	 *	Fill the buffer with up to `Size' bytes.
	 */

LONG __regargs
LoadClip(UBYTE *Buffer,LONG Size)
{
	struct IFFHandle	*Handle;
	LONG			 Bytes = 0;

		/* Allocate an IFFHandle... */

	if(Handle = AllocIFF())
	{
			/* Make it operate on the clipboard. */

		if(Handle -> iff_Stream = (ULONG)OpenClipboard(PRIMARY_CLIP))
		{
				/* Tell iffparse.library that it is to deal
				 * with the clipboard, not with an AmigaDOS file.
				 */

			InitIFFasClip(Handle);

				/* Open the handle for reading. */

			if(!OpenIFF(Handle,IFFF_READ))
			{
					/* Tell the parser to stop at the
					 * beginning of character data
					 * inside formatted text chunks.
					 */

				if(!StopChunk(Handle,'FTXT','CHRS'))
				{
						/* Start the parser and process
						 * the data encountered.
						 */

					if(!ParseIFF(Handle,IFFPARSE_SCAN))
					{
						struct ContextNode *ContextNode;

							/* Determine the current chunk information
							 * (we will need it in order to query the
							 * size of it).
							 */

						if(ContextNode = CurrentChunk(Handle))
						{
							LONG BytesRead;

								/* Don't read more data
								 * than we will be able
								 * to handle.
								 */

							if(Size > ContextNode -> cn_Size)
								Size = ContextNode -> cn_Size;

								/* Read as much data as required. */

							BytesRead = ReadChunkBytes(Handle,Buffer,Size);

								/* If any data was available,
								 * remember how much we were
								 * able to read.
								 */

							if(BytesRead > 0)
								Bytes = BytesRead;
						}
					}
				}

					/* Close the iff stream. */

				CloseIFF(Handle);
			}

				/* Free clipboard resources. */

			CloseClipboard((struct ClipboardHandle *)Handle -> iff_Stream);
		}

			/* Free the IFFHandle data. */

		FreeIFF(Handle);
	}

		/* Return the number of bytes we were actually able to read. */

	return(Bytes);
}

	/* ShowMessage(UBYTE *Text,...):
	 *
	 *	Display text in the message line of the control panel.
	 */

VOID __stdargs
ShowMessage(UBYTE *Text,...)
{
	va_list VarArgs;

		/* Fill the buffer with the data to be displayed. */

	va_start(VarArgs,Text);
	VSPrintf(MessageBuffer,Text,VarArgs);
	va_end(VarArgs);

		/* If the control panel is still available, make the
		 * text appear in the message line.
		 */

	if(Window)
	{
		GT_SetGadgetAttrs(GadgetArray[GAD_TEXT],Window,NULL,
			GTTX_Text,	MessageBuffer,
		TAG_DONE);
	}
}

	/* ShowRequest(UBYTE *Text,UBYTE *Gadgets,...):
	 *
	 *	Display an Intuition EasyRequest.
	 */

BOOL __stdargs
ShowRequest(UBYTE *Text,UBYTE *Gadgets,...)
{
	struct EasyStruct	Easy;
	BOOL			Result;
	ULONG			IDCMP = NULL;
	va_list	 		VarArgs;

		/* Initialize the EasyStruct structure with default values. */

	Easy . es_StructSize	= sizeof(struct EasyStruct);
	Easy . es_Flags		= NULL;
	Easy . es_Title		= (UBYTE *)"Find Request";
	Easy . es_TextFormat	= (UBYTE *)Text;
	Easy . es_GadgetFormat	= (UBYTE *)Gadgets;

		/* Pick up the arguments passed to this routine and
		 * put up the requester.
		 */

	va_start(VarArgs,Gadgets);
	Result = EasyRequestArgs(Window,&Easy,&IDCMP,VarArgs);
	va_end(VarArgs);

		/* Return the result. */

	return(Result);
}

	/* BuildBits(ULONG Protection):
	 *
	 *	Build a protection bit string.
	 */

UBYTE * __regargs
BuildBits(ULONG Protection)
{
	STATIC UBYTE	 Buffer[9],
			*Bits[2] = { "----apsh", "dewr----"} ;
	WORD		 i,j = 0;

		/* Count the bits down... */

	for(i = 7 ; i >= 0 ; i--)
	{
			/* Is the bit set? */

		if(Protection & (1 << i))
			Buffer[j++] = Bits[0][i];
		else
			Buffer[j++] = Bits[1][i];
	}

		/* Provide null-termination. */

	Buffer[8] = 0;

		/* Return the working buffer. */

	return(Buffer);
}

	/* BuildDate(struct DateStamp *Stamp):
	 *
	 *	Build time and date strings from a DateStamp.
	 */

VOID __regargs
BuildDate(struct DateStamp *Stamp)
{
	struct DateTime DateTime;

		/* Initialize conversion structure. */

	DateTime . dat_Stamp	= *Stamp;
	DateTime . dat_Flags	= DTF_SUBST | FORMAT_DOS;
	DateTime . dat_StrDay	= NULL;
	DateTime . dat_StrDate	= Date;
	DateTime . dat_StrTime	= Time;

		/* Convert the date. */

	if(!DateToStr(&DateTime))
	{
			/* If the conversion failed, provide default data. */

		strcpy(Date,"----------");
		strcpy(Time,"--:--:--");
	}
}

	/* BuildPathList(UBYTE *String):
	 *
	 *	Build a list of nodes, representing a file search path from
	 *	a full path string.
	 */

VOID __regargs
BuildPathList(UBYTE *String)
{
	UBYTE	Buffer[256];
	WORD	i,j = 0;

		/* Copy the path string. */

	strcpy(Buffer,String);

		/* Use the original string pointer as an index pointer into the buffer. */

	String = Buffer;

		/* Reset the path list. */

	NewList(&FileList);

		/* Scan the string looking for the colon terminating
		 * the root directory name.
		 */

	while(String[j])
	{
		if(String[j] == ':')
		{
			String[j] = 0;

			break;
		}
		else
			j++;
	}

		/* Copy the root directory name to the first node name. */

	SPrintf(FileNames[0] . Name,"%s:",String);

		/* Add the node to list (oh well, since the list at this
		 * point is empty, AddTail() would have accomplished
		 * the same).
		 */

	AddHead(&FileList,&FileNames[0]);

		/* Proceed to the next path component. */

	String = &String[j + 1];

		/* Start at the beginning. */

	i = 1;

		/* Process the remaining list. */

	FOREVER
	{
		j = 0;

			/* Try to find the next slash terminating
			 * the next directory name.
			 */

		while(String[j])
		{
			if(String[j] == '/')
				break;
			else
				j++;
		}

			/* Did we reach the end of the path? */

		if(String[j])
		{
				/* Provide null-termination. */

			String[j] = 0;

				/* Copy the directory name into the next node. */

			SPrintf(FileNames[i] . Name," %s",String);

				/* Add the node to the end of the list. */

			AddTail(&FileList,&FileNames[i++]);

				/* Proceed to the next path component. */

			String = &String[j + 1];
		}
		else
		{
				/* Now we are at the base file name. */

			SPrintf(FileNames[i] . Name,"  %s",String);

				/* Add the node to the end of the list. */

			AddTail(&FileList,&FileNames[i]);

			break;
		}
	}
}

	/* ClearRect(WORD Len,WORD Top):
	 *
	 *	Erase the remaining space in a line of the main display list.
	 */

VOID __regargs
ClearRect(WORD Len,WORD Top)
{
	BYTE FgPen = RPort -> FgPen;

	SetAPen(RPort,RPort -> BgPen);
	RectFill(RPort,10 + 4 + Len * SystemFontWidth,ListTopLine + 2 + SystemFontHeight * Top,ListRightColumn - 4,ListTopLine + 2 + SystemFontHeight * (Top + 1) - 1);
	SetAPen(RPort,FgPen);
}

	/* RefreshList():
	 *
	 *	Refresh the contents of the main display list.
	 */

VOID
RefreshList()
{
		/* Are we to display text? */

	if(!NumNames || Scanning)
	{
		BYTE FgPen = RPort -> FgPen;

			/* Obviously not, clear the main display area. */

		SetAPen(RPort,0);
		RectFill(RPort,10 + 4,ListTopLine + 2,ListRightColumn - 4,ListTopLine + 2 + (10 * SystemFontHeight) - 1);
		SetAPen(RPort,FgPen);
	}
	else
	{
		WORD i;

			/* Draw all five lines. */

		for(i = 0 ; i < 5 ; i++)
		{
				/* Are we still processing data in the list? */

			if(ListTop + i < NumNames)
			{
				WORD Len = strlen(NameList[ListTop + i] -> NamePart);

					/* Move to the position to print the file name. */

				Move(RPort,10 + 4,ListTopLine + 2 + SystemFontBase + (2 * SystemFontHeight) * i);

					/* If the current entry happens to be selected,
					 * highlight it.
					 */

				if(NameList[ListTop + i] -> Selected)
				{
						/* Are we allowed to use more than two colours? */

					if(NewLook)
					{
						SetBPen(RPort,2);

							/* Print the file name. */

						Text(RPort,NameList[ListTop + i] -> NamePart,Len);

							/* Clear the area to the right of
							 * the file name.
							 */

						ClearRect(Len,i * 2);

							/* Move one line down. */

						Move(RPort,10 + 4,ListTopLine + 2 + SystemFontBase + SystemFontHeight + (2 * SystemFontHeight) * i);

							/* Is a file type available? */

						if(FileTypes[NameList[ListTop + i] -> Type])
						{
								/* Determine file type length. */

							Len = strlen(FileTypes[NameList[ListTop + i] -> Type]);

							SetAPen(RPort,3);

								/* Display the file type. */

							Text(RPort,FileTypes[NameList[ListTop + i] -> Type],Len);

							SetAPen(RPort,1);
						}
						else
							Len = 0;

							/* Clear the area to the right of
							 * the file name.
							 */

						ClearRect(Len,i * 2 + 1);
					}
					else
					{
							/* Roughly the same as above,
							 * designed for use with two
							 * colours only.
							 */

						SetAPen(RPort,0);
						SetBPen(RPort,1);

						Text(RPort,NameList[ListTop + i] -> NamePart,Len);

						ClearRect(Len,i * 2);

						Move(RPort,10 + 4,ListTopLine + 2 + SystemFontBase + SystemFontHeight + (2 * SystemFontHeight) * i);

						if(FileTypes[NameList[ListTop + i] -> Type])
						{
							Len = strlen(FileTypes[NameList[ListTop + i] -> Type]);

							Text(RPort,FileTypes[NameList[ListTop + i] -> Type],Len);
						}
						else
							Len = 0;

						ClearRect(Len,i * 2 + 1);

						SetAPen(RPort,1);
					}

					SetBPen(RPort,0);
				}
				else
				{
						/* Same as above, but text is not highlighted. */

					Text(RPort,NameList[ListTop + i] -> NamePart,Len);

					ClearRect(Len,i * 2);

					Move(RPort,10 + 4,ListTopLine + 2 + SystemFontBase + SystemFontHeight + (2 * SystemFontHeight) * i);

					if(FileTypes[NameList[ListTop + i] -> Type])
					{
						Len = strlen(FileTypes[NameList[ListTop + i] -> Type]);

						SetAPen(RPort,3);

						Text(RPort,FileTypes[NameList[ListTop + i] -> Type],Len);

						SetAPen(RPort,1);
					}
					else
						Len = 0;

					ClearRect(Len,i * 2 + 1);
				}
			}
			else
			{
					/* Clear any remaining lines. */

				SetAPen(RPort,0);

				RectFill(RPort,14,ListTopLine + 2 + (2 * SystemFontHeight) * i,ListRightColumn - 4,ListTopLine + 2 + (10 * SystemFontHeight) - 1);

				SetAPen(RPort,1);

				break;
			}
		}
	}
}

	/* SelectSearchArea():
	 *
	 *	Puts up a file requester and allows the user to select
	 *	the directory the user wishes the search to begin at.
	 */

BYTE
SelectSearchArea()
{
		/* If no path name is available, use the current directory name. */

	if(!AreaName[0])
	{
		if(!NameFromLock(FindProcess -> pr_CurrentDir,AreaName,256))
			AreaName[0] = 0;
	}

		/* Open up the file requester. */

	if(AslRequestTags(FileRequest,
		ASL_Window,	Window,
		ASL_Dir,	AreaName,
	TAG_DONE))
	{
			/* Did we get a directory name? */

		if(FileRequest -> rf_Dir[0])
		{
				/* Copy the directory name. */

			strcpy(AreaName,FileRequest -> rf_Dir);

			return(TRUE);
		}
	}

	return(FALSE);
}

	/* RefreshFile():
	 *
	 *	Refresh the file info field.
	 */

VOID
RefreshFile()
{
		/* Build the file creation time and date. */

	BuildDate(&NameList[LastSelected] -> Date);

		/* Draw the text headers. */

	DrawFileInfo( 0,0,0,"Creation Date:");
	DrawFileInfo( 9,2,0,"Size:");
	DrawFileInfo( 7,3,0,"Blocks:");
	DrawFileInfo( 3,5,0,"Attributes:");

		/* Draw the data. */

	DrawFileInfo(15,0,1,Date);
	DrawFileInfo(15,1,1,Time);
	DrawFileInfo(15,5,1,BuildBits(NameList[LastSelected] -> Protection));

		/* If the current file is in fact a directory or a link to
		 * a directory, don't show its size.
		 */

	if(NameList[LastSelected] -> Type == TYPE_DIR || NameList[LastSelected] -> Type == TYPE_LINK)
	{
		DrawFileInfo(15,2,1,"-");
		DrawFileInfo(15,3,1,"-");
	}
	else
	{
			/* Show the file size and the number of blocks it occupies. */

		DrawFileInfo(15,2,1,"%ld",NameList[LastSelected] -> Size);
		DrawFileInfo(15,3,1,"%ld",NameList[LastSelected] -> Blocks);
	}

		/* Detach the list from the path list display. */

	GT_SetGadgetAttrs(GadgetArray[GAD_FILELIST],Window,NULL,
		GTLV_Labels,	~0,
	TAG_DONE);

		/* Build the path name list... */

	BuildPathList(&NameList[LastSelected] -> Name[0]);

		/* Attach the path name list to the display. */

	GT_SetGadgetAttrs(GadgetArray[GAD_FILELIST],Window,NULL,
		GTLV_Labels,	&FileList,
	TAG_DONE);

		/* Enable the `copy path to clipboard' menu item. */

	OnMenu(Window,FULLMENUNUM(1,0,0));
}

	/* RefreshGfx():
	 *
	 *	Refresh the contents of the window.
	 */

VOID
RefreshGfx()
{
		/* Refresh the file information display. */

	if(NumNames && LastSelected != -1 && LastSelected < NumNames)
	{
		if(NameList[LastSelected] -> Selected)
			RefreshFile();
	}

		/* Draw the main display list border. */

	DrawBevelBox(RPort,10,ListTopLine,WindowWidth - 20 - 2 * SystemFontWidth,SystemFontHeight * 10 + 4,
		GT_VisualInfo,	VisualInfo,
	TAG_DONE);

		/* Draw the file information display border. */

	DrawBevelBox(RPort,10,PathTopLine,(WindowWidth - 20 - INTERWIDTH) / 2,6 * SystemFontHeight + 4,
		GT_VisualInfo,	VisualInfo,
		GTBB_Recessed,	TRUE,
	TAG_DONE);

		/* Refresh the gadget list. */

	RefreshGList(GadgetList,Window,NULL,(UWORD)-1);

		/* Refresh the remaining imagery. */

	GT_RefreshWindow(Window,NULL);

		/* Activate a string gadget, depends on the state of the
		 * string gadgets.
		 */

	if(!(GT_STRING(GadgetArray[GAD_SEARCHAREA]))[0])
		ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHAREA],Window,NULL);
	else
	{
		if(!(GT_STRING(GadgetArray[GAD_SEARCHFOR]))[0])
			ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHFOR],Window,NULL);
	}

		/* Refresh the main display list. */

	RefreshList();
}

	/* Iconify(BYTE Complain):
	 *
	 *	Iconify the window.
	 */

BYTE __regargs
Iconify(BYTE Complain)
{
	struct DiskObject	*Icon		= NULL;
	BYTE			 ClosedGfx	= FALSE,
				 Success	= FALSE;

		/* If we have a window, save the string gadgets contents. */

	if(Window)
	{
		DisableWindow(TRUE);

		strcpy(AreaName,GT_STRING(GadgetArray[GAD_SEARCHAREA]));
		strcpy(SearchString,GT_STRING(GadgetArray[GAD_SEARCHFOR]));

		UseWildcards = GT_CHECKED(GadgetArray[GAD_WILDCARDS]);
	}

		/* Get the program icon. */

	if(WBenchMsg)
	{
		if(WBenchMsg -> sm_ArgList[0] . wa_Name)
			Icon = GetDiskObjectNew(WBenchMsg -> sm_ArgList[0] . wa_Name);
	}

		/* No icon? Use the default name. */

	if(!Icon)
		Icon = GetDiskObjectNew("Find");

		/* Did we get any icon? */

	if(Icon)
	{
			/* Not a tool icon? */

		if(Icon -> do_Type != WBTOOL)
		{
				/* Get rid of it... */

			FreeDiskObject(Icon);

				/* Get the default tool icon. */

			Icon = GetDefDiskObject(WBTOOL);
		}
	}

		/* Did we get an icon? */

	if(Icon)
	{
		struct MsgPort *IconPort;

			/* Default icon position. */

		Icon -> do_CurrentX = NO_ICON_POSITION;
		Icon -> do_CurrentY = NO_ICON_POSITION;

			/* Create the Workbench reply port. */

		if(IconPort = CreateMsgPort())
		{
			struct AppIcon *AppIcon;

				/* Add the application icon. */

			if(AppIcon = AddAppIcon(0,0,"Find Files",IconPort,NULL,Icon,NULL))
			{
				struct AppMessage	*AppMessage;
				BYTE			 GotName = FALSE;
				ULONG			 Mask;

					/* Close the control panel. */

				CloseGfx();

					/* Set some flags. */

				ClosedGfx	= TRUE;
				WasIconified	= TRUE;
				IconTerminated	= FALSE;
				Success		= TRUE;

					/* Wait for messages. */

				while(!IconTerminated)
				{
					Mask = Wait(SIG_CX | SIG_ICON | SIG_KILL);

						/* Are we to quit? */

					if(Mask & SIG_KILL)
						IconTerminated = Terminated = TRUE;

						/* Pick up application messages. */

					if(Mask & SIG_ICON)
					{
						while(AppMessage = (struct AppMessage *)GetMsg(IconPort))
						{
								/* If we did not get a name yet,
								 * try to get it from the AppMessage.
								 */

							if(!GotName)
							{
								LONG i;

									/* Run down the list of entries... */

								for(i = 0 ; i < AppMessage -> am_NumArgs ; i++)
								{
										/* Does it have a lock attached? */

									if(AppMessage -> am_ArgList[i] . wa_Lock)
									{
											/* Turn it into a name. */

										if(NameFromLock(AppMessage -> am_ArgList[i] . wa_Lock,AreaName,256))
										{
											struct FileInfoBlock __aligned FileInfo;

												/* Examine the type. */

											if(Examine(AppMessage -> am_ArgList[i] . wa_Lock,&FileInfo))
											{
													/* Does the lock refer to a file? */

												if(FileInfo . fib_DirEntryType < 0)
												{
													UBYTE *Stop;

														/* Take the path part. */

													Stop = PathPart(AreaName);

														/* Cut off the file name. */

													*Stop = 0;

													GotName = TRUE;

													break;
												}
												else
													GotName = TRUE;
											}
											else
												AreaName[0] = 0;
										}
										else
											AreaName[0] = 0;
									}
								}
							}

								/* As soon as the AppMessage come in, leave
								 * the iconified state.
								 */

							IconTerminated = TRUE;

							ReplyMsg(AppMessage);
						}
					}

						/* Check for commodities messages. */

					if(Mask & SIG_CX)
					{
						CxMsg *Message;

						while(Message = (CxMsg *)GetMsg(CxPort))
							HandleCxMsg(Message);
					}
				}

					/* Remove the application icon. */

				RemoveAppIcon(AppIcon);

					/* Reply pending messages. */

				while(AppMessage = (struct AppMessage *)GetMsg(IconPort))
					ReplyMsg(AppMessage);
			}
			else
			{
				if(Complain)
					ShowRequest("Failed to add application icon\n(is Workbench running?).","Continue");
			}

			DeleteMsgPort(IconPort);
		}
		else
		{
			if(Complain)
				ShowRequest("Failed to create MsgPort.","Continue");
		}

		FreeDiskObject(Icon);
	}
	else
	{
		if(Complain)
			ShowRequest("Failed to open tool icon.","Continue");
	}

		/* If we did in fact close the window, reopen it. */

	if(ClosedGfx)
	{
		if(!Terminated)
		{
			OpenGfx();

			RefreshGfx();
		}
	}
	else
	{
		if(Window)
			EnableWindow();
	}

	return(Success);
}

	/* ClearFileInfo():
	 *
	 *	Clear the file information area.
	 */

VOID
ClearFileInfo()
{
	BYTE FgPen = RPort -> FgPen;

	SetAPen(RPort,0);
	RectFill(RPort,10 + 2,PathTopLine + 2,10 + 2 + ((WindowWidth - 20 - INTERWIDTH) / 2) - 5,PathTopLine + 2 + 6 * SystemFontHeight - 1);
	SetAPen(RPort,FgPen);
}

	/* DrawFileInfo(WORD X,WORD Y,BYTE Cut,UBYTE *Format,...):
	 *
	 *	Draw text in the file info area.
	 */

VOID __stdargs
DrawFileInfo(WORD X,WORD Y,BYTE Cut,UBYTE *Format,...)
{
	STATIC UBYTE	Buffer[100];
	va_list		VarArgs;

		/* Fill the buffer with the text. */

	va_start(VarArgs,Format);
	VSPrintf(Buffer,Format,VarArgs);
	va_end(VarArgs);

		/* Move to the approriate position. */

	Move(RPort,14 + SystemFontWidth * X,PathTopLine + 2 + SystemFontBase + SystemFontHeight * Y);

		/* If desired, add a few blank spaces and cut off the leading
		 * eleven characters.
		 */

	if(Cut)
	{
		strcat(Buffer,"            ");

		Buffer[11] = 0;
	}

		/* Print the text. */

	Text(RPort,Buffer,strlen(Buffer));
}

	/* FreeNameList():
	 *
	 *	Free the linked list of names.
	 */

VOID
FreeNameList()
{
		/* Do we have a list to free? */

	if(NameList)
	{
		LONG i;

			/* Free each list entry. */

		for(i = 0 ; i < NumNames ; i++)
			FreeVec(NameList[i]);

			/* Free the name list itself. */

		FreeVec(NameList);

		NameList = NULL;
	}

		/* Reset the counters. */

	NumNames = MaxNames = 0;
}

	/* AddNameList(UBYTE *Name):
	 *
	 *	Add another name to the list.
	 */

struct NameString * __regargs
AddNameList(UBYTE *Name)
{
	WORD Len;

		/* Will we need to extend the list? */

	if(NumNames + 1 > MaxNames)
	{
		struct NameString **NewList;

			/* Allocate space for ten more list entries. */

		if(NewList = (struct NameString **)AllocVec(sizeof(struct NameString *) * (MaxNames + 10),MEMF_CLEAR))
		{
				/* Move the contents of the original name list
				 * to the newly created name list and free
				 * the old list.
				 */

			if(NameList)
			{
				LONG i;

				for(i = 0 ; i < NumNames ; i++)
					NewList[i] = NameList[i];

				FreeVec(NameList);
			}

				/* Update counter and data. */

			MaxNames += 10;
			NameList  = NewList;
		}
		else
			return(NULL);
	}

		/* Determine length of the string to add. */

	Len = strlen(Name);

		/* Is the name longer than the longest name? */

	if(Len > MaxNameLen)
		MaxNameLen = Len;

		/* Allocate space for the new string. */

	if(NameList[NumNames] = (struct NameString *)AllocVec(sizeof(struct NameString) + Len,MEMF_CLEAR))
	{
			/* Copy the name. */

		strcpy(NameList[NumNames] -> Name,Name);

			/* Determine the file part. */

		NameList[NumNames] -> NamePart = FilePart(NameList[NumNames] -> Name);

			/* Return the new name list entry. */

		return(NameList[NumNames++]);
	}
	else
		return(NULL);
}

	/* MatchSetup():
	 *
	 *	Set up for pattern matching (either through AmigaDOS wildcards
	 *	or using the Boyer-Moore algorithm).
	 */

VOID __regargs
MatchSetup(UBYTE *Pattern,BYTE Wildcards)
{
		/* Save the flag. */

	UseWildcards = Wildcards;

		/* Set up for the desired pattern matching method. */

	if(Wildcards)
		ParsePatternNoCase(Pattern,MatchData,256);
	else
	{
		WORD i;

			/* Determine pattern width. */

		PatternWidth = strlen(Pattern);

			/* Convert the pattern to upper case.  */

		for(i = 0 ; i <= PatternWidth ; i++)
			NewPattern[i] = ToUpper(Pattern[i]);

			/* Initialize the default distance values. */

		for(i = 0 ; i < 256 ; i++)
			PatternDistance[i] = PatternWidth;

			/* Now take care of the pattern contents. */

		for(i = 0 ; i < PatternWidth - 1 ; i++)
			PatternDistance[NewPattern[i]] = PatternWidth - i - 1;
	}
}

	/* MatchString(UBYTE *String):
	 *
	 *	Match a string against a previously defined pattern.
	 */

BYTE __regargs
MatchString(UBYTE *String)
{
		/* Use the selected pattern matching mode. */

	if(UseWildcards)
		return((BYTE)MatchPatternNoCase(MatchData,String));
	else
	{
		WORD j = PatternWidth,k,l,SearchWidth = strlen(String);

			/* Perform Boyer-Moore search... */

		do
		{
			k = PatternWidth;
			l = j;

			do
			{
				k--;
				l--;
			}
			while(k >= 0 && NewPattern[k] == ToUpper(String[l]));

			j += PatternDistance[ToUpper(String[j - 1])];
		}
		while(k >= 0 && j <= SearchWidth);

		if(k < 0)
			return(TRUE);
		else
			return(FALSE);
	}
}

	/* CheckAbort():
	 *
	 *	Check for `abort' condition.
	 */

BYTE
CheckAbort()
{
		/* Did we get a message from the window? */

	if(SetSignal(0,0) & SIG_WINDOW)
	{
		struct IntuiMessage	*Massage;
		struct Gadget		*Gadget;
		UWORD			 Code;
		ULONG			 Class;

			/* Process all messages. */

		while(Massage = (struct IntuiMessage *)GT_GetIMsg(Window -> UserPort))
		{
			Class	= Massage -> Class;
			Code	= Massage -> Code;
			Gadget	= (struct Gadget *)Massage -> IAddress;

			GT_ReplyIMsg(Massage);

				/* Refresh the window contents? */

			if(Class == IDCMP_REFRESHWINDOW)
				RefreshGfx();

				/* Did the user press the `Stop' button? */

			if(Class == IDCMP_GADGETUP)
			{
				switch(Gadget -> GadgetID)
				{
					case GAD_START:		Aborted = TRUE;
								break;

					default:		break;
				}
			}

				/* Did the user press the `Stop' key? */

			if(Class == IDCMP_VANILLAKEY)
			{
				switch(ToUpper((UBYTE)Code))
				{
					case 'S':
					case '\r':	Aborted = TRUE;
							break;

					default:	break;
				}
			}
		}

		SetSignal(0,SIG_WINDOW);
	}

		/* Did we get a `Stop' signal? */

	if(SetSignal(0,0) & SIG_KILL)
	{
		Aborted		= TRUE;
		Terminated	= TRUE;

		SetSignal(0,SIG_KILL);
	}

	return(Aborted);
}

	/* StartSearch(BPTR Dir,UBYTE *Name,UBYTE Levels):
	 *
	 *	This is the recursive directory scanner, the search
	 *	process is actually done here.
	 */

VOID __regargs
StartSearch(BPTR Dir,UBYTE *Name,UBYTE Levels)
{
		/* Run until finished or aborted. */

	if(!Aborted && !CheckAbort())
	{
		struct FileInfoBlock __aligned	FileInfo;
		UBYTE				NewName[256];

			/* Examine the directory. */

		if(Examine(Dir,&FileInfo))
		{
			WORD Len = strlen(Name);

				/* Remember the name. */

			strcpy(NewName,Name);

				/* Scan the entire directory. */

			while(ExNext(Dir,&FileInfo))
			{
					/* Check for abort condition. */

				if(Aborted || CheckAbort())
					break;

					/* Add the file name to the base name. */

				if(AddPart(NewName,FileInfo . fib_FileName,256))
				{
						/* Did we get a directory? */

					if(FileInfo . fib_DirEntryType > 0)
					{
						BYTE IsLink = (FileInfo . fib_DirEntryType == 3 || FileInfo . fib_DirEntryType == 4) ? TRUE : FALSE;

							/* Does the name match the pattern? */

						if(MatchString(FileInfo . fib_FileName))
						{
							struct NameString *NameString = AddNameList(NewName);

								/* If the name fits into the list, fill in
								 * the data connected with it.
								 */

							if(NameString)
							{
								NameString -> Type		= IsLink ? TYPE_LINK : TYPE_DIR;
								NameString -> Protection	= FileInfo . fib_Protection;
								NameString -> Date		= FileInfo . fib_Date;
								NameString -> Blocks		= FileInfo . fib_NumBlocks;

									/* Update the counters. */

								if(IsLink)
									NumLinks++;
								else
									NumDirs++;
							}
						}

							/* So we found a directory, we will enter
							 * it only if we did not enter the eighth
							 * recursion level yet and the directory
							 * is not a link to another directory.
							 */

						if(Levels < 8 && !IsLink)
						{
							BPTR FileLock;

								/* Obtain a lock on the directory to enter
								 * and enter it.
								 */

							if(FileLock = Lock(FileInfo . fib_FileName,ACCESS_READ))
							{
								BPTR OldDir = CurrentDir(FileLock);

									/* Continue searching... */

								StartSearch(FileLock,NewName,Levels + 1);

									/* Get back to the original directory. */

								CurrentDir(OldDir);

									/* Unlock the directory. */

								UnLock(FileLock);
							}
						}
					}
					else
					{
							/* So we got a file, does its name match
							 * the pattern?
							 */

						if(MatchString(FileInfo . fib_FileName))
						{
							struct NameString *NameString = AddNameList(NewName);

								/* If the name fits into the list, fill in
								 * the data connected with it.
								 */

							if(NameString)
							{
								NameString -> Type		= Identify(NewName);
								NameString -> Protection	= FileInfo . fib_Protection;
								NameString -> Size		= FileInfo . fib_Size;
								NameString -> Blocks		= FileInfo . fib_NumBlocks;
								NameString -> Date		= FileInfo . fib_Date;

									/* Update the counter. */

								NumFiles++;
							}
						}
					}
				}

					/* Cut off the file name we had appended to the directory name. */

				NewName[Len] = 0;
			}
		}
	}
}

	/* Compare(struct NameString **A,struct NameString **B):
	 *
	 *	The element comparison routine, qsort() requires it.
	 */

LONG __stdargs
Compare(struct NameString **A,struct NameString **B)
{
	return(Stricmp((*A) -> NamePart,(*B) -> NamePart));
}

	/* IsAssign(UBYTE *Name):
	 *
	 *	Checks if a device/volume name refers in fact to an assignment.
	 */

BYTE __regargs
IsAssign(UBYTE *Name)
{
	WORD NameLen	= strlen(Name) - 1;
	BYTE Result	= FALSE;

		/* Does the name have a colon in the last character? */

	if(Name[NameLen] == ':')
	{
		struct DosList *DosList;

			/* Open the DosList for reading assignments. */

		if(DosList = AttemptLockDosList(LDF_ASSIGNS | LDF_READ))
		{
			UBYTE *AssignName;

				/* Scan the list for assignments... */

			while(DosList = NextDosEntry(DosList,LDF_ASSIGNS))
			{
					/* Make it a real pointer. */

				AssignName = (UBYTE *)BADDR(DosList -> dol_Name);

					/* Is it as long as the name we are looking for? */

				if(AssignName[0] == NameLen)
				{
						/* Compare the names. */

					if(!Strnicmp(&AssignName[1],Name,NameLen))
					{
						Result = TRUE;

						break;
					}
				}
			}

				/* Unlock the list again. */

			UnLockDosList(LDF_ASSIGNS | LDF_READ);
		}
	}

	return(Result);
}

	/* MultiSearch(UBYTE *Name,UBYTE *TempName):
	 *
	 *	Search along the path connected with a multi-volume assignment.
	 */

VOID __regargs
MultiSearch(UBYTE *Name,UBYTE *TempName)
{
	struct DevProc	*DevProc	= NULL;
	struct MsgPort	*FileSysTask	= GetFileSysTask();

		/* Remember the base name. */

	strcpy(TempName,Name);

		/* Scan along all paths... */

	do
	{
			/* Get the FileLock referring to the assignment. */

		if(DevProc = GetDeviceProc(Name,DevProc))
		{
				/* Compensate for ZERO locks. */

			SetFileSysTask(DevProc -> dvp_Port);

				/* Turn the FileLock into a name. */

			if(NameFromLock(DevProc -> dvp_Lock,TempName,256))
			{
				BPTR DirLock = Lock(TempName,ACCESS_READ);

					/* If we got a valid name, enter the
					 * directory associated with it.
					 */

				if(DirLock)
				{
					BYTE	Levels,
						Len	= strlen(TempName);
					BPTR	OldLock	= CurrentDir(DirLock);

						/* Determine the directory
						 * level this name refers to.
						 */

					if(TempName[Len - 1] == ':')
						Levels = 0;
					else
					{
						BYTE i;

						Levels = 1;

						for(i = 0 ; i < Len ; i++)
						{
							if(TempName[i] == '/')
								Levels++;
						}
					}

						/* If possible, scan the directory. */

					if(Levels < 8)
						StartSearch(DirLock,TempName,Levels);

						/* Get back to the original directory. */

					CurrentDir(OldLock);

						/* Unlock the directory. */

					UnLock(DirLock);
				}
			}
		}
		else
			break;
	}
	while(!Aborted && !CheckAbort() && DevProc && (DevProc -> dvp_Flags & DVPF_ASSIGN));

		/* Get back to the original default filing system task. */

	SetFileSysTask(FileSysTask);

		/* Free any data allocated with the DeviceProc structure. */

	if(DevProc)
		FreeDeviceProc(DevProc);
}

	/* ShowInfo():
	 *
	 *	Show an information message concerning the number of
	 *	files, directories and linked directories found.
	 */

VOID
ShowInfo()
{
	UBYTE	*Files	= (NumFiles > 1)	? "files"	: "file",
		*Dirs	= (NumDirs  > 1)	? "directories"	: "directory",
		*Links	= (NumLinks > 1)	? "links"	: "link";

	if(NumFiles == 0 && NumDirs == 0 && NumLinks == 0)
		ShowMessage("No matching files were found.");
	else
	{
		if(NumFiles)
		{
			if(NumDirs)
			{
				if(NumLinks)
					ShowMessage("Found %ld %s, %ld %s and %ld %s.",NumFiles,Files,NumDirs,Dirs,NumLinks,Links);
				else
					ShowMessage("Found %ld %s and %ld %s.",NumFiles,Files,NumDirs,Dirs);
			}
			else
			{
				if(NumLinks)
					ShowMessage("Found %ld %s and %ld %s.",NumFiles,Files,NumLinks,Links);
				else
					ShowMessage("Found %ld %s.",NumFiles,Files);
			}
		}
		else
		{
			if(NumDirs)
			{
				if(NumLinks)
					ShowMessage("Found %ld %s and %ld %s.",NumDirs,Dirs,NumLinks,Links);
				else
					ShowMessage("Found %ld %s.",NumDirs,Dirs);
			}
			else
				ShowMessage("Found %ld %s.",NumLinks,Links);
		}
	}
}

	/* FindFile():
	 *
	 *	This is the big one. Set up for file search.
	 */

VOID
FindFile()
{
		/* Disable all gadgets we do not really require. */

	DisableWindow(FALSE);

		/* Remember the contents of the string gadgets. */

	strcpy(AreaName,	GT_STRING(GadgetArray[GAD_SEARCHAREA]));
	strcpy(SearchString,	GT_STRING(GadgetArray[GAD_SEARCHFOR]));

		/* Do we have an area to search? */

	if(AreaName[0])
	{
			/* Do we have a pattern to search for? */

		if(SearchString[0])
		{
			BPTR DirLock;

				/* Set up match pattern. */

			MatchSetup(SearchString,GT_CHECKED(GadgetArray[GAD_WILDCARDS]));

				/* Get a lock on the search area. */

			if(DirLock = Lock(AreaName,ACCESS_READ))
			{
				STATIC UBYTE Name[256];

					/* Does the search area name refer to
					 * a multivolume-assignment?
					 */

				if(IsAssign(AreaName))
				{
						/* Reset the abort flag. */

					Aborted = FALSE;

						/* Free any previously allocated
						 * name list.
						 */

					FreeNameList();

						/* Start at the top. */

					ListTop = 0;

						/* Clear the main display area. */

					RefreshList();

						/* Clear the file information area. */

					ClearFileInfo();

						/* There is no longer any data in
						 * the list.
						 */

					GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
						GTSC_Top,	0,
						GTSC_Total,	0,
					TAG_DONE);

						/* Detach the path name list. */

					GT_SetGadgetAttrs(GadgetArray[GAD_FILELIST],Window,NULL,
						GTLV_Labels,	~0,
					TAG_DONE);

						/* Attach an empty list. */

					GT_SetGadgetAttrs(GadgetArray[GAD_FILELIST],Window,NULL,
						GTLV_Labels,	&EmptyList,
					TAG_DONE);

						/* Yes, we are scanning. */

					Scanning = TRUE;

						/* Nothing has been selected yet. */

					LastSelected = -1;

						/* Reset the counters. */

					NumFiles = NumDirs = NumLinks = 0;

						/* Show what we will do. */

					ShowMessage("Searching...");

						/* Reset the maximum file name length. */

					MaxNameLen = 0;

						/* Start the search... */

					MultiSearch(AreaName,Name);

						/* We are no longer scanning. */

					Scanning = FALSE;

						/* Adjust the slider to represent the
						 * number of entries in the list.
						 */

					GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
						GTSC_Total,	NumNames,
					TAG_DONE);

						/* Turn off the `Save list...' and
						 * `Copy path to clipboard' menu
						 * items.
						 */

					OffMenu(Window,FULLMENUNUM(1,0,0));
					OffMenu(Window,FULLMENUNUM(0,0,NOSUB));

						/* Did we find any files? */

					if(NumNames)
					{
							/* Turn the `Save list...' back on. */

						OnMenu(Window,FULLMENUNUM(0,0,NOSUB));

							/* Show what we are doing. */

						ShowMessage("Sorting...");

							/* Sort the list... */

						qsort(NameList,NumNames,sizeof(struct NameString *),Compare);

							/* Clear the main display window. */

						SetAPen(RPort,0);

						RectFill(RPort,14,ListTopLine + 2,ListRightColumn - 4,ListTopLine + 2 + (10 * SystemFontHeight) - 1);

						SetAPen(RPort,1);

							/* Redraw the main display window. */

						RefreshList();
					}

						/* Show how many files/directories/links we got. */

					ShowInfo();
				}
				else
				{
						/* Same as above, just for a single directory. */

					if(NameFromLock(DirLock,Name,256))
					{
						BYTE	Levels,
							Len	= strlen(Name);
						BPTR	OldLock	= CurrentDir(DirLock);

						if(Name[Len - 1] == ':')
							Levels = 0;
						else
						{
							BYTE i;

							Levels = 1;

							for(i = 0 ; i < Len ; i++)
							{
								if(Name[i] == '/')
									Levels++;
							}
						}

						if(Levels < 8)
						{
							Aborted = FALSE;

							FreeNameList();

							ListTop = 0;

							RefreshList();

							ClearFileInfo();

							GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
								GTSC_Top,	0,
								GTSC_Total,	0,
							TAG_DONE);

							GT_SetGadgetAttrs(GadgetArray[GAD_FILELIST],Window,NULL,
								GTLV_Labels,	~0,
							TAG_DONE);

							GT_SetGadgetAttrs(GadgetArray[GAD_FILELIST],Window,NULL,
								GTLV_Labels,	&EmptyList,
							TAG_DONE);

							Scanning = TRUE;

							LastSelected = -1;

							NumFiles = NumDirs = NumLinks = 0;

							ShowMessage("Searching...");

							MaxNameLen = 0;

							StartSearch(DirLock,Name,Levels);

							Scanning = FALSE;

							GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
								GTSC_Total,	NumNames,
							TAG_DONE);

							OffMenu(Window,FULLMENUNUM(1,0,0));
							OffMenu(Window,FULLMENUNUM(0,0,NOSUB));

							if(NumNames)
							{
								OnMenu(Window,FULLMENUNUM(0,0,NOSUB));

								ShowMessage("Sorting...");

								qsort(NameList,NumNames,sizeof(struct NameString *),Compare);

								SetAPen(RPort,0);

								RectFill(RPort,14,ListTopLine + 2,ListRightColumn - 4,ListTopLine + 2 + (10 * SystemFontHeight) - 1);

								SetAPen(RPort,1);

								RefreshList();
							}

							ShowInfo();

							CurrentDir(OldLock);
						}
						else
							ShowRequest("Sorry, path name too long.","Continue");
					}
					else
						ShowRequest("Failed to build path name.","Continue");
				}

				UnLock(DirLock);
			}
			else
			{
				UBYTE Buffer[100];

					/* Display the error. */

				if(Fault(IoErr(),"",Buffer,100))
				{
					WORD Len = strlen(Buffer) - 1;

					if(Buffer[Len] == '\n')
						Buffer[Len] = 0;

					ShowRequest("Error accessing \"%s\"%s.","Continue",AreaName,Buffer);
				}
				else
					ShowRequest("Error accessing \"%s\".","Continue",AreaName);
			}
		}
		else
			ShowRequest("You forgot to specify a search pattern.","Continue");
	}
	else
		ShowRequest("You forgot to specify the search area.","Continue");

		/* Enable the window. */

	EnableWindow();

		/* Remove all pending AppMessages. */

	if(WorkbenchPort)
	{
		struct Message *Massage;

		while(Massage = GetMsg(WorkbenchPort))
			ReplyMsg(Massage);
	}
}

	/* CreateAllGadgets():
	 *
	 *	Create all the gadgets required by the control panel.
	 */

struct Gadget * __regargs
CreateAllGadgets(struct Gadget **GadgetArray,struct Gadget **GadgetList,APTR VisualInfo,UWORD TopEdge)
{
	struct Gadget		*Gadget;
	struct NewGadget	 NewGadget;
	WORD			 Counter = 0,
				 NewTop,
				 NewLeft;

	memset(&NewGadget,0,sizeof(struct NewGadget));

	if(Gadget = CreateContext(GadgetList))
	{
		NewGadget . ng_Width		= 29 * SystemFontWidth + 12;
		NewGadget . ng_Height		= SystemFontHeight + 6;
		NewGadget . ng_GadgetText	= "Search _Area";
		NewGadget . ng_TextAttr		= &SystemFont;
		NewGadget . ng_VisualInfo	= VisualInfo;
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= PLACETEXT_LEFT;
		NewGadget . ng_LeftEdge		= 10 + 12 * SystemFontWidth;
		NewGadget . ng_TopEdge		= INTERHEIGHT + TopEdge;

		GadgetArray[Counter++] = Gadget = CreateGadget(STRING_KIND,Gadget,&NewGadget,
			GTST_String,	AreaName,
			GTST_MaxChars,	256,
			GTST_EditHook,	&StringHook,
			GT_Underscore,	'_',
		TAG_DONE);

		NewGadget . ng_GadgetText	= "Search _For";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_TopEdge		= NewGadget . ng_TopEdge + NewGadget . ng_Height + INTERHEIGHT;

		GadgetArray[Counter++] = Gadget = CreateGadget(STRING_KIND,Gadget,&NewGadget,
			GTST_MaxChars,	256,
			GTST_String,	SearchString,
			GTST_EditHook,	&StringHook,
			GT_Underscore,	'_',
		TAG_DONE);

		NewTop = NewGadget . ng_TopEdge + NewGadget . ng_Height + INTERHEIGHT;

		NewGadget . ng_GadgetText	= "_Wildcards";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= PLACETEXT_RIGHT;
		NewGadget . ng_LeftEdge		= NewGadget . ng_LeftEdge + NewGadget . ng_Width + INTERWIDTH;
		NewGadget . ng_TopEdge		= NewGadget . ng_TopEdge + (NewGadget . ng_Height - 12) / 2;
		NewGadget . ng_Height		= 12;
		NewGadget . ng_Width		= 26;

		GadgetArray[Counter++] = Gadget = CreateGadget(CHECKBOX_KIND,Gadget,&NewGadget,
			GT_Underscore,	'_',
			GTCB_Checked,	UseWildcards,
		TAG_DONE);

		if((NewLeft = NewGadget . ng_LeftEdge + NewGadget . ng_Width + 10 * SystemFontWidth + INTERWIDTH) > WindowWidth - NewGadget . ng_Width - 10)
		{
			UWORD More = (NewLeft - (WindowWidth - NewGadget . ng_Width - 10) + SystemFontWidth - 1) / SystemFontWidth;

			WindowWidth += More * SystemFontWidth;
		}

		NewGadget . ng_GadgetText	= "?";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;
		NewGadget . ng_TextAttr		= &SystemFont;
		NewGadget . ng_Height		= (SystemFontHeight + 6) * 2 + INTERHEIGHT;
		NewGadget . ng_Width		= (SystemFontWidth * 3) + 4;
		NewGadget . ng_LeftEdge		= WindowWidth - NewGadget . ng_Width - 10;
		NewGadget . ng_TopEdge		= INTERHEIGHT + TopEdge;

		GadgetArray[Counter++] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
			TAG_DONE);

		if(NewGadget . ng_TopEdge + NewGadget . ng_Height + INTERHEIGHT > NewTop)
			NewTop = NewGadget . ng_TopEdge + NewGadget . ng_Height + INTERHEIGHT;

		NewGadget . ng_GadgetText	= "";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;
		NewGadget . ng_TextAttr		= &SystemFont;
		NewGadget . ng_Height		= SystemFontHeight + 6;
		NewGadget . ng_Width		= WindowWidth - 20;
		NewGadget . ng_TopEdge		= NewTop;
		NewGadget . ng_LeftEdge		= 10;

		GadgetArray[Counter++] = Gadget = CreateGadget(TEXT_KIND,Gadget,&NewGadget,
			GTTX_Border,	TRUE,
			GTTX_Text,	MessageBuffer,
		TAG_DONE);

		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;
		NewGadget . ng_TopEdge		= NewGadget . ng_TopEdge + NewGadget . ng_Height + INTERHEIGHT;
		NewGadget . ng_Height		= SystemFontHeight * 10 + 4;
		NewGadget . ng_Width		= 2 * SystemFontWidth;
		NewGadget . ng_LeftEdge		= WindowWidth - NewGadget . ng_Width - 10;

		ListTopLine	= NewGadget . ng_TopEdge;
		ListRightColumn	= NewGadget . ng_LeftEdge - 1;

		GadgetArray[Counter++] = Gadget = CreateGadget(SCROLLER_KIND,Gadget,&NewGadget,
			GTSC_Top,	ListTop,
			GTSC_Visible,	5,
			GTSC_Arrows,	SystemFontHeight,
			GTSC_Total,	NumNames,
			PGA_Freedom,	LORIENT_VERT,
			GA_Immediate,	TRUE,
			GA_RelVerify,	TRUE,
		TAG_DONE);

		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;
		NewGadget . ng_TopEdge		= NewGadget . ng_TopEdge + NewGadget . ng_Height + INTERHEIGHT;
		NewGadget . ng_Height		= 6 * SystemFontHeight + 4;
		NewGadget . ng_Width		= (WindowWidth - 20 - INTERWIDTH) / 2;
		NewGadget . ng_LeftEdge		= 10 + INTERWIDTH + NewGadget . ng_Width;

		PathTopLine = NewGadget . ng_TopEdge;

		GadgetArray[Counter] = Gadget = CreateGadget(LISTVIEW_KIND,Gadget,&NewGadget,
			GTLV_ReadOnly,		TRUE,
			GTLV_ScrollWidth,	2 * SystemFontWidth,
		TAG_DONE);
	}

	return(Gadget);
}

	/* CloseGfx():
	 *
	 *	Close all the graphics data.
	 */

VOID
CloseGfx()
{
		/* Shut down the Workbench interface. */

	if(WorkbenchWindow)
	{
		RemoveAppWindow(WorkbenchWindow);

		WorkbenchWindow = NULL;
	}

		/* Remove the Workbench interface port. */

	if(WorkbenchPort)
	{
		struct Message *Massage;

		while(Massage = GetMsg(WorkbenchPort))
			ReplyMsg(Massage);

		DeleteMsgPort(WorkbenchPort);

		WorkbenchPort = NULL;
	}

		/* Close the window. */

	if(Window)
	{
		Window -> Flags |= WFLG_RMBTRAP;

		ClearMenuStrip(Window);

		CloseWindow(Window);

		Window = NULL;
	}

		/* Reset the zoom box data. */

	ZoomBox . Left = -1;

		/* Free the menu. */

	if(FindMenu)
	{
		FreeMenus(FindMenu);

		FindMenu = NULL;
	}

		/* Free the DrawInfo data. */

	if(DrawInfo)
	{
		FreeScreenDrawInfo(DefaultScreen,DrawInfo);

		DrawInfo = NULL;
	}

		/* Release the screen our window was residing on. */

	if(DefaultScreen)
	{
		UnlockPubScreen(NULL,DefaultScreen);

		DefaultScreen = NULL;
	}

		/* Close the default text font. */

	if(DefaultFont)
	{
		CloseFont(DefaultFont);

		DefaultFont = NULL;
	}

		/* Free the VisualInfo data. */

	if(VisualInfo)
	{
		FreeVisualInfo(VisualInfo);

		VisualInfo = NULL;
	}

		/* Free the gadget list. */

	if(GadgetList)
	{
		FreeGadgets(GadgetList);

		GadgetList = NULL;
	}
}

	/* CentreWindow():
	 *
	 *	Centre the window to be opened somewhere below the
	 *	mouse pointer.
	 */

VOID __regargs
CentreWindow(struct Screen *Screen,WORD *LeftEdge,WORD *TopEdge,WORD Left,WORD Top)
{
	(*LeftEdge)	= Screen -> MouseX - Left;
	(*TopEdge)	= Screen -> MouseY - Top;

	while((*LeftEdge) + WindowWidth > Screen -> Width)
		(*LeftEdge)--;

	while((*LeftEdge) < 0)
		(*LeftEdge)++;

	while((*TopEdge) + WindowHeight > Screen -> Height)
		(*TopEdge)--;

	while((*TopEdge) < 0)
		(*TopEdge)++;
}

	/* OpenGfx():
	 *
	 *	Open all graphics data.
	 */

BYTE
OpenGfx()
{
	WORD Left,Top;

		/* If no public screen has been specified, gain access
		 * to the default screen.
		 */

	if(!DefaultScreen)
	{
		if(!(DefaultScreen = LockPubScreen(NULL)))
			return(FALSE);
	}

		/* Inquire the drawing pen information. */

	if(!(DrawInfo = GetScreenDrawInfo(DefaultScreen)))
		return(FALSE);

		/* Determine whether we are to use more than two colours for
		 * text rendering.
		 */

	if((DrawInfo -> dri_Flags & DRIF_NEWLOOK) && DrawInfo -> dri_Depth > 1)
		NewLook = TRUE;
	else
		NewLook = FALSE;

		/* Get the VisualInfo data required for gadtools objects. */

	if(!(VisualInfo = GetVisualInfo(DefaultScreen,TAG_DONE)))
		return(FALSE);

		/* Turn the currently active system default font information
		 * into a TextAttr structure fit to submit to gadtools.
		 */

	Forbid();

	strcpy(SystemFontName,GfxBase -> DefaultFont -> tf_Message . mn_Node . ln_Name);

	SystemFontWidth			= GfxBase -> DefaultFont -> tf_XSize;
	SystemFontHeight		= GfxBase -> DefaultFont -> tf_YSize;
	SystemFontBase			= GfxBase -> DefaultFont -> tf_Baseline;

	SystemFont . ta_Name		= SystemFontName;
	SystemFont . ta_YSize		= GfxBase -> DefaultFont -> tf_YSize;
	SystemFont . ta_Style		= GfxBase -> DefaultFont -> tf_Style;
	SystemFont . ta_Flags		= GfxBase -> DefaultFont -> tf_Flags & ~FPF_REMOVED;

	strcpy(BigSystemFontName,SystemFontName);

	BigSystemFont . ta_Name		= SystemFontName;
	BigSystemFont . ta_YSize	= SystemFont . ta_YSize * 2;
	BigSystemFont . ta_Style	= SystemFont . ta_Style;
	BigSystemFont . ta_Flags	= SystemFont . ta_Flags & ~FPF_DESIGNED;

	Permit();

		/* Calculate the window width. */

	WindowWidth = 20 + 58 * SystemFontWidth + 4 + 2 * SystemFontWidth;

		/* Make sure that the font we just prepared will be available lateron. */

	if(!(DefaultFont = OpenFont(&SystemFont)))
		return(FALSE);

		/* Create all the gadgets for the control panel. */

	if(!CreateAllGadgets(&GadgetArray[0],&GadgetList,VisualInfo,DefaultScreen -> WBorTop + DefaultScreen -> Font -> ta_YSize + 1))
		return(FALSE);

		/* Taking a look at the gadgets in the window to be opened,
		 * calculate the height of the window.
		 */

	WindowHeight = GadgetArray[GAD_FILELIST] -> TopEdge + GadgetArray[GAD_FILELIST] -> Height + INTERHEIGHT + DefaultScreen -> WBorBottom;

		/* Will the window fit on the screen? */

	if(WindowWidth > DefaultScreen -> Width || WindowHeight > DefaultScreen -> Height)
	{
		ShowRequest("The window to be opened would be\ntoo large to fit on the screen.","Sorry");

		return(FALSE);
	}

		/* Create the window menus. */

	if(!(FindMenu = CreateMenus(FindMenuConfig,
		GTMN_FrontPen,	0,
	TAG_DONE)))
		return(FALSE);

		/* Perform menu layout. */

	if(!LayoutMenus(FindMenu,VisualInfo,
		GTMN_TextAttr,	DefaultScreen -> Font,
	TAG_DONE))
		return(FALSE);

		/* Initialize the window zoom box data if not already done. */

	if(ZoomBox . Left == -1)
	{
		ZoomBox . Left		= 0;
		ZoomBox . Top		= 0;
		ZoomBox . Width		= WindowWidth / 2;
		ZoomBox . Height	= DefaultScreen -> WBorTop + DefaultScreen -> Font -> ta_YSize + 1;
	}

		/* Centre the window so that it will open with the mouse pointer
		 * below the `Start' gadget.
		 */

	CentreWindow(DefaultScreen,&Left,&Top,GadgetArray[GAD_START] -> LeftEdge + GadgetArray[GAD_START] -> Width /  2,GadgetArray[GAD_START] -> TopEdge + GadgetArray[GAD_START] -> Height /  2);

		/* The window is not iconified yet. */

	IconTerminated = TRUE;

		/* Prepare the window title. */

	if(Broker)
		SPrintf(WindowTitle,"Find Files: HotKey=%s",HotkeyBuffer);
	else
		strcpy(WindowTitle,"Find Files");

		/* Finally, open the new window. */

	if(!(Window = OpenWindowTags(NULL,
		WA_Width,	WindowWidth,
		WA_Height,	WindowHeight,
		WA_Left,	Left,
		WA_Top,		Top,

		WA_Activate,	TRUE,
		WA_DragBar,	TRUE,
		WA_DepthGadget,	TRUE,
		WA_CloseGadget,	TRUE,
		WA_RMBTrap,	TRUE,
		WA_Zoom,	&ZoomBox,

		WA_IDCMP,	IDCMP_CLOSEWINDOW | IDCMP_RAWKEY | IDCMP_VANILLAKEY | IDCMP_MOUSEBUTTONS | IDCMP_MENUPICK | IDCMP_REFRESHWINDOW | IDCMP_ACTIVEWINDOW | STRINGIDCMP | ARROWIDCMP | SCROLLERIDCMP | CHECKBOXIDCMP | LISTVIEWIDCMP | BUTTONIDCMP,

		WA_Title,	WindowTitle,

		WA_CustomScreen,DefaultScreen,
	TAG_DONE)))
		return(FALSE);

		/* Create the Workbench interface MsgPort. */

	if(WorkbenchPort = CreateMsgPort())
		WorkbenchWindow = AddAppWindow(0,0,Window,WorkbenchPort,NULL);

		/* A handy shortcut to the window's RastPort. */

	RPort = Window -> RPort;

		/* Set the wait pointer for the window. */

	SetWait(Window);

		/* Make AmigaDOS requesters come up on the current screen. */

	FindProcess -> pr_WindowPtr = (APTR)Window;

		/* Make sure that the string gadgets will use the desired font. */

	SetFont(RPort,DefaultFont);

		/* Draw the main display area border. */

	DrawBevelBox(RPort,10,ListTopLine,WindowWidth - 20 - 2 * SystemFontWidth,SystemFontHeight * 10 + 4,
		GT_VisualInfo,	VisualInfo,
	TAG_DONE);

		/* Draw the file information area border. */

	DrawBevelBox(RPort,10,PathTopLine,(WindowWidth - 20 - INTERWIDTH) / 2,6 * SystemFontHeight + 4,
		GT_VisualInfo,	VisualInfo,
		GTBB_Recessed,	TRUE,
	TAG_DONE);

		/* Add the gadgets we just created and make them appear. */

	AddGList(Window,GadgetList,(UWORD)-1,(UWORD)-1,NULL);
	RefreshGList(GadgetList,Window,NULL,(UWORD)-1);
	GT_RefreshWindow(Window,NULL);

		/* Set the default rendering pen to 1 and the rendering mode to overwrite. */

	SetAPen(RPort,1);
	SetDrMd(RPort,JAM2);

		/* Refresh the main display area. */

	RefreshList();

		/* Attach the menu to the window. */

	SetMenuStrip(Window,FindMenu);

		/* Make the menu strip available to the user. */

	Window -> Flags &= ~WFLG_RMBTRAP;

		/* Restore the mouse pointer. */

	ClearPointer(Window);

		/* Return success. */

	return(TRUE);
}

	/* CloseAll(LONG ReturnCode):
	 *
	 *	Close and free all resources not directly associated with OpenGfx().
	 */

VOID __regargs
CloseAll(LONG ReturnCode)
{
		/* Free the list of file names. */

	FreeNameList();

		/* Tell the clipboard process to quit. */

	if(ClipProcess)
	{
		Signal(ClipProcess,SIG_KILL);

		Wait(SIG_REPLY);
	}

		/* Free the handshake signal. */

	if(ReplySignal != -1)
		FreeSignal(ReplySignal);

		/* Free both file requester structures. */

	if(FileRequest)
		FreeAslRequest(FileRequest);

	if(SaveFileRequest)
		FreeAslRequest(SaveFileRequest);

		/* Close commodities.library. */

	if(CxBase)
		CloseLibrary(CxBase);

		/* Close iffparse.library. */

	if(IFFParseBase)
		CloseLibrary(IFFParseBase);

		/* Close workbench.library. */

	if(WorkbenchBase)
		CloseLibrary(WorkbenchBase);

		/* Close icon.library. */

	if(IconBase)
		CloseLibrary(IconBase);

		/* Close asl.library. */

	if(AslBase)
		CloseLibrary(AslBase);

		/* Close utility.library. */

	if(UtilityBase)
		CloseLibrary(UtilityBase);

		/* Close gadtools.library. */

	if(GadToolsBase)
		CloseLibrary(GadToolsBase);

		/* Close graphics.library. */

	if(GfxBase)
		CloseLibrary(GfxBase);

		/* Close intuition.library. */

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

		/* Terminate the program. */

	exit(ReturnCode);
}

	/* OpenAll():
	 *
	 *	Opens and initialize the basic resources required by the program.
	 */

VOID
OpenAll()
{
	WORD i;

		/* Initialize the path name node list with correct name buffers. */

	for(i = 0 ; i < 10 ; i++)
		FileNames[i] . VanillaNode . ln_Name = FileNames[i] . Name;

		/* Clear the list to remain empty. */

	NewList(&EmptyList);

		/* Open all the libraries we need. */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37)))
		CloseAll(RETURN_FAIL + 0);

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",37)))
		CloseAll(RETURN_FAIL + 1);

	if(!(GadToolsBase = OpenLibrary("gadtools.library",37)))
		CloseAll(RETURN_FAIL + 2);

	if(!(UtilityBase = OpenLibrary("utility.library",37)))
		CloseAll(RETURN_FAIL + 3);

	if(!(AslBase = OpenLibrary("asl.library",37)))
		CloseAll(RETURN_FAIL + 4);

	if(!(IconBase = OpenLibrary("icon.library",37)))
		CloseAll(RETURN_FAIL + 5);

	if(!(IFFParseBase = OpenLibrary("iffparse.library",37)))
		CloseAll(RETURN_FAIL + 6);

	if(!(WorkbenchBase = OpenLibrary("workbench.library",37)))
		CloseAll(RETURN_FAIL + 7);

		/* Try to open commodities.library, but don't worry if
		 * the open fails.
		 */

	CxBase = OpenLibrary("commodities.library",37);

		/* Allocate the first file requester structure. */

	if(!(FileRequest = (struct FileRequester *)AllocAslRequestTags(ASL_FileRequest,
		ASL_Hail,	"Select Search Area",
		ASL_FuncFlags,	FILF_PATGAD,
		ASL_Pattern,	"#?",
		ASL_OKText,	"Select",
		ASL_ExtFlags1,	FIL1F_NOFILES | FIL1F_MATCHDIRS,
	TAG_DONE)))
		CloseAll(RETURN_FAIL + 8);

		/* Allocate the second file requester structure. */

	if(!(SaveFileRequest = (struct FileRequester *)AllocAslRequestTags(ASL_FileRequest,
		ASL_FuncFlags,	FILF_SAVE,
		ASL_OKText,	"Save",
	TAG_DONE)))
		CloseAll(RETURN_FAIL + 9);

		/* Allocate a handshake signal bit. */

	if((ReplySignal = AllocSignal(-1)) == -1)
		CloseAll(RETURN_FAIL + 10);

		/* Try to create the clipboard server, but don't worry if
		 * the creation fails.
		 */

	ClipProcess = CreateNewProcTags(
		NP_Entry,	ClipServer,
		NP_Name,	"Clipboard Server",
		NP_WindowPtr,	-1,
		NP_Priority,	20,
	TAG_DONE);

		/* Wait for handshake signal. */

	Wait(SIG_REPLY);

		/* Initialize the information message buffer. */

	strcpy(MessageBuffer,"Find Files v1.7 © Copyright 1991-92 by MXM");

		/* Initialize the callback hook required by the string gadgets. */

	StringHook . h_Entry	= (APTR)EditRoutine;
	StringHook . h_SubEntry	= NULL;
	StringHook . h_Data	= 0;
}

	/* ShutdownCx():
	 *
	 *	Shuts down the commodities.library interface.
	 */

VOID
ShutdownCx()
{
		/* Did the situation allow us to create the signal port? */

	if(CxPort)
	{
		struct Message *Message;

			/* Remove the input event broker. */

		if(Broker)
			DeleteCxObjAll(Broker);

			/* Remove the port from the public list. */

		RemPort(CxPort);

			/* Accept and reply all pending messages. */

		while(Message = GetMsg(CxPort))
			ReplyMsg(Message);

			/* Delete the signal port. */

		DeleteMsgPort(CxPort);

			/* Clear the pointers to avoid confusion. */

		CxPort = NULL;
		Broker = NULL;
	}
}

	/* SetupCx():
	 *
	 *	Set up the commodities.library interface.
	 */

BYTE
SetupCx()
{
		/* Close down previous installation. */

	ShutdownCx();

		/* Did the startup code succeed in opening commodities.library? */

	if(CxBase)
	{
			/* Create a signal port. */

		if(CxPort = CreateMsgPort())
		{
				/* Give the port a name. */

			CxPort -> mp_Node . ln_Name = NewBroker . nb_Name;

				/* Add the port to the public list. */

			AddPort(CxPort);

				/* Stuff the interface port into the NewBroker definition. */

			NewBroker . nb_Port = CxPort;

				/* If started from Workbench, process the tooltype entries. */

			if(ToolTypes)
			{
					/* Install the hotkey definition, the default
					 * being `shift help'.
					 */

				strcpy(HotkeyBuffer,ArgString(ToolTypes,"CX_POPKEY","shift help"));

					/* Install the commodities priority. */

				NewBroker . nb_Pri = ArgInt(ToolTypes,"CX_PRIORITY",0);
			}
			else
			{
					/* Do the same setups for Shell startup,
					 * first take card of the hotkey buffer.
					 */

				if(!HotkeyBuffer[0])
					strcpy(HotkeyBuffer,"shift help");

					/* Install the commodities priority. */

				NewBroker . nb_Pri = CxPriority;
			}

				/* Create the broker. */

			if(Broker = CxBroker(&NewBroker,NULL))
			{
					/* Attach the hotkey. */

				AttachCxObj(Broker,HotKey(HotkeyBuffer,CxPort,NULL));

					/* Did the setup succeed? */

				if(!CxObjError(Broker))
				{
						/* Activate the broker. */

					ActivateCxObj(Broker,TRUE);

						/* Return success. */

					return(TRUE);
				}
			}

				/* Something went wrong, shut down
				 * the installation.
				 */

			ShutdownCx();
		}
	}

		/* Return failure. */

	return(FALSE);
}

	/* SetupWindow():
	 *
	 *	Do something sensible with the window, bring it to the front and
	 *	activate it or move it to the frontmost screen in case the screen
	 *	happens to be a public screen.
	 */

VOID
SetupWindow()
{
	struct Screen		*FirstScreen,*NewDefaultScreen = NULL;
	UBYTE			 PubName[MAXPUBSCREENNAME + 1];
	ULONG			 IntuiLock;
	struct List		*PubScreenList;
	struct PubScreenNode	*ScreenNode;
	BYTE			 IsPublic = FALSE;

		/* Lock IntuitionBase... */

	IntuiLock = LockIBase(NULL);

		/* ...extract the frontmost screen... */

	FirstScreen = IntuitionBase -> FirstScreen;

		/* ...and unlock IntuitionBase again. */

	UnlockIBase(IntuiLock);

		/* Lock the public screen list. */

	PubScreenList = LockPubScreenList();

		/* Search the public screen list for the frontmost screen. */

	for(ScreenNode = (struct PubScreenNode *)PubScreenList -> lh_Head ; ScreenNode -> psn_Node . ln_Succ ; ScreenNode = (struct PubScreenNode *)ScreenNode -> psn_Node . ln_Succ)
	{
			/* Does the screen match the frontmost screen and
			 * has it been enabled for public access?
			 */

		if(ScreenNode -> psn_Screen == FirstScreen && !(ScreenNode -> psn_Flags & PSNF_PRIVATE))
		{
				/* Yes, this is a public screen. */

			IsPublic = TRUE;

				/* Copy the name to refer to it. */

			strcpy(PubName,ScreenNode -> psn_Node . ln_Name);

			break;
		}
	}

		/* Unlock the public screen list again. */

	UnlockPubScreenList();

		/* Is the window still open? */

	if(Window)
	{
			/* Is the window residing on the front screen? */

		if(FirstScreen == Window -> WScreen)
		{
				/* Move the screen up if necessary. */

			if(FirstScreen -> TopEdge > 0)
				MoveScreen(FirstScreen,0,-FirstScreen -> TopEdge);

				/* Move the window to the front. */

			WindowToFront(Window);

				/* Wait a few ticks to allow Intuition to bring
				 * the screen to the front.
				 */

			Delay(5);

				/* Don't let the user change the window size for
				 * a moment.
				 */

			Forbid();

				/* If the window happens to be smaller than the
				 * original size we specified, it must be in
				 * `zoomed' state. If so, `unzoom' it.
				 */

			if(Window -> Width < WindowWidth)
			{
				Permit();

				ZipWindow(Window);
			}
			else
				Permit();

				/* Activate the window and return. */

			ActivateWindow(Window);

			return;
		}
	}

		/* If we have a public screen our window is to appear on,
		 * make our window jump to it. To do this, gain access to
		 * the public screen we got before.
		 */

	if(IsPublic)
	{
			/* Try to access the first screen by using its name. */

		NewDefaultScreen = LockPubScreen(PubName);
	}

		/* Now it's time to decide what to do. If the program
		 * is still in iconified state, get back to normal. If
		 * not, reopen the window.
		 */

	if(!IconTerminated)
	{
		IconTerminated = TRUE;

		if(NewDefaultScreen)
		{
				/* Unlock the previous public screen. */

			UnlockPubScreen(NULL,DefaultScreen);

			if(DrawInfo)
			{
				FreeScreenDrawInfo(DefaultScreen,DrawInfo);

				DrawInfo = NULL;
			}

				/* Take over the new identifier. */

			DefaultScreen = NewDefaultScreen;
		}
	}
	else
	{
		CloseGfx();

		DefaultScreen = NewDefaultScreen;

		if(OpenGfx())
			RefreshGfx();
	}
}

	/* HandleCxMsg(CxMsg *Message):
	 *
	 *	Handle a commodities toolkit message.
	 */

VOID __regargs
HandleCxMsg(CxMsg *Message)
{
	ULONG	MessageID	= CxMsgID(Message),
		MessageType	= CxMsgType(Message);

		/* Reply the message, having extracted ID and type first. */

	ReplyMsg((struct Message *)Message);

		/* Check for the message type... */

	switch(MessageType)
	{
			/* The hotkey was pressed, deal with the window. */

		case CXM_IEVENT:	SetupWindow();
					break;

			/* It's an internal commodities command. */

		case CXM_COMMAND:	switch(MessageID)
					{
							/* Disable the broker. */

						case CXCMD_DISABLE:	ActivateCxObj(Broker,FALSE);
									break;

							/* Enable the broker again. */

						case CXCMD_ENABLE:	ActivateCxObj(Broker,TRUE);
									break;

							/* Make the window appear. */

						case CXCMD_APPEAR:
						case CXCMD_UNIQUE:	if(!Window)
										IconTerminated = TRUE;
									else
										SetupWindow();

									break;

							/* Cause the window to appear. */

						case CXCMD_DISAPPEAR:	if(Window)
										DoIconify = TRUE;

									break;

							/* Terminate execution. */

						case CXCMD_KILL:	Terminated = TRUE;

									if(!Window)
										IconTerminated = TRUE;

									break;
					}

					break;
	}
}

	/* ListName(BPTR File,LONG Index):
	 *
	 *	Write the information associated with a name list entry to a file
	 *	using a `List' style format.
	 */

LONG __regargs
ListName(BPTR File,LONG Index)
{
	UBYTE SizeBuffer[20];

		/* Build date and time strings. */

	BuildDate(&NameList[Index] -> Date);

		/* Initialize the size/type buffer. */

	switch(NameList[Index] -> Type)
	{
		case TYPE_DIR:	strcpy(SizeBuffer,"Dir");
				break;

		case TYPE_LINK:	strcpy(SizeBuffer,"Link");
				break;

		default:	SPrintf(SizeBuffer,"%ld",NameList[Index] -> Size);
				break;
	}

		/* Output the formatted string. */

	return(FPrintf(File,MaxNameFormat,NameList[Index] -> Name,SizeBuffer,BuildBits(NameList[Index] -> Protection),Date,Time));
}

	/* SaveList(LONG Mode):
	 *
	 *	Save the current file name list to a disk file.
	 */

VOID __regargs
SaveList(LONG Mode)
{
		/* The three file requester hail texts. */

	STATIC UBYTE *HailText[3] =
	{
		"Save full path names",
		"Save names only",
		"Save in `List' style format"
	};

		/* Temporary storage. */

	UBYTE DummyBuffer[512],*Dummy;

		/* Block and byte counters. */

	ULONG NumBlocks = 0,NumBytes = 0;

		/* If no file name has been selected yet, initialize
		 * the directory name buffer with the name of the
		 * current directory.
		 */

Start:	if(!FullFileName[0])
	{
		if(!NameFromLock(FindProcess -> pr_CurrentDir,FullFileName,512))
			LastFileName[0] = FullFileName[0] = 0;
		else
			LastFileName[0] = 0;
	}

		/* Save the file name. */

	strcpy(DummyBuffer,FullFileName);

		/* Look for the path name part. */

	Dummy = PathPart(DummyBuffer);

		/* Cut off the file name. */

	*Dummy = 0;

		/* Open the file requester. */

	if(AslRequestTags(SaveFileRequest,
		ASL_Window,	Window,
		ASL_Dir,	DummyBuffer,
		ASL_File,	LastFileName,
		ASL_Hail,	HailText[Mode - MEN_FULL],
	TAG_DONE))
	{
			/* Did we get a file name to deal with? */

		if(SaveFileRequest -> rf_File[0])
		{
				/* Save the directory name. */

			strcpy(FullFileName,SaveFileRequest -> rf_Dir);

				/* Add the file name part. */

			if(AddPart(FullFileName,SaveFileRequest -> rf_File,512))
			{
				BPTR	File	= NULL,
					FileLock;
				LONG	Error	= 0,
					Written	= 1,
					i;

					/* Save the file name separately. */

				strcpy(LastFileName,SaveFileRequest -> rf_File);

					/* Try to get lock on the file name in
					 * question and try to examine it.
					 */

				if(FileLock = Lock(FullFileName,ACCESS_READ))
				{
					struct FileInfoBlock __aligned FileInfo;

						/* Examine the file in question. */

					if(Examine(FileLock,&FileInfo))
					{
							/* Does the name refer to a directory instead of a file? */

						if(FileInfo . fib_DirEntryType > 0)
						{
							ShowRequest("Destination \"%s\" is directory, not a file.","Continue",LastFileName);

							UnLock(FileLock);

							goto Start;
						}
						else
						{
								/* Can we delete (overwrite) it? */

							if(FileInfo . fib_Protection & FIBF_DELETE)
							{
								ShowRequest("File \"%s\" is protected from deletion and\ncannot be overridden.","Continue",LastFileName);

								UnLock(FileLock);

								goto Start;
							}
							else
							{
									/* Can we write any data to it? */

								if(FileInfo . fib_Protection & FIBF_WRITE)
								{
									ShowRequest("File \"%s\" is not write-enabled and\ncannot be overridden.","Continue",LastFileName);

									UnLock(FileLock);

									goto Start;
								}
							}
						}

							/* Is there already data in the file which
							 * we may overwrite?
							 */

						if(FileInfo . fib_Size > 0)
						{
								/* Ask the user what to do with the file. */

							switch(ShowRequest("Destination file \"%s\" already exists,\ndo you still want to save the file list?","Override file|Append file|Abort",LastFileName))
							{
									/* Overwrite the file. */

								case 1:	break;

									/* Append data to the file;
									 * release the filelock first.
									 */

								case 2:	UnLock(FileLock);

									FileLock = NULL;

										/* Open the file for mixed read and write access. */

									if(!(File = Open(FullFileName,MODE_READWRITE)))
										goto OpenError;
									else
									{
											/* Seek to the end of the file.*/

										if(Seek(File,0,OFFSET_END) == -1)
										{
												/* Query error condition. */

											Error = IoErr();

												/* Close the file. */

											Close(File);

												/* Clear the ID to avoid confusion. */

											File = NULL;
										}
									}

									break;

									/* Abort the process. */

								case 0:	UnLock(FileLock);

									return;
							}
						}
					}
					else
						Error = IoErr();

						/* Unlock the filelock again. */

					if(FileLock)
						UnLock(FileLock);
				}
				else
				{
					Error = IoErr();

						/* If unable to find the object, don't
						 * treat it as an error.
						 */

					if(Error == ERROR_OBJECT_NOT_FOUND)
						Error = 0;
				}

					/* Display an error message. */

				if(Error)
				{
						/* Get the AmigaDOS error message. */

					if(Fault(Error,"",DummyBuffer,512))
					{
						ShowRequest("An error occured while accessing file \"%s\":%s","Continue",LastFileName,DummyBuffer);

						goto Start;
					}
					else
					{
						DisplayBeep(Window -> WScreen);

						return;
					}
				}

					/* If not already open, create a new file. */

				if(!File)
				{
					if(!(File = Open(FullFileName,MODE_NEWFILE)))
					{
							/* Query error condition. */

OpenError:					Error = IoErr();

							/* Get the AmigaDOS error message. */

						if(Fault(Error,"",DummyBuffer,512))
						{
							ShowRequest("An error occured while opening file \"%s\":%s","Continue",LastFileName,DummyBuffer);

							goto Start;
						}
						else
						{
							DisplayBeep(Window -> WScreen);

							return;
						}
					}
				}

					/* In which mode are we to write the name list? */

				switch(Mode)
				{
						/* Write full names only. */

					case MEN_FULL:	for(i = 0 ; i < NumNames ; i++)
							{
									/* Write the full file name. */

								if(FPrintf(File,"%s\n",NameList[i] -> Name) < 1)
								{
									Error = IoErr();

									if(Fault(Error,"",DummyBuffer,512))
									{
										if(!ShowRequest("An error occured while writing to file \"%s\":%s","Continue|Abort",LastFileName,DummyBuffer))
											break;
									}
									else
									{
										DisplayBeep(Window -> WScreen);

										break;
									}
								}
							}

							break;

						/* Write the base file names only. */

					case MEN_FILE:	for(i = 0 ; i < NumNames ; i++)
							{
									/* Write the base file name. */

								if(FPrintf(File,"%s\n",NameList[i] -> NamePart) < 1)
								{
									Error = IoErr();

									if(Fault(Error,"",DummyBuffer,512))
									{
										if(!ShowRequest("An error occured while writing to file \"%s\":%s","Continue|Abort",LastFileName,DummyBuffer))
											break;
									}
									else
									{
										DisplayBeep(Window -> WScreen);

										break;
									}
								}
							}

							break;

						/* Write it in `List' style format, set up the
						 * formatting string.
						 */

					case MEN_LIST:	SPrintf(MaxNameFormat,"%%-%ld%s %%8s %%-8s %%-8s %%-8s\n",(MaxNameLen < 25) ? 25 : MaxNameLen + 1,"s");

								/* Run down the list of names. */

							for(i = 0 ; i < NumNames ; i++)
							{
									/* Add the number of blocks and bytes for
									 * the current file.
									 */

								NumBytes	+= NameList[i] -> Size;
								NumBlocks	+= NameList[i] -> Blocks;

									/* Write the data. */

								if((Written = ListName(File,i)) < 1)
								{
									Error = IoErr();

									if(Fault(Error,"",DummyBuffer,512))
									{
										if(!ShowRequest("An error occured while writing to file \"%s\":%s","Continue|Abort",LastFileName,DummyBuffer))
											break;
									}
									else
									{
										DisplayBeep(Window -> WScreen);

										break;
									}
								}
							}

								/* Add the number of files if possible. */

							if(NumFiles && Written > 0)
								Written = FPrintf(File,"%ld %s - ",NumFiles,(NumFiles > 1) ? "files" : "file");

								/* Add the number of directories if possible. */

							if(NumDirs && Written > 0)
								Written = FPrintf(File,"%ld %s - ",NumDirs,(NumDirs > 1) ? "directories" : "directory");

								/* Add the number of links if possible. */

							if(NumLinks && Written > 0)
								Written = FPrintf(File,"%ld %s - ",NumLinks,(NumLinks > 1) ? "links" : "link");

								/* Add the number of blocks and bytes. */

							if(Written > 0)
								FPrintf(File,"%ld blocks - %ld bytes\n",NumBlocks,NumBytes);

							break;
				}

					/* Close the file we opened. */

				Close(File);

					/* Take a look at the file we dealt with. */

				if(FileLock = Lock(FullFileName,ACCESS_READ))
				{
					struct FileInfoBlock __aligned FileInfo;

						/* Examine the file... */

					if(Examine(FileLock,&FileInfo))
					{
							/* Did it remain empty? */

						if(!FileInfo . fib_Size)
						{
								/* Ask the user what to do with it. */

							if(ShowRequest("Do you wish the incomplete file \"%s\"\nto be removed?","Yes|No",LastFileName))
							{
									/* Free the filelock referring to it. */

								UnLock(FileLock);

								FileLock = NULL;

									/* Delete the file. */

								DeleteFile(FullFileName);
							}
						}
					}

						/* Free the filelock if still available. */

					if(FileLock)
						UnLock(FileLock);
				}
			}
		}
	}
}

	/* FindServer():
	 *
	 *	The `find' main program, implemented as a subprocess to allow us to
	 *	specify the stack size to use.
	 */

VOID __saveds
FindServer()
{
	struct IntuiMessage	*Massage;
	ULONG			 Class,Code,Qualifier,Mask;
	WORD			 MouseX,MouseY;
	struct Gadget		*Gadget;

		/* Reset flags. */

	DoIconify	= FALSE;
	Terminated	= FALSE;

		/* Set up the commodities.library interface. */

	SetupCx();

		/* Look what to do next, are we to start up in iconified state
		 * or are we to open the control panel?
		 */

	if(SelectWhich == GAD_NONE_ICONIFY)
		Iconify(TRUE);
	else
	{
			/* Open the control panel. */

		if(OpenGfx())
		{
				/* Are we to activate any string gadget? */

			if(SelectWhich != GAD_NONE)
				ActivateGadget(LastActiveGadget = GadgetArray[SelectWhich],Window,NULL);
		}
	}

		/* Process the messages. */

	while(Window && !Terminated)
	{
			/* Wait for a couple of messages. */

		Mask = Wait(SIG_WORKBENCH | SIG_WINDOW | SIG_CX | SIG_KILL);

			/* Did we get a Workbench message? */

		if(Mask & SIG_WORKBENCH)
		{
			struct AppMessage	*AppMessage;
			BYTE			 GotName = FALSE;

				/* Process all application messages. */

			while(AppMessage = (struct AppMessage *)GetMsg(WorkbenchPort))
			{
					/* Did we get a new name already? */

				if(!GotName)
				{
					LONG i;

						/* Run down the list of arguments. */

					for(i = 0 ; i < AppMessage -> am_NumArgs ; i++)
					{
							/* Does the argument sport a filelock? */

						if(AppMessage -> am_ArgList[i] . wa_Lock)
						{
								/* Try to turn the filelock into a name. */

							if(NameFromLock(AppMessage -> am_ArgList[i] . wa_Lock,AreaName,256))
							{
								struct FileInfoBlock __aligned FileInfo;

									/* Take a look at the object referenced
									 * by the filelock.
									 */

								if(Examine(AppMessage -> am_ArgList[i] . wa_Lock,&FileInfo))
								{
										/* Does the filelock refer to
										 * a file instead of a directory?
										 */

									if(FileInfo . fib_DirEntryType < 0)
									{
										UBYTE *Stop;

											/* Look for the path name part. */

										Stop = PathPart(AreaName);

											/* Cut off the file name. */

										*Stop = 0;

											/* Now we've got a name to work with. */

										GotName = TRUE;

										break;
									}
									else
										GotName = TRUE;
								}
								else
									AreaName[0] = 0;
							}
							else
								AreaName[0] = 0;
						}
					}
				}

					/* Reply the application message. */

				ReplyMsg(AppMessage);
			}

				/* Did we get a name to work with? */

			if(GotName)
			{
					/* Set the area name string accordingly. */

				GT_SetGadgetAttrs(GadgetArray[GAD_SEARCHAREA],Window,NULL,
					GTST_String,	AreaName,
				TAG_DONE);

					/* The user will probably want to change
					 * the name to search for as he already
					 * has given a new area to search by
					 * dropping an icon on the window.
					 */

				LastActiveGadget = GadgetArray[GAD_SEARCHFOR];

					/* If the pattern gadget still contains
					 * no text, activate the window which
					 * will also activate the pattern gadget.
					 */

				if(!(GT_STRING(GadgetArray[GAD_SEARCHFOR]))[0])
					ActivateWindow(Window);
			}
		}

			/* Did we get a window message? */

		if(Mask & SIG_WINDOW)
		{
				/* Process all messages. */

			while(Window && !Terminated && (Massage = (struct IntuiMessage *)GT_GetIMsg(Window -> UserPort)))
			{
					/* Extract the data we will need. */

				Class		= Massage -> Class;
				Code		= Massage -> Code;
				Qualifier	= Massage -> Qualifier;

				Gadget		= (struct Gadget *)Massage -> IAddress;
				MouseX		= Massage -> MouseX;
				MouseY		= Massage -> MouseY;

					/* Reply the message. */

				GT_ReplyIMsg(Massage);

					/* Refresh the window contents. */

				if(Class == IDCMP_REFRESHWINDOW)
					RefreshGfx();

					/* Activate a string gadget. */

				if(Class == IDCMP_ACTIVEWINDOW && LastActiveGadget)
					ActivateGadget(LastActiveGadget,Window,NULL);

					/* Process a cursor keypress. */

				if(Class == IDCMP_RAWKEY)
				{
					switch(Code)
					{
						case CURSORUP:	if(Qualifier & (IEQUALIFIER_LALT|IEQUALIFIER_RALT))
								{
									if(NumNames > 0 && LastSelected == -1)
										LastSelected = NumNames;

									if(LastSelected > 0)
									{
										BYTE Selected;

										if(LastSelected < NumNames)
										{
											Selected = NameList[LastSelected] -> Selected;

											NameList[LastSelected] -> Selected = FALSE;
										}
										else
											Selected = TRUE;

										if(Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
										{
											if(LastSelected > 5)
												LastSelected -= 5;
											else
												LastSelected = 0;
										}
										else
										{
											if(Qualifier & IEQUALIFIER_CONTROL)
												LastSelected = 0;
											else
												LastSelected--;
										}

										if(LastSelected < ListTop)
											ListTop = LastSelected;

										if(ListTop + 5 <= LastSelected)
											ListTop = LastSelected - 4;

										GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
											GTSC_Top,	ListTop,
										TAG_DONE);

										NameList[LastSelected] -> Selected = Selected;

										RefreshList();

										RefreshFile();
									}
								}
								else
								{
									if(ListTop)
									{
										if(Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
										{
											if(ListTop > 5)
												ListTop -= 5;
											else
												ListTop = 0;
										}
										else
										{
											if(Qualifier & IEQUALIFIER_CONTROL)
												ListTop = 0;
											else
												ListTop--;
										}

										GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
											GTSC_Top,	ListTop,
										TAG_DONE);

										RefreshList();
									}
								}

								break;

						case CURSORDOWN:if(Qualifier & (IEQUALIFIER_LALT|IEQUALIFIER_RALT))
								{
									if(NumNames > 0 && LastSelected == -1)
									{
										LastSelected = 0;

										if(LastSelected < ListTop)
											ListTop = LastSelected;

										if(ListTop + 5 <= LastSelected)
											ListTop = LastSelected - 4;

										GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
											GTSC_Top,	ListTop,
										TAG_DONE);

										NameList[LastSelected] -> Selected = TRUE;

										RefreshList();

										RefreshFile();
									}
									else
									{
										if(LastSelected != -1 && LastSelected < NumNames - 1)
										{
											BYTE Selected = NameList[LastSelected] -> Selected;

											NameList[LastSelected] -> Selected = FALSE;

											if(Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
											{
												if(LastSelected + 5 < NumNames - 5)
													LastSelected += 5;
												else
												{
													if(NumNames >= 5)
														LastSelected = NumNames - 5;
													else
													{
														if(NumNames)
															LastSelected = NumNames - 1;
														else
															LastSelected = NumNames;
													}
												}
											}
											else
											{
												if(Qualifier & IEQUALIFIER_CONTROL)
												{
													if(NumNames >= 5)
														LastSelected = NumNames - 5;
													else
													{
														if(NumNames)
															LastSelected = NumNames - 1;
														else
															LastSelected = NumNames;
													}
												}
												else
													LastSelected++;
											}

											if(LastSelected < ListTop)
												ListTop = LastSelected;

											if(ListTop + 5 <= LastSelected)
												ListTop = LastSelected - 4;

											GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
												GTSC_Top,	ListTop,
											TAG_DONE);

											NameList[LastSelected] -> Selected = Selected;

											RefreshList();

											RefreshFile();
										}
									}
								}
								else
								{
									if(ListTop < NumNames - 5)
									{
										if(Qualifier & (IEQUALIFIER_LSHIFT|IEQUALIFIER_RSHIFT))
										{
											if(ListTop + 5 < NumNames - 5)
												ListTop += 5;
											else
												ListTop = NumNames - 5;
										}
										else
										{
											if(Qualifier & IEQUALIFIER_CONTROL)
												ListTop = NumNames - 5;
											else
												ListTop++;
										}

										GT_SetGadgetAttrs(GadgetArray[GAD_SCROLLER],Window,NULL,
											GTSC_Top,	ListTop,
										TAG_DONE);

										RefreshList();
									}
								}

								break;

						default:	break;
					}
				}

					/* Did the user move the scroller at the
					 * right hand side of the main display area?
					 */

				if(Class == IDCMP_MOUSEMOVE)
				{
					switch(Gadget -> GadgetID)
					{
						case GAD_SCROLLER:	ListTop = Code;
									RefreshList();
									break;

						default:		break;
					}
				}

					/* Did the user press a gadget? */

				if(Class == IDCMP_GADGETUP)
				{
					switch(Gadget -> GadgetID)
					{
							/* The return or tab key was pressed
							 * while the cursor was inside the
							 * `Search Area' gadget.
							 */

						case GAD_SEARCHAREA:	ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHFOR],Window,NULL);
									break;

							/* If the tab key was pressed while the
							 * cursor was inside the `Search For'
							 * gadget, the message code will reflect
							 * it. In any other case, the return
							 * key must have been pressed.
							 */

						case GAD_SEARCHFOR:	if(Code == '\t')
										LastActiveGadget = GadgetArray[GAD_SEARCHAREA];
									else
										LastActiveGadget = NULL;

									break;

							/* The user clicked the scroller
							 * at the right hand side of
							 * the main display area.
							 */

						case GAD_SCROLLER:	ListTop = Code;
									RefreshList();
									break;

							/* The user started the search
							 * process.
							 */

						case GAD_START:		FindFile();
									break;

						default:		break;
					}
				}

					/* The user pressed a mouse button. */

				if(Class == IDCMP_MOUSEBUTTONS)
				{
					if(Code == SELECTDOWN)
					{
						LastActiveGadget = NULL;

							/* Was the mouse within the `natural'
							 * borders of the main display area?
							 */

						if(MouseX >= 14 && MouseX <= ListRightColumn && MouseY > ListTopLine + 1 && MouseY < ListTopLine + 2 + 10 * SystemFontHeight)
						{
							WORD Line = (MouseY - ListTopLine - 2) / (SystemFontHeight * 2);

								/* Does the line selected
								 * cover the existing list
								 * entries?
								 */

							if(ListTop + Line < NumNames)
							{
									/* Clear previous selection. */

								if(LastSelected != -1)
									NameList[LastSelected] -> Selected = FALSE;

									/* Make new selection. */

								LastSelected = ListTop + Line;

									/* Set the selection flag. */

								NameList[LastSelected] -> Selected = TRUE;

									/* Redraw the name list. */

								RefreshList();

									/* Show the file information. */

								RefreshFile();
							}
						}
					}
				}

					/* Are we to close the window? */

				if(Class == IDCMP_CLOSEWINDOW)
				{
						/* If we had just returned from
						 * iconified state, return to it.
						 */

					if(WasIconified)
						DoIconify = TRUE;
					else
						Terminated = TRUE;

					break;
				}

					/* The user pressed a non-cursor key. */

				if(Class == IDCMP_VANILLAKEY)
				{
						/* Convert the key code to upper case. */

					switch(ToUpper((UBYTE)Code))
					{
							/* Activate the `Search Area' string gadget. */

						case 'A':	ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHAREA],Window,NULL);
								break;

							/* Activate the `Search For' string gadget. */

						case 'F':	ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHFOR],Window,NULL);
								break;

							/* Toggle the state of the `Wildcards' button. */

						case 'W':	GT_SetGadgetAttrs(GadgetArray[GAD_WILDCARDS],Window,NULL,
									GTCB_Checked,	!GT_CHECKED(GadgetArray[GAD_WILDCARDS]),
								TAG_DONE);

								break;

							/* Start the search process. */

						case 'S':
						case '\r':	FindFile();
								break;

							/* Terminate the program or
							 * return to iconified state,
							 * similar to the IDCMP_CLOSEWINDOW
							 * message.
							 */

						case 'Q':
						case '\33':	if(WasIconified)
									DoIconify = TRUE;
								else
									Terminated = TRUE;

								break;

						default:	break;
					}
				}

					/* A menu item has been selected. */

				if(Class == IDCMP_MENUPICK)
				{
					struct MenuItem *Item;

						/* Process all menu items selected. */

					while(Code != MENUNULL)
					{
							/* Query the menu item selected. */

						if(Item = ItemAddress(FindMenu,Code))
						{
								/* Take a look at the menu item selected. */

							switch((LONG)GTMENUITEM_USERDATA(Item))
							{
									/* The file name list is to be saved. */

								case MEN_FULL:
								case MEN_FILE:
								case MEN_LIST:		DisableWindow(TRUE);

											SaveList((LONG)GTMENUITEM_USERDATA(Item));

											EnableWindow();

											break;

									/* Show the `About...' requester. */

								case MEN_ABOUT:		DisableWindow(TRUE);

											ShowRequest("    Find Files v1.7\nWritten by Olaf Barthel\n   © 1991-92 by MXM\n  All Rights Reserved","Continue");

											EnableWindow();

											break;

									/* Terminate the program. */

								case MEN_QUIT:		Terminated = TRUE;
											break;

									/* Copy the full path name of the currently
									 * selected file to the clipboard.
									 */

								case MEN_COPY:		if(LastSelected != -1 && LastSelected < NumNames)
											{
												if(NameList[LastSelected] -> Selected)
													SaveClip(NameList[LastSelected] -> Name,strlen(NameList[LastSelected] -> Name));
											}

											break;

									/* Use a file requester to select the search area. */

								case MEN_SELECTAREA:	DisableWindow(TRUE);

												/* Save the current contents of the `Search Area' gadget. */

											strcpy(AreaName,GT_STRING(GadgetArray[GAD_SEARCHAREA]));

												/* Select a new directory... */

											if(SelectSearchArea())
											{
													/* Set the new name. */

												GT_SetGadgetAttrs(GadgetArray[GAD_SEARCHAREA],Window,NULL,
													GTST_String,	AreaName,
												TAG_DONE);

													/* The user may want to change the search pattern. */

												ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHFOR],Window,NULL);
											}
											else
											{
													/* Determine which string gadget to activate. */

												if(!(GT_STRING(GadgetArray[GAD_SEARCHAREA]))[0])
													ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHAREA],Window,NULL);
												else
												{
													if(!(GT_STRING(GadgetArray[GAD_SEARCHFOR]))[0])
														ActivateGadget(LastActiveGadget = GadgetArray[GAD_SEARCHFOR],Window,NULL);
												}
											}

											EnableWindow();

											break;

									/* Iconify the program. */

								case MEN_ICONIFY:	DoIconify = TRUE;
											break;

								default:		break;
							}

								/* Proceed to the next menu item selected. */

							Code = Item -> NextSelect;
						}
						else
							break;
					}

						/* If a string gadget had been activated
						 * before the menu selection was processed,
						 * reactivate it.
						 */

					if(LastActiveGadget)
						ActivateGadget(LastActiveGadget,Window,NULL);
				}
			}

				/* Are we to iconify the window? */

			if(DoIconify)
			{
				Iconify(TRUE);

				DoIconify = FALSE;
			}
		}

			/* Did we get a commodities toolkit message? */

		if(Mask & SIG_CX)
		{
			CxMsg *Message;

				/* Process all messages. */

			while(Message = (CxMsg *)GetMsg(CxPort))
				HandleCxMsg(Message);

				/* Are we to iconify the window? */

			if(DoIconify)
			{
					/* Try to put an icon on the Workbench main window. */

				if(!Iconify(FALSE))
				{
						/* It didn't seem to work,
						 * try to shrink the window
						 * then.
						 */

					Forbid();

					if(Window -> Width == WindowWidth)
					{
						Permit();

						ZipWindow(Window);
					}
					else
						Permit();
				}

				DoIconify = FALSE;
			}
		}

			/* Did we receive a termination signal? */

		if(Mask & SIG_KILL)
			Terminated = TRUE;
	}

		/* Close the control panel. */

	CloseGfx();

		/* Shut down the commodities toolkit interface. */

	ShutdownCx();

		/* Lock the system... */

	Forbid();

		/* ...and wave goodbye. */

	Signal(MainProcess,SIG_REPLY);
}

	/* main(int argc,char **argv):
	 *
	 *	The classical main program.
	 */

VOID __stdargs
main(int argc,char **argv)
{
		/* Are we running under control of Kickstart 2.0 or higher? */

	if(SysBase -> LibNode . lib_Version < 37)
		exit(20);

		/* Look who we are. */

	MainProcess = (struct Process *)SysBase -> ThisTask;

		/* Were we started from Shell? */

	if(argc)
	{
		UBYTE **ArgArray;

			/* Allocate space for six arguments. */

		if(ArgArray = (UBYTE **)AllocVec(sizeof(UBYTE *) * 6,MEMF_CLEAR))
		{
			struct RDArgs *ArgsPtr;

				/* Read the arguments... */

			if(ArgsPtr = ReadArgs("Area,Pattern,N=NoWildcards/S,K=CX_POPKEY/K,P=CX_PRIORITY/K/N,U=CX_POPUP/K",(LONG *)ArgArray,NULL))
			{
					/* Did the user specify a search area to be used? */

				if(ArgArray[ARG_AREA])
				{
						/* Is the path name valid? */

					if(ArgArray[ARG_AREA][0])
					{
						strcpy(AreaName,ArgArray[ARG_AREA]);

						SelectWhich = GAD_SEARCHFOR;
					}
					else
					{
							/* Take the current directory name instead. */

						if(NameFromLock(MainProcess -> pr_CurrentDir,AreaName,256))
							SelectWhich = GAD_SEARCHFOR;
						else
							AreaName[0] = 0;
					}
				}

					/* Did the user specify a pattern to be used? */

				if(ArgArray[ARG_PATTERN])
				{
						/* Copy the pattern. */

					strcpy(SearchString,ArgArray[ARG_PATTERN]);

						/* Are we to select a string gadget
						 * or do we already have enough
						 * data to work with?
						 */

					if(ArgArray[ARG_AREA] && AreaName[0])
						SelectWhich = GAD_NONE;
				}

					/* Are wildcard patterns to be used? */

				if(ArgArray[ARG_NOWILDCARDS])
					UseWildcards = FALSE;

					/* Did the user specify a commodities
					 * toolkit hotkey?
					 */

				if(ArgArray[ARG_POPKEY])
					strcpy(HotkeyBuffer,ArgArray[ARG_POPKEY]);

					/* Did the user specify a commodities toolkit
					 * broker priority?
					 */

				if(ArgArray[ARG_PRI])
					CxPriority = *((LONG *)ArgArray[ARG_PRI]);

					/* Did the user want the program to come
					 * up in iconified state.
					 */

				if(ArgArray[ARG_POPUP])
				{
					if(!Stricmp(ArgArray[ARG_POPUP],"no"))
						SelectWhich = GAD_NONE_ICONIFY;
				}

					/* Free the argument data. */

				FreeArgs(ArgsPtr);
			}
			else
			{
				LONG Error = IoErr();

					/* Complain about the error. */

				PrintFault(Error,"Find");

					/* Free the argument array. */

				FreeVec(ArgArray);

					/* Remember the error code. */

				SetIoErr(Error);

					/* Terminate with failure code. */

				exit(RETURN_FAIL);
			}

				/* Free the argument array. */

			FreeVec(ArgArray);
		}

			/* Get the program running. */

		OpenAll();
	}
	else
	{
		struct DiskObject *Icon;

			/* Allocate all resources we need. */

		OpenAll();

			/* Parse the tooltypes for use with the commodities
			 * toolkit interface.
			 */

		if(CxBase)
		{
			ToolTypes = ArgArrayInit(argc,argv);

			if(!Stricmp(ArgString(ToolTypes,"CX_POPUP","yes"),"no"))
				SelectWhich = GAD_NONE_ICONIFY;
		}

			/* Did we get any arguments passed in? */

		if(WBenchMsg -> sm_NumArgs > 1)
		{
			LONG i;

				/* Run down the list of arguments. */

			for(i = 1 ; i < WBenchMsg -> sm_NumArgs ; i++)
			{
					/* Does the argument have a filelock attached? */

				if(WBenchMsg -> sm_ArgList[i] . wa_Lock)
				{
						/* Turn the filelock into a file name. */

					if(NameFromLock(WBenchMsg -> sm_ArgList[i] . wa_Lock,AreaName,256))
					{
						struct FileInfoBlock __aligned FileInfo;

							/* Examine object referenced by
							 * the filelock...
							 */

						if(Examine(WBenchMsg -> sm_ArgList[i] . wa_Lock,&FileInfo))
						{
								/* Does the name refer
								 * to a file instead of
								 * a directory?
								 */

							if(FileInfo . fib_DirEntryType < 0)
							{
								UBYTE *Stop;

									/* Determine the path part. */

								Stop = PathPart(AreaName);

									/* Cut off the file name. */

								*Stop = 0;
							}

								/* Determine the gadget to activate
								 * when opening the control panel.
								 */

							if(SelectWhich != GAD_NONE_ICONIFY)
								SelectWhich = GAD_SEARCHFOR;

							break;
						}
					}
				}
			}
		}

			/* Try to read the icon for our program. */

		if(Icon = GetDiskObject(WBenchMsg -> sm_ArgList[0] . wa_Name))
		{
				/* If the `ICONIFY' tooltype is present,
				 * come up in iconified state.
				 */

			if(FindToolType(Icon -> do_ToolTypes,"ICONIFY"))
				SelectWhich = GAD_NONE_ICONIFY;

				/* Release the icon data. */

			FreeDiskObject(Icon);
		}
	}

		/* Create the actual find process. */

	if(FindProcess = CreateNewProcTags(
		NP_Entry,	FindServer,
		NP_StackSize,	20000,
		NP_Name,	"Find Server",
	TAG_DONE))
	{
		FOREVER
		{
				/* Wait for termination or reply signal. */

			ULONG Mask = Wait(SIG_REPLY | SIG_KILL);

				/* Abort if the reply signal comes in and
				 * flag the find process to terminate if
				 * the termination signal arrives.
				 */

			if(Mask & SIG_REPLY)
				break;
			else
				Signal(FindProcess,SIG_KILL);
		}
	}

		/* That's all folks. */

	CloseAll(RETURN_OK);
}
