/***************************************************************************/
/*				   FindFile				   */
/*				 Version 1.01				   */
/*			       August 30, 1992				   */
/*									   */
/* This is a file-find utility with a 2.0-style GadTools interface.  It    */
/* doesn't support the CLI, although it can be run from the CLI;  it is    */
/* meant as a Workbench utility.					   */
/*									   */
/* This program is Copyright ©1992 by Dave Schreiber, All Rights Reserved. */
/* This program may not be sold for more than a small copying and shipping */
/* and handling fee, except by written permission of Dave Schreiber.	   */
/*									   */
/* To compile, type:							   */
/*    lmk								   */
/*									   */
/* Version List:							   */
/*    1.01 - Changed the default wildcard from '*' to '#?' (since '*'      */
/*	     needs to be explicitly turned on under Workbench 2.04 and not */
/*	     everyone has it turned on).				   */
/*	     August 30, 1992						   */
/*    1.00 - First release (August 27, 1992)                               */
/*									   */
/***************************************************************************/

/*System headers*/

#include <exec/types.h>
#include <exec/exec.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <utility/tagitem.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>

#include <libraries/gadtools.h>
#include <libraries/asl.h>
#include <libraries/iffparse.h>
#include <workbench/workbench.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/gadtools.h>
#include <proto/asl.h>
#include <proto/iffparse.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/wb.h>

/*Program-specific headers*/

#include "FF.h"
#include "GUI.h"
#include "Lists.h"

/*SAS/C V5.10 doesn't have these (2.04) function calls in it's copy of*/
/*amiga.lib, so these are necessary*/

#pragma libcall DOSBase ParsePatternNoCase 3C6 32103
#pragma libcall DOSBase MatchPatternNoCase 3CC 2102


/*All the libraries we need*/
struct Library *IntuitionBase,*GfxBase,*GadToolsBase,*AslBase,
	       *WorkbenchBase,*IFFParseBase;

/*Flags for user settings*/
BOOL Links=FALSE;	   /*Recur into linked directories?*/
BOOL Recur=TRUE;	   /*Recur at all?*/
BOOL Wildcards=TRUE;	   /*Treat the 'Search For:' field as having wildcards?*/

/*The linked list of files*/
struct List *fileList=NULL;

/*The linked list of a file's path components*/
struct List *dirList=NULL;

/*The message port for the AppWindow*/
struct MsgPort *appMsgPort=NULL;

/*This holds the AppWindow data*/
struct AppWindow *appWdw=NULL;

/*The ordinal number (in the filelist) of the current filename*/
ULONG currentFilename=~0;

/*This contains the four pointer images that are animated during a find*/
/*They are not in proper "pointer" format, and must be interleaved and put*/
/*into pointerData, below.  This is done in setupPointerBuffers()*/

USHORT PointerImageData[4][26] =
{
   {
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xC000,0x01C0,0x1FC0,
	0xFC00,0xC000,0x01C0,0x1FC0,0xFC00,0xC000,0x01C0,0x1FC0,
	0xFC00,0xC000
   },
   {
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0x01C0,0x1FC0,0xFC00,
	0xC000,0x01C0,0x1FC0,0xFC00,0xC000,0x01C0,0x1FC0,0xFC00,
	0xC000,0x01C0
   },
   {
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0x1FC0,0xFC00,0xC000,
	0x01C0,0x1FC0,0xFC00,0xC000,0x01C0,0x1FC0,0xFC00,0xC000,
	0x01C0,0x1FC0
   },
   {
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,
	0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFFC0,0xFC00,0xC000,0x01C0,
	0x1FC0,0xFC00,0xC000,0x01C0,0x1FC0,0xFC00,0xC000,0x01C0,
	0x1FC0,0xFC00
   }
};

USHORT chip pointerData[4][30];

/*This keeps track of what 'frame' the pointer 'animation' is at*/
UBYTE pointerCount=0;

/*The version string.  Do a 'version FF' to display it*/
char *version="$VER: FindFile V1.01 (30.8.92)";

ULONG numFiles;

/*****************************************************************************/
/*	     The first second contains the kernel of file-finding code	     */
/*****************************************************************************/

void _main(void)
{
   char Pattern[256];
   char Directory[256];
   FindStat result=FIND_CONTINUE;

   /*Copy and interleave the pointer image data*/
   setupPointerBuffers();

   /*Open all the libraries that we need*/
   IntuitionBase=(struct Library *)OpenLibrary("intuition.library",37L);
   GfxBase=(struct Library *)OpenLibrary("graphics.library",37L);
   AslBase=(struct Library *)OpenLibrary("asl.library",37L);
   GadToolsBase=(struct Library *)OpenLibrary("gadtools.library",37L);
   WorkbenchBase=(struct Library *)OpenLibrary("workbench.library",37L);
   IFFParseBase=(struct Library *)OpenLibrary("iffparse.library",37L);

   /*If any of them didn't open, abort*/
   if(IntuitionBase==NULL || GfxBase==NULL || AslBase==NULL ||
	 GadToolsBase==NULL || IFFParseBase==NULL)
      cleanup(100);

   /*Get the visual info, etc.*/
   if(SetupScreen()==0)
   {
      /*Open the window*/
      if(OpenProject0Window()==0)
      {
	 /*Create the AppWindow message port and, if successful, make*/
	 /*the window into an AppWindow*/
	 if((appMsgPort=CreateMsgPort())!=NULL)
	    appWdw=(struct AppWindow *)AddAppWindowA(1,0,Project0Wnd,
						     appMsgPort,NULL);

	 /*Loop until the user tells us to quit*/
	 /*getUserSelections() either returns FALSE, in which case we*/
	 /*quit, or TRUE, in which case we search*/
	 while((result!=FIND_CLOSE) && getUserSelections())
	 {
	    /*Get the 'Search for' string*/
	    strcpy(Pattern,((struct StringInfo *)
		  (Project0Gadgets[GD_Pattern]->SpecialInfo))->Buffer);

	    /*If there is no pattern or search string, print an error*/
	    if(Pattern[0]==0)
	       printError("Please enter a search pattern or name",NULL,NULL);
	    else  /*Otherwise, do the search*/
	    {
	       /*There is no current filename*/
	       currentFilename=~0;

	       /*Create the 'busy' pointer*/
	       setBusyPointer();

	       /*We don't want to be bothered with AppWindow messages while*/
	       /*searching*/
	       if(appWdw!=NULL)
		  RemoveAppWindow(appWdw);

	       /*Delete an old file list, if one exists, and initialize a*/
	       /*fresh list*/
	       initFileList();

	       /*Disable all the gadgets that can be disabled, but enable */
	       /*the 'Stop' gadget*/
	       disableWindowGadgets();

	       /*Get the 'Search for' string*/
	       strcpy(Pattern,((struct StringInfo *)
		     (Project0Gadgets[GD_Pattern]->SpecialInfo))->Buffer);

	       /*Get the 'Directory' string*/
	       strcpy(Directory,((struct StringInfo *)
		     (Project0Gadgets[GD_Dir]->SpecialInfo))->Buffer);

	       numFiles=0;
	       /*And do the search*/
	       result=findFiles(Directory,Pattern,Recur);

	       /*The search is finished at this point, so enable all window*/
	       /*gadgets except for the Stop gadget, which is disabled*/
	       enableWindowGadgets();

	       /*Restore the old pointer*/
	       restorePointer();

	       /*Reset the AppWindow, if it existed in the first place*/
	       if(appMsgPort!=NULL)
		  appWdw=(struct AppWindow *)AddAppWindowA(1,0,Project0Wnd,
							   appMsgPort,NULL);
	    }
	    /*And loop back for another round...*/
	 }

	 /*The user wants to quit, so delete the AppWindow if it was created*/
	 if(appWdw!=NULL)
	 {
	    RemoveAppWindow(appWdw);
	    DeleteMsgPort(appMsgPort);
	 }

	 /*Delete the file and directory lists, if any exist*/
	 deleteFileList();
	 deleteDirList();

	 /*Close the window*/
	 CloseProject0Window();
      }

      /*Delete the visual info, etc.*/
      CloseDownScreen();

      /*Close libraries and exit with no errors*/
      cleanup(0);
   }
}

/*This is the interface between the main part of the program and the*/
/*recursive function that actually finds files.*/
FindStat findFiles(char *startingDir,char *pattern,BOOL recur)
{
   char patResult[516];
   BPTR fileLock;
   UWORD c;
   FindStat result=FIND_CONTINUE;

   /*Clear the buffer that will hold the working pattern*/
   for(c=0;c<516;c++)
      patResult[c]=0;

   /*If the user specified that the 'Search For' string didn't contain*/
   /*a literal, create a working pattern from the given pattern*/
   if(Wildcards)
      ParsePatternNoCase(pattern,patResult,256);
   else
      /*Otherwise, just use the string as is*/
      strcpy(patResult,pattern);

   /*Get a lock on the first directory*/
   fileLock=Lock(startingDir,ACCESS_READ);

   if(fileLock==NULL)
      return(FIND_CONTINUE);

   /*And start the find*/
   result=doFileFind(fileLock,patResult,recur);

   /*We're done, so unlock the lock on the starting directory*/
   UnLock(fileLock);

   /*And return the result of the search (quit or continue)*/
   return(result);
}

char findFileName[256]; 	     // Needs to be extern

/*This is the recursive part of the file-search process.  It goes through*/
/*each file, checking to see if a name matches the given literal/pattern.*/
/*If a directory is found, and if the user specified directory recursion,*/
/*we call doFileFind(), giving it a lock on the subdirectory to search.*/
FindStat doFileFind(BPTR startLock,char *pattern,BOOL recur)
{
   struct TagItem tag;
   struct FileInfoBlock *curFib;
   BPTR oldLock;
   BPTR fileLock;
   BOOL success;
   FindStat result=FIND_CONTINUE;

   tag.ti_Tag=TAG_DONE;
   tag.ti_Data=0;

   /*Allocate a FileInfoBlock to hold the results from Execute()/ExNext().*/
   if((curFib=AllocDosObject(DOS_FIB,&tag))!=NULL)
   {
      /*Make the search directory into the current directory*/
      oldLock=CurrentDir(startLock);

      /*Get the name of the first directory*/
      success=Examine(startLock,curFib);

      /*Get the name of the first file/dir in the directory*/
      success=ExNext(startLock,curFib);

      /*Loop while we keep finding files and the user doesn't abort*/
      while(success && result==FIND_CONTINUE)
      {
	 /*Update the pointer, to let the user know we're still alive*/
	 updateBusyPointer();

	 /*Get the name of the directory that the file is in*/
	 NameFromLock(startLock,findFileName,255);
	 if(findFileName[0]!=(char)0 &&
			     findFileName[strlen(findFileName)-1]!=':')
	    strcat(findFileName,"/");
	 strcat(findFileName,curFib->fib_FileName);

	 /*If we're doing a wildcard search, check to see if the file-*/
	 /*name matches the pattern*/
	 if(Wildcards)
	 {
	    if(MatchPatternNoCase(pattern,curFib->fib_FileName))
	       updateFileList(findFileName,curFib->fib_FileName,
						  curFib->fib_DirEntryType);
	 }
	 else
	    /*If not, just do a regular string compare*/
	    if(stricmp(pattern,curFib->fib_FileName)==0)
	       updateFileList(findFileName,curFib->fib_FileName,
					       curFib->fib_DirEntryType);

	 /*Check to see if the user wants to abort, see a filename*/
	 /*listed, etc.*/
	 result=checkUserStatus();

	 /*If this is a directory, and if we can recur into directories,*/
	 /*and if this is a link to a directory and recuring into that	*/
	 /*is OK, and if the user didn't abort, AND if the file isn't   */
	 /*a soft link (in which case we can't tell if its a file or a  */
	 /*directory), then descend into the directory.*/
	 if(curFib->fib_DirEntryType > 0 && recur && result==FIND_CONTINUE
	       && (Links || curFib->fib_DirEntryType != ST_LINKDIR)
	       && (curFib->fib_DirEntryType != ST_SOFTLINK))
	    {
	       fileLock=Lock(curFib->fib_FileName,SHARED_LOCK);
	       if(fileLock!=NULL)
		  result=doFileFind(fileLock,pattern,TRUE);
	       UnLock(fileLock);
	    }
	 else
	    numFiles++;

	 /*Get the next file/dir name*/
	 success=ExNext(startLock,curFib);
      }

      /*We're done, so restore the current directory to it's entry value*/
      CurrentDir(oldLock);

      /*Free the FileInfoBlock that was allocated*/
      FreeDosObject(DOS_FIB,curFib);
   }

   /*Return the result of the search*/
   return(result);
}

/*This handles closing libraries and exiting.  It will only close those*/
/*libraries that were actually opened.*/
void cleanup(ULONG err)
{
   if(IntuitionBase!=NULL)
      CloseLibrary((struct Library *)IntuitionBase);

   if(GfxBase!=NULL)
      CloseLibrary((struct Library *)GfxBase);

   if(GadToolsBase!=NULL)
      CloseLibrary(GadToolsBase);

   if(AslBase!=NULL)
      CloseLibrary(AslBase);

   if(WorkbenchBase!=NULL)
      CloseLibrary(WorkbenchBase);

   if(IFFParseBase!=NULL)
      CloseLibrary(IFFParseBase);

   exit(err);
}

/*****************************************************************************/
/*    The next section is the code that handles computer-human interaction   */
/*****************************************************************************/

/*This code handles input before a search takes place*/
/*It returns TRUE if the user wants to start a search, or FALSE if she wants*/
/*to quit*/
BOOL getUserSelections(void)
{
   struct IntuiMessage *mesg;
   ULONG class;
   UWORD code;
   struct Gadget *gad;
   char dirName[256];
   struct TagItem tags[2];
   ULONG sigbit;
   struct AppMessage *amsg;

   tags[0].ti_Tag=GTCB_Checked;
   tags[1].ti_Tag=TAG_DONE;
   tags[1].ti_Data=NULL;

   /*Loop until we return*/
   while(TRUE)
   {
      /*Wait for input*/
      if(appWdw!=NULL)
	 sigbit=Wait(1<<Project0Wnd->UserPort->mp_SigBit|1<<appMsgPort->mp_SigBit);
      else
	 sigbit=Wait(1<<Project0Wnd->UserPort->mp_SigBit);

      /*Is it a non-AppWindow event?*/
      if(sigbit & (1<<Project0Wnd->UserPort->mp_SigBit))
      {
	 /*Get the message*/
	 mesg=GT_GetIMsg(Project0Wnd->UserPort);

	 /*Loop while messages are forthcoming*/
	 while(mesg!=NULL)
	 {
	    class=mesg->Class;
	    code=mesg->Code;
	    gad=(struct Gadget *)mesg->IAddress;
	    GT_ReplyIMsg(mesg);

	    /*Determine what class of message was received*/
	    switch(class)
	    {
	       /*Window close*/
	       case IDCMP_CLOSEWINDOW:
		  return(FALSE);

	       /*Menu choice*/
	       case IDCMP_MENUPICK:
		  if(!handleMenuInput(code))
		     return(FALSE);
		  break;

	       /*Gadget press*/
	       case IDCMP_GADGETUP:
		  switch(gad->GadgetID)
		  {
		     /*The three checkbox gadgets*/
		     case GD_Recur:
			tags[0].ti_Tag=GA_Disabled;
			if(Recur=!Recur)
			   tags[0].ti_Data=FALSE;
			else
			   tags[0].ti_Data=TRUE;

			GT_SetGadgetAttrsA(Project0Gadgets[GD_Links],
					   Project0Wnd,NULL, tags);

			tags[0].ti_Tag=GTCB_Checked;
			break;
		     case GD_Links:
			Links=!Links;
			break;
		     case GD_Wildcards:
			Wildcards=!Wildcards;
			break;

		     /*This activates the 'Directory' string gadget when*/
		     /*the user presses 'RETURN' while in the 'Search for'*/
		     /*gadget.*/
		     case GD_Pattern:
			ActivateGadget(Project0Gadgets[GD_Dir],Project0Wnd,NULL);
			break;

		     /*The browse gadget*/
		     case GD_Browse:
			/*Copy the current 'Directory' string into*/
			/*a buffer to give to the file requester*/
			strcpy(dirName,((struct StringInfo *)
			   (Project0Gadgets[GD_Dir]->SpecialInfo))->Buffer);

			/*Call the file requester (the NULL means that we */
			/*don't want a filename returned, just a directory*/
			browse("Please select a directory",dirName,NULL);

			/*Place the directory name into the string gadget*/
			tags[0].ti_Tag=GTST_String;
			tags[0].ti_Data=(ULONG)dirName;

			GT_SetGadgetAttrsA(Project0Gadgets[GD_Dir],
					   Project0Wnd,NULL, tags);

			/*Restore this value to this array position, so that*/
			/*the VANILLAKEY toggles below don't have to worry*/
			/*about setting it*/
			tags[0].ti_Tag=GTCB_Checked;
			break;

		     /*The user made a selection from the file list.  Fill*/
		     /*the directory component list with appropriate info.*/
		     case GD_FileList:
			putInDirList(code);
			break;

		     /*Start the search*/
		     case GD_Go:
			return(TRUE);
			break;
		  }
		  break;

	       /*Keyboard gadget equivalents*/
	       case IDCMP_VANILLAKEY:
		  switch(code)
		  {
		     /*Browse, same as above*/
		     case 'r':
		     case 'R':
			strcpy(dirName,((struct StringInfo *)
			   (Project0Gadgets[GD_Dir]->SpecialInfo))->Buffer);

			browse("Please select a directory",dirName,NULL);
			tags[0].ti_Tag=GTST_String;
			tags[0].ti_Data=(ULONG)dirName;

			GT_SetGadgetAttrsA(Project0Gadgets[GD_Dir],
					   Project0Wnd,NULL, tags);

			tags[0].ti_Tag=GTCB_Checked;
			break;

		     /*Search for*/
		     case 'f':
		     case 'F':
			ActivateGadget(Project0Gadgets[GD_Pattern],Project0Wnd,
				       NULL);
			break;

		     /*Directory*/
		     case 'd':
		     case 'D':
			ActivateGadget(Project0Gadgets[GD_Dir],Project0Wnd,NULL);
			break;

		     /*Go*/
		     case 'g':
		     case 'G':
			return(TRUE);

		     /*Subdirectory recursion toggle*/
		     case 'b':
		     case 'B':
			tags[0].ti_Data=(Recur=!Recur);
			GT_SetGadgetAttrsA(Project0Gadgets[GD_Recur],
					   Project0Wnd,NULL, tags);
			tags[0].ti_Tag=GA_Disabled;
			if(Recur)
			   tags[0].ti_Data=FALSE;
			else
			   tags[0].ti_Data=TRUE;

			GT_SetGadgetAttrsA(Project0Gadgets[GD_Links],
					   Project0Wnd,NULL, tags);

			tags[0].ti_Tag=GTCB_Checked;
			break;

		     /*'Descend into links' toggle*/
		     case 'l':
		     case 'L':
			if(Recur)
			{
			   tags[0].ti_Data=(Links=!Links);
			   GT_SetGadgetAttrsA(Project0Gadgets[GD_Links],
					      Project0Wnd,NULL, tags);
			}
			break;

		     /*'Treat "Search for" as pattern' toggle*/
		     case 'w':
		     case 'W':
			tags[0].ti_Data=(Wildcards=!Wildcards);
			GT_SetGadgetAttrsA(Project0Gadgets[GD_Wildcards],
					   Project0Wnd,NULL, tags);
			break;

		  }
		  break;
	    }
	    /*Get the next input event*/
	    mesg=GT_GetIMsg(Project0Wnd->UserPort);
	 }
      }

      /*Check to see if this was an AppWindow event*/
      if(sigbit & (1<<appMsgPort->mp_SigBit))
      {
	 BPTR dirLock;

	 /*Loop while there are AppWindow events, although if the user*/
	 /*drags multiple icons into the window at once, only the first*/
	 /*one will be used*/
	 while((amsg=(struct AppMessage *)GetMsg(appMsgPort))!=NULL)
	 {
	    /*Get the lock associated with the directory of the icon*/
	    dirLock=amsg->am_ArgList[0].wa_Lock;

	    /*Get the directory name*/
	    NameFromLock(dirLock,dirName,255);

	    /*Place that directory name into the 'Directory' string gadget*/
	    tags[0].ti_Tag=GTST_String;
	    tags[0].ti_Data=(ULONG)dirName;

	    GT_SetGadgetAttrsA(Project0Gadgets[GD_Dir],
			       Project0Wnd,NULL, tags);
	    tags[0].ti_Tag=GTCB_Checked;

	    /*Reply to the AppWindow message*/
	    ReplyMsg((struct Message *)amsg);
	 }
      }
   }
}

/*Prepare window gadgets for the start of a search*/
void disableWindowGadgets(void)
{
   struct TagItem tags[2];
   UBYTE gad;

   tags[0].ti_Tag=GA_Disabled;
   tags[0].ti_Data=TRUE;
   tags[1].ti_Tag=TAG_DONE;
   tags[1].ti_Data=0;

   /*Disable each gadget, except for 'Stop' (which is already disabled)*/
   for(gad=0;gad<10;gad++)
   {
      /*and the two listview gadgets (the file and dir lists), since*/
      /*they can't be disabled*/
      if(gad==GD_FileList)
	 gad+=2;

      GT_SetGadgetAttrsA(Project0Gadgets[gad],Project0Wnd,NULL,tags);
   }

   /*Enable the 'Stop' gadget*/
   tags[0].ti_Data=FALSE;
   GT_SetGadgetAttrsA(Project0Gadgets[GD_Stop],Project0Wnd,NULL,tags);

}

/*The inverse of disableWindowGadgets(), called when a search is complete*/
void enableWindowGadgets(void)
{
   struct TagItem tags[2];
   UBYTE gad;

   tags[0].ti_Tag=GA_Disabled;
   tags[0].ti_Data=FALSE;
   tags[1].ti_Tag=TAG_DONE;
   tags[1].ti_Data=0;

   /*Enable all gadgets except for the listviews and Stop gadget*/
   for(gad=0;gad<10;gad++)
   {
      if(gad==GD_FileList)
	 gad+=2;

      if(gad!=GD_Links || Recur)
	 GT_SetGadgetAttrsA(Project0Gadgets[gad],Project0Wnd,NULL,tags);
   }

   /*Disable the 'Stop' gadget*/
   tags[0].ti_Data=TRUE;
   GT_SetGadgetAttrsA(Project0Gadgets[GD_Stop],Project0Wnd,NULL,tags);
}

/*Get user input while a search is progressing*/
FindStat checkUserStatus(void)
{
   struct IntuiMessage *mesg;
   ULONG class;
   UWORD code;
   struct Gadget *gad;

   /*Get a message, if one exists (we don't Wait() since we want to return*/
   /*immediately if we don't get input*/
   mesg=GT_GetIMsg(Project0Wnd->UserPort);
   while(mesg!=NULL)
   {
      class=mesg->Class;
      code=mesg->Code;
      gad=(struct Gadget *)mesg->IAddress;
      GT_ReplyIMsg(mesg);

      switch(class)
      {
	 /*Gadget release;  it can only be one of two*/
	 case IDCMP_GADGETUP:
	    switch(gad->GadgetID)
	    {
	       /*Abort the search*/
	       case GD_Stop:
		  return(FIND_STOP);

	       /*Put the directory components of the chosen filename into*/
	       /*the directory list*/
	       case GD_FileList:
		  putInDirList(code);
		  break;
	       break;
	    }

	 /*The keyboard equivalent of 'Stop'*/
	 case IDCMP_VANILLAKEY:
	    if(code=='s' || code=='S')
	       return(FIND_STOP);
	    break;

	 /*Menu selections*/
	 /*Needs to be reworked*/
	 case IDCMP_MENUPICK:
	    if(!handleMenuInput(code))
	       return(FIND_CLOSE);
	    break;

	 /*Close gadget*/
	 case IDCMP_CLOSEWINDOW:
	    return(FIND_CLOSE);
      }
      /*Get the next message*/
      mesg=GT_GetIMsg(Project0Wnd->UserPort);
   }

   /*If we've reached this point, the user hasn't pressed 'Stop', so */
   /*continue searching*/
   return(FIND_CONTINUE);
}

/*Tags for the file requester*/
struct TagItem fileReqTags[8]=
{
   {ASL_Hail,NULL},
   {ASL_Height,193},
   {ASL_Width,200},
   {ASL_TopEdge,5},
   {ASL_LeftEdge,10},
   {ASL_Dir,NULL},
   {ASL_File,NULL},
   {TAG_DONE,NULL}
};

/*This function brings up a file requester*/
/*(hail==The title of the requester window)*/
BOOL browse(char *hail,char *dir,char *name)
{
   BOOL stat;
   struct FileRequester *fr;
   fileReqTags[0].ti_Data=(ULONG)hail;
   fileReqTags[5].ti_Data=(ULONG)dir;
   fileReqTags[6].ti_Data=(ULONG)name;

   /*Open the requester*/
   if((fr=(struct FileRequester *)AllocAslRequest(ASL_FileRequest,
			      fileReqTags))!=NULL)
   {
      /*If the user didn't click on CANCEL*/
      if(stat=AslRequest(fr,NULL))
      {
	 /*If the user wants the directory name, return it*/
	 if(dir!=NULL)
	    strcpy(dir,fr->rf_Dir);

	 /*Likewise for the filename*/
	 if(name!=NULL)
	    strcpy(name,fr->rf_File);
      }

      /*Delete the file requester structure*/
      FreeAslRequest(fr);
   }

   return(stat);
}

BOOL handleMenuInput(UWORD menuNumber)
{
   while(menuNumber!=MENUNULL)
   {
      if(MENUNUM(menuNumber)==0)
      {
	 switch(ITEMNUM(menuNumber))
	 {
	    case 0:
	       if(fileList!=NULL &&
		      getItem(fileList,0)!=NULL)
		  saveFileList(SUBNUM(menuNumber)==0);
	       else
		  printError("There is no list to save",NULL,NULL);
	       break;
	    case 2:
	       printError("FindFile V1.01","August 30, 1992",
			       "©1992 Dave Schreiber");
	       break;
	    case 4:
	       return(FALSE);
	 }
      }
      else
	 if(ITEMNUM(menuNumber)==0)
	 {
	    if(SUBNUM(menuNumber)==0)
	       copyNameToClipboard(TRUE);
	    else
	       copyNameToClipboard(FALSE);
	 }
	 else
	    if(SUBNUM(menuNumber)==0)
	       clipFileList(TRUE);
	    else
	       clipFileList(FALSE);

      menuNumber=((struct MenuItem *)ItemAddress(Project0Menus,menuNumber))->NextSelect;
   }
   return(TRUE);
}


/*****************************************************************************/
/*	     The next section handles the file and directory lists	     */
/*****************************************************************************/

/*Intialize the file list*/
void initFileList(void)
{
   /*This will delete an old file list, if one exists*/
   deleteFileList();

   /*Create a new list*/
   initList(&fileList);
   return;
}

/*Add a name to the file list*/
void updateFileList(char *name,char *nameOnly,LONG dirEntryType)
{
   struct TagItem tags[2];
   char prefixedName[40];

   tags[0].ti_Tag=GTLV_Labels;
   tags[0].ti_Data=~0L;
   tags[1].ti_Tag=TAG_DONE;
   tags[1].ti_Data=NULL;

   switch(dirEntryType)
   {
      case ST_USERDIR:
	 strcpy(prefixedName,"(dir)  ");
	 break;
      case ST_LINKDIR:
	 strcpy(prefixedName,"<hld>  ");
	 break;
      case ST_SOFTLINK:
	 strcpy(prefixedName,"<sl>   ");
	 break;
      case ST_LINKFILE:
	 strcpy(prefixedName,"<hl>   ");
	 break;
      default:
	 strcpy(prefixedName,"       ");
	 break;
   }
   strcat(prefixedName,nameOnly);

   /*Update the list*/
   Forbid();
   addItem(fileList,name,prefixedName,TRUE);
   Permit();

   /*Inform Intuition/Gadtools of the change*/
   tags[0].ti_Data=(ULONG)fileList;
   GT_SetGadgetAttrsA(Project0Gadgets[GD_FileList], Project0Wnd,NULL, tags);

   return;
}

/*Delete the file list, if one exists*/
void deleteFileList(void)
{
   struct TagItem tags[2];

   tags[0].ti_Tag=GTLV_Labels;
   tags[1].ti_Tag=TAG_DONE;
   tags[1].ti_Data=NULL;

   /*If there is a list*/
   if(fileList!=NULL)
   {
      tags[0].ti_Data=0L;

      /*Detach the list from the file listview gadget*/
      GT_SetGadgetAttrsA(Project0Gadgets[GD_FileList], Project0Wnd,NULL, tags);

      /*Delete the list itself*/
      freeList(&fileList);
   }

   return;
}

/*Put a filename into the directory listview*/
void putInDirList(UWORD code)
{
   dosNameNode *name;
   char filename[256];
   char part[36];
   char finalPart[40];
   char *currentPart;
   int s=0;
   struct TagItem tags[2];
   BOOL first=TRUE;

   currentFilename=code;
   tags[0].ti_Tag=GTLV_Labels;
   tags[0].ti_Data=~0L;
   tags[1].ti_Tag=TAG_DONE;
   tags[1].ti_Data=NULL;

   /*Delete a previous directory component list, if one exists*/
   deleteDirList();

   /*Initialize a new list*/
   initList(&dirList);

   /*Get the node corresponding to the name that the user selected*/
   name=getItem(fileList,code);

   /*Get the filename*/
   strcpy(filename,name->fullFilename);

   /*This pointer points to the part of the filename that we're currently*/
   /*working on*/
   currentPart=filename;

   /*Loop until we run out of filename*/
   while(*currentPart!=0)
   {
      s=0;

      /*Copy a filename component to 'part'*/
      do
      {
	 part[s++]=*currentPart++;
      }
      while(part[s-1]!=':' && part[s-1]!='/' && part[s-1]!=0);

      /*If the component ends with a 0, it is the actual filename*/
      if(part[s-1]==0)
      {
	 currentPart--;

	 /*Indent 2 spaces*/
	 strcpy(finalPart,"  ");
	 strcat(finalPart,&name->node.ln_Name[STARTOFNAME]);
      }
      /*Ends with a ':'?  Its the device name (no indent)*/
      else if(part[s-1]==':')
      {
	 part[s]=0;
	 strcpy(finalPart,part);
      }
      /*Otherwise, ending with a slash==directory name*/
      else if(part[s-1]=='/')
      {
	 part[s-1]=0;

	 /*Indent with one space*/
	 strcpy(finalPart," ");
	 strcat(finalPart,part);
      }


      /*If this isn't the first item in the list, detach the list from the*/
      /*dirlist listview*/
      if(!first)
	 GT_SetGadgetAttrsA(Project0Gadgets[GD_DirList], Project0Wnd,NULL, tags);
      else
	 first=FALSE;

      /*Add the item to the list*/
      addItem(dirList,finalPart,NULL,FALSE);
      tags[0].ti_Data=(ULONG)dirList;

      /*Update the listview*/
      GT_SetGadgetAttrsA(Project0Gadgets[GD_DirList], Project0Wnd,NULL, tags);
   }

   return;
}

/*Delete the directory component list, if it exists*/
void deleteDirList(void)
{
   struct TagItem tags[2];

   tags[0].ti_Tag=GTLV_Labels;
   tags[0].ti_Data=0L;
   tags[1].ti_Tag=TAG_DONE;
   tags[1].ti_Data=NULL;

   if(dirList!=NULL)
   {
      /*Detach the list from the listview*/
      GT_SetGadgetAttrsA(Project0Gadgets[GD_DirList], Project0Wnd,NULL, tags);

      /*And delete it*/
      freeList(&dirList);
   }

   return;
}

/*****************************************************************************/
/*		  This code handles error and status reporting		     */
/*****************************************************************************/

struct EasyStruct errorReq=
{
   sizeof(struct EasyStruct),
   0,
   "Program Request...",
   NULL,
   "Ok"
};

/*The three possible formatting strings*/
char *oneLine="%s";
char *twoLine="%s\n%s";
char *threeLine="%s\n%s\n%s";

/*Print a one, two, or three line error message in an EasyRequester*/
void printError(char *first,char *second,char *third)
{
   APTR args[4];
   args[0]=args[3]=NULL;

   /*Three lines*/
   if(third!=NULL)
   {
      args[2]=third;
      args[1]=second;
      args[0]=first;
      errorReq.es_TextFormat=threeLine;
   }
   /*Two lines*/
   else if(second!=NULL)
   {
      args[1]=second;
      args[0]=first;
      errorReq.es_TextFormat=twoLine;
   }
   /*One line*/
   else if(first!=NULL)
   {
      args[0]=first;
      errorReq.es_TextFormat=oneLine;
   }

   /*Put up the requester*/
   EasyRequestArgs(NULL,&errorReq,NULL,args);
   return;
}

/*Copy the pointer imagery from a buffer holding Gadget Image style image data*/
/*to a chip memory buffer that will hold the images in the interleaved format*/
/*required by the Amiga's sprite machinery*/
void setupPointerBuffers(void)
{
   UBYTE pointer,value;

   /*For each of the four pointer animation 'frames'*/
   for(pointer=0;pointer<4;pointer++)
   {
      /*Initialize the required 0 at the beginning and end of the sprite data*/
      pointerData[pointer][0]=pointerData[pointer][1]=0;
      pointerData[pointer][28]=pointerData[pointer][29]=0;

      /*Copy the data over*/
      for(value=0;value<13;value++)
      {
	 pointerData[pointer][2*value+2]=PointerImageData[pointer][value];
	 pointerData[pointer][2*value+3]=PointerImageData[pointer][value+13];
      }
   }
   return;
}

/*Set the pointer to 'frame' 0 of the busy-pointer animation*/
void setBusyPointer(void)
{
   pointerCount=0;

   SetPointer(Project0Wnd,pointerData[0],13,10,-1,0);
   return;
}

/*Advance to the next 'frame' of the pointer animation*/
void updateBusyPointer()
{
   /*If at the end, loop back*/
   if(++pointerCount==4)
      pointerCount=0;

   SetPointer(Project0Wnd,pointerData[pointerCount],13,10,-1,0);
   return;
}

/*Remove the busy-pointer animation and restore the system pointer*/
void restorePointer()
{
   ClearPointer(Project0Wnd);
   return;
}

/*****************************************************************************/
/*		   The next section handles clipboard I/O		     */
/*****************************************************************************/

struct IFFHandle *iff=NULL;
struct ContextNode *cn;

/*Open the clipboard and create an iffparse.library parsing instance to*/
/*be used for communication with the clipboard (all data is written out*/
/*in FTXT format.*/
/*Returns TRUE if successful, FALSE if not*/
BOOL openClipboard(UBYTE unit)
{
   /*Get the IFF handle*/
   if((iff=(struct IFFHandle *)AllocIFF())!=NULL)
   {
      /*Open the clipboard*/
      if((iff->iff_Stream=(ULONG)OpenClipboard(unit))!=NULL)
      {
	 /*Initialize the IFF handle*/
	 InitIFFasClip(iff);

	 /*and open it*/
	 if(OpenIFF(iff,IFFF_WRITE)==0)
	 {
	    /*Create a 'FORM FTXT' header*/
	    if(PushChunk(iff,ID_FTXT,ID_FORM,IFFSIZE_UNKNOWN)==0)
	    {
	       /*Create a 'CHRS' chunk*/
	       if(PushChunk(iff,0,ID_CHRS,IFFSIZE_UNKNOWN)==0)
		  return(TRUE);

	       PopChunk(iff);
	    }
	    CloseIFF(iff);
	 }
	 CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
      }
      FreeIFF(iff);
   }
   return(FALSE);
}

/*Write a given string to the clipboard*/
void writeStringToClipboard(char *string,ULONG stringLen)
{
   if(stringLen==~0)
      WriteChunkBytes(iff,string,strlen(string));
   else
      WriteChunkBytes(iff,string,stringLen);
   return;
}

/*Close the clipboard*/
void closeClipboard(void)
{
   PopChunk(iff);
   PopChunk(iff);
   CloseIFF(iff);
   CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
   FreeIFF(iff);

   return;
}

/*Copy the currently selected filename to the clipboard*/
void copyNameToClipboard(BOOL full)
{
   dosNameNode *name;

   /*If there is a current filename...*/
   if(currentFilename!=~0)
   {
      /*Get the name*/
      name=getItem(fileList,currentFilename);

      /*IF the clipboard was opened successfully*/
      if(openClipboard(0))
      {
	 /*Write the full path name*/
	 if(full)
	    writeStringToClipboard(name->fullFilename,~0L);
	 else
	    /*Or just the filename*/
	    writeStringToClipboard(&name->node.ln_Name[STARTOFNAME],~0L);

	 /*Close the clipboard*/
	 closeClipboard();
      }
   }

   return;
}

/*Send the entire file list to the clipboard*/
void clipFileList(BOOL full)
{
   dosNameNode *filename=(dosNameNode *)fileList->lh_Head;
   if(filename==NULL)
      return;

   /*Open the clipboard*/
   if(openClipboard(0))
   {
      /*For each name in the list*/
      while(filename->node.ln_Succ!=NULL)
      {
	 /*Write either the*/
	 if(full)
	    /*Full directory path*/
	    writeStringToClipboard(filename->fullFilename,~0);
	 else
	    /*Or just the filename*/
	    writeStringToClipboard(&filename->node.ln_Name[STARTOFNAME],~0);

	 /*End each line with a line feed*/
	 writeStringToClipboard("\n",1);

	 /*Get the next filename*/
	 filename=(dosNameNode *)filename->node.ln_Succ;
      }
      /*Close the clipboard*/
      closeClipboard();
   }
   return;
}


/*****************************************************************************/
/*		The next section is the code that handles disk I/O	     */
/*****************************************************************************/

static char saveDirName[256]={NULL};

static char saveFilename[36]={NULL};

/*This function save a file list to a file*/
void saveFileList(BOOL full)
{
   char fullFilename[256];
   dosNameNode *filename;
   BPTR filePointer;
   BOOL stat;

   /*Get the filename*/
   stat=browse("Please select a filename",saveDirName,saveFilename);

   /*If we didn't get a name, or the user pressed CANCEL, or there is no*/
   /*filelist, return*/
   if(saveFilename[0]==0 || !stat || (fileList==NULL))
      return;

   /*Create the full filename*/
   strcpy(fullFilename,saveDirName);
   if(fullFilename[strlen(fullFilename)-1]!=':' && fullFilename[0]!=0)
      strcat(fullFilename,"/");
   strcat(fullFilename,saveFilename);

   /*Get the first item in the file list*/
   filename=getItem(fileList,0);

   /*Open the file*/
   if((filePointer=Open(fullFilename,MODE_NEWFILE))!=NULL)
   {
      /*Loop through each filename*/
      while(filename->node.ln_Succ!=NULL)
      {
	 /*Write out either the */
	 if(full)
	    /*Full pathname*/
	    Write(filePointer,filename->fullFilename,strlen(filename->fullFilename));
	 else
	    /*Or just the filename*/
	    Write(filePointer,&filename->node.ln_Name[STARTOFNAME+1],
		     strlen(filename->node.ln_Name)-STARTOFNAME);

	 /*Append with a carrage return*/
	 Write(filePointer,"\n",1);

	 /*Get the next node*/
	 filename=(dosNameNode *)filename->node.ln_Succ;
      }
      /*Close the file*/
      Close(filePointer);
   }

   return;
}

/*End of FF.c*/

