/*
 *  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:  cView.c          Handles the VIEW mode of the Load PArt requester.
 */


#include "cGadget.h"
#include "cRequest.h"
#include "cReqLP.h" 
#include "cParts.h"

#define IMAGEW          (LP_PNAMEW-2)
#define IMAGEH          (LP_PNAMEH-2)
#define BOXSIZE         4
#define BOXW            ((IMAGEW-3)/BOXSIZE)
#define BOXH            ((IMAGEH-3)/BOXSIZE)

#define SCROLLDELAY     6

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

/*
 *  Image structure used to draw the small picture of the parts
 */

static struct Image ViewImage =
   {1,1, IMAGEW,IMAGEH,SCREEND, NULL, 0xFF, 0x00, NULL};

int Viewing;                    /* TRUE if in VIEW mode */
static LISTINFO *ActiveList;    /* which list's slider is active */


extern void LPGadgetUp(),LPGadgetDown(),DoListMouse();


/*
 *  PartToImage()
 *
 *  Clear the image to the background color
 *  If the part fits within the image, center it, otherwise, clip it
 *  Set the drawing mode
 *  for each cell in the part
 *    if the cell is within the visible area of the part within the image
 *      if the cell is non-zero
 *        fill in the cell in the image
 */

static void PartToImage(rp,thePart)
struct RastPort *rp;
PART *thePart;
{
   register short X,Y;
   register UBYTE *theByte = thePart->Data;
   short x,y,w,h;
   short dx,dy;
   short Bx,By;

   SetRast(rp,BACKGROUND);
   if (thePart->w > BOXW)
   {
      x = (thePart->w - BOXW) / 2;
      w = BOXW + x;
      dx = 2;
   } else {
      x = 0;
      w = thePart->w;
      dx = (BOXW - w) / 2 * BOXSIZE + 2;
   }
   if (thePart->h > BOXH)
   {
      y = (thePart->h - BOXH) / 2;
      h = BOXH + y;
      dy = 2;
   } else {
      y = 0;
      h = thePart->h;
      dy = (BOXH - h) / 2 * BOXSIZE + 2;
   }
   
   SetDrMd(rp,JAM1);
   for (Y=0; Y<thePart->h; Y++)
   {
      for (X=0; X<thePart->w; X++,theByte++)
      {
         if (X >= x && X < w && Y >= y && Y < h)
         {
            if (*theByte)
            {
               Bx = dx + BOXSIZE * (X - x);
               By = dy + BOXSIZE * (Y - y);
               SetAPen(rp,(int)(*theByte));
               RectFill(rp,Bx,By,Bx+BOXSIZE-2,By+BOXSIZE-2);
            }
         }
      }
   }
}


/*
 *  SetViewImage()
 *
 *  Set up the list to point to this item
 *  If the item exists
 *    set the list to select this item
 *    get the part's image
 *    show the part's name
 *  otherwise
 *    clear the image area and name
 *  If the Load Part requester is up, refresh the gadgets
 */

static void SetViewImage(theList,theItem)
LISTINFO *theList;
LISTITEM *theItem;
{
   theList->ItemTop = theItem;
   if (theItem)
   {
      theList->ItemSelect = theItem;
      theList->SelectedItem = theList->TopItem;
      PartToImage(irp,theItem);
      SetStringInfo(theList->Name,theItem->Name);
   } else {
      SetRast(irp,FOREGROUND);
      SetStringInfo(theList->Name,"");
   }
   if (ActiveRequest)
   {
      RefreshGList(theList->Gadget,myWindow,theList->Request,1);
      RefreshGList(theList->Name,myWindow,theList->Request,1);
   }
}


/*
 *  UpdateViewSlider()
 *
 *  Calculate the new body and pot values for the current list position
 *  If the Load Part requester is up,
 *    if the values are not the current ones, update the slider gadget
 *  otherwise
 *   set the slider values to the new values
 */

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

   if (theList->ListLen > 1)
   {
      Body = 0xFFFF / theList->ListLen;
      Pot  = 0xFFFF * theList->TopItem / (theList->ListLen - 1);
   } 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;
   }
}


/*
 *  RemoveViewItem()
 *
 *  Decrease the list length
 *  If the deleted item is the head of the list, set the head to the next one
 *  if the deleted item is the displayed item,
 *    select the next item in the list, if possible, and the previous
 *    item if not, and NULL, if no previous or next item.
 *  otherwise, if the deleted item is before the top item in the list,
 *    decrement the top item counter to reflect the deletion
 *  Unselect the item, if it was selected
 *  Show the new selected item
 *  Update the sliders to the new position and length
 */

void RemoveViewItem(theList,theItem)
LISTINFO *theList;
LISTITEM *theItem;
{
   LISTITEM *ItemTop = theList->ItemTop;

   theList->ListLen--;
   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->ItemSelect) theList->ItemSelect->Flags &= ~LI_SELECTED;
   theList->ItemSelect = NULL;
   SetViewImage(theList,ItemTop);
   UpdateViewSlider(theList);
}


/*
 *  SetView()
 *
 *  Initialise the list manager to use the given list
 *  setup the image of the current item
 *  update the sliders to their new position and size
 */

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

   theItem = InitList(theList,theItem,SelectItem);
   SetViewImage(theList,theItem);
   UpdateViewSlider(theList);
}


/*
 *  DoScrollUp()
 *
 *  while the arrow is being held down
 *    if there are more parts to scroll
 *      move to the next part and display it
 *      update the sliders
 *      delay (if necessary)
 */

void DoViewScrollUp(theList,theGadget,theMessage)
LISTINFO *theList;
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   while (theGadget->Flags & SELECTED)
   {
      if (theList->TopItem)
      {
         theList->TopItem--;
         SetViewImage(theList,theList->ItemTop->Next);
         RefreshGList(theList->Gadget,myWindow,theList->Request,1);
         UpdateViewSlider(theList);
         #if SCROLLDELAY > 0
            Delay(SCROLLDELAY);
         #endif
      }
   }
}

/*
 *  DoScrollDown()
 *
 *  while the arrow is being held down
 *    if there are more parts to scroll
 *      move to the next part and display it
 *      update the sliders
 *      delay (if necessary)
 */

void DoViewScrollDown(theList,theGadget,theMessage)
LISTINFO *theList;
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   while (theGadget->Flags & SELECTED)
   {
      if (theList->TopItem + 1 < theList->ListLen)
      {
         theList->TopItem++;
         SetViewImage(theList,theList->ItemTop->Prev);
         RefreshGList(theList->Gadget,myWindow,theList->Request,1);
         UpdateViewSlider(theList);
         #if SCROLLDELAY > 0
            Delay(SCROLLDELAY);
         #endif
      }
   }
}


/*
 *  DoViewSlider()
 *
 *  Find the position in the list of the new slider location
 *  If the position has changed
 *    Look through the list until we get to the specified position
 *    display the proper part image
 */

void DoViewSlider(theList)
LISTINFO *theList;
{
   LISTITEM *theItem = theList->Item;
   short Top;
   short Size = theList->ListLen - 1;
   
   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)
   {
      theList->TopItem = Top;
      while (Top-- && theItem) theItem = theItem->Prev;
      SetViewImage(theList,theItem);
   }
}


/*
 *  ViewGadgetUp()
 *
 *  Do the right thing for gadget up events in VIEW mode:
 *
 *  Parts slider:
 *    stop getting mouse moves and make no slider as active
 *    update the slider position
 *
 *  Everything else:
 *    do the regular gadget up routine
 */

void ViewGadgetUp(theGadget,theMessage)
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   switch(theGadget->GadgetID)
   {
      case LP_PSLIDE:
         StopMouseMoves(); ActiveList = NULL;
         DoViewSlider(theGadget->UserData);
         break;

      default:
         LPGadgetUp(theGadget,theMessage);
         break;
   }
}


/*
 * ViewGadgetDown()
 *
 *  Do the right thing for gadget down events in VIEW mode:
 *
 *  Ignore hits in the name and parts list gadgets
 *  Arrows:  scroll the parts list in VIEW mode
 *  Slider:  if the knob is hit, start dragging poperations
 *  Everything else:  do the regular gadget down routine
 */

void ViewGadgetDown(theGadget,theMessage)
struct Gadget *theGadget;
struct IntuiMessage *theMessage;
{
   LISTINFO *theList;

   switch(theGadget->GadgetID)
   {
      case LP_PNAMES:
      case LP_NAME:
         break;

      case LP_PUARROW:
         DoViewScrollUp(theGadget->UserData,theGadget,theMessage);
         break;

      case LP_PDARROW:
         DoViewScrollDown(theGadget->UserData,theGadget,theMessage);
         break;

      case LP_PSLIDE:
         theList = (LISTINFO *)theGadget->UserData;
         if (SLIDERINFO->Flags & KNOBHIT)
         {
            DoViewSlider(theList);
            StartMouseMoves();
            ActiveList = theList;
         }
         break;

      default:
         LPGadgetDown(theGadget,theMessage);
         break;
   }
}


/*
 *  DoViewMouse()
 *
 *  if a list is active, do the slider operation in VIEW mode
 */

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


/*
 *  SetViewMode()
 *
 *  If already in VIEW mode
 *    clear the image bitmap
 *    set things up for a text list again
 *    call the list manager to initialize the list
 *    change the gadget routines back to normal
 *    cancel VIEW mode
 *  Otherwise
 *    set up the image to use the bitmap data area
 *    set up the list gadget to use the image
 *    display the selected part
 *    set the gadget routines to the VIEW mode routines
 *    set VIEW mode to TRUE
 */

void SetViewMode(theList,theItem,SelectItem)
LISTINFO *theList;
LISTITEM *theItem;
LISTITEM *SelectItem;
{
   if (Viewing)
   {
      SetRast(irp,FOREGROUND);
      RefreshGList(theList->Gadget,myWindow,theList->Request,1);
      theList->Gadget->GadgetText = theList->IText;
      theList->Gadget->GadgetRender = (APTR)theList->Image;
      SetList(theList->Gadget,theItem,SelectItem);
      theList->Request->er_GadgetDown = &LPGadgetDown;
      theList->Request->er_GadgetUp   = &LPGadgetUp;
      theList->Request->er_MouseMove  = &DoListMouse;
      Viewing = FALSE;
   } else {
      ViewImage.ImageData = (USHORT *)irp->BitMap->Planes[0];
      theList->Gadget->GadgetText = NULL;
      theList->Gadget->GadgetRender = (APTR)&ViewImage;
      SetView(theList->Gadget,theItem,SelectItem);
      theList->Request->er_GadgetDown = &ViewGadgetDown;
      theList->Request->er_GadgetUp   = &ViewGadgetUp;
      theList->Request->er_MouseMove  = &DoViewMouse;
      Viewing = TRUE;
   }
}
