/*
 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Stephan Aßmus, superstippi@gmx.de
 *		Marc Flerackers, mflerackers@androme.be
 *		John Scipione, jcipione@gmail.com
 */


#include <BMCPrivate.h>

#include <algorithm>

#include <ControlLook.h>
#include <LayoutUtils.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Message.h>
#include <MessageRunner.h>
#include <Window.h>


static const float kPopUpIndicatorWidth = 13.0f;


#if __GNUC__ == 2


// This is kept only for binary compatibility with BeOS R5. This class was
// used in their BMenuField implementation and we may come across some archived
// BMenuField that needs it.
class _BMCItem_: public BMenuItem {
public:
	_BMCItem_(BMessage* data);
	static BArchivable* Instantiate(BMessage *data);
};


_BMCItem_::_BMCItem_(BMessage* data)
	:
	BMenuItem(data)
{
}


/*static*/ BArchivable*
_BMCItem_::Instantiate(BMessage *data) {
	if (validate_instantiation(data, "_BMCItem_"))
		return new _BMCItem_(data);

	return NULL;
}


#endif


//	#pragma mark - _BMCFilter_


_BMCFilter_::_BMCFilter_(BMenuField* menuField, uint32 what)
	:
	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, what),
	fMenuField(menuField)
{
}


_BMCFilter_::~_BMCFilter_()
{
}


filter_result
_BMCFilter_::Filter(BMessage* message, BHandler** handler)
{
	if (message->what == B_MOUSE_DOWN) {
		if (BView* view = dynamic_cast<BView*>(*handler)) {
			BPoint point;
			message->FindPoint("be:view_where", &point);
			view->ConvertToParent(&point);
			message->ReplacePoint("be:view_where", point);
			*handler = fMenuField;
		}
	}

	return B_DISPATCH_MESSAGE;
}


//	#pragma mark - _BMCMenuBar_


_BMCMenuBar_::_BMCMenuBar_(BRect frame, bool fixedSize, BMenuField* menuField)
	:
	BMenuBar(frame, "_mc_mb_", B_FOLLOW_LEFT | B_FOLLOW_TOP, B_ITEMS_IN_ROW,
		!fixedSize),
	fMenuField(menuField),
	fFixedSize(fixedSize),
	fShowPopUpMarker(true)
{
	_Init();
}


_BMCMenuBar_::_BMCMenuBar_(BMenuField* menuField)
	:
	BMenuBar("_mc_mb_", B_ITEMS_IN_ROW),
	fMenuField(menuField),
	fFixedSize(true),
	fShowPopUpMarker(true)
{
	_Init();
}


_BMCMenuBar_::_BMCMenuBar_(BMessage* data)
	:
	BMenuBar(data),
	fMenuField(NULL),
	fFixedSize(true),
	fShowPopUpMarker(true)
{
	SetFlags(Flags() | B_FRAME_EVENTS);

	bool resizeToFit;
	if (data->FindBool("_rsize_to_fit", &resizeToFit) == B_OK)
		fFixedSize = !resizeToFit;
}


_BMCMenuBar_::~_BMCMenuBar_()
{
}


//	#pragma mark - _BMCMenuBar_ public methods


BArchivable*
_BMCMenuBar_::Instantiate(BMessage* data)
{
	if (validate_instantiation(data, "_BMCMenuBar_"))
		return new _BMCMenuBar_(data);

	return NULL;
}


void
_BMCMenuBar_::AttachedToWindow()
{
	fMenuField = static_cast<BMenuField*>(Parent());

	// Don't cause the KeyMenuBar to change by being attached
	BMenuBar* menuBar = Window()->KeyMenuBar();
	BMenuBar::AttachedToWindow();
	Window()->SetKeyMenuBar(menuBar);

	if (fFixedSize && (Flags() & B_SUPPORTS_LAYOUT) == 0)
		SetResizingMode(B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);

	if (Parent() != NULL) {
		color_which which = Parent()->LowUIColor();
		if (which == B_NO_COLOR)
			SetLowColor(Parent()->LowColor());
		else
			SetLowUIColor(which);

	} else
		SetLowUIColor(B_MENU_BACKGROUND_COLOR);

	fPreviousWidth = Bounds().Width();
}


void
_BMCMenuBar_::Draw(BRect updateRect)
{
	if (fFixedSize) {
		// Set the width of the menu bar because the menu bar bounds may have
		// been expanded by the selected menu item.
		ResizeTo(fMenuField->_MenuBarWidth(), Bounds().Height());
	} else {
		// For compatability with BeOS R5:
		//  - Set to the minimum of the menu bar width set by the menu frame
		//    and the selected menu item width.
		//  - Set the height to the preferred height ignoring the height of the
		//    menu field.
		float height;
		BMenuBar::GetPreferredSize(NULL, &height);
		ResizeTo(std::min(Bounds().Width(), fMenuField->_MenuBarWidth()),
			height);
	}

	BRect rect(Bounds());
	rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
	uint32 flags = 0;
	if (!IsEnabled())
		flags |= BControlLook::B_DISABLED;
	if (IsFocus())
		flags |= BControlLook::B_FOCUSED;

	be_control_look->DrawMenuFieldBackground(this, rect,
		updateRect, base, fShowPopUpMarker, flags);

	DrawItems(updateRect);
}


void
_BMCMenuBar_::FrameResized(float width, float height)
{
	// we need to take care of cleaning up the parent menu field
	float diff = width - fPreviousWidth;
	fPreviousWidth = width;

	if (Window() != NULL && diff != 0) {
		BRect dirty(fMenuField->Bounds());
		if (diff > 0) {
			// clean up the dirty right border of
			// the menu field when enlarging
			dirty.right = Frame().right + kVMargin;
			dirty.left = dirty.right - diff - kVMargin * 2;
			fMenuField->Invalidate(dirty);
		} else if (diff < 0) {
			// clean up the dirty right line of
			// the menu field when shrinking
			dirty.left = Frame().right - kVMargin;
			fMenuField->Invalidate(dirty);
		}
	}

	BMenuBar::FrameResized(width, height);
}


void
_BMCMenuBar_::MakeFocus(bool focused)
{
	if (IsFocus() == focused)
		return;

	BMenuBar::MakeFocus(focused);
}


void
_BMCMenuBar_::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case 'TICK':
		{
			BMenuItem* item = ItemAt(0);

			if (item != NULL && item->Submenu() != NULL
				&& item->Submenu()->Window() != NULL) {
				BMessage message(B_KEY_DOWN);

				message.AddInt8("byte", B_ESCAPE);
				message.AddInt8("key", B_ESCAPE);
				message.AddInt32("modifiers", 0);
				message.AddInt8("raw_char", B_ESCAPE);

				Window()->PostMessage(&message, this, NULL);
			}
		}
		// fall through
		default:
			BMenuBar::MessageReceived(message);
			break;
	}
}


void
_BMCMenuBar_::SetMaxContentWidth(float width)
{
	float left;
	float right;
	GetItemMargins(&left, NULL, &right, NULL);

	BMenuBar::SetMaxContentWidth(width - (left + right));
}


void
_BMCMenuBar_::SetEnabled(bool enabled)
{
	fMenuField->SetEnabled(enabled);

	BMenuBar::SetEnabled(enabled);
}


BSize
_BMCMenuBar_::MinSize()
{
	BSize size;
	BMenuBar::GetPreferredSize(&size.width, &size.height);
	if (fShowPopUpMarker) {
		// account for popup indicator + a few pixels margin
		size.width += kPopUpIndicatorWidth;
	}

	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
}


BSize
_BMCMenuBar_::MaxSize()
{
	// The maximum width of a normal BMenuBar is unlimited, but we want it
	// limited.
	BSize size;
	BMenuBar::GetPreferredSize(&size.width, &size.height);

	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
}


//	#pragma mark - _BMCMenuBar_ private methods


void
_BMCMenuBar_::_Init()
{
	SetFlags(Flags() | B_FRAME_EVENTS | B_FULL_UPDATE_ON_RESIZE);
	SetBorder(B_BORDER_CONTENTS);

	float left, top, right, bottom;
	GetItemMargins(&left, &top, &right, &bottom);

#if 0
	// TODO: Better fix would be to make BMenuItem draw text properly
	// centered
	font_height fontHeight;
	GetFontHeight(&fontHeight);
	top = ceilf((Bounds().Height() - ceilf(fontHeight.ascent)
		- ceilf(fontHeight.descent)) / 2) + 1;
	bottom = top - 1;
#else
	// TODO: Fix content location properly. This is just a quick fix to
	// make the BMenuField label and the super-item of the BMenuBar
	// align vertically.
	top++;
	bottom--;
#endif

	left = right = be_control_look->DefaultLabelSpacing();

	SetItemMargins(left, top,
		right + (fShowPopUpMarker ? kPopUpIndicatorWidth : 0), bottom);
}
