/*
 * Copyright 2004 DarkWyrm <darkwyrm@earthlink.net>
 * Copyright 2013 FeemanLou
 * Copyright 2014-2015 Haiku, Inc. All rights reserved.
 *
 * Distributed under the terms of the MIT license.
 *
 * Originally written by DarkWyrm <darkwyrm@earthlink.net>
 * Updated by FreemanLou as part of Google GCI 2013
 *
 * Authors:
 *		DarkWyrm, darkwyrm@earthlink.net
 *		FeemanLou
 *		John Scipione, jscipione@gmail.com
 */


#include <AbstractSpinner.h>

#include <algorithm>

#include <AbstractLayoutItem.h>
#include <Alignment.h>
#include <ControlLook.h>
#include <Font.h>
#include <GradientLinear.h>
#include <LayoutItem.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <MessageFilter.h>
#include <Point.h>
#include <PropertyInfo.h>
#include <TextView.h>
#include <View.h>
#include <Window.h>

#include "Thread.h"


static const float kFrameMargin			= 2.0f;

const char* const kFrameField			= "BAbstractSpinner:layoutItem:frame";
const char* const kLabelItemField		= "BAbstractSpinner:labelItem";
const char* const kTextViewItemField	= "BAbstractSpinner:textViewItem";


static property_info sProperties[] = {
	{
		"Align",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the alignment of the spinner label.",
		0,
		{ B_INT32_TYPE }
	},
	{
		"Align",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the alignment of the spinner label.",
		0,
		{ B_INT32_TYPE }
	},

	{
		"ButtonStyle",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the style of the spinner buttons.",
		0,
		{ B_INT32_TYPE }
	},
	{
		"ButtonStyle",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the style of the spinner buttons.",
		0,
		{ B_INT32_TYPE }
	},

	{
		"Divider",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the divider position of the spinner.",
		0,
		{ B_FLOAT_TYPE }
	},
	{
		"Divider",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the divider position of the spinner.",
		0,
		{ B_FLOAT_TYPE }
	},

	{
		"Enabled",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns whether or not the spinner is enabled.",
		0,
		{ B_BOOL_TYPE }
	},
	{
		"Enabled",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets whether or not the spinner is enabled.",
		0,
		{ B_BOOL_TYPE }
	},

	{
		"Label",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the spinner label.",
		0,
		{ B_STRING_TYPE }
	},
	{
		"Label",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the spinner label.",
		0,
		{ B_STRING_TYPE }
	},

	{
		"Message",
		{ B_GET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0 },
		"Returns the spinner invocation message.",
		0,
		{ B_MESSAGE_TYPE }
	},
	{
		"Message",
		{ B_SET_PROPERTY, 0 },
		{ B_DIRECT_SPECIFIER, 0},
		"Sets the spinner invocation message.",
		0,
		{ B_MESSAGE_TYPE }
	},

	{ 0 }
};


typedef enum {
	SPINNER_INCREMENT,
	SPINNER_DECREMENT
} spinner_direction;


class SpinnerButton : public BView {
public:
								SpinnerButton(BRect frame, const char* name,
									spinner_direction direction);
	virtual						~SpinnerButton();

	virtual	void				AttachedToWindow();
	virtual	void				DetachedFromWindow();
	virtual	void				Draw(BRect updateRect);
	virtual	void				MouseDown(BPoint where);
	virtual	void				MouseUp(BPoint where);
	virtual	void				MouseMoved(BPoint where, uint32 transit,
									const BMessage* message);

			bool				IsEnabled() const { return fIsEnabled; }
	virtual	void				SetEnabled(bool enable) { fIsEnabled = enable; };

private:
			void				_DoneTracking(BPoint where);
			void				_Track(BPoint where, uint32);

			spinner_direction	fSpinnerDirection;
			BAbstractSpinner*	fParent;
			bool				fIsEnabled;
			bool				fIsMouseDown;
			bool				fIsMouseOver;
			bigtime_t			fRepeatDelay;
};


class SpinnerTextView : public BTextView {
public:
								SpinnerTextView(BRect rect, BRect textRect);
	virtual						~SpinnerTextView();

	virtual	void				AttachedToWindow();
	virtual	void				DetachedFromWindow();
	virtual	void				KeyDown(const char* bytes, int32 numBytes);
	virtual	void				MakeFocus(bool focus);

private:
			BAbstractSpinner*	fParent;
};


class BAbstractSpinner::LabelLayoutItem : public BAbstractLayoutItem {
public:
								LabelLayoutItem(BAbstractSpinner* parent);
								LabelLayoutItem(BMessage* archive);

	virtual	bool				IsVisible();
	virtual	void				SetVisible(bool visible);

	virtual	BRect				Frame();
	virtual	void				SetFrame(BRect frame);

			void				SetParent(BAbstractSpinner* parent);
	virtual	BView*				View();

	virtual	BSize				BaseMinSize();
	virtual	BSize				BaseMaxSize();
	virtual	BSize				BasePreferredSize();
	virtual	BAlignment			BaseAlignment();

			BRect				FrameInParent() const;

	virtual status_t			Archive(BMessage* into, bool deep = true) const;
	static	BArchivable*		Instantiate(BMessage* from);

private:
			BAbstractSpinner*	fParent;
			BRect				fFrame;
};


class BAbstractSpinner::TextViewLayoutItem : public BAbstractLayoutItem {
public:
								TextViewLayoutItem(BAbstractSpinner* parent);
								TextViewLayoutItem(BMessage* archive);

	virtual	bool				IsVisible();
	virtual	void				SetVisible(bool visible);

	virtual	BRect				Frame();
	virtual	void				SetFrame(BRect frame);

			void				SetParent(BAbstractSpinner* parent);
	virtual	BView*				View();

	virtual	BSize				BaseMinSize();
	virtual	BSize				BaseMaxSize();
	virtual	BSize				BasePreferredSize();
	virtual	BAlignment			BaseAlignment();

			BRect				FrameInParent() const;

	virtual status_t			Archive(BMessage* into, bool deep = true) const;
	static	BArchivable*		Instantiate(BMessage* from);

private:
			BAbstractSpinner*	fParent;
			BRect				fFrame;
};


struct BAbstractSpinner::LayoutData {
	LayoutData(float width, float height)
	:
	label_layout_item(NULL),
	text_view_layout_item(NULL),
	label_width(0),
	label_height(0),
	text_view_width(0),
	text_view_height(0),
	previous_width(width),
	previous_height(height),
	valid(false)
	{
	}

	LabelLayoutItem* label_layout_item;
	TextViewLayoutItem* text_view_layout_item;

	font_height font_info;

	float label_width;
	float label_height;
	float text_view_width;
	float text_view_height;

	float previous_width;
	float previous_height;

	BSize min;
	BAlignment alignment;

	bool valid;
};


//	#pragma mark - SpinnerButton


SpinnerButton::SpinnerButton(BRect frame, const char* name,
	spinner_direction direction)
	:
	BView(frame, name, B_FOLLOW_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW),
	fSpinnerDirection(direction),
	fParent(NULL),
	fIsEnabled(true),
	fIsMouseDown(false),
	fIsMouseOver(false),
	fRepeatDelay(100000)
{
}


SpinnerButton::~SpinnerButton()
{
}


void
SpinnerButton::AttachedToWindow()
{
	fParent = static_cast<BAbstractSpinner*>(Parent());

	AdoptParentColors();
	BView::AttachedToWindow();
}


void
SpinnerButton::DetachedFromWindow()
{
	fParent = NULL;

	BView::DetachedFromWindow();
}


void
SpinnerButton::Draw(BRect updateRect)
{
	BRect rect(Bounds());
	if (!rect.IsValid() || !rect.Intersects(updateRect))
		return;

	BView::Draw(updateRect);

	float frameTint = B_DARKEN_1_TINT;

	float fgTint;
	if (!fIsEnabled)
		fgTint = B_DARKEN_1_TINT;
	else if (fIsMouseDown)
		fgTint = B_DARKEN_MAX_TINT;
	else
		fgTint = 1.777f;	// 216 --> 48.2 (48)

	float bgTint;
	if (fIsEnabled && fIsMouseOver)
		bgTint = B_DARKEN_1_TINT;
	else
		bgTint = B_NO_TINT;

	rgb_color bgColor = ui_color(B_PANEL_BACKGROUND_COLOR);
	if (bgColor.red + bgColor.green + bgColor.blue <= 128 * 3) {
		// if dark background make the tint lighter
		frameTint = 2.0f - frameTint;
		fgTint = 2.0f - fgTint;
		bgTint = 2.0f - bgTint;
	}

	uint32 borders = be_control_look->B_TOP_BORDER
		| be_control_look->B_BOTTOM_BORDER;

	if (fSpinnerDirection == SPINNER_INCREMENT)
		borders |= be_control_look->B_RIGHT_BORDER;
	else
		borders |= be_control_look->B_LEFT_BORDER;

	uint32 flags = fIsMouseDown ? BControlLook::B_ACTIVATED : 0;

	// draw the button
	be_control_look->DrawButtonFrame(this, rect, updateRect,
		tint_color(bgColor, frameTint), bgColor, flags, borders);
	be_control_look->DrawButtonBackground(this, rect, updateRect,
		tint_color(bgColor, bgTint), flags, borders);

	switch (fParent->ButtonStyle()) {
		case SPINNER_BUTTON_HORIZONTAL_ARROWS:
		{
			int32 arrowDirection = fSpinnerDirection == SPINNER_INCREMENT
				? be_control_look->B_RIGHT_ARROW
				: be_control_look->B_LEFT_ARROW;

			rect.InsetBy(0.0f, 1.0f);
			be_control_look->DrawArrowShape(this, rect, updateRect, bgColor,
				arrowDirection, 0, fgTint);
			break;
		}

		case SPINNER_BUTTON_VERTICAL_ARROWS:
		{
			int32 arrowDirection = fSpinnerDirection == SPINNER_INCREMENT
				? be_control_look->B_UP_ARROW
				: be_control_look->B_DOWN_ARROW;

			rect.InsetBy(0.0f, 1.0f);
			be_control_look->DrawArrowShape(this, rect, updateRect, bgColor,
				arrowDirection, 0, fgTint);
			break;
		}

		default:
		case SPINNER_BUTTON_PLUS_MINUS:
		{
			BFont font;
			fParent->GetFont(&font);
			float inset = floorf(font.Size() / 4);
			rect.InsetBy(inset, inset);

			if (rect.IntegerWidth() % 2 != 0)
				rect.right -= 1;

			if (rect.IntegerHeight() % 2 != 0)
				rect.bottom -= 1;

			SetHighColor(tint_color(bgColor, fgTint));

			// draw the +/-
			float halfHeight = floorf(rect.Height() / 2);
			StrokeLine(BPoint(rect.left, rect.top + halfHeight),
				BPoint(rect.right, rect.top + halfHeight));
			if (fSpinnerDirection == SPINNER_INCREMENT) {
				float halfWidth = floorf(rect.Width() / 2);
				StrokeLine(BPoint(rect.left + halfWidth, rect.top + 1),
					BPoint(rect.left + halfWidth, rect.bottom - 1));
			}
		}
	}
}


void
SpinnerButton::MouseDown(BPoint where)
{
	if (fIsEnabled) {
		fIsMouseDown = true;
		Invalidate();
		fRepeatDelay = 100000;
		MouseDownThread<SpinnerButton>::TrackMouse(this,
			&SpinnerButton::_DoneTracking, &SpinnerButton::_Track);
	}

	BView::MouseDown(where);
}


void
SpinnerButton::MouseMoved(BPoint where, uint32 transit,
	const BMessage* message)
{
	switch (transit) {
		case B_ENTERED_VIEW:
		case B_INSIDE_VIEW:
		{
			BPoint where;
			uint32 buttons;
			GetMouse(&where, &buttons);
			fIsMouseOver = Bounds().Contains(where) && buttons == 0;
			if (!fIsMouseDown)
				Invalidate();

			break;
		}

		case B_EXITED_VIEW:
		case B_OUTSIDE_VIEW:
			fIsMouseOver = false;
			MouseUp(Bounds().LeftTop());
			break;
	}

	BView::MouseMoved(where, transit, message);
}


void
SpinnerButton::MouseUp(BPoint where)
{
	fIsMouseDown = false;
	Invalidate();

	BView::MouseUp(where);
}


//	#pragma mark  - SpinnerButton private methods


void
SpinnerButton::_DoneTracking(BPoint where)
{
	if (fIsMouseDown || !Bounds().Contains(where))
		fIsMouseDown = false;
}


void
SpinnerButton::_Track(BPoint where, uint32)
{
	if (fParent == NULL || !Bounds().Contains(where)) {
		fIsMouseDown = false;
		return;
	}
	fIsMouseDown = true;

	fSpinnerDirection == SPINNER_INCREMENT
		? fParent->Increment()
		: fParent->Decrement();

	snooze(fRepeatDelay);
	fRepeatDelay = 10000;
}


//	#pragma mark - SpinnerTextView


SpinnerTextView::SpinnerTextView(BRect rect, BRect textRect)
	:
	BTextView(rect, "textview", textRect, B_FOLLOW_ALL,
		B_WILL_DRAW | B_NAVIGABLE),
	fParent(NULL)
{
	MakeResizable(true);
}


SpinnerTextView::~SpinnerTextView()
{
}


void
SpinnerTextView::AttachedToWindow()
{
	fParent = static_cast<BAbstractSpinner*>(Parent());

	BTextView::AttachedToWindow();
}


void
SpinnerTextView::DetachedFromWindow()
{
	fParent = NULL;

	BTextView::DetachedFromWindow();
}


void
SpinnerTextView::KeyDown(const char* bytes, int32 numBytes)
{
	if (fParent == NULL) {
		BTextView::KeyDown(bytes, numBytes);
		return;
	}

	switch (bytes[0]) {
		case B_ENTER:
		case B_SPACE:
			fParent->SetValueFromText();
			break;

		case B_TAB:
			fParent->KeyDown(bytes, numBytes);
			break;

		case B_LEFT_ARROW:
			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
				&& (modifiers() & B_CONTROL_KEY) != 0) {
				// need to hold down control, otherwise can't move cursor
				fParent->Decrement();
			} else
				BTextView::KeyDown(bytes, numBytes);
			break;

		case B_UP_ARROW:
			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
				fParent->Increment();
			else
				BTextView::KeyDown(bytes, numBytes);
			break;

		case B_RIGHT_ARROW:
			if (fParent->ButtonStyle() == SPINNER_BUTTON_HORIZONTAL_ARROWS
				&& (modifiers() & B_CONTROL_KEY) != 0) {
				// need to hold down control, otherwise can't move cursor
				fParent->Increment();
			} else
				BTextView::KeyDown(bytes, numBytes);
			break;

		case B_DOWN_ARROW:
			if (fParent->ButtonStyle() != SPINNER_BUTTON_HORIZONTAL_ARROWS)
				fParent->Decrement();
			else
				BTextView::KeyDown(bytes, numBytes);
			break;

		default:
			BTextView::KeyDown(bytes, numBytes);
			break;
	}
}


void
SpinnerTextView::MakeFocus(bool focus)
{
	BTextView::MakeFocus(focus);

	if (fParent == NULL)
		return;

	if (focus)
		SelectAll();
	else
		fParent->SetValueFromText();

	fParent->_DrawTextView(fParent->Bounds());
}


//	#pragma mark - BAbstractSpinner::LabelLayoutItem


BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BAbstractSpinner* parent)
	:
	fParent(parent),
	fFrame()
{
}


BAbstractSpinner::LabelLayoutItem::LabelLayoutItem(BMessage* from)
	:
	BAbstractLayoutItem(from),
	fParent(NULL),
	fFrame()
{
	from->FindRect(kFrameField, &fFrame);
}


bool
BAbstractSpinner::LabelLayoutItem::IsVisible()
{
	return !fParent->IsHidden(fParent);
}


void
BAbstractSpinner::LabelLayoutItem::SetVisible(bool visible)
{
}


BRect
BAbstractSpinner::LabelLayoutItem::Frame()
{
	return fFrame;
}


void
BAbstractSpinner::LabelLayoutItem::SetFrame(BRect frame)
{
	fFrame = frame;
	fParent->_UpdateFrame();
}


void
BAbstractSpinner::LabelLayoutItem::SetParent(BAbstractSpinner* parent)
{
	fParent = parent;
}


BView*
BAbstractSpinner::LabelLayoutItem::View()
{
	return fParent;
}


BSize
BAbstractSpinner::LabelLayoutItem::BaseMinSize()
{
	fParent->_ValidateLayoutData();

	if (fParent->Label() == NULL)
		return BSize(-1.0f, -1.0f);

	return BSize(fParent->fLayoutData->label_width
			+ be_control_look->DefaultLabelSpacing(),
		fParent->fLayoutData->label_height);
}


BSize
BAbstractSpinner::LabelLayoutItem::BaseMaxSize()
{
	return BaseMinSize();
}


BSize
BAbstractSpinner::LabelLayoutItem::BasePreferredSize()
{
	return BaseMinSize();
}


BAlignment
BAbstractSpinner::LabelLayoutItem::BaseAlignment()
{
	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
}


BRect
BAbstractSpinner::LabelLayoutItem::FrameInParent() const
{
	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
}


status_t
BAbstractSpinner::LabelLayoutItem::Archive(BMessage* into, bool deep) const
{
	BArchiver archiver(into);
	status_t result = BAbstractLayoutItem::Archive(into, deep);

	if (result == B_OK)
		result = into->AddRect(kFrameField, fFrame);

	return archiver.Finish(result);
}


BArchivable*
BAbstractSpinner::LabelLayoutItem::Instantiate(BMessage* from)
{
	if (validate_instantiation(from, "BAbstractSpinner::LabelLayoutItem"))
		return new LabelLayoutItem(from);

	return NULL;
}


//	#pragma mark - BAbstractSpinner::TextViewLayoutItem


BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BAbstractSpinner* parent)
	:
	fParent(parent),
	fFrame()
{
	SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
}


BAbstractSpinner::TextViewLayoutItem::TextViewLayoutItem(BMessage* from)
	:
	BAbstractLayoutItem(from),
	fParent(NULL),
	fFrame()
{
	from->FindRect(kFrameField, &fFrame);
}


bool
BAbstractSpinner::TextViewLayoutItem::IsVisible()
{
	return !fParent->IsHidden(fParent);
}


void
BAbstractSpinner::TextViewLayoutItem::SetVisible(bool visible)
{
	// not allowed
}


BRect
BAbstractSpinner::TextViewLayoutItem::Frame()
{
	return fFrame;
}


void
BAbstractSpinner::TextViewLayoutItem::SetFrame(BRect frame)
{
	fFrame = frame;
	fParent->_UpdateFrame();
}


void
BAbstractSpinner::TextViewLayoutItem::SetParent(BAbstractSpinner* parent)
{
	fParent = parent;
}


BView*
BAbstractSpinner::TextViewLayoutItem::View()
{
	return fParent;
}


BSize
BAbstractSpinner::TextViewLayoutItem::BaseMinSize()
{
	fParent->_ValidateLayoutData();

	BSize size(fParent->fLayoutData->text_view_width,
		fParent->fLayoutData->text_view_height);

	return size;
}


BSize
BAbstractSpinner::TextViewLayoutItem::BaseMaxSize()
{
	return BaseMinSize();
}


BSize
BAbstractSpinner::TextViewLayoutItem::BasePreferredSize()
{
	return BaseMinSize();
}


BAlignment
BAbstractSpinner::TextViewLayoutItem::BaseAlignment()
{
	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
}


BRect
BAbstractSpinner::TextViewLayoutItem::FrameInParent() const
{
	return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
}


status_t
BAbstractSpinner::TextViewLayoutItem::Archive(BMessage* into, bool deep) const
{
	BArchiver archiver(into);
	status_t result = BAbstractLayoutItem::Archive(into, deep);

	if (result == B_OK)
		result = into->AddRect(kFrameField, fFrame);

	return archiver.Finish(result);
}


BArchivable*
BAbstractSpinner::TextViewLayoutItem::Instantiate(BMessage* from)
{
	if (validate_instantiation(from, "BAbstractSpinner::TextViewLayoutItem"))
		return new LabelLayoutItem(from);

	return NULL;
}


//	#pragma mark - BAbstractSpinner


BAbstractSpinner::BAbstractSpinner(BRect frame, const char* name, const char* label,
	BMessage* message, uint32 resizingMode, uint32 flags)
	:
	BControl(frame, name, label, message, resizingMode,
		flags | B_WILL_DRAW | B_FRAME_EVENTS)
{
	_InitObject();
}


BAbstractSpinner::BAbstractSpinner(const char* name, const char* label, BMessage* message,
	uint32 flags)
	:
	BControl(name, label, message, flags | B_WILL_DRAW | B_FRAME_EVENTS)
{
	_InitObject();
}


BAbstractSpinner::BAbstractSpinner(BMessage* data)
	:
	BControl(data),
	fButtonStyle(SPINNER_BUTTON_PLUS_MINUS)
{
	_InitObject();

	if (data->FindInt32("_align") != B_OK)
		fAlignment = B_ALIGN_LEFT;

	if (data->FindInt32("_button_style") != B_OK)
		fButtonStyle = SPINNER_BUTTON_PLUS_MINUS;

	if (data->FindInt32("_divider") != B_OK)
		fDivider = 0.0f;
}


BAbstractSpinner::~BAbstractSpinner()
{
	delete fLayoutData;
	fLayoutData = NULL;
}


BArchivable*
BAbstractSpinner::Instantiate(BMessage* data)
{
	// cannot instantiate an abstract spinner
	return NULL;
}


status_t
BAbstractSpinner::Archive(BMessage* data, bool deep) const
{
	status_t status = BControl::Archive(data, deep);
	data->AddString("class", "Spinner");

	if (status == B_OK)
		status = data->AddInt32("_align", fAlignment);

	if (status == B_OK)
		data->AddInt32("_button_style", fButtonStyle);

	if (status == B_OK)
		status = data->AddFloat("_divider", fDivider);

	return status;
}


status_t
BAbstractSpinner::GetSupportedSuites(BMessage* message)
{
	message->AddString("suites", "suite/vnd.Haiku-spinner");

	BPropertyInfo prop_info(sProperties);
	message->AddFlat("messages", &prop_info);

	return BView::GetSupportedSuites(message);
}


BHandler*
BAbstractSpinner::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
	int32 form, const char* property)
{
	return BView::ResolveSpecifier(message, index, specifier, form,
		property);
}


void
BAbstractSpinner::AttachedToWindow()
{
	if (!Messenger().IsValid())
		SetTarget(Window());

	BControl::SetValue(Value());
		// sets the text and enables or disables the arrows

	_UpdateTextViewColors(IsEnabled());
	fTextView->MakeEditable(IsEnabled());

	BView::AttachedToWindow();
}


void
BAbstractSpinner::Draw(BRect updateRect)
{
	_DrawLabel(updateRect);
	_DrawTextView(updateRect);
	fIncrement->Invalidate();
	fDecrement->Invalidate();
}


void
BAbstractSpinner::FrameResized(float width, float height)
{
	BView::FrameResized(width, height);

	// TODO: this causes flickering still...

	// changes in width

	BRect bounds = Bounds();

	if (bounds.Width() > fLayoutData->previous_width) {
		// invalidate the region between the old and the new right border
		BRect rect = bounds;
		rect.left += fLayoutData->previous_width - kFrameMargin;
		rect.right--;
		Invalidate(rect);
	} else if (bounds.Width() < fLayoutData->previous_width) {
		// invalidate the region of the new right border
		BRect rect = bounds;
		rect.left = rect.right - kFrameMargin;
		Invalidate(rect);
	}

	// changes in height

	if (bounds.Height() > fLayoutData->previous_height) {
		// invalidate the region between the old and the new bottom border
		BRect rect = bounds;
		rect.top += fLayoutData->previous_height - kFrameMargin;
		rect.bottom--;
		Invalidate(rect);
		// invalidate label area
		rect = bounds;
		rect.right = fDivider;
		Invalidate(rect);
	} else if (bounds.Height() < fLayoutData->previous_height) {
		// invalidate the region of the new bottom border
		BRect rect = bounds;
		rect.top = rect.bottom - kFrameMargin;
		Invalidate(rect);
		// invalidate label area
		rect = bounds;
		rect.right = fDivider;
		Invalidate(rect);
	}

	fLayoutData->previous_width = bounds.Width();
	fLayoutData->previous_height = bounds.Height();
}


void
BAbstractSpinner::ValueChanged()
{
	// hook method - does nothing
}


void
BAbstractSpinner::MessageReceived(BMessage* message)
{
	if (!IsEnabled() && message->what == B_COLORS_UPDATED)
		_UpdateTextViewColors(false);

	BControl::MessageReceived(message);
}


void
BAbstractSpinner::MakeFocus(bool focus)
{
	fTextView->MakeFocus(focus);
}


void
BAbstractSpinner::ResizeToPreferred()
{
	BView::ResizeToPreferred();

	const char* label = Label();
	if (label != NULL) {
		fDivider = ceilf(StringWidth(label))
			+ be_control_look->DefaultLabelSpacing();
	} else
		fDivider = 0.0f;

	_LayoutTextView();
}


void
BAbstractSpinner::SetFlags(uint32 flags)
{
	// If the textview is navigable, set it to not navigable if needed,
	// else if it is not navigable, set it to navigable if needed
	if (fTextView->Flags() & B_NAVIGABLE) {
		if (!(flags & B_NAVIGABLE))
			fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
	} else {
		if (flags & B_NAVIGABLE)
			fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
	}

	// Don't make this one navigable
	flags &= ~B_NAVIGABLE;

	BView::SetFlags(flags);
}


void
BAbstractSpinner::WindowActivated(bool active)
{
	_DrawTextView(fTextView->Frame());
}


void
BAbstractSpinner::SetAlignment(alignment align)
{
	fAlignment = align;
}


void
BAbstractSpinner::SetButtonStyle(spinner_button_style buttonStyle)
{
	fButtonStyle = buttonStyle;
}


void
BAbstractSpinner::SetDivider(float position)
{
	position = roundf(position);

	float delta = fDivider - position;
	if (delta == 0.0f)
		return;

	fDivider = position;

	if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
		// We should never get here, since layout support means, we also
		// layout the divider, and don't use this method at all.
		Relayout();
	} else {
		_LayoutTextView();
		Invalidate();
	}
}


void
BAbstractSpinner::SetEnabled(bool enable)
{
	if (IsEnabled() == enable)
		return;

	BControl::SetEnabled(enable);

	fTextView->MakeEditable(enable);
	if (enable)
		fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
	else
		fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);

	_UpdateTextViewColors(enable);
	fTextView->Invalidate();

	_LayoutTextView();
	Invalidate();
	if (Window() != NULL)
		Window()->UpdateIfNeeded();
}


void
BAbstractSpinner::SetLabel(const char* label)
{
	BControl::SetLabel(label);

	if (Window() != NULL)
		Window()->UpdateIfNeeded();
}


bool
BAbstractSpinner::IsDecrementEnabled() const
{
	return fDecrement->IsEnabled();
}


void
BAbstractSpinner::SetDecrementEnabled(bool enable)
{
	if (IsDecrementEnabled() == enable)
		return;

	fDecrement->SetEnabled(enable);
	fDecrement->Invalidate();
}


bool
BAbstractSpinner::IsIncrementEnabled() const
{
	return fIncrement->IsEnabled();
}


void
BAbstractSpinner::SetIncrementEnabled(bool enable)
{
	if (IsIncrementEnabled() == enable)
		return;

	fIncrement->SetEnabled(enable);
	fIncrement->Invalidate();
}


BSize
BAbstractSpinner::MinSize()
{
	_ValidateLayoutData();
	return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
}


BSize
BAbstractSpinner::MaxSize()
{
	_ValidateLayoutData();

	BSize max = fLayoutData->min;
	max.width = B_SIZE_UNLIMITED;

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


BSize
BAbstractSpinner::PreferredSize()
{
	_ValidateLayoutData();
	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
		fLayoutData->min);
}


BAlignment
BAbstractSpinner::LayoutAlignment()
{
	_ValidateLayoutData();
	return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
		BAlignment(B_ALIGN_LEFT, B_ALIGN_VERTICAL_CENTER));
}


BLayoutItem*
BAbstractSpinner::CreateLabelLayoutItem()
{
	if (fLayoutData->label_layout_item == NULL)
		fLayoutData->label_layout_item = new LabelLayoutItem(this);

	return fLayoutData->label_layout_item;
}


BLayoutItem*
BAbstractSpinner::CreateTextViewLayoutItem()
{
	if (fLayoutData->text_view_layout_item == NULL)
		fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);

	return fLayoutData->text_view_layout_item;
}


BTextView*
BAbstractSpinner::TextView() const
{
	return dynamic_cast<BTextView*>(fTextView);
}


//	#pragma mark - BAbstractSpinner protected methods


status_t
BAbstractSpinner::AllArchived(BMessage* into) const
{
	status_t result;
	if ((result = BControl::AllArchived(into)) != B_OK)
		return result;

	BArchiver archiver(into);

	BArchivable* textViewItem = fLayoutData->text_view_layout_item;
	if (archiver.IsArchived(textViewItem))
		result = archiver.AddArchivable(kTextViewItemField, textViewItem);

	if (result != B_OK)
		return result;

	BArchivable* labelBarItem = fLayoutData->label_layout_item;
	if (archiver.IsArchived(labelBarItem))
		result = archiver.AddArchivable(kLabelItemField, labelBarItem);

	return result;
}


status_t
BAbstractSpinner::AllUnarchived(const BMessage* from)
{
	BUnarchiver unarchiver(from);

	status_t result = B_OK;
	if ((result = BControl::AllUnarchived(from)) != B_OK)
		return result;

	if (unarchiver.IsInstantiated(kTextViewItemField)) {
		TextViewLayoutItem*& textViewItem
			= fLayoutData->text_view_layout_item;
		result = unarchiver.FindObject(kTextViewItemField,
			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, textViewItem);

		if (result == B_OK)
			textViewItem->SetParent(this);
		else
			return result;
	}

	if (unarchiver.IsInstantiated(kLabelItemField)) {
		LabelLayoutItem*& labelItem = fLayoutData->label_layout_item;
		result = unarchiver.FindObject(kLabelItemField,
			BUnarchiver::B_DONT_ASSUME_OWNERSHIP, labelItem);

		if (result == B_OK)
			labelItem->SetParent(this);
	}

	return result;
}


void
BAbstractSpinner::DoLayout()
{
	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
		return;

	if (GetLayout()) {
		BControl::DoLayout();
		return;
	}

	_ValidateLayoutData();

	BSize size(Bounds().Size());
	if (size.width < fLayoutData->min.width)
		size.width = fLayoutData->min.width;

	if (size.height < fLayoutData->min.height)
		size.height = fLayoutData->min.height;

	float divider = 0;
	if (fLayoutData->label_layout_item != NULL
		&& fLayoutData->text_view_layout_item != NULL
		&& fLayoutData->label_layout_item->Frame().IsValid()
		&& fLayoutData->text_view_layout_item->Frame().IsValid()) {
		divider = fLayoutData->text_view_layout_item->Frame().left
			- fLayoutData->label_layout_item->Frame().left;
	} else if (fLayoutData->label_width > 0) {
		divider = fLayoutData->label_width
			+ be_control_look->DefaultLabelSpacing();
	}
	fDivider = divider;

	BRect dirty(fTextView->Frame());
	_LayoutTextView();

	// invalidate dirty region
	dirty = dirty | fTextView->Frame();
	dirty = dirty | fIncrement->Frame();
	dirty = dirty | fDecrement->Frame();

	Invalidate(dirty);
}


void
BAbstractSpinner::LayoutInvalidated(bool descendants)
{
	if (fLayoutData != NULL)
		fLayoutData->valid = false;
}


//	#pragma mark - BAbstractSpinner private methods


void
BAbstractSpinner::_DrawLabel(BRect updateRect)
{
	BRect rect(Bounds());
	rect.right = fDivider;
	if (!rect.IsValid() || !rect.Intersects(updateRect))
		return;

	_ValidateLayoutData();

	const char* label = Label();
	if (label == NULL)
		return;

	// horizontal position
	float x;
	switch (fAlignment) {
		case B_ALIGN_RIGHT:
			x = fDivider - fLayoutData->label_width - 3.0f;
			break;

		case B_ALIGN_CENTER:
			x = fDivider - roundf(fLayoutData->label_width / 2.0f);
			break;

		default:
			x = 0.0f;
			break;
	}

	// vertical position
	font_height& fontHeight = fLayoutData->font_info;
	float y = rect.top
		+ roundf((rect.Height() + 1.0f - fontHeight.ascent
			- fontHeight.descent) / 2.0f)
		+ fontHeight.ascent;

	uint32 flags = be_control_look->Flags(this);

	// erase the is control flag before drawing the label so that the label
	// will get drawn using B_PANEL_TEXT_COLOR.
	flags &= ~BControlLook::B_IS_CONTROL;

	be_control_look->DrawLabel(this, label, LowColor(), flags, BPoint(x, y));
}


void
BAbstractSpinner::_DrawTextView(BRect updateRect)
{
	BRect rect = fTextView->Frame();
	rect.InsetBy(-kFrameMargin, -kFrameMargin);
	if (!rect.IsValid() || !rect.Intersects(updateRect))
		return;

	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
	uint32 flags = 0;
	if (!IsEnabled())
		flags |= BControlLook::B_DISABLED;

	if (fTextView->IsFocus() && Window()->IsActive())
		flags |= BControlLook::B_FOCUSED;

	be_control_look->DrawTextControlBorder(this, rect, updateRect, base,
		flags);
}


void
BAbstractSpinner::_InitObject()
{
	fAlignment = B_ALIGN_LEFT;
	fButtonStyle = SPINNER_BUTTON_PLUS_MINUS;

	if (Label() != NULL) {
		fDivider = StringWidth(Label())
			+ be_control_look->DefaultLabelSpacing();
	} else
		fDivider = 0.0f;

	BControl::SetEnabled(true);
	BControl::SetValue(0);

	BRect rect(Bounds());
	fLayoutData = new LayoutData(rect.Width(), rect.Height());

	rect.left = fDivider;
	rect.InsetBy(kFrameMargin, kFrameMargin);
	rect.right -= rect.Height() * 2 + kFrameMargin * 2 + 1.0f;
	BRect textRect(rect.OffsetToCopy(B_ORIGIN));

	fTextView = new SpinnerTextView(rect, textRect);
	AddChild(fTextView);

	rect.InsetBy(0.0f, -kFrameMargin);

	rect.left = rect.right + kFrameMargin * 2;
	rect.right = rect.left + rect.Height() - kFrameMargin * 2;

	fDecrement = new SpinnerButton(rect, "decrement", SPINNER_DECREMENT);
	AddChild(fDecrement);

	rect.left = rect.right + 1.0f;
	rect.right = rect.left + rect.Height() - kFrameMargin * 2;

	fIncrement = new SpinnerButton(rect, "increment", SPINNER_INCREMENT);
	AddChild(fIncrement);

	uint32 navigableFlags = Flags() & B_NAVIGABLE;
	if (navigableFlags != 0)
		BControl::SetFlags(Flags() & ~B_NAVIGABLE);
}


void
BAbstractSpinner::_LayoutTextView()
{
	BRect rect;
	if (fLayoutData->text_view_layout_item != NULL) {
		rect = fLayoutData->text_view_layout_item->FrameInParent();
	} else {
		rect = Bounds();
		rect.left = fDivider;
	}
	rect.InsetBy(kFrameMargin, kFrameMargin);
	rect.right -= rect.Height() * 2 + kFrameMargin * 2 + 1.0f;

	fTextView->MoveTo(rect.left, rect.top);
	fTextView->ResizeTo(rect.Width(), rect.Height());
	fTextView->SetTextRect(rect.OffsetToCopy(B_ORIGIN));

	rect.InsetBy(0.0f, -kFrameMargin);

	rect.left = rect.right + kFrameMargin * 2;
	rect.right = rect.left + rect.Height() - kFrameMargin * 2;

	fDecrement->ResizeTo(rect.Width(), rect.Height());
	fDecrement->MoveTo(rect.LeftTop());

	rect.left = rect.right + 1.0f;
	rect.right = rect.left + rect.Height() - kFrameMargin * 2;

	fIncrement->ResizeTo(rect.Width(), rect.Height());
	fIncrement->MoveTo(rect.LeftTop());
}


void
BAbstractSpinner::_UpdateFrame()
{
	if (fLayoutData->label_layout_item == NULL
		|| fLayoutData->text_view_layout_item == NULL) {
		return;
	}

	BRect labelFrame = fLayoutData->label_layout_item->Frame();
	BRect textViewFrame = fLayoutData->text_view_layout_item->Frame();

	if (!labelFrame.IsValid() || !textViewFrame.IsValid())
		return;

	// update divider
	fDivider = textViewFrame.left - labelFrame.left;

	BRect frame = textViewFrame | labelFrame;
	MoveTo(frame.left, frame.top);
	BSize oldSize = Bounds().Size();
	ResizeTo(frame.Width(), frame.Height());
	BSize newSize = Bounds().Size();

	// If the size changes, ResizeTo() will trigger a relayout, otherwise
	// we need to do that explicitly.
	if (newSize != oldSize)
		Relayout();
}


void
BAbstractSpinner::_UpdateTextViewColors(bool enable)
{
	// Mimick BTextControl's appearance.
	rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR);

	if (enable) {
		fTextView->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
		fTextView->SetLowUIColor(ViewUIColor());
	} else {
		rgb_color color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
		color = disable_color(ViewColor(), color);
		textColor = disable_color(textColor, ViewColor());

		fTextView->SetViewColor(color);
		fTextView->SetLowColor(color);
	}

	BFont font;
	fTextView->GetFontAndColor(0, &font);
	fTextView->SetFontAndColor(&font, B_FONT_ALL, &textColor);
}


void
BAbstractSpinner::_ValidateLayoutData()
{
	if (fLayoutData->valid)
		return;

	font_height& fontHeight = fLayoutData->font_info;
	GetFontHeight(&fontHeight);

	if (Label() != NULL) {
		fLayoutData->label_width = StringWidth(Label());
		fLayoutData->label_height = ceilf(fontHeight.ascent
			+ fontHeight.descent + fontHeight.leading);
	} else {
		fLayoutData->label_width = 0;
		fLayoutData->label_height = 0;
	}

	float divider = 0;
	if (fLayoutData->label_width > 0) {
		divider = ceilf(fLayoutData->label_width
			+ be_control_look->DefaultLabelSpacing());
	}

	if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
		divider = std::max(divider, fDivider);

	float minTextWidth = fTextView->StringWidth("99999");

	float textViewHeight = fTextView->LineHeight(0) + kFrameMargin * 2;
	float textViewWidth = minTextWidth + textViewHeight * 2;

	fLayoutData->text_view_width = textViewWidth;
	fLayoutData->text_view_height = textViewHeight;

	BSize min(textViewWidth, textViewHeight);
	if (divider > 0.0f)
		min.width += divider;

	if (fLayoutData->label_height > min.height)
		min.height = fLayoutData->label_height;

	fLayoutData->min = min;
	fLayoutData->valid = true;

	ResetLayoutInvalidation();
}


// FBC padding

void BAbstractSpinner::_ReservedAbstractSpinner20() {}
void BAbstractSpinner::_ReservedAbstractSpinner19() {}
void BAbstractSpinner::_ReservedAbstractSpinner18() {}
void BAbstractSpinner::_ReservedAbstractSpinner17() {}
void BAbstractSpinner::_ReservedAbstractSpinner16() {}
void BAbstractSpinner::_ReservedAbstractSpinner15() {}
void BAbstractSpinner::_ReservedAbstractSpinner14() {}
void BAbstractSpinner::_ReservedAbstractSpinner13() {}
void BAbstractSpinner::_ReservedAbstractSpinner12() {}
void BAbstractSpinner::_ReservedAbstractSpinner11() {}
void BAbstractSpinner::_ReservedAbstractSpinner10() {}
void BAbstractSpinner::_ReservedAbstractSpinner9() {}
void BAbstractSpinner::_ReservedAbstractSpinner8() {}
void BAbstractSpinner::_ReservedAbstractSpinner7() {}
void BAbstractSpinner::_ReservedAbstractSpinner6() {}
void BAbstractSpinner::_ReservedAbstractSpinner5() {}
void BAbstractSpinner::_ReservedAbstractSpinner4() {}
void BAbstractSpinner::_ReservedAbstractSpinner3() {}
void BAbstractSpinner::_ReservedAbstractSpinner2() {}
void BAbstractSpinner::_ReservedAbstractSpinner1() {}
