/** 
 * @file llbutton.cpp
 * @brief LLButton base class
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#include "llmenubutton.h"

// Linden library includes
#include "lltoggleablemenu.h"
#include "llstring.h"
#include "v4color.h"

static LLDefaultChildRegistry::Register<LLMenuButton> r("menu_button");

void LLMenuButton::MenuPositions::declareValues()
{
	declare("topleft", MP_TOP_LEFT);
	declare("topright", MP_TOP_RIGHT);
	declare("bottomleft", MP_BOTTOM_LEFT);
	declare("bottomright", MP_BOTTOM_RIGHT);
}

LLMenuButton::Params::Params()
:	menu_filename("menu_filename"),
	position("menu_position", MP_BOTTOM_LEFT)
{
	addSynonym(position, "position");
}


LLMenuButton::LLMenuButton(const LLMenuButton::Params& p)
:	LLButton(p),
	mIsMenuShown(false),
	mMenuPosition(p.position),
	mOwnMenu(false)
{
	std::string menu_filename = p.menu_filename;

	setMenu(menu_filename, mMenuPosition);
	updateMenuOrigin();
}

LLMenuButton::~LLMenuButton()
{
	cleanup();
}

boost::signals2::connection LLMenuButton::setMouseDownCallback( const mouse_signal_t::slot_type& cb )
{
	return LLUICtrl::setMouseDownCallback(cb);
}

void LLMenuButton::hideMenu()
{
	LLToggleableMenu* menu = getMenu();
	if (menu)
	{
		menu->setVisible(FALSE);
	}
}

LLToggleableMenu* LLMenuButton::getMenu()
{
	return dynamic_cast<LLToggleableMenu*>(mMenuHandle.get());
}

void LLMenuButton::setMenu(const std::string& menu_filename, EMenuPosition position /*MP_TOP_LEFT*/)
{
	if (menu_filename.empty())
	{
		return;
	}

	llassert(LLMenuGL::sMenuContainer != NULL);
	LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
	if (!menu)
	{
		LL_WARNS() << "Error loading menu_button menu" << LL_ENDL;
		return;
	}

	setMenu(menu, position, true);
}

void LLMenuButton::setMenu(LLToggleableMenu* menu, EMenuPosition position /*MP_TOP_LEFT*/, bool take_ownership /*false*/)
{
	if (!menu) return;

	cleanup(); // destroy the previous memnu if we own it

	mMenuHandle = menu->getHandle();
	mMenuPosition = position;
	mOwnMenu = take_ownership;

	menu->setVisibilityChangeCallback(boost::bind(&LLMenuButton::onMenuVisibilityChange, this, _2));
}

BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask )
{
	if (!getMenu()) return FALSE;

	if( KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key))
	{
		// *HACK: We emit the mouse down signal to fire the callback bound to the
		// menu emerging event before actually displaying the menu. See STORM-263.
		LLUICtrl::handleMouseDown(-1, -1, MASK_NONE);

		toggleMenu();
		return TRUE;
	}

	LLToggleableMenu* menu = getMenu();
	if (menu && menu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE)
	{
		menu->setVisible(FALSE);
		return TRUE;
	}
	
	return FALSE;
}

BOOL LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask)
{
	LLButton::handleMouseDown(x, y, mask);

	toggleMenu();
	
	return TRUE;
}

void LLMenuButton::toggleMenu()
{
	if (mValidateSignal && !(*mValidateSignal)(this, LLSD()))
	{
		return;
	}

	LLToggleableMenu* menu = getMenu();
	if (!menu) return;

	// Store the button rectangle to toggle menu visibility if a mouse event
	// occurred inside or outside the button rect.
	menu->setButtonRect(this);

	if (!menu->toggleVisibility() && mIsMenuShown)
	{
		setForcePressedState(false);
		mIsMenuShown = false;
	}
	else
	{
		menu->buildDrawLabels();
		menu->arrangeAndClear();
		menu->updateParent(LLMenuGL::sMenuContainer);

		updateMenuOrigin();

		LLMenuGL::showPopup(getParent(), menu, mX, mY);

		setForcePressedState(true);
		mIsMenuShown = true;
	}
}

void LLMenuButton::updateMenuOrigin()
{
	LLToggleableMenu* menu = getMenu();
	if (!menu) return;

	LLRect rect = getRect();

	switch (mMenuPosition)
	{
		case MP_TOP_LEFT:
		{
			mX = rect.mLeft;
			mY = rect.mTop + menu->getRect().getHeight();
			break;
		}
		case MP_TOP_RIGHT:
		{
			const LLRect& menu_rect = menu->getRect();
			mX = rect.mRight - menu_rect.getWidth();
			mY = rect.mTop + menu_rect.getHeight();
			break;
		}
		case MP_BOTTOM_LEFT:
		{
			mX = rect.mLeft;
			mY = rect.mBottom;
			break;
		}
		case MP_BOTTOM_RIGHT:
		{
			const LLRect& menu_rect = menu->getRect();
			mX = rect.mRight - menu_rect.getWidth();
			mY = rect.mBottom;
			break;
		}
	}
}

void LLMenuButton::onMenuVisibilityChange(const LLSD& param)
{
	bool new_visibility = param["visibility"].asBoolean();
	bool is_closed_by_button_click = param["closed_by_button_click"].asBoolean();

	// Reset the button "pressed" state only if the menu is shown by this particular
	// menu button (not any other control) and is not being closed by a click on the button.
	if (!new_visibility && !is_closed_by_button_click && mIsMenuShown)
	{
		setForcePressedState(false);
		mIsMenuShown = false;
	}
}

void LLMenuButton::cleanup()
{
	if (mMenuHandle.get() && mOwnMenu)
	{
		mMenuHandle.get()->die();
	}
}
