/*
 *  CELLS       An Implementation of the WireWorld cellular automata
 *              as described in Scientific American, Jan 1990.
 *
 *              Copyright 1990 by Davide P. Cervone.
 *  You may use this code, provided this copyright notice is kept intact.
 *  See the CELLS.HELP file for complete information on distribution conditions.
 */

/*
 *  File:  cLSList.c            Handles maintenance of File Requester lists
 *                              and miscellaneous gadgets.
 */


#include "Cells.h"
#include "cRequest.h"
#include "cReqLS.h"
#include "cBoxes.h"
#include "cMemory.h"
#include "cDos.h"

extern char LSDirName[];
extern char LSNameBuf[];
extern char DirName[];

static char Message[MAXBUFFER];     /* message box buffer */
static FILEINFO FirstFile;          /* list of files for display */
int RequestID;                      /* type or file requester (save,load) */

extern FILEINFO mSort();


#define LSGADGET    LSRequest.er_Request.ReqGadget


/*
 *  LSMessage()
 *
 *  If a messageis to be displayed
 *    copy the message into the buffer area and get its length
 *    set up the gadget to use the new message text
 *    split the message into two lines if necessary
 *    center the messages within the message box
 *  otherwise (no message)
 *    clear the message area
 *  Refresh the gadget if the requester is already active
 */

void LSMessage(s,x1,x2,x3,x4,x5,x6)
char *s,*x1,*x2,*x3,*x4,*x5,*x6;
{
   struct Gadget *theGadget = &LSGADGET[LS_MESSAGE];
   struct IntuiText *theText = (struct IntuiText *)theGadget->UserData;
   short Len,i;

   Forbid();
   if (s)
   {
      sprintf(Message,s,x1,x2,x3,x4,x5,x6);
      Len = strlen(Message);
      theGadget->GadgetText = theText;
      theGadget->GadgetText->IText = Message;
      if (Len > 33)
      {
         theText[0].NextText = &theText[1];
         theText[0].TopEdge  = 2;
         theText[1].TopEdge  = 11;
         for (i=33; i>0 && Message[i] != ' '; i--);
         if (i == 0) i = 33;
         Message[i] = 0;
         theText[0].LeftEdge = (LS_MESSAGEW - CHAR_WIDTH*i++) / 2;
         theText[1].LeftEdge = (LS_MESSAGEW - CHAR_WIDTH*(Len-i)) / 2;
         theText[1].IText = &Message[i];
      } else {
         theText[0].LeftEdge = (LS_MESSAGEW - CHAR_WIDTH*Len) / 2;
         theText[0].TopEdge  = 1 + LNAMEH/2;
         theText[0].NextText = NULL;
      }
   } else {
      theGadget->GadgetText = NULL;
   }
   Permit();
   if (ActiveRequest == &LSRequest)
      RefreshGList(theGadget,myWindow,ActiveRequest,1);
}


/*
 *  RecallLSMessage()
 *
 *  If the message box has no message being displayed,
 *    put the gadget text pointer back to the last message shown
 *    and refresh the gadget
 */

void RecallLSMessage(theGadget)
struct Gadget *theGadget;
{
   if (theGadget->GadgetText == NULL)
   {
      Forbid();
      theGadget->GadgetText = (struct IntuiText *)theGadget->UserData;
      RefreshGList(theGadget,myWindow,&LSRequest,1);
      Permit();
   }
}


/*
 *  SetLSDirName()
 *
 *  If the directory name is too long, copy only the last part, otherwise
 *    copy the entire name into the directory name gadget and center it
 *  If the requester is active
 *    refresh the directory name (twice if it is currently selected, since
 *    it is a GADGHCOMP gadget)
 */

static void SetLSDirName(Name)
char *Name;
{
   struct IntuiText *theText = LSGADGET[LS_DIR].GadgetText;
   short Len = strlen(Name);

   if (Len > 20) strcpy(LSDirName,&Name[Len-20]); else strcpy(LSDirName,Name);
   theText->LeftEdge = (LS_DIRW - CHAR_WIDTH * strlen(Name) - SLIDEWH) / 2;
   if (theText->LeftEdge < 3) theText->LeftEdge = 3;
   if (ActiveRequest == &LSRequest)
   {
      if (LSGADGET[LS_DIR].Flags & SELECTED)
         RefreshRequest(&LSRequest,LS_DIR,NULL);
      RefreshRequest(&LSRequest,LS_DIR,NULL);
   }
}


/*
 *  SetLSTexts()
 *
 *  Get the directory name and set the directory gadget to it
 *  Set the requester title to the give title and center it
 *  Set the name and ID of the LOAD/SAVE button
 *  Set the title of the check gadget and make sure it is not checked
 *  Set the initial message, if any
 *  Save the type of requester (SAVE or LOAD) for future reference
 */

void SetLSTexts(Title,Button,Check,ID)
char *Title,*Button,*Check;
int ID;
{
   GetProcessDir(DirName);
   SetLSDirName(DirName);
   LSRequest.er_Request.ReqText->IText = Title;
   LSRequest.er_Request.ReqText->LeftEdge =
      (LS_PANELW - CHAR_WIDTH * strlen(Title)) / 2;
   LSGADGET[LS_LOAD].GadgetText->IText = Button;
   LSGADGET[LS_LOAD].GadgetText->LeftEdge = 15;
   LSGADGET[LS_LOAD].GadgetID = ID;
   LSGADGET[LS_CHECK].GadgetText->IText = Check;
   LSGADGET[LS_CHECK].Flags &= ~SELECTED;
   if (LSGADGET[LS_NLIST].Flags & GADGDISABLED)
   {
      LSMessage("Click on Directory Name for a List of Files");
   } else {
      LSMessage(NULL);
      LSGADGET[LS_NLIST].GadgetText = LSListInfo.IText;
      UnsetListSelect(&LSListInfo,FALSE);
   }
   RequestID = ID;
}


/*
 *  DisableLSList()
 *
 *  If the list is not already disabled
 *    clear any selection and set the list to a NULL list
 *    remove the IntuiTexts from the gadget
 *    make the background gadget full sized
 *    disable the gadget and its slider and arrows
 *    refresh the disabled gadgets
 */

void DisableLSList(theGadget)
struct Gadget *theGadget;
{
   short i;

   if ((theGadget->Flags & GADGDISABLED) == 0)
   {
      UnsetListSelect(theGadget->UserData,TRUE);
      SetList(theGadget,NULL,NULL);
      Forbid();
      theGadget->GadgetText = NULL;
      LSListInfo.Image->PlaneOnOff = BACKGROUND;
      LSListInfo.Image->Height = LS_NLISTH-2;
      LSListInfo.Image->TopEdge = 1;
      for (i=0; i<4; theGadget[i++].Flags |= GADGDISABLED);
      RefreshGList(theGadget,myWindow,&LSRequest,4);
      Permit();
   }
}


/*
 *  EnableLSList()
 *
 *  If the list is disabled
 *    Set the list gadget's text to the list's text array
 *    Reset the gadget's background color (the image is still full size)
 *    enable the list, its slider and its arrows
 *    refresh the list and its sliders
 *    refresh the list border (kludge: the list gadget really should be smaller)
 *    Now reset the gadget image to its normal height
 */

void EnableLSList(theGadget)
struct Gadget *theGadget;
{
   short i;

   if (theGadget->Flags & GADGDISABLED);
   {
      Forbid();
      theGadget->GadgetText = LSListInfo.IText;
      LSListInfo.Image->PlaneOnOff = FOREGROUND;
      for (i=0; i<4; theGadget[i++].Flags &= ~GADGDISABLED);
      RefreshGList(theGadget,myWindow,&LSRequest,4);
      RefreshRequest(&LSRequest,LS_BRDRGADG,NULL);
      LSListInfo.Image->Height = LNAMEH;
      Permit();
   }
}


/*
 *  CompareFiles()
 *
 *  Compare two file names to see if they are equal, or which one is
 *  first alphabetically (NOFIRST is a kludge to ignore the leading '/' in 
 *  directory names).  The list is to be sorted in reverse order, so the
 *  file names are reversed inthe stricmp() call
 */

static CompareFiles(File1,File2)
FILEINFO File1,File2;
{
   char *s1 = File1->Name;
   char *s2 = File2->Name;
   
   if (File1->Flags & LI_NOFIRST) s1++;
   if (File2->Flags & LI_NOFIRST) s2++;
   return(stricmp(s2,s1));
}


/*
 *  FreeFile()
 *
 *  Frees the memory associated with a file name in the directory list
 */

static void FreeFile(theFile)
FILEINFO theFile;
{
   if (theFile->Name) FREECHAR(theFile->Name,theFile->NameLen);
   FREESTRUCT(FileInfo,theFile);
}


/*
 *  ClearDirectoryList()
 *
 *  For each file in the list, clear the memory for the file
 */

void ClearDirectoryList()
{
   FILEINFO theFile;

   while (FirstFile)
   {
      theFile = FirstFile;
      FirstFile = FirstFile->Next;
      FreeFile(theFile);
   }
}


/*
 *  FindFile()
 *
 *  Look through the directory list for the given file name (put it into
 *  a fake file item so that CompareFiles can be used).  Returns NULL if
 *  the file is not in the list.
 */

FILEINFO FindFile(Name)
char *Name;
{
   FILEINFO theFile = FirstFile;
   int NotFound = TRUE;
   struct FileInfo CmpFile;
   
   CmpFile.Name = Name;
   while (theFile && NotFound)
   {
      if (CompareFiles(theFile,&CmpFile) == 0) NotFound = FALSE;
      else theFile = theFile->Next;
   }
   return(theFile);
}


/*
 *  RemoveFile()
 *
 *  Unlink the file from the directory list, remove the item from the
 *  list gadget, and free the file's memory
 */

void RemoveFile(theFile)
FILEINFO theFile;
{
   if (theFile->Next) theFile->Next->Prev = theFile->Prev;
   if (theFile->Prev) theFile->Prev->Next = theFile->Next;
   if (theFile == FirstFile) FirstFile = theFile->Next;
   RemoveListItem(&LSListInfo,theFile);
   FreeFile(theFile);
}


/*
 *  MakeFile()
 *
 *  If a new FileInfo structure can be allocated
 *    get the length of the file name
 *    if the file is a directory, increase the length (for the '/')
 *    if we can allocate a character string of the given size
 *      if the file is a directory
 *        set the file's flags and copy the name preceeeded by a '/'
 *      otherwise
 *        just copy the name
 *    otherwise
 *      free the structure and return NULL
 */

static FILEINFO MakeFile(Fib)
FIB Fib;
{
   FILEINFO theFile = NULL;
   
   if (NEWSTRUCT(FileInfo,theFile))
   {
      theFile->NameLen = strlen(Fib->fib_FileName);
      if (Fib->fib_DirEntryType > 0) theFile->NameLen++;
      if (NEWCHAR(theFile->Name,theFile->NameLen))
      {
         if (Fib->fib_DirEntryType > 0)
         {
            theFile->Flags = FI_DIRECTORY | LI_NOFIRST;
            theFile->Name[0] = '/';
            strcpy(&theFile->Name[1],Fib->fib_FileName);
         } else {
            strcpy(theFile->Name,Fib->fib_FileName);
         }
      } else {
         FREESTRUCT(FileInfo,theFile);
         theFile = NULL;
      }
   }
   return(theFile);
}


/*
 *  MakeDevice()
 *
 *  If a new FileInfo structure can be allocated
 *    get the device name length plus one (for ':')
 *    if we can get a characgter string that long
 *      get the device's name string
 *      mark the device as a directory (for purposes or openning)
 *    otherwise
 *      free the structure and return NULL
 */

static FILEINFO MakeDevice(theDevice)
struct DeviceList *theDevice;
{
   FILEINFO theFile = NULL;
   
   if (NEWSTRUCT(FileInfo,theFile))
   {
      theFile->NameLen = BCPL_TO_SIZE(theDevice->dl_Name)+1;
      if (NEWCHAR(theFile->Name,theFile->NameLen))
      {
         GetDevName(theFile->Name,theDevice);
         theFile->Flags = FI_DIRECTORY;
      } else {
         FREESTRUCT(FileInfo,theFile);
         theFile = NULL;
      }
   }
   return(theFile);
}


/*
 *  AddFile()
 *
 *  If a file was given
 *    link it into the directory list and increment the length of the list
 *  otherwise
 *    give an error message
 */

static int AddFile(theFile,Count)
FILEINFO theFile;
int Count;
{
   if (theFile)
   {
      theFile->Prev = NULL;
      theFile->Next = FirstFile;
      if (FirstFile) FirstFile->Prev = theFile;
      FirstFile = theFile;
      Count++;
   } else {
      LSMessage("Can't Get Memory for File Info");
   }
   return(Count);
}


/*
 *  GetDirectoryList()
 *
 *  If the directory can be locked,
 *    and it can be examined
 *      Clear the list gadget's contents
 *      put up a message about reading the directory
 *      clear the old list
 *      while there are more files in the directory, add them to the list
 *      if there was some error while reading the directory
 *        give the message, otherwise tell how many were read
 *      sort the list alphabetically
 *    Unlock the directory
 */

static void GetDirectoryList()
{
   short Count = 0;
   LOCK lock;
   FIB Fib;

   if (DosLock(&lock,DirName,TYPE_DIR))
   {
      if (DosExamine(lock,&Fib,TYPE_DIR))
      {
         SetList(&LSGADGET[LS_NLIST],NULL,NULL);
         LSMessage("Reading Directory...");
         ClearDirectoryList();
         while (ExNext(lock,Fib))
            Count = AddFile(MakeFile(Fib),Count);
         if (IoErr() != ERROR_NO_MORE_ENTRIES)
            DosError("Read","Directory");
           else
            LSMessage("%d File%s",Count,(Count == 1)? "": "s");
         FirstFile = mSort(FirstFile,&CompareFiles,NULL);
      }
      UnLock(lock);
   }
}


/*
 *  GetDeviceList()
 *
 *  Put up the message about the device list
 *  Clear the old list
 *  Starting at the first device, while ther are more devices in the list
 *    add the device to the list, and go on to the next item
 *    (only the MOUNTED devices are listed; i.e., only ones with active tasks)
 *  Sort the list, and tell how many devices were listed
 */

static void GetDeviceList()
{
   short Count = 0; 
   struct DeviceList *theDevice;

   LSMessage("Reading Device List...");
   ClearDirectoryList();
   Forbid();
   theDevice = FIRSTDEV;
   while (theDevice)
   {
      if (theDevice->dl_Task) Count = AddFile(MakeDevice(theDevice),Count);
      theDevice = DEVLIST(theDevice->dl_Next);
   }
   Permit();
   FirstFile = mSort(FirstFile,&CompareFiles,NULL);
   LSMessage("%d Device%s",Count,(Count == 1)? "": "s");
}


/*
 *  SetLSDirectory()
 *
 *  Set the directory gadget to the given name
 *  Clear the file name gadget and refresh it
 *  If the directory list is active
 *    get the list of files in the current directory
 *    remove any selected item, and show the new list
 */

void SetLSDirectory(Name)
char *Name;
{
   Forbid();
   SetLSDirName(Name);
   SetStringInfo(&LSGADGET[LS_NAME],"");
   RefreshRequest(&LSRequest,LS_NAME,NULL);
   Permit();

   if ((LSGADGET[LS_NLIST].Flags & GADGDISABLED) == 0)
   {
      UnsetListSelect(&LSListInfo,TRUE);
      GetDirectoryList();
      SetList(&LSGADGET[LS_NLIST],FirstFile,NULL);
   }
}


/*
 *  SetLSDevices()
 *
 *  Set the directory name to blank
 *  Clear the file name gadget
 *  If the directory list is active
 *    get the list of devices
 *    clear any old selection and show the new list
 */

void SetLSDevices()
{
   Forbid();
   SetLSDirName("");
   SetStringInfo(&LSGADGET[LS_NAME],"");
   RefreshRequest(&LSRequest,LS_NAME,NULL);
   Permit();

   if ((LSGADGET[LS_NLIST].Flags & GADGDISABLED) == 0)
   {
      GetDeviceList();
      UnsetListSelect(&LSListInfo,TRUE);
      SetList(&LSGADGET[LS_NLIST],FirstFile,NULL);
   }
}

static char *OldButton;         /* button name before it became "REWRITE" */


/*
 *  SetReWrite()
 *
 *  Display the warning message
 *  Save the old button name (LOAD or SAVE)
 *  Set the button name and ID to REWRITE and refresh the button
 */

void SetReWrite()
{
   struct Gadget *theGadget = &LSGADGET[LS_LOAD];

   LSMessage("File Exists: Press REWRITE to Over-Write it");
   Forbid();
   OldButton = theGadget->GadgetText->IText;
   theGadget->GadgetText->IText = "ReWrite";
   theGadget->GadgetText->LeftEdge = 7;
   theGadget->GadgetID = LS_REWRITE;
   RefreshGList(theGadget,myWindow,&LSRequest,1);
   Permit();
}


/*
 *  UnsetReWrite()
 *
 *  If there is a button name being saved
 *    Clear the REWRITE name
 *    Set the name and ID back to what it was, and refresh the button
 *    Clear the old button name and the message box
 */

void UnsetReWrite()
{
   struct Gadget *theGadget = &LSGADGET[LS_LOAD];

   if (OldButton)
   {
      Forbid();
      theGadget->GadgetText->IText = "       ";
      RefreshGList(theGadget,myWindow,&LSRequest,1);
      theGadget->GadgetText->IText = OldButton;
      theGadget->GadgetText->LeftEdge = 15;
      theGadget->GadgetID = RequestID;
      RefreshGList(theGadget,myWindow,&LSRequest,1);
      Permit();
      OldButton = NULL;
      LSMessage(NULL);
   }
}


/*
 *  ActivateLSName()
 *
 *  If the list is disabled,
 *    activate the name gadget
 *  otherwise
 *    ask the list manager to activate the name (and deselect any 
 *    selected items, etc)
 *  [there is a bug in Intuition that causes this to fail if the string gadget
 *  was the previously active gadget; i.e., when you press RETURN in the file
 *  name gadget]
 */

void ActivateLSName()
{
   if (LSGADGET[LS_NLIST].Flags & GADGDISABLED)
      ActivateGadget(&LSGADGET[LS_NAME],myWindow,&LSRequest);
     else
      ActivateName(&LSListInfo);
}
