/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * 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.
 */

#include "CContextMenuAttachment.h"

#include "CDrawingState.h"
#include "CApplicationEventAttachment.h"

//-----------------------------------
CContextMenuAttachment::CContextMenuAttachment(LStream* inStream)
:	LAttachment(inStream)
//-----------------------------------
{
	*inStream >> mMenuID >> mTextTraitsID;
	mHostView = dynamic_cast<LView*>(LAttachable::GetDefaultAttachable());
	mHostCommander = dynamic_cast<LCommander*>(LAttachable::GetDefaultAttachable());
	Assert_(mHostView);
	Assert_(mHostCommander);
}

//-----------------------------------
CContextMenuAttachment::~CContextMenuAttachment()
//-----------------------------------
{
} // CContextMenuAttachment::CContextMenuAttachment

//-----------------------------------
CContextMenuAttachment::EClickState CContextMenuAttachment::WaitMouseAction(
	const Point& 	inInitialPoint,
	Int32			inWhen,
	Int32			inDelay)
//-----------------------------------
{
	//mHostView->FocusDraw();

	if (CApplicationEventAttachment::CurrentEventHasModifiers(controlKey))
	{
		return eMouseTimeout;
	}

	while (::StillDown())
	{
		Point theCurrentPoint;
		::GetMouse(&theCurrentPoint);
		if ((abs(theCurrentPoint.h - inInitialPoint.h) >= eMouseHysteresis) ||
			(abs(theCurrentPoint.v - inInitialPoint.v) >= eMouseHysteresis))
			return eMouseDragging;
		Int32 now = ::TickCount();
		if (abs( now - inWhen ) > inDelay)
			return eMouseTimeout;
	}
	return eMouseUpEarly;
} // CContextMenuAttachment::WaitMouseAction

//-----------------------------------
UInt16 CContextMenuAttachment::PruneMenu(LMenu* inMenu)
// Prune the menu.  Either enable each item, or remove it.  Returns the menu count.
//-----------------------------------
{

// Flag bits
#define kKeepItemDisabled		1<<0 /* Don't remove this item, show it disabled */
#define kSupercommanderCannotEnable	1<<1
#define kWantsToBeDefault	1<<2
// (NOTE: Bit 7 is not usable, because the icon ID must have this bit set!)
	MenuHandle menuH = inMenu->GetMacMenuH();
	Int16 index = 0; CommandT command;
	Boolean previousItemWasSeparator = true; // true for first item
	Boolean isSeparator = false;
	Int16 itemCount = ::CountMItems(menuH);
	UInt16 defaultItem = 0;
	UInt16 firstEnabledItem = 0;
	while (inMenu->FindNextCommand(index, command))
	{
		Boolean enabled = false;
		Str255 commandText; // may be changed by host commander!
		::GetMenuItemText(menuH, index, commandText);
		Int16 itemFlags; // icon ID used for flags
		::GetItemIcon(menuH, index, &itemFlags);
		// Get rid of the icon ID (which we use for the flag hack)
		if (itemFlags)
			::SetItemIcon(menuH, index, 0);
		if (command > 0)
		{
			Boolean usesMark = false;
			Char16 outMark;
			Boolean statusKnown = false;
			// If a command is not to be enabled by virtue of its super alone, check
			// the super first.  If the super enables it, it's outta there.
			if ((itemFlags & kSupercommanderCannotEnable) != 0)
			{
				LCommander* super = mHostCommander->GetSuperCommander();
				if (super)
				{
					Boolean superEnabled = false;
					mHostCommander->ProcessCommandStatus(
						command, superEnabled, usesMark, outMark, commandText);
					if (superEnabled)
					{
						enabled = false;
						statusKnown = true;
					}
				}
			}
			// Normal case.  Ask the commander, let it use super if appropriate:
			if (!statusKnown)
				mHostCommander->ProcessCommandStatus(
					command, enabled, usesMark, outMark, commandText);
		}
		if (enabled)
		{
			::EnableItem(menuH, index);
			::SetMenuItemText(menuH, index, commandText);
			// Allow a separator on the next line
			previousItemWasSeparator = false;
			// First item that says "I can be default" wins...
			if (defaultItem == 0 && (itemFlags & kWantsToBeDefault))
				defaultItem = index;
			if (firstEnabledItem == 0)
				firstEnabledItem = index;
		}
		else // disabled, including separators
		{
			isSeparator = commandText[1] == '-';
			if (!isSeparator || previousItemWasSeparator)
			{
				if (!isSeparator && (itemFlags & kKeepItemDisabled))
				{
					::DisableItem(menuH, index);
					previousItemWasSeparator = false;
				}
				else
				{
					inMenu->RemoveItem(index);
					// decrement index so that we refind the next item
					index--;
					itemCount--;
					if (!itemCount) break;
					// Uh Oh, all items after the last separator were removed!
					if (index == itemCount && previousItemWasSeparator)
					{
						inMenu->RemoveItem(index);
						index--;
						itemCount--;
						break;
					}
				}
			}
			if (isSeparator)
				previousItemWasSeparator = true;
		}
	} // while
	// The caller will only show the menu if we return a result > 0.  So handle the
	// case when there are items, but all items are disabled.
	if (defaultItem == 0 && itemCount > 0)
		defaultItem = firstEnabledItem ? firstEnabledItem : 1;
	return defaultItem;
} // CContextMenuAttachment::PruneMenu

//-----------------------------------
void CContextMenuAttachment::DoPopup(const SMouseDownEvent& inMouseDown, EClickState& outResult)
//-----------------------------------
{
	Try_
	{
		LMenu* menu = BuildMenu();
		MenuHandle menuH = menu->GetMacMenuH();
		
		CommandT command = 0;
		// Call PruneMenu to strip out disabled commands.  Do nothing if no commands
		// are available.
		UInt16 defaultItem = PruneMenu(menu);
		if (defaultItem > 0)
		{
			Int16 saveFont = ::LMGetSysFontFam();
			Int16 saveSize = ::LMGetSysFontSize();
			StMercutioMDEFTextState theMercutioMDEFTextState;
			if (mTextTraitsID)
			{				
				mHostView->FocusDraw();
				TextTraitsH traitsH = UTextTraits::LoadTextTraits(mTextTraitsID);
				if (traitsH)
				{
					mHostView->FocusDraw();
					::LMSetSysFontFam((**traitsH).fontNumber);
					::LMSetSysFontSize((**traitsH).size);
					::LMSetLastSPExtra(-1L);
				}
			}
			// Handle the actual insertion into the hierarchical menubar
			::InsertMenu(menuH, hierMenu);
			// Bring up the menu.
			Point whereGlobal = inMouseDown.wherePort;
			mHostView->PortToGlobalPoint(whereGlobal);
			Int16 result = ::PopUpMenuSelect(
							menuH,
							whereGlobal.v - 5,
							whereGlobal.h - 5,
							defaultItem);
			mExecuteHost = false;
			outResult = eHandledByAttachment;
			// Restore the system font
			::LMSetSysFontFam(saveFont);
			::LMSetSysFontSize(saveSize);
			::LMSetLastSPExtra(-1L);
			if (result)
				command = menu->CommandFromIndex(result);
		}
		delete menu;
		if (command)
			mHostCommander->ObeyCommand(command, (void*)&inMouseDown);
	}
	Catch_(inError)
	{
	}
	EndCatch_
} // CContextMenuAttachment::DoPopup


//
// Execute
//
// Overridden to listen for multiple messages (context menu and context menu cursor)
//
Boolean
CContextMenuAttachment :: Execute ( MessageT inMessage, void *ioParam )
{
	Boolean	executeHost = true;
	
	if ((inMessage == msg_ContextMenu) || (inMessage == msg_ContextMenuCursor)) {
		ExecuteSelf(inMessage, ioParam);
		executeHost = mExecuteHost;
	}
	
	return executeHost;
}


//-----------------------------------
void CContextMenuAttachment::ExecuteSelf(
	MessageT	inMessage,
	void*		ioParam)
//-----------------------------------
{
	mExecuteHost = true;
	if (!mHostView || !mHostCommander)
		return;

	switch ( inMessage ) {
		case msg_ContextMenu:
			SExecuteParams& params = *(SExecuteParams*)ioParam;		
			const SMouseDownEvent& mouseDown = *params.inMouseDown;
			params.outResult = WaitMouseAction(
										mouseDown.whereLocal,
										mouseDown.macEvent.when,
										2 * GetDblTime());
			if (params.outResult == eMouseTimeout)
				DoPopup(mouseDown, params.outResult);
			break;
	
		case msg_ContextMenuCursor:
			Assert_(ioParam != NULL);
			ChangeCursor ( (EventRecord*)ioParam );
			break;
			
	} // case of which message
	
} // CContextMenuAttachment::ExecuteSelf

//
// BuildMenu
// THROWS int on error
//
// Reads in the menu with the id given in the PPob. This can be overridden to read the menu
// from some other place, such as from the back-end.
//

LMenu*
CContextMenuAttachment :: BuildMenu ( )
{
	LMenu* menu = new LMenu(mMenuID);
	MenuHandle menuH = menu->GetMacMenuH();
	if (!menuH)
		throw ResError();
	::DetachResource((Handle)menuH);
	
	return menu;

} // CContextMenuAttachment::BuildMenu


//
// ChangeCursor
//
// Makes the mouse the contextual menu cursor from OS8 if cmd key is down
//
void
CContextMenuAttachment :: ChangeCursor ( const EventRecord* inEvent )
{
	if ( inEvent->modifiers & controlKey ) {
		CursHandle contextCurs = ::GetCursor ( kContextualCursorID );
		::SetCursor ( *contextCurs );
		mExecuteHost = false;
	}
	else
		::InitCursor();

} // ChangeCursor
