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

#include "wHandler.h"
#include <graphics/gels.h>


static struct Bob *IconBob = NULL;      /* List of bobs being dragged */
static WORD LastMouseX,LastMouseY;      /* Last place bobs were drawn */

static struct GelsInfo wGelsInfo;       /* Blank GelsInfo */
static struct VSprite DummyA,DummyB;    /* Dummy sprites for GelsInfo */


#define BOBSHADOW       BIT(0)          /* Means a shadow was allocated */
#define BOBIMAGE        BIT(1)          /* Means an Image was allocated */


/*
 *  Macros for easy memory allocation/deallocation
 */

#define NEWBOB(b)       NEWSTRUCT(Bob,b)
#define FREEBOB(b)      FREESTRUCT(Bob,b)

#define NEWVSPRITE(s)   NEWSTRUCT(VSprite,s)
#define FREEVSPRITE(s)  FREESTRUCT(VSprite,s)

#define NEWRASTER(s,i,d)  (s = AllocRaster((i)->Width,(i)->Height*d))
#define FREERASTER(s,b,d) FreeRaster(s,SPRITE(b)->VUserExt,SPRITE(b)->Height*d)

#define NEWSHADOW(s,i)  NEWRASTER(s,i,1)
#define FREESHADOW(b)   FREERASTER((b)->ImageShadow,b,1)

#define NEWSAVE(p,i,s)  NEWRASTER(p,i,s->BitMap.Depth)
#define FREESAVE(b,s)   FREERASTER((b)->SaveBuffer,b,s->BitMap.Depth)

#define NEWIMAGE(s,i)   NEWRASTER(s,i,(i)->Depth)
#define FREEIMAGE(b)    FREERASTER(SPRITE(b)->ImageData,b,SPRITE(b)->Depth)

#define SPRITE(b)       ((b)->BobVSprite)



/*
 *  DrawGels()
 *
 *  Get the old screen GelsInfo and replace it by our own
 *  Sort the Gels for the screen
 *  Draw the icons on the screen
 *  Put back the old GelsInfo
 */

static void DrawGels(theScreen)
WSCREEN *theScreen;
{
   struct GelsInfo *OldGI;

   Forbid();
   OldGI = theScreen->Screen->RastPort.GelsInfo;
   theScreen->Screen->RastPort.GelsInfo = &wGelsInfo;
   SortGList(&(theScreen->Screen->RastPort));
   DrawGList(&(theScreen->Screen->RastPort),&(theScreen->Screen->ViewPort));
   theScreen->Screen->RastPort.GelsInfo = OldGI;
   Permit();
}


/*
 *  FreeBob()
 *
 *  If we have a bob to work with
 *    If a shadow was allocated, free it
 *    If a save buffer was allocated, free it
 *    If the bob has a sprite
 *      If the sprite image was allocated, free it
 *      Free the sprite itself
 *    Finally, free the bob
 */

static void FreeBob(theBob,theScreen)
struct Bob *theBob;
WSCREEN *theScreen;
{
   if (theBob)
   {
      if (theBob->ImageShadow && (theBob->BUserExt & BOBSHADOW))
          FREESHADOW(theBob);
      if (theBob->SaveBuffer) FREESAVE(theBob,theScreen->Screen);
      if (SPRITE(theBob))
      {
         if (SPRITE(theBob)->ImageData && (theBob->BUserExt & BOBIMAGE))
            FREEIMAGE(theBob);
         FREEVSPRITE(SPRITE(theBob));
      }
      FREEBOB(theBob);
   }
}


/*
 *  DisposeBobs()
 *
 *  While there are more bobs in the list
 *     Free the bob and go on
 *  Clear the list pointer
 */

static void DisposeBobs(theScreen)
WSCREEN *theScreen;
{
   struct Bob *theBob;
   struct Bob *NextBob = IconBob;

   while ((theBob = NextBob) != NULL)
   {
      NextBob = theBob->After;
      FreeBob(theBob,theScreen);
   }
   IconBob = NULL;
}


/*
 *  MASK calculates the mask for the last word of the sprite data in each row
 *  sw is the sprite width in words, bw is the width in pixels
 */

#define MASK(bw,sw)     ~((1 << (sw * 16 - bw)) - 1)


/*
 *  MakeMask()
 *
 *  Get the Bob's sprite, and calculate its last image-data word
 *  Calculate the last-word-of-row mask
 *  Starting at the end of the sprite data, copy FFFF to each word
 *  If the mask is not FFFF, go through and trim the final word of each
 *    row to the correct mask value
 */

static void MakeMask(theBob)
struct Bob *theBob;
{
   struct VSprite *theSprite = SPRITE(theBob);
   WORD  *last = theBob->ImageShadow + theSprite->Width * theSprite->Height - 1;
   UWORD theMask = MASK(theSprite->VUserExt,theSprite->Width);
   WORD  *w;
   
   for (w=last; w >= theBob->ImageShadow; w--) *w = 0xFFFF;
   if (theMask != 0xFFFF)
      for (w=last; w >= theBob->ImageShadow; w-=theSprite->Width) *w = theMask;
}


/*
 *  NewBob()
 *
 *  Get the select imagery from the gadget
 *  If we can allocate a new Bob structure
 *    An a new VSPRITE structure
 *      Add the sprite to the bob
 *      Save the image width (in pixels) in the VUserExt field
 *      If we can allocate a save buffer of the proper size
 *        Set the sprite fields appropriately
 *        Set the sprite's mask (use the Icon's one if it has one, 
 *          otherwise, if this is a default image, use the default mask,
 *          otherwise try to create a mask for the image, if possible
 *          otherwise don't use a mask at all
 *        Mark that everything went OK
 *  If the Bob was not completely successful, free it and return NULL
 */

static struct Bob *NewBob(theWGadget)
struct wGadget *theWGadget;
{
   struct Gadget *theGadget = &(theWGadget->Gadget);
   WICONREF *theIcon = (WICONREF *)theGadget->UserData;
   struct Bob *theBob;
   struct Image *theImage;
   struct VSprite *theSprite;
   short BobNotOK = TRUE;

   if (theGadget->Flags & GADGHIMAGE)
      theImage = ((struct Image *)(theGadget->SelectRender));
     else
      theImage = ((struct Image *)(theGadget->GadgetRender));

   if (NEWBOB(theBob))
   {
      if (NEWVSPRITE(theBob->BobVSprite))
      {
         theSprite = SPRITE(theBob);
         theSprite->VUserExt = theImage->Width;
         if (NEWSAVE(theBob->SaveBuffer,theImage,theIcon->Screen->Screen))
         {
            theSprite->Flags     = SAVEBACK | OVERLAY;
            theSprite->Height    = theImage->Height;
            theSprite->Width     = (theImage->Width + 15) >> 4;
            theSprite->Depth     = theImage->Depth;
            theSprite->VSBob     = theBob;
            theSprite->X         = theGadget->LeftEdge;
            theSprite->Y         = theGadget->TopEdge;
            if (theIcon->Icon.Mask)
            {
               theBob->ImageShadow = (WORD *)theIcon->Icon.Mask;
            } else if ((theImage == DefaultImage || theImage == DefaultSelect)
                         && theImage != NULL && DefaultMask) {
               theBob->ImageShadow = DefaultMask->ImageData;
            } else if ((theImage == DefaultScreenImage ||
                        theImage == DefaultScreenSelect)
                         && theImage != NULL && DefaultScreenMask) {
               theBob->ImageShadow = DefaultScreenMask->ImageData;
            } else if (NEWSHADOW(theBob->ImageShadow,theImage)) {
               MakeMask(theBob);
               theBob->BUserExt |= BOBSHADOW;
            } else {
               theSprite->Flags &= ~OVERLAY;
            }
            BobNotOK = FALSE;
         }
      }
   }
   if (BobNotOK)
   {
      FreeBob(theBob,theIcon->Screen);
      theBob = NULL;
   }
   return(theBob);
}


/*
 *  GetInvertedImage()
 *
 *  Get the Bob's sprite and its select imagery, if any
 *  Get a pointer to the first gadget on the screen
 *  Get a pointer to the first Bob in the list
 *  If the gadget has a select image
 *    Copy the image information to the sprite
 *  Otherwise
 *    Look through the gadget (an bob) list for a gadget with the same image
 *    If the first one in the list is the gadget we are working on (ie, no 
 *      other gadget has the same image)
 *      Allocate a raster for the sprite's image, if possible
 *        Set up dummy BitMaps to point to the sprite data and the image data
 *        Get the size of the image to be copied, in pixels
 *        Blit an inverted copy of the image into the sprite's data area
 *        Mark that the image was allocated
 *      Otherwise, use the image data area itself (may not be contiguous, tho!)
 *      Copy the plane pick and on-off (inverted)
 *    Otherwise (we found a gadget with the same image)
 *      No need to make another copy, so just duplicate the old values
 */

static void GetInvertedImage(theBob,theGadget,theScreen)
struct Bob *theBob;
struct Gadget *theGadget;
WSCREEN *theScreen;
{
   struct VSprite *theSprite = SPRITE(theBob);
   struct Image *theImage = (struct Image *)(theGadget->SelectRender);
   struct wGadget *CheckGadget = theScreen->Selected;
   struct Bob *CheckBob = IconBob;
   struct BitMap SrcBitMap, DstBitMap;
   long SizeX, SizeY;
   char *SrcBytes;
   char *DstBytes;
   short i;

   if (theImage)
   {
      theSprite->ImageData = theImage->ImageData;
      theSprite->PlanePick = theImage->PlanePick;
      theSprite->PlaneOnOff = theImage->PlaneOnOff;
   } else {
      while (CheckGadget->Gadget.GadgetRender != theGadget->GadgetRender)
      {
         CheckGadget = CheckGadget->NextSelect;
         CheckBob = CheckBob->After;
      }
      if (CheckGadget == (struct wGadget *)theGadget)
      {
         theImage = (struct Image *)(theGadget->GadgetRender);
         if (NEWIMAGE(theSprite->ImageData,theImage))
         {
            SrcBytes = (char *) theImage->ImageData;
            DstBytes = (char *) theSprite->ImageData;

            SrcBitMap.BytesPerRow  = theSprite->Width * 2;
            SrcBitMap.Rows         = theSprite->Height;
            SrcBitMap.Flags        = 0;
            SrcBitMap.Depth        = theSprite->Depth;
            for (i=0; i<SrcBitMap.Depth; i++)
               SrcBitMap.Planes[i] = (PLANEPTR) (SrcBytes +
                  (SrcBitMap.BytesPerRow * SrcBitMap.Rows) * i);

            DstBitMap.BytesPerRow = theSprite->Width * 2;
            DstBitMap.Rows        = theSprite->Height;
            DstBitMap.Flags       = 0;
            DstBitMap.Depth       = theSprite->Depth;
            for (i=0; i<DstBitMap.Depth; i++)
               DstBitMap.Planes[i] = (PLANEPTR) (DstBytes +
                  (DstBitMap.BytesPerRow * DstBitMap.Rows) * i);

            SizeX = SrcBitMap.BytesPerRow * 8;
            SizeY = SrcBitMap.Rows;
            #define BLT_COPYINVERT   0x30
            BltBitMap(&SrcBitMap,0L,0L,&DstBitMap,0L,0L,SizeX,SizeY,
               BLT_COPYINVERT,0xFF,NULL);
      
            theBob->BUserExt |= BOBIMAGE;
         } else {
            theSprite->ImageData = theImage->ImageData;
         }
         theSprite->PlanePick = theImage->PlanePick;
         theSprite->PlaneOnOff = ~(theImage->PlaneOnOff);
      } else {
         theSprite->ImageData  = CheckBob->BobVSprite->ImageData;
         theSprite->PlanePick  = CheckBob->BobVSprite->PlanePick;
         theSprite->PlaneOnOff = CheckBob->BobVSprite->PlaneOnOff;
      }
   }
}


/*
 *  CreateBobs()
 *
 *  Get the old GelsInfo from the screen
 *  Init our GelsInfo and place it in the screen
 *  While ther are selected gadgets to look at
 *    If the gadget is allowed tobe moved
 *      If we can get a new bob for the gadget
 *        Add the bob to the end of the bob list
 *        Get its inverted imagery (or select imagery if it has it)
 *        Add the bob to the screen
 *    Go on to the next selected gadget
 *  Put back the old GelsInfo
 */

static void CreateBobs(theScreen)
WSCREEN *theScreen;
{
   struct wGadget *theGadget = theScreen->Selected;
   struct Bob *theBob;
   struct Bob *LastBob = NULL;
   struct GelsInfo *OldGI;

   Forbid();
   OldGI = theScreen->Screen->RastPort.GelsInfo;
   InitGels(&DummyA,&DummyB,&wGelsInfo);
   theScreen->Screen->RastPort.GelsInfo = &wGelsInfo;
   while (theGadget)
   {
      if ((GADGETICON->Icon.Flags & (WI_NOMOVE| WI_LOCKED)) == FALSE)
      {
         if (theBob = NewBob(theGadget))
         {
            if (IconBob == NULL) IconBob = theBob;
            if (LastBob) LastBob->After = theBob;
            theBob->After = NULL;
            theBob->Before = LastBob;
            LastBob = theBob;
            GetInvertedImage(theBob,theGadget,theScreen);
            AddBob(theBob,&(theScreen->Screen->RastPort));
         }
      }
      theGadget = theGadget->NextSelect;
   }
   theScreen->Screen->RastPort.GelsInfo = OldGI;
   Permit();
}


/*
 *  RemoveBobs()
 *
 *  While there are more bobs in the list
 *    Request that the bob be removed from the screen
 *  Draw the gels list (to remove the bobs)
 *  If the screen's layers were locked, unlock them
 *  Free the memory used by the bobs
 */

static void RemoveBobs(theScreen)
WSCREEN *theScreen;
{
   struct Bob *theBob = IconBob;

   while (theBob)
   {
      RemBob(theBob);
      theBob = theBob->After;
   }
   DrawGels(theScreen);
   if (theScreen->Flags & WI_LAYERLOCK)
   {
      UnlockLayers(&(theScreen->Screen->LayerInfo));
      theScreen->Flags &= ~WI_LAYERLOCK;
   }
   DisposeBobs(theScreen);
}


/*
 *  SetBobPosition()
 *
 *  While ther are more bobs in the list
 *    Change the position of the bob and go on to the next one
 */

static void SetBobPosition(dx,dy)
WORD dx,dy;
{
   struct Bob *theBob = IconBob;

   while (theBob)
   {
      theBob->BobVSprite->X += dx;
      theBob->BobVSprite->Y += dy;
      theBob = theBob->After;
   }
}


/*
 *  StartDragging()
 *
 *  Add RMBTRAP to the backdrop window flags (no menus during dragging)
 *  Add MOUSEMOVEs to the desired Intuition events
 *  Mark the screen to start dragging on the first mouse move
 *  Record the mouse position so that move offsets can be calculated
 */

void StartDragging(theIcon,MouseX,MouseY)
WICONREF *theIcon;
WORD MouseX,MouseY;
{
   Forbid();
   theIcon->Screen->BackDrop->Flags |= RMBTRAP;
   ModifyIDCMP(theIcon->Screen->BackDrop,IDCMPFLAGS | MOUSEMOVE);
   theIcon->Screen->Flags |= WI_STARTDRAG;
   LastMouseX = MouseX;
   LastMouseY = MouseY;
   Permit();
}


/*
 *  CancelDragging()
 *
 *  If the screen has icons dragging or is waiting to drag
 *    If there are bobs, remove them
 *    Reset the RMPTRAP flag so menus will work again
 *    Remove the MOUSEMOVE IDCMP flag
 *    Clear the dragging flags for the screen
 */

void CancelDragging(theScreen)
WSCREEN *theScreen;
{
   Forbid();
   if (theScreen->Flags & (WI_DRAGGING | WI_STARTDRAG))
   {
      if (IconBob) RemoveBobs(theScreen);
      theScreen->BackDrop->Flags &= ~RMBTRAP;
      ModifyIDCMP(theScreen->BackDrop,IDCMPFLAGS);
      theScreen->Flags &= ~(WI_DRAGGING | WI_STARTDRAG);
   }
   Permit();
}


/*
 *  EndDragging()
 *
 *  If there are bobs and they have been dragged
 *    Find the layer where the mouse is pointing
 *    If the layer is the screen's backdrop wIconify window, then
 *       Move the bobs to their final positions
 *       Get the change in position of the bobs (from their initial locations)
 *       Remove the bobs from the screen
 *       While there are gadgets to be updated
 *         If the gadget is movable, set its new position
 *         Go on to the next selected gadget
 *     Otherwise (icons dropped into some other window)
 *       Remove the bobs
 *       Show an error message and "beep" the screen.
 *  Cancel the dragging
 */

void EndDragging(theScreen,MouseX,MouseY)
WSCREEN *theScreen;
WORD MouseX,MouseY;
{
   struct wGadget *theGadget = theScreen->Selected;
   struct Layer *theLayer;
   WORD dx,dy;
   extern struct Layer *WhichLayer();

   if (IconBob && (theScreen->Flags & WI_DRAGGING))
   {
      theLayer = WhichLayer(&(theScreen->Screen->LayerInfo),
         MouseX,MouseY + theScreen->BackDrop->TopEdge);
      if (theLayer && (struct Window *)theLayer->Window == theScreen->BackDrop)
      {
         SetBobPosition(MouseX-LastMouseX,MouseY-LastMouseY);
         dx = IconBob->BobVSprite->X - theGadget->Gadget.LeftEdge;
         dy = IconBob->BobVSprite->Y - theGadget->Gadget.TopEdge;
         RemoveBobs(theScreen);
         while (theGadget)
         {
            if ((GADGETICON->Icon.Flags & (WI_NOMOVE| WI_LOCKED)) == FALSE)
               MoveGadget(theGadget,dx,dy,theScreen);
            theGadget = theGadget->NextSelect;
         }
      } else {
         RemoveBobs(theScreen);
         IconError("Icons can't be moved to that window!");
         DisplayBeep(theScreen->Screen);
      }
   }
   CancelDragging(theScreen);
}


/*
 *  MoveGels()
 *
 *  If the bobs have not yet been dragged
 *    Create the bob list
 *    If there are bobs to drag
 *      Lock the screen layers
 *      Mark the screen as locked and dragging
 *  Move the bobs to their new position
 *  Save the current mouse position for later
 *  Draw the bobs in their new location
 */

void MoveGels(theScreen,MouseX,MouseY)
WSCREEN *theScreen;
WORD MouseX,MouseY;
{
   if ((theScreen->Flags & WI_DRAGGING) == FALSE)
   {
      CreateBobs(theScreen);
      if (IconBob)
      {
         LockLayers(&(theScreen->Screen->LayerInfo));
         theScreen->Flags |= WI_DRAGGING | WI_LAYERLOCK;
      }
   }
   SetBobPosition(MouseX-LastMouseX,MouseY-LastMouseY);
   LastMouseX = MouseX; LastMouseY = MouseY;
   DrawGels(theScreen);
}
