/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

// 
// nsMacMessagePump
//
// This file contains the default implementation for the mac event loop. Events that
// pertain to the layout engine are routed there via a MessageSink that is passed in
// at creation time. Events not destined for layout are handled here (such as window
// moved).
//
// Clients may either use this implementation or write their own. Embedding applications
// will almost certainly write their own because they will want control of the event
// loop to do other processing. There is nothing in the architecture which forces the
// embedding app to use anything called a "message pump" so the event loop can actually
// live anywhere the app wants.
//

#include "nsMacMessagePump.h"
#include "nsMacMessageSink.h"
#include "nsWidgetsCID.h"
#include "nsToolkit.h"
#include "nscore.h"

#include <MacWindows.h>
#include <LPeriodical.h>
#include <ToolUtils.h>
#include <LowMem.h>

#define DRAW_ON_RESIZE

const char SUSPENDRESUMEMESSAGE = 0x01;
const char MOUSEMOVEDMESSAGE = 0xFA;

const short	kMinWindowWidth = 300;
const short kMinWindowHeight = 150;

NS_WIDGET nsMacMessagePump::nsWindowlessMenuEventHandler nsMacMessagePump::gWindowlessMenuEventHandler = nsnull;


// a small helper routine, inlined for efficiency
bool IsUserWindow(WindowPtr);
inline bool IsUserWindow(WindowPtr wp)
{
	return wp && (::GetWindowKind(wp) >= kApplicationWindowKind);
}


//=================================================================
/*  Constructor
 *  @update  dc 08/31/98
 *  @param   aToolkit -- The toolkit created by the application
 *  @return  NONE
 */
nsMacMessagePump::nsMacMessagePump(nsToolkit *aToolkit, nsMacMessageSink* aSink)
	: mToolkit(aToolkit), mMessageSink(aSink)
{
	mRunning = PR_FALSE;
}

//=================================================================
/*  Destructor
 *  @update  dc 08/31/98
 *  @param   NONE
 *  @return  NONE
 */
nsMacMessagePump::~nsMacMessagePump()
{
  //¥¥¥ release the toolkits and sinks? not if we use COM_auto_ptr.
}

//=================================================================
/*  Runs the message pump for the macintosh.  Turns them into Raptor events
 *  @update  dc 08/31/98
 *  @param   NONE
 *  @return  A boolean which states how the pump terminated
 */
PRBool 
nsMacMessagePump::DoMessagePump()
{
	Boolean					haveEvent;
	EventRecord			theEvent;
	long						sleep				=	0;
	unsigned short	eventMask 	= (everyEvent - diskMask);

	mRunning = PR_TRUE;
	mInBackground = PR_FALSE;
	
	// calculate the region to watch
	RgnHandle mouseRgn = ::NewRgn();
	::SetRectRgn(mouseRgn, -32000, -32000, -32001, -32001);
	
	while (mRunning)
	{			
		::LMSetSysEvtMask(eventMask);	// we need keyUp events
		haveEvent = ::WaitNextEvent(eventMask, &theEvent, sleep, mouseRgn);

		if (haveEvent)
		{
			switch(theEvent.what)
			{
				case keyUp:
				case keyDown:
				case autoKey:
					DoKey(theEvent);
					break;

				case mouseDown:
					DoMouseDown(theEvent);
					break;

				case mouseUp:
					DoMouseUp(theEvent);
					break;

				case updateEvt:
					DoUpdate(theEvent);
					break;

				case activateEvt:
					DoActivate(theEvent);
					break;

				case osEvt:
					unsigned char eventType = ((theEvent.message >> 24) & 0x00ff);
					switch (eventType)
					{
						case SUSPENDRESUMEMESSAGE:
							if (theEvent.message & 0x00000001)
								mInBackground = PR_FALSE;		// resume message
							else
								mInBackground = PR_TRUE;		// suspend message
							break;

						case MOUSEMOVEDMESSAGE:
							DoMouseMove(theEvent);
							break;
					}
					break;
			}
		}
		else
		{
			DoIdle(theEvent);
			if (mRunning)
				LPeriodical::DevoteTimeToIdlers(theEvent);
		}

		if (mRunning)
			LPeriodical::DevoteTimeToRepeaters(theEvent);
	}

  return NS_OK;
}

#pragma mark -
//-------------------------------------------------------------------------
//
// DoUpdate
//
//-------------------------------------------------------------------------
void nsMacMessagePump::DoUpdate(EventRecord &anEvent)
{
	WindowPtr whichWindow = (WindowPtr)anEvent.message;
	if (IsUserWindow(whichWindow))
	{
		GrafPtr savePort;
		::GetPort(&savePort);
		::SetPort(whichWindow);
		::BeginUpdate(whichWindow);
#if 0		//¥¥¥test¥¥¥
				static Boolean aBool = 1;
				RGBColor green = {0,65535,0};
				RGBColor red   = {65535,0,0};
				::RGBForeColor((aBool ? &green : &red));
				aBool ^= 1;
				::PenSize(2,2);
				::ClipRect(&whichWindow->portRect);
				::FrameRgn(whichWindow->visRgn);
#endif	//¥¥¥¥¥¥¥¥¥
		// The app can do its own updates here
		DispatchOSEventToRaptor(anEvent, whichWindow);
		::EndUpdate(whichWindow);
		::SetPort(savePort);
	}
}


//-------------------------------------------------------------------------
//
// DoMouseDown
//
//-------------------------------------------------------------------------

void nsMacMessagePump::DoMouseDown(EventRecord &anEvent)
{
		WindowPtr			whichWindow;
		PRInt16				partCode;

	partCode = ::FindWindow(anEvent.where, &whichWindow);

	switch (partCode)
	{
			case inSysWindow:
				break;

			case inMenuBar:
			{
			  long menuResult = ::MenuSelect(anEvent.where);
			  if (HiWord(menuResult) != 0)
			      DoMenu(anEvent, menuResult);
				break;
			}

			case inContent:
			{
				::SetPort(whichWindow);
				if (IsWindowHilited(whichWindow))
					DispatchOSEventToRaptor(anEvent, whichWindow);
				else
					::SelectWindow(whichWindow);
				break;
			}

			case inDrag:
			{
				::SetPort(whichWindow);
				if (!(anEvent.modifiers & cmdKey))
					::SelectWindow(whichWindow);
				Rect screenRect = (**::GetGrayRgn()).rgnBBox;
				::DragWindow(whichWindow, anEvent.where, &screenRect);

				::GetMouse(&anEvent.where);
				::LocalToGlobal(&anEvent.where);
				// it's not really necessary to send that event to Raptor but (who knows?)
				// some windows may want to know that they have been moved
				DispatchOSEventToRaptor(anEvent, whichWindow);
				break;
			}

			case inGrow:
			{
				::SetPort(whichWindow);
#ifdef DRAW_ON_RESIZE
				Point oldPt = anEvent.where;
				while (::WaitMouseUp())
				{
					LPeriodical::DevoteTimeToRepeaters(anEvent);

					Point origin = {0,0};
					::LocalToGlobal(&origin);
					Point newPt;
					::GetMouse(&newPt);
					::LocalToGlobal(&newPt);
					if (::DeltaPoint(oldPt, newPt))
					{
						Rect portRect = whichWindow->portRect;
						short	width = newPt.h - origin.h;
						short	height = newPt.v - origin.v;
						if (width < kMinWindowWidth)
							width = kMinWindowWidth;
						if (height < kMinWindowHeight)
							height = kMinWindowHeight;

						oldPt = newPt;
						::SizeWindow(whichWindow, width, height, true);
						::DrawGrowIcon(whichWindow);

						anEvent.where.h = width;	// simulate a click in the grow icon
						anEvent.where.v = height;
						::LocalToGlobal(&anEvent.where);
						DispatchOSEventToRaptor(anEvent, whichWindow);

						Boolean					haveEvent;
						EventRecord			updateEvent;
						haveEvent = ::WaitNextEvent(updateMask, &updateEvent, 0, nil);
						if (haveEvent)
							DoUpdate(updateEvent);
					}
				}
#else
				Rect sizeRect = (**::GetGrayRgn()).rgnBBox;
				sizeRect.top = kMinWindowHeight;
				sizeRect.left = kMinWindowWidth;
				long newSize = ::GrowWindow(whichWindow, anEvent.where, &sizeRect);
				if (newSize != 0)
					::SizeWindow(whichWindow, newSize & 0x0FFFF, (newSize >> 16) & 0x0FFFF, true);
				::DrawGrowIcon(whichWindow);
				Point newPt;
				::GetMouse(&newPt);
				::LocalToGlobal(&newPt);
				anEvent.where = newPt;	// important!
				DispatchOSEventToRaptor(anEvent, whichWindow);
#endif
				break;
			}

			case inGoAway:
			{
				::SetPort(whichWindow);
				if (::TrackGoAway(whichWindow, anEvent.where))
					DispatchOSEventToRaptor(anEvent, whichWindow);
				break;
			}

			case inZoomIn:
			case inZoomOut:
				break;
	}
}


//-------------------------------------------------------------------------
//
// DoMouseUp
//
//-------------------------------------------------------------------------
void nsMacMessagePump::DoMouseUp(EventRecord &anEvent)
{
		WindowPtr			whichWindow;
		PRInt16				partCode;

	partCode = ::FindWindow(anEvent.where, &whichWindow);
	if (whichWindow == nil)
	{
		// We need to report the event even when it happens over no window:
		// when the user clicks a widget, keeps the mouse button pressed and
		// releases it outside the window, the event needs to be reported to
		// the widget so that it can deactivate itself.
		whichWindow = ::FrontWindow();
	}
	DispatchOSEventToRaptor(anEvent, whichWindow);
}


//-------------------------------------------------------------------------
//
// DoMouseMove
//
//-------------------------------------------------------------------------
void  nsMacMessagePump::DoMouseMove(EventRecord &anEvent)
{
	// same thing as DoMouseUp
		WindowPtr			whichWindow;
		PRInt16				partCode;

	partCode = ::FindWindow(anEvent.where, &whichWindow);
	if (whichWindow == nil)
		whichWindow = ::FrontWindow();
	DispatchOSEventToRaptor(anEvent, whichWindow);
}


//-------------------------------------------------------------------------
//
// DoKey
// 
// This is called for keydown, keyup, and key repeating events. So we need
// to be careful not to do things twice.
//-------------------------------------------------------------------------
void  nsMacMessagePump::DoKey(EventRecord &anEvent)
{
	char theChar = (char)(anEvent.message & charCodeMask);
	if ((anEvent.what == keyDown) && ((anEvent.modifiers & cmdKey) != 0))
	{
		// do a menu key command
		long menuResult = ::MenuKey(theChar);
		if (HiWord(menuResult) != 0)
			DoMenu(anEvent, menuResult);
	}
	else
	{
		DispatchOSEventToRaptor(anEvent, ::FrontWindow());
	}
}


//-------------------------------------------------------------------------
//
// DoMenu
//
//-------------------------------------------------------------------------
void  nsMacMessagePump::DoMenu(EventRecord &anEvent, long menuResult)
{
	// The app can handle its menu commands here or
	// in the nsNativeBrowserWindow and nsNativeViewerApp
	if (::FrontWindow() != nil)
	{
		DispatchMenuCommandToRaptor(anEvent, menuResult);
	}
	else
	{
		if (gWindowlessMenuEventHandler != nsnull)
			gWindowlessMenuEventHandler(menuResult);
	}
	HiliteMenu(0);
}


//-------------------------------------------------------------------------
//
// DoActivate
//
//-------------------------------------------------------------------------
void  nsMacMessagePump::DoActivate(EventRecord &anEvent)
{
	WindowPtr whichWindow = (WindowPtr)anEvent.message;
	::SetPort(whichWindow);
	if (anEvent.modifiers & activeFlag)
	{
		::BringToFront(whichWindow);
		::HiliteWindow(whichWindow,TRUE);
	}
	else
	{
		::HiliteWindow(whichWindow,FALSE);
	}

	DispatchOSEventToRaptor(anEvent, whichWindow);
}


//-------------------------------------------------------------------------
//
// DoIdle
//
//-------------------------------------------------------------------------
void  nsMacMessagePump::DoIdle(EventRecord &anEvent)
{
	// send mouseMove event
		static Point	lastWhere = {0, 0};

	if (*(long*)&lastWhere == *(long*)&anEvent.where)
		return;

	lastWhere = anEvent.where;
	DoMouseMove(anEvent);
}


#pragma mark -
//-------------------------------------------------------------------------
//
// DispatchOSEventToRaptor
//
//-------------------------------------------------------------------------
void  nsMacMessagePump::DispatchOSEventToRaptor(
													EventRecord 	&anEvent,
													WindowPtr			aWindow)
{
	if (aWindow && IsUserWindow(aWindow))
		mMessageSink->DispatchOSEvent(anEvent, aWindow);
}


//-------------------------------------------------------------------------
//
// DispatchMenuCommandToRaptor
//
//-------------------------------------------------------------------------
void nsMacMessagePump::DispatchMenuCommandToRaptor(
													EventRecord 	&anEvent,
													long					menuResult)
{
	WindowPtr whichWindow = ::FrontWindow();
	if (whichWindow && IsUserWindow(whichWindow))
		mMessageSink->DispatchMenuCommand(anEvent, menuResult);

}
