/*
	"Floating.c"
	
	This file offers substitute routines for commonly used calls to the
	window manager in order to gain the advantage of supporting multiple
	floating windows (such as an info window, a tool pallete, etc.)
*/


/*
	From John DeWeese's (jdeweese@systex.net) "README - Floating.c" (see info-mac):

	Originally based on a set of C routines written by Patrick Doane (America OnLine : 
	Patrick5). Later translated into pascal by F. Pottier (pottier@clipper.ens.fr).
	Thanks go to these two, and also to Troy Gaul for placing his Infinity Windoid in 
	the public domain.

	This code is placed in the public domain. It may be used by anybody, for any purposes. 
	It would be kind to give credits to me as well as to the aforementioned people, though. 
	If you experience any problems, or have ideas for enhancements, tell me about it. 
	I can't guarantee I'll spend much time on it, since I'm only a student and not a 
	professional programmer, but I'll definitely look into it.

*/


/*
	Change History
	(while in the hands of John DeWeese)
	
	8/30/95 -
	Altered DragTheWindow to more closely emulate the standard DragWindow(),
	added an if (StillDown()) after SelectTheWindow() to give it a time
	cushioned effect.
	
	11/14/95 -
	Commented out the ReactToRemoval() in the function HideTheWindow()
	because of a display bug it cause on my PowerMac with System 7.5.
	Since this bug was tested on other systems as well, I believe this
	has cured it. If windows are not hiliting at all once one is closed,
	this procedure may be suspect. Thus, it is simply commented.
	
	11/20/95 -
	Removed the superfluous PaintOne() and CalcVis() from SelectTheWindow().
	PaintOne() was the cause of a mysterious flicker upon update of the 
	window, and CalcVis() seems to be docile, but uneccessary.

  Further changes by Hans Ronne:
  
  6/30/99 -
	Uncomment ReactToRemoval() call in HideTheWindow(). This is necessary
	in order for the underlying window to become active. I haven't seen the
	display bug. Perhaps it is no longer a problem since System 7.6.
	
	Uncomment HideFloats() and ShowFloats() prototypes in Floating.h to stop
	missing prototype warnings in CodeWarrior.
	
	Paste acknowledgements and public domain statement from John DeWeese's
	"README - Floating.c" file into "Floating.c".

	7/5/99 -
	ActivateWindow did not work properly. It hilited/unhilited the window but 
	did not generate any activate events at all. I couldn't figure out this
	code so I rewrote the function using the PPostEvent trap. It now works OK, 
	also with modeless dialogs. Possibly, the original function could be fixed 
	using the pascal sources, which are on DejaNews (search for "floating.p")
	if anybody wants to try that. Something may have gone wrong during the 
	pascal to c conversion.

	2/6/00 -
	New functions IsDialog and MakeDialogFloat added to support floating
	non-modal dialogs. UpdateFloats and SelectTheWindow modified accordingly.
*/

#include "Floating.h"

// global variables
WindowPtr bottomFloat;
WindowPtr topFloat;
WindowPtr topWindow;

/*
ActivateWindow hilites/unhilites the window and then generates an
activate/deactivate event for it. In order to generate activate
events, it mucks with low-mem globals. It would be cleaner to
directly call the activate routine for the window. The advantage
of this method is that it also works with modeless dialogs.
The Dialog Manager doesn't know that a dialog is active unless
it actually receives an activate event.
*/

#if 0		/* This function does not work. See comments above. */

void ActivateWindow (WindowPtr window, Boolean on)
{
	WindowPtr *p;

	if (window != nil)
	{
		HiliteWindow(window, on);
		if (on) {
			p = (WindowPtr *)(CurActivate);		
		} else {
			p = (WindowPtr *)(CurDeactive);
		}
		p = &window;
	}
}

#else		/* Rewritten by Hans Ronne. */

void ActivateWindow (WindowPtr window, Boolean on)
{
  EvQEl *myQElPtr;

	if (window != nil)
	{
		HiliteWindow(window, on);
		if (on) {
			PPostEvent(activateEvt,(unsigned long) window, &myQElPtr);
		  /* activeFlag is the 0 bit, so we don't need any bit gymnastics. 
		  Just set the modifiers to true instead. */
			myQElPtr->evtQModifiers = true; 
		} else {
			PPostEvent(activateEvt,(unsigned long) window, &myQElPtr);
			myQElPtr->evtQModifiers = false;
		}
	}
}

#endif

/*
InitFloats is to be called at the beginning of the program,
before creating any windows. It sets our global variables to nil.
*/

void InitFloats (void)
{
	topFloat = nil;
	bottomFloat = nil;
	topWindow = nil;
}

/*
IsFloating and IsVisible are little, foolproof routines that tell
whether a window is floating or visible. Using them avoids errors
such as examining WindowPeek(whichWindow)->visible when whichWindow == nil
*/

Boolean IsFloating (WindowPtr whichWindow)
{
	if (whichWindow == nil)
		return false;
	else
		return (((WindowPeek)whichWindow)->windowKind == kFloatingKind);
}

Boolean IsDialog (WindowPtr whichWindow)
{
	if (whichWindow == nil)
		return false;
	else
		return (((WindowPeek)whichWindow)->windowKind == dialogKind);
}

Boolean IsVisible (WindowPtr whichWindow)
{
	if (whichWindow == nil)
		return false;
	else
		return (((WindowPeek)whichWindow)->visible);
}

/*
UpdateFloats updates the topFloat and bottomFloat variables by walking
down the window list. It then determines topWindow by taking the first
visible window after bottomFloat.
*/

void UpdateFloats (void)
{
	WindowPtr theWindow = FrontWindow();
		
	if (IsFloating(theWindow) || IsDialog(theWindow))
	{
		topFloat = theWindow;
		while ((theWindow != nil) && (IsFloating(theWindow) || IsDialog(theWindow)))
		{
			bottomFloat = theWindow;
			theWindow = (WindowPtr)(((WindowPeek)theWindow)->nextWindow);
		}
		
		while ((theWindow != nil) && (!IsVisible(theWindow)))
			theWindow = (WindowPtr)(((WindowPeek)theWindow)->nextWindow);
		topWindow = theWindow;
	}
	else	
	{
		topFloat = nil;	
		bottomFloat = nil;
		topWindow = FrontWindow();
	}
}

/*
MakeFloat turns a newly created window into a floating window and
brings it to the front. The window must have been created via a call
to [Get]NewWindow().
*/

void MakeFloat (WindowPtr theWindow)
{
	BringToFront(theWindow);
	ShowHide(theWindow, true);
	if (topFloat == nil)
		bottomFloat = theWindow;	
	topFloat = theWindow;
	((WindowPeek)theWindow)->windowKind = kFloatingKind;		
}

void MakeDialogFloat (WindowPtr theWindow)
{
	BringToFront(theWindow);
	ShowHide(theWindow, true);
	if (topFloat == nil)
		bottomFloat = theWindow;	
	topFloat = theWindow;
	((WindowPeek)theWindow)->windowKind = kDialogWindowKind;		
}

/*
ReactToRemoval handles a few necessary steps after killing whichWindow.
underWindow is the window that was under whichWindow
(i.e. next to it in the window list).
*/

void ReactToRemoval (WindowPeek underWindow)
{
	while ((underWindow != nil) && (!IsVisible((WindowPtr)underWindow)))
	{
		underWindow = underWindow->nextWindow;			
	}
	ActivateWindow((WindowPtr)underWindow, true);
	UpdateFloats();
}

void HideTheWindow (WindowPtr whichWindow)
{
	WindowPeek underWindow;	

	underWindow = ((WindowPeek)whichWindow)->nextWindow;		
	HideWindow(whichWindow);
	ReactToRemoval(underWindow);
}

void DisposeTheWindow (WindowPtr whichWindow)
{
	WindowPeek underWindow;

	underWindow = ((WindowPeek)whichWindow)->nextWindow;	
	DisposeWindow(whichWindow);
	ReactToRemoval(underWindow);
}

/*
SelectTheWindow is the most important routine. Here's what we do:
If whichWindow is not visible, show it.
- If whichWindow is a floating window, just bring it to the front.
- If it is a regular window and there are no floating windows, bring
   it to the front, activate it, deactivate the former front window.
- If it is a regular window and there are floating windows, send it
   behind bottomFloat, activate it, deactivate the old topWindow
Finally, update the global float variables.
*/

void SelectTheWindow (WindowPtr whichWindow)
{	
	if (!(((WindowPeek)whichWindow)->visible))
		ShowHide(whichWindow, true);

	if (IsFloating(whichWindow) || IsDialog(whichWindow))
	{
		BringToFront(whichWindow);
		HiliteWindow(whichWindow, true);
	}
	else
	{
		if (whichWindow != topWindow)
		{	
			if (bottomFloat != nil)					
			{
				SendBehind(whichWindow, bottomFloat);		
  			//PaintOne(whichWindow, ((WindowPeek)whichWindow)->strucRgn);
	  		//CalcVis(whichWindow);
			}
			else
				BringToFront(whichWindow);

			ActivateWindow(whichWindow, true);
			ActivateWindow(topWindow, false);
		}
	}
	
	UpdateFloats();
}

/*
DragTheWindow is to be called instead of DragWindow, because DragWindow
brings the window to the front unless the command key is down. We use
DragGrayRgn to drag a gray outline of the window. We clip the dragging
to a region consisting of the whole desktop minus the windows that are
in front of the window we're dragging. To emulate DragWindow's behavior,
we send the window to the front, unless the command key is down.
*/

void DragTheWindow (WindowPtr whichWindow, EventRecord *Event)
{
	GrafPtr savePort;
	GrafPort wPort;
	Point thePoint;
	long newLoc;
	WindowPeek theWindow;
	Rect dragRect;
	RgnHandle dragRgn;
	
	if (!(Event->modifiers & cmdKey))
		SelectTheWindow(whichWindow);
	
	if (StillDown())
	{
		GetPort(&savePort);
		SetPort(whichWindow);
		thePoint.h = whichWindow->portRect.left;
		thePoint.v = whichWindow->portRect.top;
		LocalToGlobal(&thePoint);
		dragRgn = NewRgn();
		CopyRgn(((WindowPeek)whichWindow)->strucRgn, dragRgn);
		
		OpenPort(&wPort);
		CopyRgn(GetGrayRgn(), wPort.visRgn);
		theWindow = (WindowPeek)(FrontWindow());
		while (theWindow != ((WindowPeek)whichWindow))
		{
			DiffRgn(wPort.visRgn, theWindow->strucRgn, wPort.visRgn);
			theWindow = theWindow->nextWindow;
		}
	
		dragRect = (*GetGrayRgn())->rgnBBox;
		InsetRect(&dragRect, 4, 4);
		
		// Here is the heart of the drag action. nil at the end is a space for a DragGrayRgnUPP
		newLoc = DragGrayRgn(dragRgn, Event->where, &dragRect, &dragRect, noConstraint, nil);
		if ((newLoc != cancelDrag) && (newLoc != 0))
			MoveWindow(whichWindow, thePoint.h + LoWord(newLoc),
															thePoint.v + HiWord(newLoc), false);
	
		DisposeRgn(dragRgn);
		ClosePort(&wPort);
		SetPort(savePort);
	}
	else
	{
		SelectTheWindow(whichWindow);
	}
}

/*
HideFloats/ShowFloats are simple routines that hide/show all floating
windows. They're useful to handle suspend/resume events.

*/

void HideFloats (void)
{
	while (IsFloating(FrontWindow()))	
		HideWindow(FrontWindow());
	//topFloat = nil;
	//bottomFloat = nil;
}

/*
**************** Note ****************
ShowFloats is buggy.

After calling ShowFloats, the window
behind the topWindow is lost track of.
Put another way, topWindow's 
"nextWindow" is updated incorrectly.
That means that even if new windows
are created, that link is still lost.
**************************************
*/

void ShowFloats (void)
{
	WindowPtr
		theWindow,
		*p;
		
	p = (WindowPtr *)(WindowList);
	theWindow = (*p);
	while (theWindow != nil)
	{
		if (IsFloating(theWindow))
		{
			ShowHide(theWindow, true);
			HiliteWindow(theWindow, true);
		}
		theWindow = (WindowPtr)((WindowPeek)theWindow)->nextWindow;
	}
}