/*
 * Copyright 2012, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Marc Flerackers (mflerackers@androme.be)
 *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
 *		John Scipione (jscipione@gmail.com)
 */


#include "InlineScrollView.h"

#include <ControlLook.h>
#include <Debug.h>
#include <InterfaceDefs.h>
#include <Menu.h>
#include <Point.h>
#include <Screen.h>
#include <Window.h>


const int kDefaultScrollStep = 19;
const int kScrollerDimension = 12;


class ScrollArrow : public BView {
public:
							ScrollArrow(BRect frame);
	virtual					~ScrollArrow();

			bool			IsEnabled() const { return fEnabled; };
			void			SetEnabled(bool enabled);

private:
			bool			fEnabled;
};


class UpScrollArrow : public ScrollArrow {
public:
							UpScrollArrow(BRect frame);
	virtual					~UpScrollArrow();

	virtual	void			Draw(BRect updateRect);
	virtual	void			MouseDown(BPoint where);
};


class DownScrollArrow : public ScrollArrow {
public:
							DownScrollArrow(BRect frame);
	virtual					~DownScrollArrow();

	virtual	void			Draw(BRect updateRect);
	virtual	void			MouseDown(BPoint where);
};


class LeftScrollArrow : public ScrollArrow {
public:
							LeftScrollArrow(BRect frame);
	virtual					~LeftScrollArrow();

	virtual	void			Draw(BRect updateRect);
	virtual	void			MouseDown(BPoint where);
};


class RightScrollArrow : public ScrollArrow {
public:
							RightScrollArrow(BRect frame);
	virtual					~RightScrollArrow();

	virtual	void			Draw(BRect updateRect);
	virtual	void			MouseDown(BPoint where);
};


//	#pragma mark -


ScrollArrow::ScrollArrow(BRect frame)
	:
	BView(frame, "menu scroll arrow", B_FOLLOW_NONE, B_WILL_DRAW),
	fEnabled(false)
{
	SetViewUIColor(B_MENU_BACKGROUND_COLOR);
}


ScrollArrow::~ScrollArrow()
{
}


void
ScrollArrow::SetEnabled(bool enabled)
{
	fEnabled = enabled;
	Invalidate();
}


//	#pragma mark -


UpScrollArrow::UpScrollArrow(BRect frame)
	:
	ScrollArrow(frame)
{
}


UpScrollArrow::~UpScrollArrow()
{
}


void
UpScrollArrow::Draw(BRect updateRect)
{
	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
		B_DARKEN_1_TINT));

	if (IsEnabled())
		SetHighColor(0, 0, 0);
	else {
		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
			B_DARKEN_2_TINT));
	}

	FillRect(Bounds(), B_SOLID_LOW);

	float middle = Bounds().right / 2;
	FillTriangle(BPoint(middle, (kScrollerDimension / 2) - 3),
		BPoint(middle + 5, (kScrollerDimension / 2) + 2),
		BPoint(middle - 5, (kScrollerDimension / 2) + 2));
}


void
UpScrollArrow::MouseDown(BPoint where)
{
	if (!IsEnabled())
		return;

	TInlineScrollView* parent = dynamic_cast<TInlineScrollView*>(Parent());
	if (parent == NULL)
		return;

	float smallStep;
	float largeStep;
	parent->GetSteps(&smallStep, &largeStep);

	BMessage* message = Window()->CurrentMessage();
	int32 modifiers = 0;
	message->FindInt32("modifiers", &modifiers);
	// pressing the shift key scrolls faster
	if ((modifiers & B_SHIFT_KEY) != 0)
		parent->ScrollBy(-largeStep);
	else
		parent->ScrollBy(-smallStep);

	snooze(5000);
}


//	#pragma mark -


DownScrollArrow::DownScrollArrow(BRect frame)
	:
	ScrollArrow(frame)
{
}


DownScrollArrow::~DownScrollArrow()
{
}


void
DownScrollArrow::Draw(BRect updateRect)
{
	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
		B_DARKEN_1_TINT));

	if (IsEnabled())
		SetHighColor(0, 0, 0);
	else {
		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
			B_DARKEN_2_TINT));
	}

	BRect frame = Bounds();
	FillRect(frame, B_SOLID_LOW);

	float middle = Bounds().right / 2;
	FillTriangle(BPoint(middle, frame.bottom - (kScrollerDimension / 2) + 3),
		BPoint(middle + 5, frame.bottom - (kScrollerDimension / 2) - 2),
		BPoint(middle - 5, frame.bottom - (kScrollerDimension / 2) - 2));
}


void
DownScrollArrow::MouseDown(BPoint where)
{
	if (!IsEnabled())
		return;

	TInlineScrollView* grandparent
		= dynamic_cast<TInlineScrollView*>(Parent()->Parent());
	if (grandparent == NULL)
		return;

	float smallStep;
	float largeStep;
	grandparent->GetSteps(&smallStep, &largeStep);

	BMessage* message = Window()->CurrentMessage();
	int32 modifiers = 0;
	message->FindInt32("modifiers", &modifiers);
	// pressing the shift key scrolls faster
	if ((modifiers & B_SHIFT_KEY) != 0)
		grandparent->ScrollBy(largeStep);
	else
		grandparent->ScrollBy(smallStep);

	snooze(5000);
}


//	#pragma mark -


LeftScrollArrow::LeftScrollArrow(BRect frame)
	:
	ScrollArrow(frame)
{
}


LeftScrollArrow::~LeftScrollArrow()
{
}


void
LeftScrollArrow::Draw(BRect updateRect)
{
	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));

	if (IsEnabled())
		SetHighColor(0, 0, 0);
	else {
		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
			B_DARKEN_2_TINT));
	}

	FillRect(Bounds(), B_SOLID_LOW);

	float middle = Bounds().bottom / 2;
	FillTriangle(BPoint((kScrollerDimension / 2) - 3, middle),
		BPoint((kScrollerDimension / 2) + 2, middle + 5),
		BPoint((kScrollerDimension / 2) + 2, middle - 5));
}


void
LeftScrollArrow::MouseDown(BPoint where)
{
	if (!IsEnabled())
		return;

	TInlineScrollView* parent = dynamic_cast<TInlineScrollView*>(Parent());
	if (parent == NULL)
		return;

	float smallStep;
	float largeStep;
	parent->GetSteps(&smallStep, &largeStep);

	BMessage* message = Window()->CurrentMessage();
	int32 modifiers = 0;
	message->FindInt32("modifiers", &modifiers);
	// pressing the shift key scrolls faster
	if ((modifiers & B_SHIFT_KEY) != 0)
		parent->ScrollBy(-largeStep);
	else
		parent->ScrollBy(-smallStep);

	snooze(5000);
}


//	#pragma mark -


RightScrollArrow::RightScrollArrow(BRect frame)
	:
	ScrollArrow(frame)
{
}


RightScrollArrow::~RightScrollArrow()
{
}


void
RightScrollArrow::Draw(BRect updateRect)
{
	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));

	if (IsEnabled())
		SetHighColor(0, 0, 0);
	else {
		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
			B_DARKEN_2_TINT));
	}

	BRect frame = Bounds();
	FillRect(frame, B_SOLID_LOW);

	float middle = Bounds().bottom / 2;
	FillTriangle(BPoint(kScrollerDimension / 2 + 3, middle),
		BPoint(kScrollerDimension / 2 - 2, middle + 5),
		BPoint(kScrollerDimension / 2 - 2, middle - 5));
}


void
RightScrollArrow::MouseDown(BPoint where)
{
	if (!IsEnabled())
		return;

	TInlineScrollView* grandparent
		= dynamic_cast<TInlineScrollView*>(Parent()->Parent());
	if (grandparent == NULL)
		return;

	float smallStep;
	float largeStep;
	grandparent->GetSteps(&smallStep, &largeStep);

	BMessage* message = Window()->CurrentMessage();
	int32 modifiers = 0;
	message->FindInt32("modifiers", &modifiers);
	// pressing the shift key scrolls faster
	if ((modifiers & B_SHIFT_KEY) != 0)
		grandparent->ScrollBy(largeStep);
	else
		grandparent->ScrollBy(smallStep);

	snooze(5000);
}


//	#pragma mark -


TInlineScrollView::TInlineScrollView(BView* target,
	enum orientation orientation)
	:
	BView(BRect(0, 0, 0, 0), "inline scroll view", B_FOLLOW_NONE, B_WILL_DRAW),
	fTarget(target),
	fBeginScrollArrow(NULL),
	fEndScrollArrow(NULL),
	fScrollStep(kDefaultScrollStep),
	fScrollValue(0),
	fScrollLimit(0),
	fOrientation(orientation)
{
}


TInlineScrollView::~TInlineScrollView()
{
	if (fBeginScrollArrow != NULL) {
		fBeginScrollArrow->RemoveSelf();
		delete fBeginScrollArrow;
		fBeginScrollArrow = NULL;
	}

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


void
TInlineScrollView::AttachedToWindow()
{
	BView::AttachedToWindow();

	if (fTarget == NULL)
		return;

	AddChild(fTarget);
	fTarget->MoveTo(0, 0);
}


void
TInlineScrollView::DetachedFromWindow()
{
	BView::DetachedFromWindow();

	if (fTarget != NULL)
		fTarget->RemoveSelf();

	if (fBeginScrollArrow != NULL)
		fBeginScrollArrow->RemoveSelf();

	if (fEndScrollArrow != NULL)
		fEndScrollArrow->RemoveSelf();
}


void
TInlineScrollView::Draw(BRect updateRect)
{
	BRect frame = Bounds();
	be_control_look->DrawButtonBackground(this, frame, updateRect,
		ui_color(B_MENU_BACKGROUND_COLOR));
}


//	#pragma mark -


void
TInlineScrollView::AttachScrollers()
{
	if (fTarget == NULL)
		return;

	BRect frame = Bounds();

	if (HasScrollers()) {
		if (fOrientation == B_VERTICAL) {
			fScrollLimit = fTarget->Bounds().Height()
				- (frame.Height() - 2 * kScrollerDimension);
		} else {
			fScrollLimit = fTarget->Bounds().Width()
				- (frame.Width() - 2 * kScrollerDimension);
		}

		if (fScrollValue > fScrollLimit) {
			// If scroll value is above limit scroll back
			float delta = fScrollLimit - fScrollValue;
			if (fOrientation == B_VERTICAL)
				fTarget->ScrollBy(0, delta);
			else
				fTarget->ScrollBy(delta, 0);

			fScrollValue = fScrollLimit;
		}
		return;
	}

	fTarget->MakeFocus(true);

	if (fOrientation == B_VERTICAL) {
		if (fBeginScrollArrow == NULL) {
			fBeginScrollArrow = new UpScrollArrow(
				BRect(frame.left, frame.top, frame.right,
					kScrollerDimension - 1));
			AddChild(fBeginScrollArrow);
		}

		if (fEndScrollArrow == NULL) {
			fEndScrollArrow = new DownScrollArrow(
				BRect(0, frame.bottom - 2 * kScrollerDimension + 1, frame.right,
					frame.bottom - kScrollerDimension));
			fTarget->AddChild(fEndScrollArrow);
		}

		fTarget->MoveBy(0, kScrollerDimension);

		fScrollLimit = fTarget->Bounds().Height()
			- (frame.Height() - 2 * kScrollerDimension);
	} else {
		if (fBeginScrollArrow == NULL) {
			fBeginScrollArrow = new LeftScrollArrow(
				BRect(frame.left, frame.top,
					frame.left + kScrollerDimension - 1, frame.bottom));
			AddChild(fBeginScrollArrow);
		}

		if (fEndScrollArrow == NULL) {
			fEndScrollArrow = new RightScrollArrow(
				BRect(frame.right - 2 * kScrollerDimension + 1, frame.top,
					frame.right, frame.bottom));
			fTarget->AddChild(fEndScrollArrow);
		}

		fTarget->MoveBy(kScrollerDimension, 0);

		fScrollLimit = fTarget->Bounds().Width()
			- (frame.Width() - 2 * kScrollerDimension);
	}

	fBeginScrollArrow->SetEnabled(false);
	fEndScrollArrow->SetEnabled(true);

	fScrollValue = 0;
}


void
TInlineScrollView::DetachScrollers()
{
	if (!HasScrollers())
		return;

	if (fEndScrollArrow) {
		fEndScrollArrow->RemoveSelf();
		delete fEndScrollArrow;
		fEndScrollArrow = NULL;
	}

	if (fBeginScrollArrow) {
		fBeginScrollArrow->RemoveSelf();
		delete fBeginScrollArrow;
		fBeginScrollArrow = NULL;
	}

	if (fTarget) {
		// We don't remember the position where the last scrolling
		// ended, so scroll back to the beginning.
		if (fOrientation == B_VERTICAL)
			fTarget->MoveBy(0, -kScrollerDimension);
		else
			fTarget->MoveBy(-kScrollerDimension, 0);

		fTarget->ScrollTo(0, 0);
		fScrollValue = 0;
	}
}


bool
TInlineScrollView::HasScrollers() const
{
	return fTarget != NULL && fBeginScrollArrow != NULL
		&& fEndScrollArrow != NULL;
}


void
TInlineScrollView::SetSmallStep(float step)
{
	fScrollStep = step;
}


void
TInlineScrollView::GetSteps(float* _smallStep, float* _largeStep) const
{
	if (_smallStep != NULL)
		*_smallStep = fScrollStep;
	if (_largeStep != NULL) {
		*_largeStep = fScrollStep * 3;
	}
}


void
TInlineScrollView::ScrollBy(const float& step)
{
	if (!HasScrollers())
		return;

	if (step > 0) {
		if (fScrollValue == 0)
			fBeginScrollArrow->SetEnabled(true);

		if (fScrollValue + step >= fScrollLimit) {
			// If we reached the limit, only scroll to the end
			if (fOrientation == B_VERTICAL) {
				fTarget->ScrollBy(0, fScrollLimit - fScrollValue);
				fEndScrollArrow->MoveBy(0, fScrollLimit - fScrollValue);
			} else {
				fTarget->ScrollBy(fScrollLimit - fScrollValue, 0);
				fEndScrollArrow->MoveBy(fScrollLimit - fScrollValue, 0);
			}
			fEndScrollArrow->SetEnabled(false);
			fScrollValue = fScrollLimit;
		} else {
			if (fOrientation == B_VERTICAL) {
				fTarget->ScrollBy(0, step);
				fEndScrollArrow->MoveBy(0, step);
			} else {
				fTarget->ScrollBy(step, 0);
				fEndScrollArrow->MoveBy(step, 0);
			}
			fScrollValue += step;
		}
	} else if (step < 0) {
		if (fScrollValue == fScrollLimit)
			fEndScrollArrow->SetEnabled(true);

		if (fScrollValue + step <= 0) {
			if (fOrientation == B_VERTICAL) {
				fTarget->ScrollBy(0, -fScrollValue);
				fEndScrollArrow->MoveBy(0, -fScrollValue);
			} else {
				fTarget->ScrollBy(-fScrollValue, 0);
				fEndScrollArrow->MoveBy(-fScrollValue, 0);
			}
			fBeginScrollArrow->SetEnabled(false);
			fScrollValue = 0;
		} else {
			if (fOrientation == B_VERTICAL) {
				fTarget->ScrollBy(0, step);
				fEndScrollArrow->MoveBy(0, step);
			} else {
				fTarget->ScrollBy(step, 0);
				fEndScrollArrow->MoveBy(step, 0);
			}
			fScrollValue += step;
		}
	}

	//fTarget->Invalidate();
}
