/*
 *  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:  cDoKey.c             Handles keyboard events
 */


#include "cGadget.h"

#define ESC         0x45
#define DBUFKEY     0x35        /* 'B' key */
#define HELP        0x5F
#define DELETE      0x46
#define BACKSPACE   0x41
#define ENTER       0x43

int Shifted;                    /* TRUE if gadgets are shifted */
int DelayedShift;               /* TRUE if gadgets should be shifted */
                                /*  at the next opportunity */


/*
 *  DisableGadget()
 *
 *  Remove the gadget, disable it, add it back in, then refresh the gadget.
 *  (the OffGadget call refreshes ALL the gadgets - too noisey)
 */

static void DisableGadget(theGadget)
struct Gadget *theGadget;
{
   int pos;

   pos = RemoveGadget(myWindow,theGadget);
   theGadget->Flags |= GADGDISABLED;
   AddGadget(myWindow,theGadget,pos);
   RefreshGList(theGadget,myWindow,NULL,1);
}


/*
 *  EnableGadget()
 *
 *  Remove the gadget, enable it, and add it back in.  Clear the
 *  gadgets background, and then refresh the gadget.
 */

static void EnableGadget(theGadget)
struct Gadget *theGadget;
{
   int x = theGadget->LeftEdge,
       y = theGadget->TopEdge,
       w = theGadget->Width,
       h = theGadget->Height;
   int pos;

   pos = RemoveGadget(myWindow,theGadget);
   theGadget->Flags &= ~GADGDISABLED;
   AddGadget(myWindow,theGadget,pos);
   SetAPen(wrp,FOREGROUND);
   SetDrMd(wrp,JAM1);
   RectFill(wrp,x,y,x+w-1,y+h-1);
   RefreshGList(theGadget,myWindow,NULL,1);
}


/*
 *  SetRunning()
 *
 *  If the START gadget is not shifted,
 *    turn the gadget into the STOP gadget and refresh it.
 *    disable the editing buttons.
 *  set the Running flag.
 */

void SetRunning()
{
   struct Gadget *theGadget = &cGadget[ID_START];
   short i;

   if (theGadget->GadgetID == ID_START)
   {
      Forbid();
      theGadget->GadgetID = ID_STOP;
      theGadget->GadgetText = &cIText[TEXT_STOP];
      if (theGadget->Flags & SELECTED) InvertGadget(theGadget);
      theGadget->MyFlags &= ~BUTTON_SELECTED;
      RefreshGList(theGadget,myWindow,NULL,1);
      Permit();
      for (i=ID_SAVE; i<ID_HELP; DisableGadget(&cGadget[i++]));
   }
   Running = TRUE;
}


/*
 *  SetStopped()
 *
 *  If the START gadget is still the STOP gadget,
 *    turn the gadget back into the START gadget,
 *    enable the editing gadgets again
 *  Reset the Running flag.
 */

void SetStopped()
{
   struct Gadget *theGadget = &cGadget[ID_START];
   short i;

   if (theGadget->GadgetID == ID_STOP)
   {
      Forbid();
      theGadget->GadgetID = ID_START;
      theGadget->GadgetText = &cIText[ID_START];
      if (theGadget->Flags & SELECTED) InvertGadget(theGadget);
      theGadget->MyFlags &= ~BUTTON_SELECTED;
      RefreshGList(theGadget,myWindow,NULL,1);
      Permit();
      for (i=ID_SAVE; i<ID_HELP; EnableGadget(&cGadget[i++]));
   }
   Running = FALSE;
}


/*
 *  ShowShiftedGadgets()
 *
 *  For each of the button gadgets:
 *    if the button is not selected and is allowed to shift, and the
 *    gadget is not already shifted, and if we are not running,
 *    or we are allowed to shift while running,
 *      set the gadget to the shifted name and refresh it.
 *    go on to the next gadget.
 *  if the Zoom gadget is still the Zoom gadget,
 *    convert it to the Shrink gadget.
 */

void ShowShiftedGadgets()
{
   struct Gadget *theGadget = &cGadget[0];
   
   while (theGadget && (theGadget->MyFlags & IS_BUTTON))
   {
      if ((theGadget->MyFlags & (BUTTON_NOSHIFT|BUTTON_SELECTED)) == 0 &&
           theGadget->GadgetID < ID_OFFSET &&
          (!Running || (theGadget->MyFlags & BUTTON_RUNSHIFT)))
      {
         Forbid();
         theGadget->GadgetText = &cIText[theGadget->GadgetID+TEXT_OFFSET];
         theGadget->GadgetID += ID_OFFSET;
         if (theGadget->Flags & SELECTED) InvertGadget(theGadget);
         theGadget->MyFlags &= ~BUTTON_SELECTED;
         RefreshGList(theGadget,myWindow,NULL,1);
         Permit();
      }
      theGadget = theGadget->NextGadget;
   }
   theGadget = &cGadget[ID_ZOOM];
   if (theGadget->GadgetID == ID_ZOOM)
   {
      Forbid();
      theGadget->GadgetRender = (APTR) &cImage[IM_SHRINK];
      theGadget->GadgetID = ID_SHRINK;
      if (theGadget->Flags & SELECTED) InvertGadget(theGadget);
      theGadget->MyFlags &= ~BUTTON_SELECTED;
      RefreshGList(theGadget,myWindow,NULL,1);
      Permit();
   }
}


/*
 *  ShowNormalGadgets()
 *
 *  For each button gadget:
 *    if the button is not selected and is allowed to shift, and the
 *    gadget is shifted, and if we are not running,
 *    or we are allowed to shift while running,
 *      set the gadget to the unshifted name and refresh it.
 *    go on to the next gadget.
 *  if the Zoom gadget is the Shrink gadget,
 *    convert it to the Zoom gadget
 */

void ShowNormalGadgets()
{
   struct Gadget *theGadget = &cGadget[0];
   
   while (theGadget && (theGadget->MyFlags & IS_BUTTON))
   {
      if ((theGadget->MyFlags & (BUTTON_NOSHIFT|BUTTON_SELECTED)) == 0 &&
           theGadget->GadgetID >= ID_OFFSET &&
          (!Running || (theGadget->MyFlags & BUTTON_RUNSHIFT)))
      {
         Forbid();
         theGadget->GadgetID -= ID_OFFSET;
         theGadget->GadgetText = &cIText[theGadget->GadgetID];
         if (theGadget->Flags & SELECTED) InvertGadget(theGadget);
         theGadget->MyFlags &= ~BUTTON_SELECTED;
         RefreshGList(theGadget,myWindow,NULL,1);
         Permit();
      }
      theGadget = theGadget->NextGadget;
   }
   theGadget = &cGadget[ID_ZOOM];
   if (theGadget->GadgetID == ID_SHRINK)
   {
      Forbid();
      theGadget->GadgetRender = (APTR) &cImage[IM_ZOOM];
      theGadget->GadgetID = ID_ZOOM;
      if (theGadget->Flags & SELECTED) InvertGadget(theGadget);
      theGadget->MyFlags &= ~BUTTON_SELECTED;
      RefreshGList(theGadget,myWindow,NULL,1);
      Permit();
   }
}


/*
 *  ShiftGadgets()
 *
 *  If no requester is active,
 *    if shift keys are pressed,
 *      if we're not already shifted, shift the gadgets
 *    Otherwise
 *      if we're already shifted, unshift the gadgets
 *  record the delayed shift
 */

void ShiftGadgets(keys)
ULONG keys;
{
   if (ActiveRequest == NULL)
   {
      if (keys)
      {
         if (!Shifted) ShowShiftedGadgets();
         Shifted = TRUE;
      } else {
         if (Shifted)  ShowNormalGadgets();
         Shifted = FALSE;
      }
   }
   DelayedShift = (keys != 0);
}


/*
 *  UpdateShiftKeys()
 *
 *  If delayed shift is different frlom the current shift, and there
 *  is no active gadget, update the gadgets as needed, and record the
 *  status.
 */

void UpdateShiftKeys()
{
   if (DelayedShift != Shifted && ActiveRequest == NULL)
   {
      if (DelayedShift)
         ShowShiftedGadgets();
        else
         ShowNormalGadgets();
      Shifted = DelayedShift;
   }
}


/*
 *  DoKey()
 *
 *  Do the right thing for each of the special keys:
 *
 *    ESC:          Attempt to quit.
 *    DBUFKEY:      Change the double-buffer mode.
 *    CURSORUP:             Increase the generation delay time.
 *    CURSORDOWN:           Decrease the generation delay time.
 *    HELP:                 Unshift all gadgets, and do the HELP function.
 *    DELETE or BACKSPACE:  Do the Clear function.
 *    ENTER:                Cancel any selection, and step the circuit.
 */

int DoKey(theCode,theMessage,NotDone)
USHORT theCode;
struct IntuiMessage *theMessage;
short NotDone;
{
   switch(theCode)
   {
      case ESC:
         NotDone = DoQuit(NotDone);
         break;

      case DBUFKEY:
         SetDBufMode(!DBufMode);
         break;

      case CURSORUP:
         if (GenerationDelay > 8)
            GenerationDelay = GenerationDelay * 4 / 3;
           else
            GenerationDelay++;
         break;

      case CURSORDOWN:
         if (GenerationDelay > 8)
            GenerationDelay = GenerationDelay * 3 / 4;
           else
            if (GenerationDelay) GenerationDelay--;
         break;

      case HELP:
         ShiftGadgets(FALSE);
         DoHelp();
         break;

      case DELETE:
      case BACKSPACE:
         DoClear();
         break;

      case ENTER:
         if (SelectType) CancelSelect();
         StepCells();
         break;
   }
   return(NotDone);
}


/*
 *  DoInactive()
 *
 *  If the inactivated window is the CELLS window,
 *    fake a menu up (to finish drawing or dragging),
 *    fake a select up (to finish drawing or selecting),
 *    unshift the gadgets.
 */

void DoInactive(theWindow,theMessage)
struct Window *theWindow;
struct IntuiMessage *theMessage;
{
   if (theWindow == myWindow)
   {
      DoMouseButton(MENUUP,theMessage->MouseX,theMessage->MouseY,theMessage);
      DoMouseButton(SELECTUP,theMessage->MouseX,theMessage->MouseY,theMessage);
      ShiftGadgets(FALSE);
   }
}
