/*
 *  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:  cSelect.c            Handles the SELECT command.
 */


#include "cGadget.h"
 
int SelectType;

static short BoxX,BoxY,BoxW,BoxH;          /* position ans size of select box */
static short SelectX,SelectY,SelectPen;    /* last selection and pen */

extern short OffMC;                         /* offset within state array */



/*
 *  InvertGadget()
 *
 *  Inverts the specified gadget on the control panel be inverting its
 *  bounding rectangle.
 */

void InvertGadget(theGadget)
struct Gadget *theGadget;
{
   short x = theGadget->LeftEdge,
         y = theGadget->TopEdge,
         w = theGadget->Width,
         h = theGadget->Height;

   SetAPen(wrp,0xFF);
   SetDrMd(wrp,COMPLEMENT);
   RectFill(wrp,x,y,x+w-1,y+h-1);
   theGadget->MyFlags ^= BUTTON_SELECTED;
}


/*
 *  BoxCell()
 *
 *  Draw a border around the specified cell in the specified Rast Port.
 */

void BoxCell(rp,x,y,Pen)
struct RastPort *rp;
short x,y;
int Pen;
{
   short X = x * CellSize + BoardX + 1;
   short Y = y * CellSize + BoardY + 1;

   SetAPen(rp,Pen);
   SetDrMd(rp,JAM1);
   Move(rp,X,Y);
   Draw(rp,X,Y+CellSize-2);
   Draw(rp,X+CellSize-2,Y+CellSize-2);
   Draw(rp,X+CellSize-2,Y);
   Draw(rp,X+1,Y);
}


/*
 *  InvertBox()
 *
 *  Invert the seletion box's boundary.
 */

void InvertBox()
{
   short X = BoxX * CellSize + BoardX;
   short Y = BoxY * CellSize + BoardY;
   short W = BoxW * CellSize;
   short H = BoxH * CellSize;
   
   wrp->Mask = SELECTPEN|GRID;
   SetAPen(wrp,SELECTPEN|GRID);
   SetDrMd(wrp,COMPLEMENT);
   Move(wrp,X,Y);
   Draw(wrp,X,Y+H);
   Draw(wrp,X+W,Y+H);
   Draw(wrp,X+W,Y);
   Draw(wrp,X+1,Y);
   wrp->Mask = 0xFF;
}


/*
 *  SelectMouseCell()
 *
 *  If the mouse event is inside the grid area
 *    If the selected cell is not the last selected cell,
 *      Record that this cell is selected
 *      Record wether we are selecting or deselecting, if necessary
 *      Get the current cell's color
 *      If we are adding to the selection
 *        Box in the newly selected cell (in both buffers)
 *        Set the cell in the selected cell array
 *      Otherwise (unselecting)
 *        Put the cell back to normal (in both buffers)
 *        Clear the cell from the selected array
 */

void SelectMouseCell(MouseX,MouseY,SetPen)
short MouseX,MouseY;
int SetPen;
{
   short x,y;
   int Pen;

   if (InBoard(MouseX,MouseY,&x,&y))
   {
      if (x != SelectX || y != SelectY)
      {
         SelectX = x; SelectY = y;
         x += GridX; y+= GridY;
         if (SetPen) SelectPen = (CellColor(NewGen,x,y) == 0);
         Pen = CellColor(CurGen,x,y);
         if (SelectPen)
         {
            BoxCell(wrp,SelectX,SelectY,MOVEPEN);
            if (DBufMode) BoxCell(rp,SelectX,SelectY,MOVEPEN);
            SetCell(NewGen,x,y,Pen);
         } else {
            BoxCell(wrp,SelectX,SelectY,Pen);
            if (DBufMode) BoxCell(rp,SelectX,SelectY,Pen);
            SetCell(NewGen,x,y,0);
         }
      }
   }
}


/*
 *  CancelSelect()
 *
 *  If we are moving or copying, cancel the move or copy.
 *  Otherwise (we are in Select mode),
 *    Stop getting mouse moves, and don't buffer moves
 *    Put the SELECT button back to normal
 *    Put the pointer back to the drawing color
 *    Refresh any selected cells to their normal colors
 */

void CancelSelect()
{
   switch(MouseMoveType)
   {
      case MM_MOVE:
      case MM_COPY:
         CancelMoveCopy(MouseMoveType);
         break;

      default:
         StopMouseMoves(); MouseMoveType = MM_NONE; BufferMouse = FALSE;
         InvertGadget(&cGadget[ID_SELECT]); SelectType = SEL_NONE;
         SetPointerColor(PenInUse);
         RefreshSelect(CurGen);
         break;
   }
}


/*
 *  StartSelectBox()
 *
 *  If the mouse event is within the grid area
 *    Buffer mouse moves, and start getting mouse events
 *    Set the select type to BOX type
 *    record the initial position and size of the box,
 *    Begin inverting the box
 */

static void StartSelectBox(MouseX,MouseY)
short MouseX,MouseY;
{
   short x,y;

   if (InBoard(MouseX,MouseY,&x,&y))
   {
      BufferMouse = TRUE; StartMouseMoves();
      SelectType = SEL_BOX;
      BoxX = x; BoxY = y; BoxW = 1; BoxH = 1;
      SelectX = x; SelectY = y;
      InvertBox();
   }
}

/*
 *  FinishSelectBox()
 *
 *  Stop getting mouse moves, and remove the box outline
 *  For each cell in the box
 *    If the cell is not a blank cell, or we are going with ALL cells
 *      If we are clearing from the selection
 *        Clear the cell frlom the selection and put back its normal colors
 *      Otherwise
 *        Set the select array to the selected color
 *        Mark the cell as in the selection
 * Update the screen once all the changes have been made
 */

static void FinishSelectBox(MouseX,MouseY,theMessage)
short MouseX,MouseY;
struct IntuiMessage *theMessage;
{
   register short X,Y;
   register short i;
   int DoAll = (theMessage->Qualifier & SHIFTKEYS);
   int ClearCells = (theMessage->Qualifier & ALTKEYS);

   StopMouseMoves(); BufferMouse = FALSE;
   InvertBox();
   SelectType = SEL_WAIT;

   for (Y=BoxY; Y<BoxY+BoxH; Y++)
   {
      for (X=BoxX, i=GridX+X+MAXGRIDW*(GridY+Y); X<BoxX+BoxW; X++,i++)
      {
         if (CurGen[i] != BLANK || DoAll)
         {
            if (ClearCells)
            {
               NewGen[i] = 0;
               BoxCell(rp,X,Y,CurGen[i]);
            } else {
               NewGen[i] = CurGen[i];
               BoxCell(rp,X,Y,MOVEPEN);
            }
         }
      }
   }
   UpdateScreen();
}


/*
 *  SizeSelectBox()
 *
 *  Find the closest cell to the mouse position
 *  If the new position is not the same as the old one
 *    Remove the old box outline
 *    Update the box size
 *    Draw the new box outline
 */

static void SizeSelectBox(MouseX,MouseY)
short MouseX,MouseY;
{
   short x,y;

   RestrictToBoard(MouseX,MouseY,&x,&y);
   if (x != BoxX + BoxW - 1 || y != BoxY + BoxH - 1)
   {
      InvertBox();
      if (x < BoxX) BoxX = x;
      if (y < BoxY) BoxY = y;
      BoxW = x - BoxX + 1;
      BoxH = y - BoxY + 1;
      InvertBox();
   }
}


/*
 *  MoveSelectBox()
 *
 *  Find the closes cell to the mouse position
 *  If it is not the same as the old position
 *    Calculate the new box position
 *    If the position has changed
 *      Remove the old box outline, move the box, and draw the new outline
 */

static void MoveSelectBox(MouseX,MouseY)
short MouseX,MouseY;
{
   short x,y;

   RestrictToBoard(MouseX,MouseY,&x,&y);
   if (x != BoxX + BoxW - 1 || y != BoxY + BoxH - 1)
   {
      x = x - BoxW + 1;
      y = y - BoxH + 1;
      if (x < 0) x = 0;
      if (y < 0) y = 0;
      if (x != BoxX || y != BoxY)
      {
         InvertBox();
         BoxX = x; BoxY = y;
         InvertBox();
      }
   }
}


/*
 *  DoSelectMove()
 *
 *  If we are dragging a box, size the box
 *  If we are selecting cells, select them
 *  If we are repositioning the box, do so
 */

void DoSelectMove(MouseX,MouseY,theMessage)
{
   switch(SelectType)
   {
      case SEL_BOX:
         SizeSelectBox(MouseX,MouseY);
         break;

      case SEL_CELLS:
         SelectMouseCell(MouseX,MouseY,FALSE);
         break;

      case SEL_REPOSITION:
         MoveSelectBox(MouseX,MouseY);
         break;
   }
}


/*
 *  DoSelectButton()
 *
 *  For a SELECTDOWN:
 *    if we aren't doing something already, start a selection box
 *
 *  For a SELECTUP:
 *    If we have a select box in action, finish it
 *
 *  For a MENUDOWN:
 *    If we are not doing anything else:
 *      Start getting mouse moves,
 *      Select the cell at the current position, and record that position
 *    If we are dragging a select box, start repositioning it
 *
 *  For a MENUUP:
 *    If we are selecting cells,
 *      stop getting mouse moves and go back to waiting
 *    If we are repositioning the selection box:
 *      go back to resizing the box
 */

void DoSelectButton(Code,MouseX,MouseY,theMessage)
USHORT Code;
short MouseX,MouseY;
struct IntuiMessage *theMessage;
{
   switch(Code)
   {
      case SELECTDOWN:
         if (SelectType == SEL_WAIT) StartSelectBox(MouseX,MouseY);
         break;

      case SELECTUP:
         if (SelectType == SEL_BOX || SelectType == SEL_REPOSITION)
             FinishSelectBox(MouseX,MouseY,theMessage);
         break;

      case MENUDOWN:
         switch(SelectType)
         {
            case SEL_WAIT:
               StartMouseMoves();
               SelectMouseCell(MouseX,MouseY,TRUE);
               SelectType = SEL_CELLS;
               SelectX = GridW; SelectY = GridH;
               break;

            case SEL_BOX:
               SelectType = SEL_REPOSITION;
               MoveSelectBox(MouseX,MouseY);
               break;
         }
         break;

      case MENUUP:
         switch(SelectType)
         {
            case SEL_CELLS:
               StopMouseMoves();
               SelectType = SEL_WAIT;
               break;

            case SEL_REPOSITION:
               SelectType = SEL_BOX;
               SizeSelectBox(MouseX,MouseY);
               break;
         }
         break;
   }
}


/*
 *  DoSelect()
 *
 *  If a selection is already in progress, cancel the selection.
 *  If a move or copy is in progress cancel it, then start a new selection
 *  Otherwise
 *    Put the pointer color back to normal and invert the SELECT button
 *    Set the flags to Select mode
 *    Clear the select grid to zero, and clear the offset counter
 */

void DoSelect()
{
   switch(MouseMoveType)
   {
      case MM_SELECT:
         CancelSelect();
         break;

      case MM_MOVE:
      case MM_COPY:
         CancelSelect();
      default:
         RestorePointerColor();
         InvertGadget(&cGadget[ID_SELECT]);
         SelectType = SEL_WAIT; MouseMoveType = MM_SELECT;
         ZeroGrid(NewGen); OffMC = 0;
         break;
   }
}
