/*
 *  VSCREENSETUP.C  Creates virtual screens that can be larger than
 *                  the actual display area of your monitor.  The virtual
 *                  screen scrolls when the mouse moves off the edge of
 *                  the display.
 *
 *                  Copyright 1988 by Davide P. Cervone, all rights reserved.
 *
 *                  You may may distibute this code as is for non-commercial
 *                  purposes, provided that this notice remains intact and the
 *                  documentation file is distributed with it.
 *
 *                  You may not modify this code or incorporate it into your
 *                  own programs without the permission of the author.
 */

/*
 *  WARNING:  This code uses and even modifies the INTUITIONPRIVATE
 *  area of the IntuitionBase structure.  Use it at your own risk.  It is
 *  likely to break under a future release of Intuition.
 */

#include "vScreen.h"

struct LayersBase *LayersBase = NULL;       /* the Layers Library */

extern struct vScreenInfo *vScreenData;     /* the data from the Handler */
extern struct Screen *VScreen;              /* the virtual screen pointer */
extern SHORT ScreenWidth,ScreenHeight;      /* the new width and height */
static UBYTE OldDepth;                      /* the screen depth */

extern void CheckLibOpen();
extern void DoExit();


#define BLT_COPY    0xC0                    /* blitter copy function */
#define MIN(x,y)    (((x)<(y))?(x):(y))     /* MIN macro */


/*
 *  FindScreen()
 *
 *  If a screen name was specified, look through the Intuition screens
 *  for the first one that matches the specified name (case does not matter),
 *  otherwise, use the active screen.
 *  if the screen was not found, exit with an error message.
 */

struct Screen *FindScreen(ScreenName)
char *ScreenName;
{
   struct Screen *theScreen;

   Forbid();
   if (ScreenName && ScreenName[0])
   {
      theScreen = IntuitionBase->FirstScreen;
      while (theScreen && (theScreen->Title == NULL ||
             stricmp(theScreen->Title,ScreenName) != 0))
                theScreen = theScreen->NextScreen;
   } else {
      theScreen = IntuitionBase->ActiveScreen;
   }
   Permit();
   if (theScreen == NULL) DoExit("Can't find screen '%s'",ScreenName);
   return(theScreen);
}


/*
 *  CheckWindows()
 *
 *  Make sure that all the windows on the virtual screen will fit on the
 *  screen when we reduce it to its original size.
 *
 *  For each window on the screen,
 *    check that its right edge will be on the smaller-sized screen.
 *    if not, move it to the left so that it will be.
 *      if that would move the left egde off the screen, then
 *        shrink the window so that it fits.
 *    Check the the bottom edge will be on the smaller screen.
 *    if no, then move it up so that it will be.
 *      if that would move the top edge off the screen, then
 *         shrink the window so that it fits.
 *    If the window needs to change size or position, do so, and record
 *      the number of changes made.
 *
 *  if any windows were changed, delay long enough for Intuition to update
 *    the windows before we actually restore the screen size.  (This is a
 *    kludge, but I don't know a better method that this.  You may need to
 *    adjust the timing factore for busier screens).
 */

static void CheckWindows(theScreen,theWidth,theHeight)
struct Screen *theScreen;
SHORT theWidth,theHeight;
{
   struct Window *theWindow;
   SHORT x,y, w,h;
   SHORT Wx,Wy, Ww,Wh;
   short wChanged = 0;

   if (theScreen)
   {
      theWindow = theScreen->FirstWindow;
      while (theWindow)
      {
         Wx = x = theWindow->LeftEdge;
         Wy = y = theWindow->TopEdge;
         Ww = w = theWindow->Width;
         Wh = h = theWindow->Height;
         
         if (x+w > theWidth)
         {
            x = theWidth - w;
            if (x < 0)
            {
               x = 0;
               w = theWidth;
            }
         }

         if (y+h > theHeight)
         {
            y = theHeight - h;
            if (y < 0)
            {
               y = 0;
               h = theHeight;
            }
         }

         if (x != Wx || y != Wy)
         {
            MoveWindow(theWindow,x-Wx,y-Wy);
            wChanged++;
         }
         
         if (w != Ww || h != Wh)
         {
            SizeWindow(theWindow,w-Ww,h-Wh);
            wChanged++;
         }

         theWindow = theWindow->NextWindow;
      }
   }
   if (wChanged) Delay(wChanged * 30L);
}


/*
 *  GetPlane()
 *
 *  Allocate a bitplane and copy one of the VScren bitplanes into it, then 
 *  free the old bitplane and replace it with the new one.
 *
 *  Two temporary bitmaps are used so that we can call BltBitMap.  The
 *  new bitplane is cleared in case it is larger than the old one, then
 *  the old one is copied.  Since BltBitMap is asynchronous, we call BltClear
 *  on a dummy section of memory to synchronize with the BltBitMap.  That way
 *  we don't free the old raster until it is fully copied.
 *
 *  GetPlane() is used to get the larger bitmap as well as the smaller one 
 *  when we restore the original screen, so MIN() is used to determine
 *  the size of the BltBitMap() action.
 */

static int GetPlane(i,Map1,Map2,junk)
int i;
struct BitMap *Map1,*Map2;
UBYTE *junk;
{
   int error = TRUE;
   long w1 = (Map1->BytesPerRow) << 3;
   long h1 = Map1->Rows;
   long w2 = (Map2->BytesPerRow) << 3;
   long h2 = Map2->Rows;

   Map2->Planes[0] = VScreen->BitMap.Planes[i];
   Map1->Planes[0] = AllocRaster(w1,h1);
   if (Map1->Planes[0])
   {
      BltClear(Map1->Planes[0],RASSIZE(w1,h1),0L);
      BltBitMap(Map2,0L,0L,Map1,0L,0L,MIN(w1,w2),MIN(h1,h2),BLT_COPY,0xFF,NULL);
      BltClear(junk,8L,0L);  /* synchronize with BltBitMap */
      VScreen->BitMap.Planes[i] = Map1->Planes[0];
      FreeRaster(Map2->Planes[0],w2,h2);
      error = FALSE;
   }
   return(error);
}


/*
 *  GetBitMap()
 *
 *  GetBitMap allocates and copies a new, larger bitmap for the virtual
 *  screen.  It does so one plane at a time, however, in order avoid having
 *  the complete old screen and the complete new screen in memory at the
 *  same time.  Two temporary bitmaps are used to perform the single-plane
 *  copies.  The junk memory is used to synchronize the BltBitMap calls (see
 *  GetPlane() above).
 *
 *  For each bitplane in the screen, get a new plane of the proper
 *  size.  If the allocation fails, try to clean up (it's usually too
 *  late, however).
 *
 *  Modify the screen's bitmap to reflect the changed size.
 *  Free the junk space.
 */

static void GetBitMap()
{
   struct BitMap MyBitMap;
   struct BitMap theBitMap;
   int i;
   UBYTE *junk;

   junk = AllocMem(8L,MEMF_CHIP);
   InitBitMap(&MyBitMap,1L,ScreenWidth,ScreenHeight);
   InitBitMap(&theBitMap,1L,VAR(OldWidth),VAR(OldHeight));
   for (i=0; i<OldDepth; i++)
   {
      if (GetPlane(i,&MyBitMap,&theBitMap,junk))
      {
         for(i--; i; i--)
         {
            if (GetPlane(i,&theBitMap,&MyBitMap,junk))
               DoExit("Bail Out!  Serious Trouble Restoring Bit Planes!");
         }
         FreeMem(junk,8L);
         DoExit("Can't Get Memory for Large Bit Planes");
      }
   }
   VScreen->BitMap.BytesPerRow = MyBitMap.BytesPerRow;
   VScreen->BitMap.Rows = MyBitMap.Rows;
   FreeMem(junk,8L);
}


/*
 *  FreeBitMap()
 *
 *  Similar to GetBitMap, except that FreeBitMap trys to allocate a bitmap
 *  the size of the original screen.
 */

static void FreeBitMap()
{
   struct BitMap MyBitMap;
   struct BitMap theBitMap;
   short i;
   UBYTE *junk;

   junk = AllocMem(8L,MEMF_CHIP);
   InitBitMap(&MyBitMap,1L,ScreenWidth,ScreenHeight);
   InitBitMap(&theBitMap,1L,VAR(OldWidth),VAR(OldHeight));
   for (i=0; i<OldDepth; i++)
   {
      if (GetPlane(i,&theBitMap,&MyBitMap,junk))
      {
         FreeMem(junk,8L);
         DoExit("Help!  Failed to Restore Bit Planes!");
      }
   }
   VScreen->BitMap.BytesPerRow = theBitMap.BytesPerRow;
   VScreen->BitMap.Rows = theBitMap.Rows;
   FreeMem(junk,8L);
}


/*
 *  SetClipRects()
 *
 *  Since the screen size is changing, we need to modify the ClipRects
 *  for the menubar's Layer.  This allows the layer to update itself
 *  properly.
 *
 *  Set the menubar bounds-rectangle size.
 *  For each ClipRect in the menubar layer,
 *    If the bounds-rectangle's right edge is at the edge of the old screen
 *      then set it to be the edge of the new screen.
 */

static void SetClipRects(OldX,NewX)
WORD OldX,NewX;
{
   struct ClipRect *theClipRect = VScreen->BarLayer->ClipRect;

   OldX--; NewX--;
   VScreen->BarLayer->bounds.MaxX = NewX;
   while (theClipRect)
   {
      if (theClipRect->bounds.MaxX == OldX)
         theClipRect->bounds.MaxX = NewX;
      theClipRect = theClipRect->Next; 
   }
}


/*
 *  EnlargeScreen()
 *
 *  Store the current state of the screen and change what needs to be 
 *  changed in order to make it bigger.
 *
 *  Open the Layers Library so that we can call LockLayers().
 *  Lock the Layers for the screen.  The Forbid() may not be necessary.
 *
 *  Get the HIRES and LACE flags for the screen.  Set up the Shift values
 *  needed to convert the screen's local coordinates to actual display
 *  coordinates (always considered 640 x 400 mode).
 *
 *  Get the pointers to the RxOffset and RyOffset variables of the RasInfo
 *  for the ViewPort of the virtual Screen.  These are what tell the
 *  graphics library which part of the bitmap to display.  These are what
 *  make it possible to scroll the screen quickly and easily.  Without them,
 *  vScreen would be impossible, but luckily, the graphics library knows how
 *  to use them.  Unfortunately, Intuition does not, so we have to bend
 *  over backwards to fool intuition.  See vScreen-Handler.c for details.
 *
 *  Get the old width and height of the screen, and the depth.
 *  Get the new, larger bitplanes for the screen.
 *
 *  Set the screen width and height, and repair the ClipRects in the
 *  menubar Layer.
 *
 *  Get the old MaxDisplay values.
 *
 *  Call the handler's SetVScreen() routine (it sets the MaxDisplay
 *  values, the Max and Min Mouse values, and some other stuff),
 *  and the FixView() routine, which calls MakeVPort() and MrgCop() to
 *  incorporate the screen changes into the Intuition View structure.
 *  Finally, load the new View so taht the larger screen will be displayed.
 *
 *  Unlock the layers and close the library.
 *
 *  Call ShowTitle, so that the menubar layer will be updated (so that
 *  it is as wide as the new screen).
 */

void EnlargeScreen()
{
   CheckLibOpen(&LayersBase,"layers.library",LAYERS_REV);
   LockLayers(&(VScreen->LayerInfo));
   Forbid();

   VAR(HiResScreen) = (VScreen->ViewPort.Modes & HIRES);
   VAR(LaceScreen)  = (VScreen->ViewPort.Modes & LACE);
   VAR(HiResShift) = (VAR(HiResScreen))? 0: 1;
   VAR(LaceShift)  = (VAR(LaceScreen))? 0: 1;

   VAR(RxOffset) = &(VScreen->ViewPort.RasInfo->RxOffset);
   VAR(RyOffset) = &(VScreen->ViewPort.RasInfo->RyOffset);
   VAR(RxOffset2) = (*(VAR(RxOffset))) << VAR(HiResShift);
   VAR(RyOffset2) = (*(VAR(RyOffset))) << VAR(LaceShift);

   VAR(OldWidth)  = VScreen->Width;
   VAR(OldHeight) = VScreen->Height;
   OldDepth = VScreen->BitMap.Depth;
   GetBitMap();

   VScreen->Width  = ScreenWidth;
   VScreen->Height = ScreenHeight;
   SetClipRects(VAR(OldWidth),ScreenWidth);

   VAR(OldMaxDH) = IntuitionBase->MaxDisplayHeight;
   VAR(OldMaxDR) = IntuitionBase->MaxDisplayRow;
   VAR(OldMaxDW) = IntuitionBase->MaxDisplayWidth;

   VAR(SetVScreen)();
   VAR(FixView)(TRUE);
   LoadView(&(IntuitionBase->ViewLord));

   Permit();
   UnlockLayers(&(VScreen->LayerInfo));
   CloseLibrary(LayersBase); LayersBase = NULL;

   ShowTitle(VScreen,(VScreen->Flags & SHOWTITLE)? TRUE: FALSE);
}


/*
 *  RestoreScreen()
 *
 *  If the screen still exists (i.e.,it was not closed while vScreen was
 *  running), then check the windows to be sure that they all will fit on
 *  the original-sized screen.
 *  Open the Layers Library, and lock the screen layers.
 *  Reset the old screen size, and set the menubar ClipRects to the old size.
 *  Set the offsets to zero, and call the Handler's ResetVScreen() routine
 *  (which resets Intuitions MaxDisplay and Min and Max Mouse fields).
 *  Get the screen depth and restore the old bitmap.
 *  Finally, unlock the screen, and remake the Intuition display so that
 *  the new sized screen is displayed.
 *  update the title bar so that it is the right size.
 */

void RestoreScreen()
{
   if (VScreen)
   {
      CheckWindows(VScreen,VAR(OldWidth),VAR(OldHeight));

      CheckLibOpen(&LayersBase,"layers.library",LAYERS_REV);
      LockLayers(&(VScreen->LayerInfo));
      Forbid();

      VScreen->Width  = VAR(OldWidth);
      VScreen->Height = VAR(OldHeight);
      SetClipRects(ScreenWidth,VAR(OldWidth));

      *(VAR(RxOffset)) = 0;
      *(VAR(RyOffset)) = 0;
      VAR(ResetVScreen)();

      OldDepth = VScreen->BitMap.Depth;
      FreeBitMap();

      Permit();
      UnlockLayers(&(VScreen->LayerInfo));
      CloseLibrary(LayersBase); LayersBase = NULL;

      RemakeDisplay();
      ShowTitle(VScreen,(VScreen->Flags & SHOWTITLE)? TRUE: FALSE);
   }
}


/*
 *  SetVariables()
 *
 *  Store that vScreenData pointer in the MsgPort structure so we can look
 *  it up later (in order to remove the handler).  Save the library pointers
 *  so that the handler can use them, and save the pointer to the virtual 
 *  screen abd its new width and height.
 */

void SetVariables(NamedPort)
struct MsgPort *NamedPort;
{
   NamedPort->mp_SigTask = (struct Task *) vScreenData;
   VAR(IntuitionBase) = IntuitionBase;
   VAR(GfxBase)       = GfxBase;
   VAR(VScreen)       = VScreen;
   VAR(ScreenWidth)   = ScreenWidth;
   VAR(ScreenHeight)  = ScreenHeight;
}


/*
 *  GetVariables()
 *
 *  Retrieve the vScreenData pointer from the MsgPort were we stored it
 *  earlier.  Get back the library pointers so that we can use them and close
 *  them.  Get back the pointer to the virtual screen and its new width
 *  and height.
 */

void GetVariables(NamedPort)
struct MsgPort *NamedPort;
{
   vScreenData   = (struct vScreenInfo *) (NamedPort->mp_SigTask);
   IntuitionBase = VAR(IntuitionBase);
   GfxBase       = VAR(GfxBase);
   VScreen       = VAR(VScreen);
   ScreenWidth   = VAR(ScreenWidth);
   ScreenHeight  = VAR(ScreenHeight);
}
