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

#include "wHandler.h"

static UWORD Xmax;                      /* maximum number of columns */
static UWORD Ymax;                      /* maximum number of rows */

#define XOFFSET     80                  /* distance between icons */
#define YOFFSET     34                  /* distance between icons */
#define XSTART      20                  /* Starting X position */
#define YSTART      28                  /* Starting Y position (from bottom) */
#define YDIFF       10                  /* vertical offset for odd rows */
#define XMAX        32                  /* maximum icons in x direction */
#define YMAX        32                  /* maximum icons in y direction */

#define GADGICON        ((WICONREF *)theGadget->UserData)


/*
 *  GetPosition()
 *
 *  Get the icon of the gadget (UserData points to the icon)
 *  Get the X position of the gadget normalized for varrying widths of icons
 *  Find the nearest X column position by dividing by the column width
 *  Get the Y position of the gadget normalized by height,with 0 at the bottom
 *  Offset the Y position for odd rows
 *  Get two different Y row positions (it may cover two positions vertically
 *    if it is not exactly placed on the grid)
 */

static void GetPosition(theGadget,X,Y1,Y2)
struct Gadget *theGadget;
WORD *X,*Y1,*Y2;
{
   WICONREF *theIcon = (WICONREF *)theGadget->UserData;

   *X  = theGadget->LeftEdge + (theGadget->Width - ICONWIDTH) / 2;
   *X  = (*X - XSTART + (XOFFSET/2)) / XOFFSET;
   *Y2 = theIcon->Screen->BackDrop->Height - theGadget->TopEdge;
   *Y2 = *Y2 - (theGadget->Height - ICONHEIGHT) - YSTART;
   if ((*X & 1) == 0) *Y2 -= YDIFF;
   *Y1 = (*Y2 + (YOFFSET  /4)) / YOFFSET;
   *Y2 = (*Y2 + (YOFFSET*3/4)) / YOFFSET;
}


/*
 *  GetPositionArray()
 *
 *  Calculate the maximum number of rows and columns, clip to the array size
 *  Clear the position array
 *  While there are gadgets to work with
 *    Get the gadget's position
 *    Add it into the array
 *    Add its second position into the array unless we are only conncered
 *      with where the gadgets "should" be (ie, snapped to the grid)
 *    Increment the gadget count if the gadget can be moved
 *    Go on to the next gadget
 *  Return the number of gadgets counted
 */

static UWORD GetPositionArray(PosArray,theScreen,Snapped)
ULONG PosArray[];
WSCREEN *theScreen;
int Snapped;
{
   struct Gadget *theGadget = theScreen->BackDrop->FirstGadget;
   WORD X,Y1,Y2;
   UWORD count = 0;
   
   Xmax = (theScreen->BackDrop->Width / XOFFSET);
   Ymax = (theScreen->BackDrop->Height - YSTART) / YOFFSET;
   if (Xmax > XMAX) Xmax = XMAX;
   if (Ymax > YMAX) Ymax = YMAX;

   for (Y1 = 0; Y1 < YMAX; Y1++) PosArray[Y1] = 0;

   while(theGadget)
   {
      GetPosition(theGadget,&X,&Y1,&Y2);
      PosArray[Y1] |= (1 << X);
      if (Snapped == FALSE) PosArray[Y2] |= (1 << X);
      if ((GADGICON->Icon.Flags & (WI_NOORGANIZE | WI_LOCKED)) == 0) count++;
      theGadget = theGadget->NextGadget;
   }
   return(count);
}


/*
 *  ValidPosition()
 *
 *  Check the row and column position to make sure it is on screen
 *  If not, move it one the screen area
 */

static void ValidPosition(X,Y)
WORD *X,*Y;
{
   if (*X < 0) *X = 0;
   if (*Y < 0) *Y = 0;
   if (*X >= Xmax) *X = Xmax - 1;
   if (*Y >  Ymax) *Y = Ymax;
}

/*
 *  Offsets for which positions to check when looking for the nearest
 *  grid location (used when two icons overlap).  There are separate
 *  choices for even and odd rows
 */

#define DCOUNT  8
static WORD dx[2][DCOUNT] =
{
   { 0, -1,  1, -1,  1, -1,  1,  0},
   { 0, -1,  1, -1,  1,  0, -1,  1}
};

static WORD dy[2][DCOUNT] =
{
   {-1,  0,  0, -1, -1,  1,  1,  1},
   {-1, -1, -1,  0,  0,  1,  1,  1}
};


/*
 *  FindClosePosition()
 *
 *  Check that the icon position is legal
 *  If the gadget overlaps another one already in the array
 *    Use the correct table for even or odd columns
 *    While there are more positions to try
 *      Get the new position for the given position
 *      Make sure the position is within the screen
 *      If the location is free
 *        Save the new position and indicate that we're done
 *  Indicate that the position is OK
 *  Return a statment about whether there was a closeby place free
 */

static int FindClosePosition(PosArray,X,Y,Y2)
ULONG PosArray[];
WORD *X,*Y,*Y2;
{
   int Found = FALSE;
   short i,j;
   WORD Xt,Yt;

   ValidPosition(X,Y);
   if ((PosArray[*Y] | PosArray[*Y2]) & (1 << *X))
   {
      j = *X & 1;
      for (i=0; i<DCOUNT && Found == FALSE; i++)
      {
         Xt = *X + dx[j][i];
         Yt = *Y + dy[j][i];
         ValidPosition(&Xt,&Yt);
         if ((PosArray[Yt] & (1 << Xt)) == 0)
         {
            Found = TRUE;
            *X = Xt; *Y = Yt;
         }
      }
   } else Found = TRUE;
   return(Found);
}


/*
 *  GetNewPosition()
 *
 *  Create a mask with 1's in all the valid column positions
 *  Look for the first row with an empty slot
 *  If we are past the end of the array,
 *    Set the position to the origin (bail out)
 *  Otherwise
 *    Look through the row from left to right for the first location
 *  Verify that what we found really IS an OK position
 */

static void GetNewPosition(PosArray,X,Y)
ULONG PosArray[];
WORD *X,*Y;
{
   ULONG Xmask = (1 << Xmax) - 1;

   for (*Y = 0; *Y < Ymax && (PosArray[*Y] & Xmask) == Xmask; (*Y)++);
   if (*Y >= Ymax)
      *Y = *X = 0;
     else 
      for (*X = 0, Xmask = 1; PosArray[*Y] & Xmask; (*X)++, Xmask <<= 1);
   ValidPosition(X,Y);
}


/*
 *  SetPosition()
 *
 *  If the icon is not locked in place
 *    Get the current X and Y (in pixels) for the icon's new position
 *      (centered the icon horizontally)
 *    If the gadget's current location is different
 *      Set the gadgets's new position
 *      If the icon is supposed to get movement reports, report the change
 */

static void SetPosition(theGadget,X,Y)
struct Gadget *theGadget;
WORD X,Y;
{
   WICONREF *theIcon = (WICONREF *)theGadget->UserData;

   if ((theIcon->Icon.Flags & WI_LOCKED) == FALSE)
   {
      Y  = theIcon->Screen->BackDrop->Height - YSTART - (Y * YOFFSET);
      if ((X & 1) == 0) Y -= YDIFF;
      X = (X * XOFFSET) + XSTART  - (theGadget->Width - ICONWIDTH) / 2;
      Y -=  theGadget->Height - ICONHEIGHT;
      if (theGadget->LeftEdge != X || theGadget->TopEdge != Y)
      {
         theGadget->LeftEdge = X; theIcon->Icon.x = X;
         theGadget->TopEdge  = Y; theIcon->Icon.y = Y;
         if (theIcon->Icon.Report & WI_REPORTMOVED)
            ReportEvent(WI_REPORTMOVED,theIcon);
      }
   }
}


/*
 *  InitPosition()
 *
 *  Get the position of the gadget and save it
 *  Get the position array for the screen (do not snap icons to the grid)
 *  If the gadget already has a position
 *    If we can't find a closeby position get the first available one
 *    If the position changed, set the new position
 *  Otherwise
 *    Get the first available position
 *    Set the gadget's position
 */

void InitPosition(theGadget)
struct Gadget *theGadget;
{
   WICONREF *theIcon = (WICONREF *)theGadget->UserData;
   ULONG PosArray[YMAX];
   WORD X,Y,Y2;
   WORD OldX,OldY;

   GetPosition(theGadget,&X,&Y,&Y2); OldX = X; OldY = Y;
   GetPositionArray(PosArray,theIcon->Screen,FALSE);
   if (theGadget->LeftEdge || theGadget->TopEdge)
   {
      if (!FindClosePosition(PosArray,&X,&Y,&Y2))
         GetNewPosition(PosArray,&X,&Y);
      if (X != OldX || Y != OldY) SetPosition(theGadget,X,Y);
   } else {
      GetNewPosition(PosArray,&X,&Y);
      SetPosition(theGadget,X,Y);
   }
}


/*
 *  CleanUpIcons()
 *
 *  Get the positions of all icons on the screen (no snapping)
 *  Clear the temporary array
 *  Start with the first gadget on the screen
 *  If there are selected gadgets
 *    Add all the unselected gadgets to the temporary array
 *    Start with the first selected gadget
 *  While there are more gadgets to handle
 *    Get the gadget's position and validate it
 *    If the icon can be moved
 *      If the icon's position is already in use
 *        Find the closest place, or the next available one
 *      Set the gadget's position
 *    Add the gadget to the arrays
 *    If gadgets are selected, go on to the next selected one
 *    Otherwise go on to the next one
 */

void CleanUpIcons(theScreen)
WSCREEN *theScreen;
{
   ULONG PosArray[YMAX];
   ULONG TmpArray[YMAX];
   WORD  X,Y,Y2;
   short Selected;
   struct wGadget *theGadget;

   Forbid();

   GetPositionArray(PosArray,theScreen,FALSE);
   for (Y = 0; Y < YMAX; Y++) TmpArray[Y] = 0;
   theGadget = (struct wGadget *)theScreen->BackDrop->FirstGadget;
   Selected = (theScreen->Selected != NULL);
   if (Selected)
   {
      while (theGadget)
      {
         if ((theGadget->Gadget.Flags & SELECTED) == FALSE)
         {
            GetPosition(theGadget,&X,&Y,&Y2);
            TmpArray[Y]  |= (1 << X);
            TmpArray[Y2] |= (1 << X);
         }
         theGadget = (struct wGadget *)theGadget->Gadget.NextGadget;
      }
      theGadget = theScreen->Selected;
   }
   
   while (theGadget)
   {
      GetPosition(theGadget,&X,&Y,&Y2);
      ValidPosition(&X,&Y);
      if ((GADGETICON->Icon.Flags & (WI_NOORGANIZE| WI_LOCKED)) == FALSE)
      {
         if ((TmpArray[Y] | TmpArray[Y2]) & (1 << X))
            if (!FindClosePosition(PosArray,&X,&Y,&Y2))
               GetNewPosition(PosArray,&X,&Y);
         SetPosition(theGadget,X,Y);
      }
      TmpArray[Y] |= (1 << X);
      PosArray[Y] |= (1 << X);
      if (Selected)
         theGadget = theGadget->NextSelect;
        else
         theGadget = (struct wGadget *)theGadget->Gadget.NextGadget;
   }
   Permit();
}


/*
 *  OrganizeIcons()
 *
 *  Start with the first gadget on the screen
 *  Count the gadgets and get the position array
 *  Find the highest row and the right-most column in that row needed
 *    in order to store that many icons
 *  Clear the temporary array
 *  While there are gadgets to check
 *    Get the gadget's position and validate it
 *    If the gadget can be moved
 *      If the icon's position is in use, or if it is outside the final
 *        block of organized icons, get a new position for the gadget
 *      Set the gadgets position
 *    Save the icon's position in both arrays
 *    Move on to the next gadget
 */

void OrganizeIcons(theScreen)
WSCREEN *theScreen;
{
   ULONG PosArray[YMAX];
   ULONG TmpArray[YMAX];
   WORD  X,Y,Y2;
   WORD  RightX, TopY;
   UWORD count;
   struct Gadget *theGadget;

   Forbid();
   theGadget = theScreen->BackDrop->FirstGadget;
   count = GetPositionArray(PosArray,theScreen,TRUE);
   TopY = count / Xmax;
   RightX = count - TopY * Xmax;
   for (Y = 0; Y < YMAX; Y++) TmpArray[Y] = 0;
   
   while (theGadget)
   {
      GetPosition(theGadget,&X,&Y,&Y2);
      ValidPosition(&X,&Y);
      if ((GADGICON->Icon.Flags & (WI_NOORGANIZE| WI_LOCKED)) == FALSE)
      {
         if ((TmpArray[Y] & (1 << X)) || Y > TopY || (Y == TopY && X >= RightX))
            GetNewPosition(PosArray,&X,&Y);
         SetPosition(theGadget,X,Y);
      }
      PosArray[Y] |= (1 << X);
      TmpArray[Y] |= (1 << X);
      theGadget = theGadget->NextGadget;
   }
   Permit();
}
