/*
 * Copyright 2001-2009, Stephan Aßmus <superstippi@gmx.de>
 * Copyright 2001-2009, Ingo Weinhold <ingo_weinhold@gmx.de>
 * All rights reserved. Distributed under the terms of the MIT license.
 */


#include "SeparatorView.h"

#include <new>

#include <math.h>
#include <stdio.h>

#include <ControlLook.h>
#include <LayoutUtils.h>
#include <Region.h>


static const float kMinBorderLength = 5.0f;


// TODO: Actually implement alignment support!
// TODO: More testing, especially archiving.


BSeparatorView::BSeparatorView(orientation orientation, border_style border)
	:
	BView(NULL, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
{
	_Init(NULL, NULL, orientation, BAlignment(B_ALIGN_HORIZONTAL_CENTER,
		B_ALIGN_VERTICAL_CENTER), border);
}


BSeparatorView::BSeparatorView(const char* name, const char* label,
	orientation orientation, border_style border, const BAlignment& alignment)
	:
	BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
{
	_Init(label, NULL, orientation, alignment, border);
}


BSeparatorView::BSeparatorView(const char* name, BView* labelView,
	orientation orientation, border_style border, const BAlignment& alignment)
	:
	BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
{
	_Init(NULL, labelView, orientation, alignment, border);
}


BSeparatorView::BSeparatorView(const char* label,
	orientation orientation, border_style border, const BAlignment& alignment)
	:
	BView("", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
{
	_Init(label, NULL, orientation, alignment, border);
}


BSeparatorView::BSeparatorView(BView* labelView,
	orientation orientation, border_style border, const BAlignment& alignment)
	:
	BView("", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE)
{
	_Init(NULL, labelView, orientation, alignment, border);
}


BSeparatorView::BSeparatorView(BMessage* archive)
	:
	BView(archive),
	fLabel(),
	fLabelView(NULL),
	fOrientation(B_HORIZONTAL),
	fAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER,
		B_ALIGN_VERTICAL_CENTER)),
	fBorder(B_FANCY_BORDER)
{
	// NULL archives will be caught by BView.

	const char* label;
	if (archive->FindString("_labelview", &label) == B_OK) {
		fLabelView = FindView(label);
	} else if (archive->FindString("_label", &label) == B_OK) {
		fLabel.SetTo(label);
	}

	int32 orientation;
	if (archive->FindInt32("_orientation", &orientation) == B_OK)
		fOrientation = (enum orientation)orientation;

	int32 hAlignment;
	int32 vAlignment;
	if (archive->FindInt32("_halignment", &hAlignment) == B_OK
		&& archive->FindInt32("_valignment", &vAlignment) == B_OK) {
		fAlignment.horizontal = (alignment)hAlignment;
		fAlignment.vertical = (vertical_alignment)vAlignment;
	}

	int32 borderStyle;
	if (archive->FindInt32("_border", &borderStyle) != B_OK)
		fBorder = (border_style)borderStyle;
}


BSeparatorView::~BSeparatorView()
{
}


// #pragma mark - Archiving


BArchivable*
BSeparatorView::Instantiate(BMessage* archive)
{
	if (validate_instantiation(archive, "BSeparatorView"))
		return new (std::nothrow) BSeparatorView(archive);

	return NULL;
}


status_t
BSeparatorView::Archive(BMessage* into, bool deep) const
{
	// TODO: Test this.
	status_t result = BView::Archive(into, deep);
	if (result != B_OK)
		return result;

	if (fLabelView != NULL)
		result = into->AddString("_labelview", fLabelView->Name());
	else
		result = into->AddString("_label", fLabel.String());

	if (result == B_OK)
		result = into->AddInt32("_orientation", fOrientation);

	if (result == B_OK)
		result = into->AddInt32("_halignment", fAlignment.horizontal);

	if (result == B_OK)
		result = into->AddInt32("_valignment", fAlignment.vertical);

	if (result == B_OK)
		result = into->AddInt32("_border", fBorder);

	return result;
}


// #pragma mark -


void
BSeparatorView::Draw(BRect updateRect)
{
	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
	if (BView* parent = Parent()) {
		if (parent->ViewColor() != B_TRANSPARENT_COLOR)
			base = parent->ViewColor();
	}

	BRect labelBounds;
	if (fLabelView != NULL) {
		labelBounds = fLabelView->Frame();
	} else if (fLabel.CountChars() > 0) {
		labelBounds = _MaxLabelBounds();
		float labelWidth = StringWidth(fLabel.String());
		if (fOrientation == B_HORIZONTAL) {
			switch (fAlignment.horizontal) {
				case B_ALIGN_LEFT:
				default:
					labelBounds.right = labelBounds.left + labelWidth;
					break;

				case B_ALIGN_RIGHT:
					labelBounds.left = labelBounds.right - labelWidth;
					break;

				case B_ALIGN_CENTER:
					labelBounds.left = (labelBounds.left + labelBounds.right
						- labelWidth) / 2;
					labelBounds.right = labelBounds.left + labelWidth;
					break;
			}
		} else {
			switch (fAlignment.vertical) {
				case B_ALIGN_TOP:
				default:
					labelBounds.bottom = labelBounds.top + labelWidth;
					break;

				case B_ALIGN_BOTTOM:
					labelBounds.top = labelBounds.bottom - labelWidth;
					break;

				case B_ALIGN_MIDDLE:
					labelBounds.top = (labelBounds.top + labelBounds.bottom
						- labelWidth) / 2;
					labelBounds.bottom = labelBounds.top + labelWidth;
					break;
			}
		}
	}

	BRect bounds = Bounds();
	BRegion region(bounds);
	if (labelBounds.IsValid()) {
		region.Exclude(labelBounds);
		ConstrainClippingRegion(&region);
	}

	float borderSize = _BorderSize();
	if (borderSize > 0.0f) {
		if (fOrientation == B_HORIZONTAL) {
			bounds.top = floorf((bounds.top + bounds.bottom + 1 - borderSize)
				/ 2);
			bounds.bottom = bounds.top + borderSize - 1;
			region.Exclude(bounds);
			be_control_look->DrawBorder(this, bounds, updateRect, base,
				fBorder, 0, BControlLook::B_TOP_BORDER);
		} else {
			bounds.left = floorf((bounds.left + bounds.right + 1 - borderSize)
				/ 2);
			bounds.right = bounds.left + borderSize - 1;
			region.Exclude(bounds);
			be_control_look->DrawBorder(this, bounds, updateRect, base,
				fBorder, 0, BControlLook::B_LEFT_BORDER);
		}
		if (labelBounds.IsValid())
			region.Include(labelBounds);

		ConstrainClippingRegion(&region);
	}

	SetLowColor(base);
	FillRect(updateRect, B_SOLID_LOW);

	if (fLabel.CountChars() > 0) {
		font_height fontHeight;
		GetFontHeight(&fontHeight);

		SetHighColor(0, 0, 0, 255);

		if (fOrientation == B_HORIZONTAL) {
			DrawString(fLabel.String(), BPoint(labelBounds.left,
				labelBounds.top + ceilf(fontHeight.ascent)));
		} else {
			DrawString(fLabel.String(), BPoint(labelBounds.left
				+ ceilf(fontHeight.ascent), labelBounds.bottom));
		}
	}
}


void
BSeparatorView::GetPreferredSize(float* _width, float* _height)
{
	float width = 0.0f;
	float height = 0.0f;

	if (fLabelView != NULL) {
		fLabelView->GetPreferredSize(&width, &height);
	} else if (fLabel.CountChars() > 0) {
		width = StringWidth(fLabel.String());
		font_height fontHeight;
		GetFontHeight(&fontHeight);
		height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
		if (fOrientation == B_VERTICAL) {
			// swap width and height
			float temp = height;
			height = width;
			width = temp;
		}
	}

	float borderSize = _BorderSize();

	// Add some room for the border
	if (fOrientation == B_HORIZONTAL) {
		width += kMinBorderLength * 2.0f;
		height = max_c(height, borderSize - 1.0f);
	} else {
		height += kMinBorderLength * 2.0f;
		width = max_c(width, borderSize - 1.0f);
	}

	if (_width != NULL)
		*_width = width;

	if (_height != NULL)
		*_height = height;
}


BSize
BSeparatorView::MinSize()
{
	BSize size;
	GetPreferredSize(&size.width, &size.height);
	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
}


BSize
BSeparatorView::MaxSize()
{
	BSize size(MinSize());
	if (fOrientation == B_HORIZONTAL)
		size.width = B_SIZE_UNLIMITED;
	else
		size.height = B_SIZE_UNLIMITED;

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


BSize
BSeparatorView::PreferredSize()
{
	BSize size;
	GetPreferredSize(&size.width, &size.height);

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


// #pragma mark -


void
BSeparatorView::SetOrientation(orientation orientation)
{
	if (orientation == fOrientation)
		return;

	fOrientation = orientation;

	BFont font;
	GetFont(&font);
	if (fOrientation == B_VERTICAL)
		font.SetRotation(90.0f);

	SetFont(&font);

	Invalidate();
	InvalidateLayout();
}


void
BSeparatorView::SetAlignment(const BAlignment& aligment)
{
	if (aligment == fAlignment)
		return;

	fAlignment = aligment;

	Invalidate();
	InvalidateLayout();
}


void
BSeparatorView::SetBorderStyle(border_style border)
{
	if (border == fBorder)
		return;

	fBorder = border;

	Invalidate();
}


void
BSeparatorView::SetLabel(const char* label)
{
	if (label == NULL)
		label = "";

	if (fLabel == label)
		return;

	fLabel.SetTo(label);

	Invalidate();
	InvalidateLayout();
}


void
BSeparatorView::SetLabel(BView* view, bool deletePrevious)
{
	if (fLabelView == view)
		return;

	if (fLabelView != NULL) {
		fLabelView->RemoveSelf();
		if (deletePrevious)
			delete fLabelView;
	}

	fLabelView = view;

	if (fLabelView != NULL)
		AddChild(view);
}


status_t
BSeparatorView::Perform(perform_code code, void* data)
{
	return BView::Perform(code, data);
}


// #pragma mark - protected/private


void
BSeparatorView::DoLayout()
{
	BView::DoLayout();

	if (fLabelView == NULL)
		return;

	BRect bounds = _MaxLabelBounds();
	BRect frame = BLayoutUtils::AlignInFrame(bounds, fLabelView->MaxSize(),
		fAlignment);

	fLabelView->MoveTo(frame.LeftTop());
	fLabelView->ResizeTo(frame.Width(), frame.Height());
}


void
BSeparatorView::_Init(const char* label, BView* labelView,
	orientation orientation, BAlignment alignment, border_style border)
{
	fLabel = "";
	fLabelView = NULL;

	fOrientation = B_HORIZONTAL;
	fAlignment = alignment;
	fBorder = border;

	SetFont(be_bold_font);
	SetViewColor(B_TRANSPARENT_32_BIT);

	SetLabel(label);
	SetLabel(labelView, true);
	SetOrientation(orientation);
}


float
BSeparatorView::_BorderSize() const
{
	// TODO: Get these values from the BControlLook class.
	switch (fBorder) {
		case B_PLAIN_BORDER:
			return 1.0f;

		case B_FANCY_BORDER:
			return 2.0f;

		case B_NO_BORDER:
		default:
			return 0.0f;
	}
}


BRect
BSeparatorView::_MaxLabelBounds() const
{
	BRect bounds = Bounds();
	if (fOrientation == B_HORIZONTAL)
		bounds.InsetBy(kMinBorderLength, 0.0f);
	else
		bounds.InsetBy(0.0f, kMinBorderLength);

	return bounds;
}


// #pragma mark - FBC padding


void BSeparatorView::_ReservedSeparatorView1() {}
void BSeparatorView::_ReservedSeparatorView2() {}
void BSeparatorView::_ReservedSeparatorView3() {}
void BSeparatorView::_ReservedSeparatorView4() {}
void BSeparatorView::_ReservedSeparatorView5() {}
void BSeparatorView::_ReservedSeparatorView6() {}
void BSeparatorView::_ReservedSeparatorView7() {}
void BSeparatorView::_ReservedSeparatorView8() {}
void BSeparatorView::_ReservedSeparatorView9() {}
void BSeparatorView::_ReservedSeparatorView10() {}
