/*
 *  WICONIFY    A utility that allows you to iconify any Intuition window
 *              on any screen, and to open WB windows on any screen.
 *
 *  wIcon.c     Handles most aspects of icon creation and maintenance
 *
 *  Copyright 1990 by Davide P. Cervone, all rights reserved.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#define INTUITION_PREFERENCES_H             /* don't need 'em */
#include <intuition/intuitionbase.h>
#include "wHandler.h"

struct Window *WindowToActivate;        /* Activate this window on SELECTUP */
static WSCREEN *Refresh;                /* List of screens to be refreshed */

#define ICONDELAY   2L      /* For window to come forward for CHANGEREFRESH */
#define MAXWAIT     10      /* Number of attempts if it hasn't come forward */
#define MOVE_DY     2048L   /* Distance to move screen when iconified */

#define WINDOW(layer)       ((struct Window *)((layer)->Window))
#define LAYERREFRESHBITS    (LAYERSIMPLE | LAYERSMART)
#define ICONSCREEN          ((WSCREEN *)theIcon->Window->UserData)



/*
 *  SetRefresh()
 *
 *  Lock the layers of the window's screen
 *  Change the window's refresh status
 *  Get the window's layer
 *  Change the layer's refresh status
 *  Unlock the screen's layers
 */

static void SetRefresh(theWindow,WindowBits)
struct Window *theWindow;
ULONG WindowBits;
{
   struct Layer *theLayer;
   UWORD LayerBits = (WindowBits == SMART_REFRESH)? LAYERSMART: LAYERSIMPLE;

   Forbid();
   LockLayerInfo(&theWindow->WScreen->LayerInfo);
   theWindow->Flags = (theWindow->Flags & ~REFRESHBITS) | WindowBits;
   theLayer = theWindow->WLayer;
   theLayer->Flags = (theLayer->Flags & ~LAYERREFRESHBITS) | LayerBits;
   UnlockLayerInfo(&theWindow->WScreen->LayerInfo);
   Permit();
}


/*
 *  SetBackdrop()
 *
 *  Lock the screen's layers
 *  Add or remove the BACKDROP flag appropriately
 *  Get the window's first layer (GZZ and requesters may give it more)
 *  While there are more layers for this window (we assume they are sequential)
 *    Set the backdrop status of the layer
 *    Go on to the next layer
 *  Start over again with the first layer behind the window
 *  For any layers nbelowing to this window
 *    Set the backdrop status of the layer
 *    Move on to the previous layer
 *  Unlock the layers
 */

static void SetBackdrop(theWindow,Backdrop)
struct Window *theWindow;
int Backdrop;
{
   struct Layer *theLayer;

   Forbid();
   LockLayerInfo(&theWindow->WScreen->LayerInfo);
   if (Backdrop)
      theWindow->Flags |=  BACKDROP;
     else
      theWindow->Flags &= ~BACKDROP;

   theLayer = theWindow->WLayer;
   while (theLayer && theLayer->Window == (APTR)theWindow)
   {
      if (Backdrop)
         theLayer->Flags |=  LAYERBACKDROP;
        else
         theLayer->Flags &= ~LAYERBACKDROP;
       theLayer = theLayer->front;
    }

   theLayer = theWindow->WLayer->back;
   while (theLayer && theLayer->Window == (APTR)theWindow)
   {
      if (Backdrop)
         theLayer->Flags |=  LAYERBACKDROP;
        else
         theLayer->Flags &= ~LAYERBACKDROP;
      theLayer = theLayer->back;
   }
   UnlockLayerInfo(&theWindow->WScreen->LayerInfo);
   Permit();
}


/*
 *  TopWindow()
 *
 *  Lock the screen's layers so they don't change while we look
 *  Start with the top-most layer
 *  Look through the layer list for the first one with a window pointer
 *  Get the layer's window pointer
 *  Unlock the layers
 *  Return the window.
 */

static struct Window *TopWindow(theScreen)
struct Screen *theScreen;
{
   struct Window *theWindow = NULL;
   struct Layer_Info *theLayerInfo = &(theScreen->LayerInfo);
   struct Layer *theLayer;
   
   LockLayers(theLayerInfo);
   theLayer = theLayerInfo->top_layer;
   while (theLayer && WINDOW(theLayer) == NULL) theLayer = theLayer->back;
   if (theLayer) theWindow = WINDOW(theLayer);
   UnlockLayers(theLayerInfo);
   return(theWindow);
}


/*
 *  HideWindow()
 *
 *  If the icon is a screen icon (not a window icon)
 *    If the screen is the active one, activate the WB Iconify window
 *    Send the screen to the back
 *    Move the screen off the bottom of the display
 *    Link the screen's icon into the WB screen list
 *  Otherwise
 *    If the icon is supposed to have its refresh status changed
 *      If the window is a SMART_REFRESH one
 *        Bring the window to the front
 *        Wait for the window to come to the front (timeout after MAXWAIT)
 *         (we must have the window completely uncovered before we change
 *         the refresh status, otherwise the refresh regions get completely
 *         messed up!)
 *        Set the refresh type to SIMPLE_REFRESH
 *        Mark the icon as changed
 *    If the window is not already a backdrop window
 *      Mark it as a backdrop window
 *      Record the change so we can undo it later
 *    If the window is getting refresh messages
 *      Turn them off (dont update while we can't see it!)
 *      Record the change
 *    Finally, send the window to the back (behind the wIconify backdrop)
 */

static void HideWindow(theIcon,Flags)
WICONREF *theIcon;
UWORD Flags;
{
   struct Window *theWindow = theIcon->Window;
   short i = MAXWAIT;

   if (theIcon->Icon.Flags & WI_SCREENICON)
   {
      Forbid();
      if (IntuitionBase->ActiveScreen == ICONSCREEN->Screen)
         ActivateWindow(RealWB->BackDrop);
      Permit();
      ScreenToBack(ICONSCREEN->Screen);
      MoveScreen(ICONSCREEN->Screen,0L,MOVE_DY);
      LinkIcon(theIcon,RealWB);
   } else {
      if ((theIcon->Icon.Flags & WI_CHANGEREFRESH) || (Flags & WI_CHANGE))
      {
         if ((theWindow->Flags & REFRESHBITS) == SMART_REFRESH)
         {
            WindowToFront(theWindow);
            while (theWindow != TopWindow(theIcon->Screen->Screen) && --i)
               Delay(ICONDELAY);
            SetRefresh(theWindow,SIMPLE_REFRESH);
            theIcon->Icon.Flags |= WI_REFRESHCHANGE;
         }
      }
      if ((theWindow->Flags & BACKDROP) == FALSE)
      {
         SetBackdrop(theWindow,TRUE);
         theIcon->Icon.Flags |= WI_BACKDROPCHANGE;
      }
      if ((theWindow->Flags & NOCAREREFRESH) == FALSE)
      {
         theWindow->Flags |= NOCAREREFRESH;
         theIcon->Icon.Flags |= WI_NOCARECHANGE;
      }
      WindowToBack(theWindow);
   }
}


/*
 *  ShowWindow()
 *
 *  If the icon is a screen icon
 *    Move the screen back to its previous location
 *    Bring it to the front
 *  Otherwise
 *    If the window was not a BACKDROP one before, fix its layers
 *    If we changes the refresh report flag, reset it
 *    Bring the window to the front
 *    If we changed the refresh type of the window
 *      Wait for the window to appear at the front (timeout after MAXWAIT)
 *      Set the refresh type to SMART_REFRESH
 *    Remove the flags that say what changes were made
 */

static void ShowWindow(theIcon)
WICONREF *theIcon;
{
   struct Window *theWindow = theIcon->Window;
   short i = MAXWAIT;

   if (theIcon->Icon.Flags & WI_SCREENICON)
   {
      MoveScreen(ICONSCREEN->Screen,0L,-MOVE_DY);
      ScreenToFront(ICONSCREEN->Screen);
   } else {
      if (theIcon->Icon.Flags & WI_BACKDROPCHANGE)
         SetBackdrop(theWindow,FALSE);
      if (theIcon->Icon.Flags & WI_NOCARECHANGE)
         theWindow->Flags &= ~NOCAREREFRESH;
      WindowToFront(theWindow);
      if (theIcon->Icon.Flags & WI_REFRESHCHANGE)
      {
         while (theWindow != TopWindow(theIcon->Screen->Screen) && --i)
            Delay(ICONDELAY);
         SetRefresh(theWindow,SMART_REFRESH);
      }
      theIcon->Icon.Flags &= ~WI_CHANGEBITS;
   }
}


/*
 *  RefreshIcons()
 *
 *  If there is a screen to refresh
 *    If we are supposed to clear the screen, mark it to be cleared
 *    If the screen is not already in the list
 *      Link it into the list and mark it as in the list
 *  (The refreshing is delayed until after all the messages are processed
 *  so we don't refresh the same screen multiple times)
 */

void RefreshIcons(theScreen,ClearIt)
WSCREEN *theScreen;
int ClearIt;
{
   Forbid();
   if (theScreen)
   {
      if (ClearIt) theScreen->Flags |= WI_CLEARIT;
      if ((theScreen->Flags & WI_REFRESH) == FALSE)
      {
         theScreen->Flags |= WI_REFRESH;
         theScreen->NextRefresh = Refresh;
         Refresh = theScreen;
      }
   }
   Permit();
}


/*
 *  DrawGadgets()
 *
 *  Get the first gadget on the screen
 *  While there are more gadgets
 *    Temporarily use the MutualExclude flag as a backward pointer
 *    Save the pointer to the last gadget seen
 *    Go on to the next one
 *  Start at the end of the list
 *  While there are more gadgets to draw
 *    Get the next gadget from MutualExclude (where we stored it above)
 *    Reset MutualExclude to zero
 *    Draw the given gadget
 *  (The gadgets are drawn in reverse order to make them overlap correctly.
 *  One could replace this routine by RefreshGadgets if desired.  There are
 *  two reason's not to, however; RefreshGadgets draws all the gadgets first
 *  then goes back and adds any select imagery or complements.  This causes
 *  undue flickering of the icons if there are many of them selected.  Also,
 *  if any selected ones overlap, the complementation cancels out on the
 *  overlapped region)
 */

static void DrawGadgets(theScreen)
WSCREEN *theScreen;
{
   struct Gadget *theGadget = theScreen->BackDrop->FirstGadget;
   struct Gadget *IconGadget = NULL;
   
   while (theGadget)
   {
      theGadget->MutualExclude = (LONG) IconGadget;
      IconGadget = theGadget;
      theGadget = theGadget->NextGadget;
   }
   while (IconGadget)
   {
      theGadget = IconGadget;
      IconGadget = (struct Gadget *) theGadget->MutualExclude;
      theGadget->MutualExclude = 0L;
      RefreshGList(theGadget,theScreen->BackDrop,NULL,ONE);
   }
}


/*
 *  RefreshScreens()
 *
 *  For each screen in the list
 *    If it still has a backdrop window (ie, the screen is not closing)
 *      If we are supposed to clear the screen, do so
 *      If the screen has gadgets, draw them
 *    Clear the screen's refresh bits
 *    Move on to the next screen
 */

void RefreshScreens()
{
   Forbid();
   while (Refresh)
   {
      if (Refresh->BackDrop)
      {
         if (Refresh->Flags & WI_CLEARIT) SetRast(Refresh->BackDrop->RPort,0L);
         if (Refresh->BackDrop->FirstGadget) DrawGadgets(Refresh);
      }
      Refresh->Flags &= ~(WI_REFRESH| WI_CLEARIT);
      Refresh = Refresh->NextRefresh;
   }
   Permit();
}


/*
 *  UnLinkRefresh()
 *
 *  Find the given screen in the refresh list and remove it.
 */

void UnLinkRefresh(theScreen)
WSCREEN *theScreen;
{
   WSCREEN **ScreenPtr;

   Forbid();
   ScreenPtr = &Refresh;
   while (*ScreenPtr && *ScreenPtr != theScreen)
      ScreenPtr = &((*ScreenPtr)->NextRefresh);
   if (*ScreenPtr) *ScreenPtr = theScreen->NextRefresh;
   Permit();
}


/*
 *  RemoveIcon()
 *
 *  If the icon has a gadget
 *    If the icon is selected
 *      Unlink the gadget from the selection list
 *      If this was the last icon on the screen update the menus
 *      Mark the icon as not selected
 *    If the screen still has a backdrop window
 *      Remove the gadget from the window
 *    Free the icon's gadget structure
 *    Clear and refresh the screen's icons
 */

void RemoveIcon(theIcon)
WICONREF *theIcon;
{
   if (theIcon->Gadget)
   {
      if (theIcon->Icon.Flags & WI_SELECTED)
      {
         UnLinkGadget(theIcon->Gadget);
         if (theIcon->Screen->Selected == NULL) UpdateActiveMenu();
         theIcon->Icon.Flags &= ~WI_SELECTED;
      }
      if (theIcon->Screen->BackDrop)
         RemoveGadget(theIcon->Screen->BackDrop,theIcon->Gadget);
      FREESTRUCT(wGadget,theIcon->Gadget); theIcon->Gadget = NULL;
      RefreshIcons(theIcon->Screen,TRUE);
   }
}


/*
 *  DeleteIcon()
 *
 *  Unlink the icon from the screen's list
 *  Remove the icon gadget from the screen
 *  If the screen has no more icons
 *    If the screen has a task waiting to close it
 *      Signal that task that it is now OK to close the screen
 *    If Iconify is waiting to end, signal that it can try again
 *    Update the menus (deactivate OPENALL, etc)
 *  If the icon is not a screen icon (which is part of the scren structure)
 *    Free the icon's memory
 */

void DeleteIcon(theIcon)
WICONREF *theIcon;
{
   UnLinkIcon(theIcon);
   RemoveIcon(theIcon);
   if (theIcon->Screen->IconRef == NULL)
   {
      if (theIcon->Screen->CloseTask)
         Signal(theIcon->Screen->CloseTask,theIcon->Screen->CloseSignal);
      if (EndSignal) Signal(IconTask,EndSignal);
      UpdateActiveMenu();
   }
   if ((theIcon->Icon.Flags & WI_SCREENICON) == FALSE)
      FREESTRUCT(wIconRef,theIcon);
}


/*
 *  CloseIcon()
 *
 *  If the icon is allowed to be closed
 *    If the icon wants to receive close messages, send one
 *    Otherwise if the icon's window currently has a requester up, no nothing
 *    Otherwise send the CLOSEWINDOW message to simulate pressing the
 *      close gadget of the window
 */

void CloseIcon(theIcon)
WICONREF *theIcon;
{
   if ((theIcon->Icon.Flags & WI_NOCLOSE) == FALSE)
   {
      if (theIcon->Icon.Report & WI_REPORTCLOSE)
         ReportEvent(WI_REPORTCLOSE,theIcon);
      else if (theIcon->Window && theIcon->Window->FirstRequest) /* nothing */;
      else SendIntuiMessage(CLOSEWINDOW,theIcon->Window);
   }
}


/*
 *  Iconify()
 *
 *  If we have an icon
 *    If the icon is not already iconified, and it is allowed to be, and there
 *        is somewhere to put the icon
 *      If the icon is for a window or (if it IS a screen icon) if the
 *          real WB screen is open and is not the screen whose icon this is
 *        Find out it the icon's window is the active one
 *        If so, activate the screen's backdrop window
 *        Get a new gadget for the icon
 *        If one was able to be allocated
 *          Mark the icon as iconified
 *          If the icon has an associated window
 *            If it has no close gadget, mark the icon as not closable
 *            Hide the window behind the wIconify backdrop window
 *          Set up the gadget for the icon
 *          Add the gadget to the backdrop window
 *          Refresh the screen
 *          If the icon wants iconification reports, send one
 *          If the icon's window was the active one, activate its icon
 */

void Iconify(theIcon,Flags)
WICONREF *theIcon;
UWORD Flags;
{
   int isActive;

   if (theIcon)
   {
      if ((theIcon->Icon.Flags & (WI_ICONIFIED| WI_NOICONIFY)) == FALSE &&
           theIcon->Screen->BackDrop)
      {
         if ((theIcon->Icon.Flags & WI_SCREENICON) == FALSE ||
             (RealWB && ICONSCREEN != RealWB))
         {
            isActive = (theIcon->Window == IntuitionBase->ActiveWindow);
            if (isActive) ActivateWindow(theIcon->Screen->BackDrop), Delay(1L);
            if (NEWSTRUCT(wGadget,theIcon->Gadget))
            {
               theIcon->Icon.Flags |= WI_ICONIFIED;
               if (theIcon->Window)
               {
                  if ((theIcon->Window->IDCMPFlags & CLOSEWINDOW) == FALSE)
                     theIcon->Icon.Flags |= WI_NOCLOSE;
                  HideWindow(theIcon,Flags);
               }
               SetupGadget(theIcon);
               AddGadget(theIcon->Screen->BackDrop,theIcon->Gadget,0);
               RefreshIcons(theIcon->Screen,TRUE);
               Forbid();
               if (theIcon->Icon.Report & WI_REPORTICONIFIED)
                  ReportEvent(WI_REPORTICONIFIED,theIcon);
               if (isActive) SelectIcon(theIcon,FALSE);
               Permit();
            }
         }
      }
   }
}


/*
 *  Restore()
 *
 *  If we have an icon to restore
 *    If it is iconified
 *      If the icon wants restores reported, report it
 *      Otherwise
 *        If the icon has a window
 *          Bring it out from behind the backdrop window
 *          If we are to activate the window right away, do so
 *          Otherwise record it for later activation
 *        If we are not saving the icon's position, clear its position
 *        Otherwise save the icon's position
 *        If we are reporting restoration events, do so
 *        If the icon is a screen icon
 *          Remove the icon from the WB screen and reset its screen pointer
 *        Otherwise if the icon has a window remove the icon from the screen
 *        (don't remove icons with no windows - they must call DeleteIcon)
 */

void Restore(theIcon,ActivateNow)
WICONREF *theIcon;
int ActivateNow;
{
   if (theIcon)
   {
      if (theIcon->Icon.Flags & WI_ICONIFIED)
      {
         if (theIcon->Icon.Report & WI_REPORTOPEN)
         {
            ReportEvent(WI_REPORTOPEN,theIcon);
         } else {
            if (theIcon->Window)
            {
               ShowWindow(theIcon);
               if (ActivateNow) ActivateWindow(theIcon->Window);
                 else WindowToActivate = theIcon->Window;
               theIcon->Icon.Flags &= ~WI_ICONIFIED;
            }
            if (theIcon->Icon.Flags & WI_NOSAVEPOS)
            {
               theIcon->Icon.x = theIcon->Icon.y = 0;
            } else {
               theIcon->Icon.x = theIcon->Gadget->Gadget.LeftEdge;
               theIcon->Icon.y = theIcon->Gadget->Gadget.TopEdge;
            }
            if (theIcon->Icon.Report & WI_REPORTRESTORE)
               ReportEvent(WI_REPORTRESTORE,theIcon);
            if (theIcon->Icon.Flags & WI_SCREENICON)
            {
               DeleteIcon(theIcon);
               theIcon->Screen = ICONSCREEN; 
            } else if (theIcon->Window) RemoveIcon(theIcon);
         }
      }
   }
}


/*
 *  OpenAllIcons()
 *
 *  If a screen was specified
 *    Get the first icon on the screen
 *    While there are more icons to handle
 *      Restore the icon and go on
 *      (we save the pointer to the next icon first since Restore may
 *      free the icon's memory and invalidate the pointer)
 */

void OpenAllIcons(theScreen)
WSCREEN *theScreen;
{
   WICONREF *theIcon,*nextIcon;

   if (theScreen)
   {
      theIcon = theScreen->IconRef;
      while (theIcon)
      {
         nextIcon = theIcon->Next;
         Restore(theIcon,TRUE);
         theIcon = nextIcon;
      }
   }
}


/*
 *  OpenSelected()
 *
 *  If there is a screen to work with
 *    Get the first gadget selected on the screen
 *    While there are more gadgets to handle
 *      Restore the window associated with gadget
 *      Go on to the next one
 *      (we save the pointer to the next gadget before restoring since
 *      Restore() will remove the gadget before it returns)
 */

void OpenSelected(theScreen,Activate)
WSCREEN *theScreen;
int Activate;
{
   struct wGadget *theGadget,*nextGadget;

   if (theScreen)
   {
      theGadget = theScreen->Selected;
      while (theGadget)
      {
         nextGadget = theGadget->NextSelect;
         Restore(theGadget->Gadget.UserData,Activate);
         theGadget = nextGadget;
      }
   }
}


/*
 *  CloseSelected()
 *
 *  Start with the first selected gadget on the screen
 *  While there are more gadgets to handle
 *    Close the icon associated with the gadget
 *    Go on to the next gadget
 */

void CloseSelected(theScreen)
WSCREEN *theScreen;
{
   struct wGadget *theGadget = theScreen->Selected;
   struct wGadget *nextGadget;
   
   Forbid();
   while (theGadget)
   {
      nextGadget = theGadget->NextSelect;
      CloseIcon(GADGETICON);
      theGadget = nextGadget;
   }
   Permit();
}


/*
 *  LockSelected()
 *
 *  Start with the first selected gadget on the screen
 *  Look through the list to see if any are locked
 *  (if they are all locked, LockIt will remain FALSE, else it will be TRUE)
 *  For each selected gadget on the screen
 *    Set the LOCKED flag appropriately
 */

void LockSelected(theScreen)
WSCREEN *theScreen;
{
   struct wGadget *theGadget = theScreen->Selected;
   int LockIt = FALSE;
   
   Forbid();
   while (theGadget && !LockIt)
   {
      LockIt = ((GADGETICON->Icon.Flags & WI_LOCKED) == FALSE);
      theGadget=theGadget->NextSelect;
   }
 
  for (theGadget = theScreen->Selected; theGadget;
        theGadget = theGadget->NextSelect)
   {
      if (LockIt)
         GADGETICON->Icon.Flags |=  WI_LOCKED;
        else
         GADGETICON->Icon.Flags &= ~WI_LOCKED;
   }
   Permit();
}
