/*
 *  ZOOM-DAEMON   A program that implements Zoom gadgets for all Intuition
 *                windows that are opened while it is running.
 *
 *              Copyright 1989 by Davide P. Cervone.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "Zoom-Handler.h"

static char *program   = PROGRAM;
static char *copyright = COPYRIGHT;

struct ExtGadget *FirstZoom;                /* Linked list of Zoom Gadgets */


/*
 *  The Image structures for the HIRES and LOWRES Zoom Gadgets
 */

static struct Image ZoomImage =
   {-6,0, ZOOMWIDTH,ZOOMHEIGHT,ZOOMDEPTH, &ZoomData[0][0], 0x03,0x00, NULL};

static struct Image LR_ZoomImage =
   {-4,0, LRZOOMWIDTH,LRZOOMHEIGHT,LRZOOMDEPTH, &LR_ZoomData[0][0],
   0x03,0x00,NULL};

/*
 *  Templates for the HIRES and LOWRES Zoom Gadgets (copied when a new
 *  instance of a Zoom Gadget is created).
 */

static struct ExtGadget ZoomGadget =
{
   NULL,NULL, NULL, 0,0,0,0, 0,
   {NULL, 0,0, 21,10, GADGHCOMP | GADGIMAGE | GRELRIGHT,
   RELVERIFY | TOPBORDER, SYSGADGET | BOOLGADGET, (APTR)&ZoomImage,
   NULL, NULL, 0, NULL, ZOOMGADG, NULL}
};

static struct ExtGadget LR_ZoomGadget =
{
   NULL,NULL, NULL, 0,0,0,0, 0,
   {NULL, 0,0, 15,10, GADGHCOMP | GADGIMAGE | GRELRIGHT,
   RELVERIFY | TOPBORDER, SYSGADGET | BOOLGADGET, (APTR)&LR_ZoomImage,
   NULL, NULL, 0, NULL, ZOOMGADG, NULL}
};


/*
 *  PositionZoomGadget()
 *
 *  Finds the position for the zoom gadget by checking for GRELRIGHT
 *  gadgets in the window's gadget list.  Gadgets in the TOPBORDER are
 *  counted, but since the DEPTH gadgets are not listed with TOPBORDER
 *  flags set, we also check for SYSGADGETs that are GRELGADGETs but not
 *  GRELBOTTOM gadgets (the SIZING gadget is GRELRIGHT and GRELBOTTOM),
 *  the DEPTH gadgets are just GRELRIGHT).  The farthest left position is
 *  recorded, and the Zoom Gadget is placed just to the left of that.
 */

static void PositionZoomGadget(zGadget,theWindow)
struct Gadget *zGadget;
struct Window *theWindow;
{
   struct Gadget *theGadget = theWindow->FirstGadget;
   int FarthestLeft = 1;
   
   while (theGadget)
   {
      if ((theGadget->Flags & (GRELRIGHT|GRELBOTTOM)) == GRELRIGHT &&
           theGadget->TopEdge <= zGadget->Height &&
           theGadget->LeftEdge < FarthestLeft)
              FarthestLeft = theGadget->LeftEdge;
      theGadget = theGadget->NextGadget;
   }
   zGadget->LeftEdge = FarthestLeft - zGadget->Width;
}


/*
 *  AddZoomGadget()
 *
 *  Allocate a new Zoom Gadget structure, and initialize it for the proper
 *  resolution of the window it is attached to.  Set the position of the
 *  gadget so that it is to the left of the depth gadgets.  Then link it 
 *  into the linked list of zoom gadgets so that we can keep track of it
 *  (Forbid() so that the linked list does not change while we are using it).
 *  Add the new gadget into the window, and refresh it so that it is 
 *  displayed properly.  Since the gadget is added to the beginning of the
 *  list, it will lie on top of the drag bar (if any).
 */

static void AddZoomGadget(theWindow)
struct Window *theWindow;
{
   ULONG Resolution = theWindow->WScreen->ViewPort.Modes & HIRES;
   struct ExtGadget *theGadget;
   
   theGadget = AllocMem(EXTGADGETSIZE,MEMFLAGS);
   if (theGadget)
   {
      theGadget->Window = theWindow;
      theGadget->Flags = 0;
      if (Resolution == HIRES)
         theGadget->Gadget = ZoomGadget.Gadget;
        else
         theGadget->Gadget = LR_ZoomGadget.Gadget;
      PositionZoomGadget(&(theGadget->Gadget),theWindow);
      if (theWindow->Flags & GIMMEZEROZERO)
         theGadget->Gadget.GadgetType |= GZZGADGET;

      Forbid();
      theGadget->Prev = NULL;
      theGadget->Next = FirstZoom;
      if (FirstZoom) FirstZoom->Prev = theGadget;
      FirstZoom = theGadget;
      Permit();

      AddGadget(theWindow,&(theGadget->Gadget),0);
      RefreshGList(&(theGadget->Gadget),theWindow,NULL,1);
   }
}


/*
 *  cOpenWindow()
 *
 *  This is called after a window has been opened:  theWindow is the 
 *  pointer to the opened window.
 *
 *  If the window gets NEWSIZE IntuiMessages, or if it includes a SIZING
 *  gadget, then add the Zoom Gadget.  We assume that windows that do not
 *  include one of these are not allowed to change size, so we don't add
 *  Zoom Gadgets to them.
 */

struct Window *cOpenWindow(theWindow)
struct Window *theWindow;
{
   if (theWindow)
   {
      if ((theWindow->IDCMPFlags & NEWSIZE) ||
          (theWindow->Flags & WINDOWSIZING))
              AddZoomGadget(theWindow);
   }
   return(theWindow);
}


/*
 *  FindZoomGadget()
 *
 *  Look through the linked list for a gadget attached to this window
 *  (alternatively, we could look through the window's gadget list for
 *  one that has the ZOOMGADG Id, but this is probably safer).
 *  Do this in Forbid() mode so that the list does not change while
 *  we are looking at it.
 */

static struct ExtGadget *FindZoomGadget(theWindow)
struct Window *theWindow;
{
   struct ExtGadget *theGadget;
   
   Forbid();
   theGadget = FirstZoom;
   while (theGadget && theGadget->Window != theWindow)
      theGadget = theGadget->Next;
   Permit();
   return(theGadget);
}


/*
 *  cCloseWindow()
 *
 *  This is called before the window is closed.
 *
 *  If a window is being closed, check to see if it has a Zoom Gadget.
 *  If so, then remove the gadget from the window, unlink it from
 *  the linked list of gadgets, and free its memory.  This is done in
 *  Forbid() mode so that the linked list of zoom gadgets does not change
 *  while we are looking at it.
 */

void cCloseWindow(theWindow)
struct Window *theWindow;
{
   struct ExtGadget *theGadget;

   if (theWindow)
   {
      Forbid();
      theGadget = FindZoomGadget(theWindow);
      if (theGadget)
      {
         RemoveGadget(theWindow,&(theGadget->Gadget));
         if (theGadget->Next) theGadget->Next->Prev = theGadget->Prev;
         if (theGadget->Prev) theGadget->Prev->Next = theGadget->Next;
         if (theGadget == FirstZoom) FirstZoom = theGadget->Next;
         FreeMem(theGadget,EXTGADGETSIZE);
      }
      Permit();
   }
}


/*
 *  CheckZoomMove()
 *
 *  Called when a new gadget is being added to a window, this checks to
 *  see if it is first in the gadget list and that it is being added to
 *  a window (not a screen or requester).  If so, look for a zoom gadget
 *  attached to this window.  If one exists, then remove it temporarily,
 *  reposition it in relation to other GRELRIGHT gadgets, and then add it
 *  back at the front of the list (make sure it's displayed on top).
 *  Ten refresh the display so that the old image is removed and the new
 *  one is displayed.
 */

static void CheckZoomMove(theParent,theGadget,Position)
struct Window *theParent;
struct Gadget *theGadget;
int Position;
{
   struct ExtGadget *zGadget;

   if (Position == 0 && (theGadget->GadgetType & (SCRGADGET|REQGADGET)) == 0)
   {
      zGadget = FindZoomGadget(theParent);
      if (zGadget && theGadget != &(zGadget->Gadget))
      {
         RemoveGadget(theParent,&(zGadget->Gadget));
         PositionZoomGadget(&(zGadget->Gadget),theParent);
         aOldAddGadget(theParent,&(zGadget->Gadget),0);
         RefreshWindowFrame(theParent);
      }
   }
}


/*
 *  cAddGadget()
 *
 *  This is called before the gadget is added, and we are expected to call
 *  aOldAddGadget somewhere in this routine.  The return address is the
 *  position of the new gadget in the list.
 *
 *  First we add the new gadget into the list, then check the list for
 *  Zoom Gadgets and reposition them as needed.
 */

int cAddGadget(theParent,theGadget,Position)
struct Window *theParent;
struct Gadget *theGadget;
int Position;
{
   Position = aOldAddGadget(theParent,theGadget,Position);
   CheckZoomMove(theParent,theGadget,Position);
   return(Position);
}


/*
 *  cAddGList()
 *
 *  This is called before the gadgets are added, and we are expected to call
 *  aOldAddGList somewhere in this routine.  The return address is the
 *  position of the new gadgets in the list.
 *
 *  First we add the new gadgets into the list, then check the list for
 *  Zoom Gadgets and reposition them as needed.
 */

int cAddGList(theParent,theGadget,Position,Count,theRequest)
struct Window *theParent;
struct Gadget *theGadget;
int Position;
int Count;
struct Requester *theRequest;
{
   Position = aOldAddGList(theParent,theGadget,Position,Count,theRequest);
   CheckZoomMove(theParent,theGadget,Position);
   return(Position);
}

/*
 *  ResizeWindow()
 *
 *  This routine provides a safe way to resize and move a window (the size
 *  and position may be changed in order to make the window fit the screen).
 *
 *  Check to be sure the width and height are mot too big.
 *  Check to be sure the position makes the whole window fit on the screen.
 *
 *  Calculate the offsets needed to make the window the right size and place
 *  If the new widths would take it off the screen,
 *    Move the window to its new position, and set the offsets to zero.
 *  Size the window to make it the new size.
 *  Move the window to its new position.
 */

static void ResizeWindow(theWindow,x,y,w,h)
struct Window *theWindow;
int x,y,w,h;
{
   struct Screen *theScreen = theWindow->WScreen;
   int dx,dy,dh,dw;

   if (w < 0) w = theScreen->Width; else
   if (w > theScreen->Width)  w = theScreen->Width;
   if (h < 0) h = theScreen->Height; else
   if (h > theScreen->Height) h = theScreen->Height;
   
   if (x < 0) x = 0; else
   if (x + w > theScreen->Width)  x = theScreen->Width  - w;
   if (y < 0) y = 0; else
   if (y + h > theScreen->Height) y = theScreen->Height - h;

   dx = x - theWindow->LeftEdge; dy = y - theWindow->TopEdge;
   dw = w - theWindow->Width; dh = h - theWindow->Height;

   if (theWindow->LeftEdge + w > theScreen->Width ||
       theWindow->TopEdge  + h > theScreen->Height)
   {
      MoveWindow(theWindow,dx,dy);
      dx = dy = 0;
   }
   if (dw || dh) SizeWindow(theWindow,dw,dh);
   if (dx || dy) MoveWindow(theWindow,dx,dy);
}


/*
 *  ZoomOutWindow()
 *
 *  If the window has already been zoomed,
 *    Resize the window to its original size.
 *    Mark the window as not ZOOMED.
 *  Otherwise the window should be sized to the full size of the screen.
 *    If the window is not zoomed in then
 *      Save the window's current position and size.
 *    Zoom the window out to the size of the screen.
 *    Mark the window as ZOOMED.
 */

static void ZoomOutWindow(theGadget)
struct ExtGadget *theGadget;
{
   struct Window *theWindow = theGadget->Window;

   if (theGadget->Flags & EG_ZOOMEDOUT)
   {
      ResizeWindow(theWindow,theGadget->x,theGadget->y,
                             theGadget->w,theGadget->h);
      theGadget->Flags = 0;
   } else {
      if ((theGadget->Flags & EG_ZOOMED) == 0)
      {
         theGadget->x = theWindow->LeftEdge;
         theGadget->y = theWindow->TopEdge;
         theGadget->w = theWindow->Width;
         theGadget->h = theWindow->Height;
      }
      ResizeWindow(theWindow,0,0,theWindow->MaxWidth,theWindow->MaxHeight);
      theGadget->Flags = EG_ZOOMEDOUT;
   }
}


/*
 *  ZoomInWindow()
 *
 *  If the window has already been zoomed,
 *    Resize the window to its original size.
 *    Mark the window as not ZOOMED.
 *  Otherwise the window should be sized to its minimum size.
 *    If the window is not zoomed out then
 *      Save the window's current position and size.
 *    Zoom the window to its smallest size.
 *    Mark the window as ZOOMED.
 */

static void ZoomInWindow(theGadget)
struct ExtGadget *theGadget;
{
   struct Window *theWindow = theGadget->Window;
   int w,h;

   if (theGadget->Flags & EG_ZOOMEDIN)
   {
      ResizeWindow(theWindow,theGadget->x,theGadget->y,
                             theGadget->w,theGadget->h);
      theGadget->Flags = 0;
   } else {
      if ((theGadget->Flags & EG_ZOOMED) == 0)
      {
         theGadget->x = theWindow->LeftEdge;
         theGadget->y = theWindow->TopEdge;
         theGadget->w = theWindow->Width;
         theGadget->h = theWindow->Height;
      }
      w = MAX(theWindow->MinWidth,-theGadget->Gadget.LeftEdge);
      h = MAX(theWindow->MinHeight,theGadget->Gadget.Height);
      ResizeWindow(theWindow,theWindow->LeftEdge,theWindow->TopEdge,w,h);
      theGadget->Flags = EG_ZOOMEDIN;
   }
}


/*
 *  PressedZoomGadget()
 *
 *  Finds the Zoom Gadget that is currently selected (if any).
 *  This is called from an Input Handler whenever a SELECTUP button
 *  event is passed to Intuition.  Intuition maintains the SELECTED flag
 *  for the Zoom Gadgets and since the ZOOM gadgets are RELVERY BOOLGADGETs
 *  they will be marked SELECTED whenever the mouse is pressed and held, and
 *  as long as the mouse is over the gadget itself.  Thus if we find a
 *  selected Zoom Gadget when the left mouse button is being released,
 *  we can be pretty sure that the user is letting go of the Zoom Gadget
 *  while the mouse is still over it, and thus we should process the
 *  gadget hit.  This is done in Forbid() mode so that the linked list does
 *  not change while we are looking at it.
 */
 
static struct ExtGadget *PressedZoomGadget()
{
   struct ExtGadget *theGadget;
   
   Forbid();
   theGadget = FirstZoom;
   while (theGadget && (theGadget->Gadget.Flags & SELECTED) == 0)
      theGadget = theGadget->Next;
   Permit();
   return(theGadget);
}


/*
 *  ZoomHandler()
 *
 *  This is the Input Handler that checks for Zoom Gadget hits.  As described
 *  in PressedZoomGadget() above, every time the left or right mouse button is 
 *  released, we check to see if a Zoom Gadget is SELECTED, and assume that
 *  that gadget is being released.  If such a gadget is found, then the
 *  window it is attached to is zoomed.  The button event is still passed
 *  to Intuition so that it will know that the mouse is no longer pressed,
 *  and will deactivate the gadget.
 *
 *  Since the Zoom Gadgets are marked as SYSGADGETs, Intuition will not
 *  send any IntuiMessages to the application about the Zoom Gadget, but since
 *  their types are none of the standard Intuition gadgets, Intuition 
 *  ignores them.  This saves having to monitor each windows IDCMP port for
 *  messages from these gadgets, which reduces the overhead both in time and
 *  programming.
 */

struct InputEvent *ZoomHandler(EventList,data)
struct InputEvent *EventList;
APTR data;
{
   struct InputEvent *theEvent = EventList;
   struct ExtGadget *theGadget;
   
   while (theEvent)
   {
      if (theEvent->ie_Class == IECLASS_RAWMOUSE &&
         (theEvent->ie_Code  == SELECTUP || theEvent->ie_Code == MENUDOWN))
      {
         theGadget = PressedZoomGadget();
         if (theGadget)
            if (theEvent->ie_Code == MENUDOWN)
            {
               ZoomInWindow(theGadget);
               theEvent->ie_Code = SELECTUP;
            } else {
               ZoomOutWindow(theGadget);
            }
      }
      theEvent = theEvent->ie_NextEvent;
   }
   return(EventList);
}
