/* Copyright (c) 2017, Computer History Museum 
All rights reserved. 
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to 
the limitations in the disclaimer below) provided that the following conditions are met: 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
   disclaimer in the documentation and/or other materials provided with the distribution. 
 * Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products 
   derived from this software without specific prior written permission. 
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE 
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE. */

#include "floatingwin.h"
#define FILE_NUM 89
// Copyright ¨ 1997 by QUALCOMM Incorporated
/**********************************************************************
 * code to support floating windows
 **********************************************************************/

#ifdef	FLOAT_WIN

#pragma segment MyWindow

void DockedDragProc(Point *pt,RgnHandle winRgn,long refCon);
void GetScreenRect(WindowPtr theWindow, Rect *rWin, Rect *rScreen);
MyWindowPtr FindLastFloaterLo(short *count,Boolean *layerProblem);

/**********************************************************************
 * MySelectWindow - patch for SelectWindow that makes sure floaters
 * remain on top, and non floaters remain underneath them.
 **********************************************************************/
void MySelectWindow(WindowPtr winWP)
{
	SelectWindow(winWP);
}

/**********************************************************************
 * FindLastFloater - returns a pointer to the last floater
 **********************************************************************/
MyWindowPtr FindLastFloater(void)
{
	short	count;
	Boolean	layerProblem;
	
	return FindLastFloaterLo(&count,&layerProblem);
}

/**********************************************************************
 * FindLastFloaterLo - returns a pointer to the last floater and counts
 *		number of floaters
 **********************************************************************/
MyWindowPtr FindLastFloaterLo(short *count,Boolean *layerProblem)
{
	WindowPtr	wp,
						lastFloater = nil;
	Boolean	nonFloaterFound = false;
	
	*count = 0;
	*layerProblem = false;
	
	for (wp = GetWindowList (); wp; wp = GetNextWindow (wp))
		if (IsFloating(wp))
		{
			lastFloater=wp;
			(*count)++;
			if (nonFloaterFound) *layerProblem = true;
		}
		else if ((GetWindowKind(wp)!=dialogKind && !IsModalPlugwindow(wp)) || IsModelessDialog (wp)) nonFloaterFound = true;
		
	return (GetWindowMyWindowPtr(lastFloater));
}

/**********************************************************************
 * MyDragWindow - patch for DragWindow to properly draw drag outlines.
 **********************************************************************/
void MyDragWindow (WindowPtr winWP, Point start, Rect *boundsRect)
{
	DragWindow(winWP, start, boundsRect);
	PositionDockedWindow(winWP);
}

/**********************************************************************
 * DockedDragProc - callback for dragging docked window around edge of screen
 **********************************************************************/
void DockedDragProc(Point *pt,RgnHandle winRgn,long refCon)
{
	short	dx,dy,dx1,dy1,dx2,dy2;
	Rect	rWin,rScreen;
	WindowPtr	theWindow = (WindowPtr) refCon;
	
	//	Get bounds of window at new location
	GetRegionBounds(winRgn,&rWin);
	OffsetRect(&rWin,pt->h-rWin.left,pt->v-rWin.top);

	GetScreenRect(theWindow,&rWin,&rScreen);
	
	dx1 = rWin.left - rScreen.left;
	dy1 = rWin.top - rScreen.top;
	dx2 = rScreen.right - rWin.right;
	dy2 = rScreen.bottom - rWin.bottom;	
	dx = dy = 0;

	//	Make sure we're on screen
	if (dx1<0) dx = -dx1;
	else if (dx2 < 0) dx = dx2;
	if (dy1<0) dy = -dy1;
	else if (dy2 < 0) dy = dy2;
	
	if (!dx && !dy)
	{
		//	Move to edge. Find the edge we are closest to
		short	dTop,dLeft,dBottom,dRight;

		dTop = rWin.top - rScreen.top;
		dLeft = rWin.left - rScreen.left;
		dBottom = rScreen.bottom - rWin.bottom;
		dRight = rScreen.right - rWin.right;
		if (dLeft < dTop && dLeft < dRight && dLeft < dBottom)
			dx = -dx1;	//	Move to left edge
		else if (dTop < dRight && dTop < dBottom)
			dy = -dy1;	//	Move to top edge
		else if (dRight < dBottom)
			dx = dx2;	//	Move to right edge
		else if (dRight == dBottom)
		{
			dy = dy2;	//	Move to bottom/right corner
			dx = dx2;
		}
		else
			dy = dy2;	//	Move to bottom
	}
	
	//	Stay away from any other docked windows
	if (theWindow)
	{
		WindowPtr	winWP;
		MyWindowPtr	win;
		OffsetRect(&rWin,dx,dy);
		
		for (winWP = FrontWindow (); winWP; winWP = GetNextWindow (winWP))
		{
 			win = GetWindowMyWindowPtr (winWP);
 			if (winWP!=theWindow && IsWindowVisible (winWP) && IsFloating(winWP) && win->windowType==kDockable)
			{
				Rect rSect,rDocked;

				GetWindowStructureBounds(winWP,&rDocked);
				if (SectRect(&rWin,&rDocked,&rSect))
				{
					//	Overlap with this docked window
					Boolean	roomAbove = rDocked.top-rScreen.top >= RectHi(rWin);
					Boolean	roomBelow = rScreen.bottom-rDocked.bottom > RectHi(rWin);
					Boolean	roomLeft = rDocked.left-rScreen.left > RectWi(rWin);
					Boolean roomRight = rScreen.right-rDocked.right > RectWi(rWin);
					Boolean moveLeft = (rWin.left+rWin.right)/2 < (rDocked.left+rDocked.right)/2;
					Boolean moveAbove = (rWin.top+rWin.bottom)/2 < (rDocked.top+rDocked.bottom)/2;
					Boolean moveVertical = RectHi(rSect) < RectWi(rSect);

					if (moveVertical)
					{
						if (!roomAbove && !roomBelow)
							moveVertical = false;
					}
					else if (!roomLeft && !roomRight)
						moveVertical = true;
						
					if (moveVertical)
					{
						//	Move above or below
						if (moveAbove)
						{
							if (!roomAbove) moveAbove = false;
						}
						else
							if (!roomBelow) moveAbove = true;
						
						if (moveAbove)
							dy -= rWin.bottom-rDocked.top;	//	Move above
						else
							dy += rDocked.bottom-rWin.top;	//	Move below
					}
					else
					{
						//	Move left or right
						if (moveLeft)
						{
							if (!roomLeft) moveLeft = false;
						}
						else
							if (!roomRight) moveLeft = true;
						
						if (moveLeft)												
								dx -= rWin.right-rDocked.left;	//	Move left
						else
							dx += rDocked.right-rWin.left;	//	Move right
					}						
				}
			}
		}
	}
	
	pt->h += dx;
	pt->v += dy;
}

/**********************************************************************
 * GetScreenRect - get rect of screen containing specified window
 **********************************************************************/
void GetScreenRect(WindowPtr theWindow, Rect *rWin, Rect *rScreen)
{
	GDHandle	gd;

	if (theWindow && GetWindowKind(theWindow)==AD_WIN)
		//	If ad window, constrain to main screen
		gd = MyGetMainDevice();
	else
	{
		utl_GetRectGD(rWin,&gd);
		if (!gd)
			//	Not on any screen! Use main screen.
			gd = MyGetMainDevice();
	}
	
	// Get screen rect minus menu bar, dock, etc.
	GetAvailableWindowPositioningBounds(gd,rScreen);
}

/**********************************************************************
 * PositionDockedWindow - make sure docked window is positioned correctly
 **********************************************************************/
void PositionDockedWindow(WindowPtr winWP)
{
	MyWindowPtr	win = GetWindowMyWindowPtr (winWP);
	Point	pt;
	RgnHandle	rgn = nil;
	Rect	rStruc,rCont;
	
	if (!win || !IsMyWindow(winWP) || win->windowType != kDockable || !IsWindowVisible (winWP)) return;
	
	GetWindowStructureBounds(winWP,&rStruc);
	pt.v = rStruc.top;
	pt.h = rStruc.left;
	DockedDragProc(&pt,rgn?rgn:MyGetWindowStructureRegion(winWP),(long)winWP);
	if (rgn) DisposeRgn(rgn);
	if (pt.h!=rStruc.left || pt.v!=rStruc.top)
	{
		//	pt is location of strucRgn. Move region needs location of contRgn
		GetWindowContentBounds(winWP,&rCont);
		MoveWindow(winWP,pt.h+rCont.left-rStruc.left,pt.v+rCont.top-rStruc.top,false);
	}
}

/**********************************************************************
 * PositionDockedWindows - make sure docked windows are positioned correctly
 **********************************************************************/
void PositionDockedWindows(void)
{
	WindowPtr	theWindow;
	
	for (theWindow = GetWindowList(); theWindow; theWindow = GetNextWindow (theWindow))
		PositionDockedWindow(theWindow);
}

/**********************************************************************
 * DockWinReduce - reduce a rect to not include docked windows
 **********************************************************************/
void DockedWinReduce(WindowPtr checkWinWP, Rect *winRect, Rect *r)
{
	WindowPtr	winWP;
	MyWindowPtr	win;
	Rect	rWin;
	
	if (winRect) rWin = *winRect;
	else if (checkWinWP) rWin = CurState(checkWinWP);
	
	for (winWP = GetWindowList (); winWP; winWP = GetNextWindow (winWP))
	{
		win = GetWindowMyWindowPtr (winWP);
		if (IsKnownWindowMyWindow(winWP) && win && (win)->windowType == kDockable && IsWindowVisible (winWP))
		{
			Rect	rDocked,rSect;
			
			GetWindowStructureBounds(winWP,&rDocked);
			if (SectRect(&rDocked,checkWinWP?&rWin:r,&rSect))
			{
				//	The window intersects a docked window
				Rect	rScreen;
				enum { kNone,kTop,kLeft,kBottom,kRight } where;
				short	minDelta = REAL_BIG;
				
				GetScreenRect(checkWinWP,&rDocked,&rScreen);
				
				//	Determine which screen edge we need to move away from
				//	Use the one that's the shortest distance
				if (rDocked.bottom - rScreen.top < minDelta)
				{
					minDelta = rDocked.bottom - rScreen.top;
					where = kTop;
				}
				if (rDocked.right - rScreen.left < minDelta)
				{
					minDelta = rDocked.right - rScreen.left;
					where = kLeft;
				}
				if (rScreen.bottom - rDocked.top < minDelta)
				{
					minDelta = rScreen.bottom - rDocked.top;
					where = kBottom;
				}
				if (rScreen.right - rDocked.left < minDelta)
				{
					minDelta = rScreen.right - rDocked.left;
					where = kRight;
				}

				switch (where)
				{
					case kTop:
						if (r->top < rDocked.bottom)
						{
							r->top = rDocked.bottom + 2;
							OffsetRect(&rWin,0,rDocked.bottom+2-rWin.top);
						}
						break;
					case kLeft:
						if (r->left < rDocked.right)
						{
							r->left = rDocked.right + 2;
							OffsetRect(&rWin,rDocked.right+2-rWin.left,0);
						}
						break;
					case kBottom:
						if (r->bottom > rDocked.top) r->bottom = rDocked.top - 2;
						break;
					case kRight:
						if (r->right > rDocked.left) r->right = rDocked.left - 2;
						break;
				}
			}
		}
	}
}

/**********************************************************************
 * DockedWinRemove - subtract docked windows from a region
 **********************************************************************/
void DockedWinRemove(RgnHandle rgn,WindowPtr ignoreWinWP)
{
	WindowPtr	winWP;
	MyWindowPtr	win;
	
	for (winWP = GetWindowList (); winWP; winWP = GetNextWindow (winWP))
	{
		win = GetWindowMyWindowPtr (winWP);
		if (winWP!=ignoreWinWP && IsKnownWindowMyWindow(winWP) && win && (win)->windowType == kDockable)
		{
			Rect	rDocked;
			
			GetWindowStructureBounds(winWP,&rDocked);
			RgnMinusRect(rgn,&rDocked);
		}
	}
}

/**********************************************************************
 * Returns true if this is the frontmost, non floating window
 **********************************************************************/
Boolean IsTopNonFloater(WindowPtr theWindow)
{
	return (theWindow == MyFrontNonFloatingWindow());
}
/**********************************************************************
 * Return a pointer to the frontmost, non floating, visible window.
 *
 * In OS X, invisible windows can be at the head of the window list.
 * Eudora opens a lot of invisible windows, and assumes they're not
 * in the list at all.  This routine exists to help work around this
 * development, and return the topmost, VISIBLE window.  It should
 * replace calls to FrontWindow_() when we're looking for the frontmost
 * visible window.
 *
 * Changing MyFrontNonFloatingWindow() to return the topmost, visible, 
 * non-floating window should be perfectly safe.  In the classic
 * version, MyFrontNonFloatingWindow NEVER returns an invisible window.
 **********************************************************************/
WindowPtr MyFrontNonFloatingWindow(void)
{
	return FrontNonFloatingWindow();
}


/**********************************************************************
 * Return a pointer to the window that is topmost on this part of the
 * screen.  Return 0 if there are no windows here, or those that are
 * aren't floating windows.
 **********************************************************************/
MyWindowPtr FloaterAtPoint(Point mouse)
{
	WindowPtr aFloater = NULL;
	Rect r;
	
	LocalToGlobal(&mouse);
	
	if (IsFloating(FrontWindow()))
	{
		aFloater = FrontWindow();
		
		while (aFloater && IsFloating(aFloater))
		{
			GetWindowContentBounds(aFloater,&r);
			if (PtInRect(mouse,&r)) return (GetWindowMyWindowPtr(aFloater));
			aFloater = GetNextWindow(aFloater);
		}
	}
	
	return (nil);
}

/**********************************************************************
 * Hide a window, but activate the next non-floating window.
 **********************************************************************/
void MyHideWindow(WindowPtr theWindow)
{

	HideWindow(theWindow);
	WindowIsInvisibleClassic(theWindow);
	if (IsFloating(theWindow)) SetKeyFocusedFloater(nil);
	if (!IsFloating(theWindow) && GetNextWindow(theWindow)) ActivateMyWindow(theWindow, !InBG);
}

/**********************************************************************
 * IsFloating - returns true if this window floats above others.  
 * Currently this includes kFloating and kDockable windows.
 **********************************************************************/
Boolean IsFloating(WindowPtr winWP)
{
	MyWindowPtr	win = GetWindowMyWindowPtr (winWP);

	return (IsKnownWindowMyWindow(winWP) && win && ((win->windowType == kFloating) || (win->windowType == kDockable)));
} 

/**********************************************************************
 * SetKeyFocusFloater - sets floating window to receive key focus if window supports keys
 **********************************************************************/
void SetKeyFocusedFloater(MyWindowPtr win)
{
	ASSERT(!keyFocusedFloater || win && keyFocusedFloater==win);
	//	Don't allow ad window to receive focus although it has a Pete handle
	keyFocusedFloater = win && win->pte && GetWindowKind (GetMyWindowWindowPtr (win)) != AD_WIN ? win : nil;
}

/**********************************************************************
 * FloatingWinIdle - periodically make sure no non-floaters are on top
 *   the only windows I'm aware of that can do this are plug-in windows
 **********************************************************************/
void FloatingWinIdle(void)
{
}


#endif	//FLOAT_WIN