/*
 *  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:  cList.c              Handles name lists for requesters
 *                              (still pretty kludgy, need work)
 */


#include "Cells.h"
#include "cRequest.h"
#include "cBoxes.h"


#define SLIDERINFO      ((struct PropInfo *)(theList->Slider->SpecialInfo))

static ULONG OldSecs,OldMics;       /* timers for double click */
static LISTINFO *ActiveList;        /* list with active slider */


/*
 *  FindListPos()
 *
 *  Assumes list is sorted (in reverse order)
 *  Look through the list until a match is found or until we have gone past
 *  the items position in the list.  Set PrevItem to point to the list item
 *  that should preceed the specified item.  Set ItemPtr to the ListItem pointer
 *  that needs to point to the specified item (it could be the head-of-list
 *  pointer iteself).
 */

void FindListPos(Name,FirstItem,PrevItem,ItemPtr)
char *Name;
LISTITEM **FirstItem;
LISTITEM **PrevItem;
LISTITEM ***ItemPtr;
{
   LISTITEM *theItem = *FirstItem;
   int NotFound = TRUE;

   *PrevItem = NULL;
   *ItemPtr = FirstItem;
   while (theItem && NotFound)
   {
      if (stricmp(Name,theItem->Name) < 0)
      {
         *PrevItem = theItem;
         *ItemPtr = &(theItem->Next);
         theItem = theItem->Next;
      } else {
         NotFound = FALSE;
         if (strcmp(Name,theItem->Name) == 0) *ItemPtr = NULL;
      }
   }
}


/*
 *  InitList()
 *
 *  Clear the list and select counters
 *  If there are items in the list,
 *    count the first item
 *    check if it is to be selected
 *    While there are items following the current one
 *      go on to the following item
 *      count the new item
 *      if the selected item has been seen, count its position, too
 *      if the current item is to be selected, do so
 *  Set the list to point to the last (first) item in the list (the list is
 *  stored as an inverted list, so this item is really the first item)
 *  If no item was selected
 *    set the list to point to the top if the list and mark no selection
 *  otherwise
 *    set the top item to the selected item, and mark the selected item
 *  return the item at the top of the list
 */

LISTITEM *InitList(theList,theItem,SelectItem)
LISTINFO *theList;
LISTITEM *theItem,*SelectItem;
{
   theList->ListLen = 0;
   theList->SelectedItem = LI_NOSELECT;
   if (theItem)
   {
      theList->ListLen++;
      if (theItem == SelectItem) theList->SelectedItem = 0;
      while (theItem->Next)
      {
         theItem = theItem->Next;
         theList->ListLen++;
         if (theList->SelectedItem != LI_NOSELECT) theList->SelectedItem++;
         if (theItem == SelectItem) theList->SelectedItem = 0;
      }
   }
   theList->Item = theItem;
   if (theList->SelectedItem == LI_NOSELECT)
   {
      theList->TopItem = 0;
      theList->ItemSelect = NULL;
   } else {
      theList->TopItem = theList->SelectedItem;
      theList->SelectedItem = 0;
      theItem = SelectItem;
      theList->ItemSelect = SelectItem;
   }
   return(theItem);
}


/*
 *  SetText()
 *
 *  Set the IntuiTexts pen and top edge correctly for its position
 *  If the text is not past the end of the list
 *    copy the items name into the text and pad it with spaces if necessary
 *  otherwise
 *    fill the text string with spaces (these wipe out any old text that
 *    may have been showing on the screen previously, the alternative would
 *    be to clear the entire list gadget area each time, which would make
 *    the gadget "flash" more).
 */

static void SetText(theList,theText,theItem,i)
LISTINFO *theList;
struct IntuiText *theText;
LISTITEM *theItem;
short i;
{
   theText->BackPen = FOREGROUND;
   theText->TopEdge = LNAMEH * i + 2;
   if (theItem)
   {
      strncpy(theText->IText,theItem->Name,theList->Width);
      i = theItem->NameLen; if (i == 0) i = strlen(theText->IText);
      while (i < theList->Width) theText->IText[i++] = ' ';
   } else {
      for (i=0; i<theList->Width; i++) theText->IText[i] = ' ';
   }
   theText->IText[theList->Width] = 0;
}


/*
 *  SetTextPositions()
 *
 *  Set the top edges of the IntuiTexts according to their positions in the
 *  list (when the list scrolls, the texts are not copied from one to another,
 *  rather, the screen positions of the texts are changed).  TopText holds the
 *  array index of the text currently at the top - the list wraps around to the
 *  top of the array when it falls off the bottom.
 */

static void SetTextPositions(theList)
LISTINFO *theList;
{
   struct IntuiText *theText = theList->IText;
   short i,j;
   
   j = theList->TopText;
   for (i=0; i<theList->Height; i++)
   {
      theText[j].TopEdge = LNAMEH * i + 2;
      if (j < theList->Height - 1) j++; else j = 0;
   }
}


/*
 *  SetListNames()
 *
 *  Set the top item in the list to the specified item
 *  Reset the text array to start at the beginning of the array
 *  For each text in the array
 *    set the text string to the specified item name and position
 *    relink the text pointers (in case they have changed)
 *    If there are more items in the list, go on to the next one
 */

static void SetListNames(theList,theItem)
LISTINFO *theList;
LISTITEM *theItem;
{
   struct IntuiText *theText = theList->IText;
   short i;
   
   theList->ItemTop = theItem;
   theList->TopText = 0;
   for (i=0; i<theList->Height; i++,theText++)
   {
      SetText(theList,theText,theItem,i);
      if (i < theList->Height-1)
         theText->NextText = &(theList->IText[i+1]);
        else
         theText->NextText = NULL;
      if (theItem) theItem = theItem->Prev;
   }
}


/*
 *  UpdateListSelect()
 *
 *  Find the position of the selected text in the text array
 *  If the selected item has changed and the new selected item is visable
 *    put the clear-selection image to appear behind the old selected item
 *    (i.e., so that it clears the previous selection, if any)
 *  otherwise
 *    put the clear-selection image so that it won't appear at all (kludge)
 *  If the selection is visible
 *    put the selection image so that it will appear behind the selected text
 *    link the selection image so that it appears
 *    set the background pen of the selected text to the selection color
 *  otherwise
 *    unlink the selection image so that it won't appear
 */

static void UpdateListSelect(theList,OldSelect)
LISTINFO *theList;
UWORD OldSelect;
{
   UWORD Select = theList->SelectedItem;
   short SelectText = (Select+theList->TopText) % theList->Height;

   if (Select != OldSelect && OldSelect >= 0 && OldSelect < theList->Height)
      theList->Image[0].TopEdge = LNAMEH * OldSelect + 1;
     else 
      theList->Image[0].TopEdge = -(theList->Gadget->TopEdge + LNAMEH);

   if (Select >= 0 && Select < theList->Height)
   {
      theList->Image[1].TopEdge = LNAMEH * Select + 1;
      theList->Image[0].NextImage = &(theList->Image[1]);
      theList->IText[SelectText].BackPen = TEXTSELECT;
   } else {
      theList->Image[0].NextImage = NULL;
   }
}


/*
 *  UnsetListSelect()
 *
 *  If the selected item follows the top item in the list
 *    if the selected item is visable, then reset its text pen color
 *    set the list's select item to a non-existant position
 *    if we are refreshing the list, do so
 */

void UnsetListSelect(theList,Refresh)
LISTINFO *theList;
int Refresh;
{
   struct IntuiText *theText = theList->IText;
   int Select = theList->SelectedItem;
   int UnSelect = -(theList->TopItem + 1);
   int i = (theList->TopText + Select) % theList->Height;
   
   if (theList->SelectedItem > UnSelect)
   {
      if (Select >= 0 && Select < theList->Height)
         theText[i].BackPen = FOREGROUND;
      theList->SelectedItem = UnSelect;
      if (Refresh)
      {
         theList->ItemSelect = NULL;
         UpdateListSelect(theList,Select);
         RefreshGList(theList->Gadget,myWindow,theList->Request,1);
      }
   }
}


/*
 *  UpdateListSlider()
 *
 *  Calculate the new body and pot values according to the current list
 *  position and length.
 *  If the list's requester is active,
 *    if the slider's position has changed, update the gadget
 *  otherwise
 *    simply set the values of the slider
 */

static void UpdateListSlider(theList)
LISTINFO *theList;
{
   int Pot,Body;

   if (theList->ListLen > theList->Height)
   {
      Body = 0xFFFF * theList->Height / theList->ListLen;
      Pot  = 0xFFFF * theList->TopItem / (theList->ListLen - theList->Height);
   } else {
      Body = 0xFFFF;
      Pot  = 0;
   }

   if (ActiveRequest)
   {
      if (Body != SLIDERINFO->VertBody || Pot != SLIDERINFO->VertPot)
          NewModifyProp(theList->Slider,myWindow,theList->Request,
             SLIDERINFO->Flags,0,Pot,0,Body,1);
   } else {
      SLIDERINFO->VertBody = Body;
      SLIDERINFO->VertPot  = Pot;
   }
}


/*
 *  SetList()
 *
 *  Initialize the list info structures
 *  If the current position of the list (due to putting selected item at the
 *  top) puts the end of the list within the visable part of the list, 
 *  move the top of the list so that the full list area is used.
 *  Set up the list text names
 *  set up the select images
 *  refresh the list, if necessary
 *  update the lider positions
 */

void SetList(theGadget,theItem,SelectItem)
struct Gadget *theGadget;
LISTITEM *theItem;
LISTITEM *SelectItem;
{
   LISTINFO *theList = (LISTINFO *)theGadget->UserData;

   theItem = InitList(theList,theItem,SelectItem);
   while (theList->TopItem + theList->Height > theList->ListLen &&
      theList->TopItem && theItem->Next)
   {
      theList->TopItem--; theList->SelectedItem++;
      theItem = theItem->Next;
   }
   SetListNames(theList,theItem);
   UpdateListSelect(theList,LI_NOSELECT);
   if (ActiveRequest) RefreshGList(theGadget,myWindow,theList->Request,1);
   UpdateListSlider(theList);
}


/*
 *  DoListScrollUp()
 *
 *  While the arrow gadger is still pressed
 *    if there are more items to scroll
 *      remove the select images and text pens
 *      move the top of the list
 *      calculate the new top of list
 *      set the text of the new item in the list
 *      set up the new top item pointer and text array index
 *      set the IntuiText positions to their new positions
 *      put back the select images
 *      refresh the list gadget
 *      update the slider
 */

void DoListScrollUp(theList,theGadget,theMessage)
LISTINFO *theList;
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   struct IntuiText *theText = theList->IText;
   short Top;
   short OldSelect;

   while (theGadget->Flags & SELECTED)
   {
      if (theList->TopItem)
      {
         OldSelect = theList->SelectedItem;
         UnsetListSelect(theList,FALSE);
         theList->TopItem--; theList->SelectedItem = OldSelect+1;
         if (theList->TopText)
            Top = theList->TopText - 1; else Top = theList->Height - 1;
         SetText(theList,&(theText[Top]),theList->ItemTop->Next,0);
         theList->ItemTop = theList->ItemTop->Next;
         theList->TopText = Top;
         SetTextPositions(theList);
         UpdateListSelect(theList,OldSelect);
         RefreshGList(theList->Gadget,myWindow,theList->Request,1);
         UpdateListSlider(theList);
      }
   }
}


/*
 *  DoListScrollDown()
 *
 *  Find the bottom of the visable area of the list
 *  While the arrow gadger is still pressed
 *    if there are more items to scroll
 *      remove the select images and text pens
 *      move the top of the list
 *      set the text of the new item in the list
 *      set up the new top item pointer and text array index
 *      set the IntuiText positions to their new positions
 *      put back the select images
 *      refresh the list gadget
 *      update the slider
 */

void DoListScrollDown(theList,theGadget,theMessage)
LISTINFO *theList;
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   struct IntuiText *theText = theList->IText;
   LISTITEM *ItemBot = theList->ItemTop;
   int count = 0;
   short OldSelect = theList->SelectedItem;

   while (ItemBot && count < theList->Height)
   {
      ItemBot = ItemBot->Prev;
      count++;
   }

   while (theGadget->Flags & SELECTED)
   {
      if (theList->TopItem + theList->Height < theList->ListLen)
      {
         OldSelect = theList->SelectedItem;
         UnsetListSelect(theList,FALSE);
         theList->TopItem++; theList->SelectedItem = OldSelect - 1;
         SetText(theList,&(theText[theList->TopText]),ItemBot,0);
         theList->ItemTop = theList->ItemTop->Prev; ItemBot = ItemBot->Prev;
         if (theList->TopText < theList->Height - 1)
            theList->TopText++; else theList->TopText = 0;
         SetTextPositions(theList);
         UpdateListSelect(theList,OldSelect);
         RefreshGList(theList->Gadget,myWindow,theList->Request,1);
         UpdateListSlider(theList);
      }
   }
}


/*
 *  DoListSlider()
 *
 *  Calculate new list position according to current slider settings
 *  If the position has changed
 *    record the old selected item
 *    set the new top item
 *    set the list name to the new position in the list
 *    update the selected image positions
 *    refresh the list gadget
 */

void DoListSlider(theList)
LISTINFO *theList;
{
   LISTITEM *theItem = theList->Item;
   short Top;
   short OldSelect;
   short Size = theList->ListLen - theList->Height;
   
   if (Size < 0) Size = 0; 
   Top = Size * SLIDERINFO->VertPot / 0xFFFF;
   if (Top > Size) Top = Size; else if (Top < 0) Top = 0;

   if (Top != theList->TopItem)
   {
      OldSelect = theList->SelectedItem;
      theList->SelectedItem += theList->TopItem - Top;
      theList->TopItem = Top;
      while (Top-- && theItem) theItem = theItem->Prev;
      SetListNames(theList,theItem);
      UpdateListSelect(theList,OldSelect);
      RefreshGList(theList->Gadget,myWindow,theList->Request,1);
   }
}


/*
 *  StartListSlider()
 *
 *  If the slider's knob was hit, move to the new location, then start
 *  mouse moves and record the list whose slider is active.
 */

void StartListSlider(theList)
LISTINFO *theList;
{
   if (SLIDERINFO->Flags & KNOBHIT)
   {
      DoListSlider(theList);
      StartMouseMoves();
      ActiveList = theList;
   }
}


/*
 *  EndListSlider()
 *
 *  Stop getting mouse moves and clear the active list, and show the new
 *  list position.
 */

void EndListSlider(theList)
LISTINFO *theList;
{
   StopMouseMoves(); ActiveList = NULL;
   DoListSlider(theList);
}


/*
 *  DoListMouse()
 *
 *  If a list slider is active, calculate the new list position according
 *  to the sliders new position.
 */

void DoListMouse(MouseX,MouseY,theMessage)
short MouseX,MouseY;
struct IntuiMessage *theMessage;
{
   if (ActiveList) DoListSlider(ActiveList);
}


/*
 *  DoListHit()
 *
 *  Find the index of the list item that was hit
 *  If the position is within the list (i.e., not past then end of the list)
 *    Find the item within the list (starting from the top of the visable list)
 *    If the item was not the selected one,
 *      remove the selected image and pen colors
 *      set the new selected item
 *      refresh the list gadget
 *      set the selected item pointer
 *      if the list has a name gadget and we have selected a new item
 *        copy the item's name into the name gadget (NOFIRST is a kludge
 *        to keep from copying a directories leading '/')
 *        refresh the name
 *        set the double-click counters
 *        return no item selected (wait for a double-click)
 *      (otherwise with no name gadget, return the selected item)
 *    otherwise (the item was already selected)
 *      if the list has a name gadget
 *        if this is not a double-click,
 *          start a new double-click clock count, and return no item
 *      otherwise
 *        don't return the already-selected item
 *  Otherwise (clicked past the end of the list)
 *    clear the selection and the gadget name string
 */

LISTITEM *DoListHit(theList,theMessage)
LISTINFO *theList;
struct IntuiMessage *theMessage;
{
   short y,i;
   short OldSelect = theList->SelectedItem;
   LISTITEM *theItem = NULL;
   
   y = theMessage->MouseY - theList->Gadget->TopEdge -
       theList->Request->er_Request.TopEdge - 1;
   i = y / LNAMEH; y = i;
   if (theList->TopItem + i < theList->ListLen)
   {
      theItem = theList->ItemTop;
      while (y-- && theItem) theItem = theItem->Prev;

      if (i != OldSelect)
      {
         UnsetListSelect(theList,FALSE);
         theList->SelectedItem = i;
         UpdateListSelect(theList,OldSelect);
         RefreshGList(theList->Gadget,myWindow,theList->Request,1);
         theList->ItemSelect = theItem;
         if (theList->Name && theItem)
         {
            if (theItem->Flags & LI_NOFIRST)
               SetStringInfo(theList->Name,theItem->Name+1);
              else
               SetStringInfo(theList->Name,theItem->Name);
            RefreshGList(theList->Name,myWindow,theList->Request,1);
            OldSecs = theMessage->Seconds;
            OldMics = theMessage->Micros;
            theItem = NULL;
         }
      } else {
         if (theList->Name)
         {
            if (DoubleClick(OldSecs,OldMics,
               theMessage->Seconds,theMessage->Micros) == FALSE)
            {
               OldSecs = theMessage->Seconds;
               OldMics = theMessage->Micros;
               theItem = NULL;
            }
         } else {
            theItem = NULL;
         }
      }
   } else if (theList->Name) {
      UnsetListSelect(theList,TRUE);
      SetStringInfo(theList->Name,"");
      RefreshGList(theList->Name,myWindow,theList->Request,1);
   }
   return(theItem);
}


/*
 *  SelectedListItem()
 *
 *  Return the list's selected item (hold-over from previous approach to
 *  selections)
 */

LISTITEM *SelectedListItem(theList)
LISTINFO *theList;
{
   return(theList->ItemSelect);
}


/*
 *  ActivateName()
 *
 *  If a name gadget is hit, clear any selection in its list.
 *  Attempt to activate the name gadget.  (There is a bug in Intuition
 *  that makes this fail if RETURN was pressed within the name gadget
 *  itself)
 */

void ActivateName(theList)
LISTINFO *theList;
{
   if (theList->Name)
   {
      UnsetListSelect(theList,TRUE);
      ActivateGadget(theList->Name,myWindow,theList->Request);
   }
}


/*
 *  ItemBeforeTop()
 *
 *  returns TRUE if the specified item preceeds the top item in the list.
 */

int ItemBeforeTop(theItem,theTop)
LISTITEM *theItem,*theTop;
{
   while (theItem != theTop && theItem) theItem = theItem->Prev;
   return(theItem == theTop);
}


/*
 *  RemoveListSelect()
 *
 *  If the list has a name gadget
 *    clear the name string and refresh
 *  otherwise if the item is the selected item
 *    if there is an item following the selected one, select it,
 *    otherwise set the selected item to the previous one
 *  otherwise
 *   clear the selection
 */

static void RemoveListSelect(theList,theItem)
LISTINFO *theList;
LISTITEM *theItem;
{
   if (theList->Name)
   {
      SetStringInfo(theList->Name,"");
      RefreshGList(theList->Name,myWindow,theList->Request,1);
   } else if (theItem == theList->ItemSelect) {
      if (theItem->Prev)
      {
         theList->ItemSelect = theItem->Prev;
      } else {
         theList->ItemSelect = theItem->Next;
         theList->SelectedItem--;
      }
   } else {
      theList->ItemSelect = NULL;
      UnsetListSelect(theList,FALSE); 
   }
}


/*
 *  RemoveListItem()
 *
 *  Reduce the list length, and unselect the items
 *  If the list head is the removed item, start the list at the next item
 *  If the item is the top one,
 *    If there is an item following it, make that the top
 *    otherwise, make the top the previous item in the list (if any)
 *  otherwise if the deleted item is before the top item
 *    decrement the list position of the top item
 *  if the deletion has moved the bottom of the list into the viable area
 *    move the list down, if possible, to use all of the list
 *  reset the list names and refresh the list
 *  update the slider
 *  return the currently selected item
 */

LISTITEM *RemoveListItem(theList,theItem)
LISTINFO *theList;
LISTITEM *theItem;
{
   short OldSelect = theList->SelectedItem;
   LISTITEM *ItemTop = theList->ItemTop;

   theList->ListLen--;
   RemoveListSelect(theList,theItem);
   if (theItem == theList->Item) theList->Item = theItem->Prev;
   if (theItem == ItemTop)
   {
      if (ItemTop->Prev)
      {
         ItemTop = ItemTop->Prev;
      } else {
         ItemTop = ItemTop->Next;
         if (theList->TopItem) theList->TopItem--;
      }
   } else if (ItemBeforeTop(theItem,ItemTop)) {
      theList->TopItem--;
   }
   if (theList->TopItem && theList->TopItem+theList->Height > theList->ListLen)
   {
      theList->TopItem--; theList->SelectedItem++;
      ItemTop = ItemTop->Next;
   }
   SetListNames(theList,ItemTop);
   UpdateListSelect(theList,OldSelect);
   RefreshGList(theList->Gadget,myWindow,theList->Request,1);
   UpdateListSlider(theList);
   return(theList->ItemSelect);
}


/*
 *  DoListPageDown()
 *
 *  If there are more items to show
 *    record the old selected position
 *    calculate how many items are left to scroll (clip off at the list height)
 *    scroll the list by the specified amount
 *    set the list to show the new position
 *    put the selection images in place
 *    refresh the list gadget
 *    update the slider to the new position
 */
 
void DoListPageDown(theList)
LISTINFO *theList;
{
   short i,OldSelect;
   LISTITEM *theItem = theList->ItemTop;

   if (theList->TopItem + theList->Height < theList->ListLen)
   {
      OldSelect = theList->SelectedItem;
      i = theList->ListLen - theList->TopItem - theList->Height;
      if (i > theList->Height) i = theList->Height;
      while (i--)
      {
         theItem = theItem->Prev;
         theList->TopItem++; theList->SelectedItem--;
      }
      SetListNames(theList,theItem);
      UpdateListSelect(theList,OldSelect);
      RefreshGList(theList->Gadget,myWindow,theList->Request,1);
      UpdateListSlider(theList);
   }
}
