⛏️ index : haiku.git

/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2009, Stephan Aßmus, superstippi@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "HeaderView.h"

#include <stdio.h>

#include <algorithm>
#include <new>

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


// #pragma mark - HeaderRenderer


HeaderRenderer::~HeaderRenderer()
{
}


void
HeaderRenderer::DrawHeaderBackground(BView* view, BRect frame, BRect updateRect,
	uint32 flags)
{
	BRect bgRect = frame;

	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
	view->SetHighColor(tint_color(base, B_DARKEN_2_TINT));
	view->StrokeLine(bgRect.LeftBottom(), bgRect.RightBottom());

	bgRect.bottom--;
	bgRect.right--;

//	if ((flags & CLICKED) != 0)
//		base = tint_color(base, B_DARKEN_1_TINT);

	be_control_look->DrawButtonBackground(view, bgRect, updateRect, base, 0,
		BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);

	view->StrokeLine(frame.RightTop(), frame.RightBottom());
}


// #pragma mark - DefaultHeaderRenderer


DefaultHeaderRenderer::DefaultHeaderRenderer()
{
}


DefaultHeaderRenderer::~DefaultHeaderRenderer()
{
}


float
DefaultHeaderRenderer::HeaderHeight(BView* view, const Header* header)
{
	BVariant value;
	if (!header->GetValue(value))
		return 0;

	if (value.Type() == B_STRING_TYPE) {
		font_height fontHeight;
		view->GetFontHeight(&fontHeight);
		return ceilf((fontHeight.ascent + fontHeight.descent) * 1.2f) + 2.0f;
	} else {
		// TODO: Support more value types.
		return 0;
	}
}


float
DefaultHeaderRenderer::PreferredHeaderWidth(BView* view, const Header* header)
{
	BVariant value;
	if (!header->GetValue(value))
		return 0;

	if (value.Type() == B_STRING_TYPE) {
		float width = view->StringWidth(value.ToString());
		return width + be_control_look->DefaultLabelSpacing() * 2.0f;
	} else {
		// TODO: Support more value types.
		return 0;
	}
}


void
DefaultHeaderRenderer::DrawHeader(BView* view, BRect frame, BRect updateRect,
	const Header* header, uint32 flags)
{
	DrawHeaderBackground(view, frame, updateRect, flags);

	BVariant value;
	if (!header->GetValue(value))
		return;

	frame.InsetBy(be_control_look->DefaultLabelSpacing(), 0);

	if (value.Type() == B_STRING_TYPE) {
		rgb_color highColor = view->HighColor();
		be_control_look->DrawLabel(view, value.ToString(), frame, updateRect,
			view->LowColor(), 0, &highColor);
	}
}


// #pragma mark - HeaderListener


HeaderListener::~HeaderListener()
{
}


void
HeaderListener::HeaderWidthChanged(Header* header)
{
}


void
HeaderListener::HeaderWidthRestrictionsChanged(Header* header)
{
}


void
HeaderListener::HeaderValueChanged(Header* header)
{
}


void
HeaderListener::HeaderRendererChanged(Header* header)
{
}


// #pragma mark - Header


Header::Header(int32 modelIndex)
	:
	fWidth(100),
	fMinWidth(0),
	fMaxWidth(10000),
	fPreferredWidth(100),
	fValue(),
	fRenderer(NULL),
	fModelIndex(modelIndex),
	fResizable(true)
{
}


Header::Header(float width, float minWidth, float maxWidth,
	float preferredWidth, int32 modelIndex)
	:
	fWidth(width),
	fMinWidth(minWidth),
	fMaxWidth(maxWidth),
	fPreferredWidth(preferredWidth),
	fValue(),
	fRenderer(NULL),
	fModelIndex(modelIndex),
	fResizable(true)
{
}


float
Header::Width() const
{
	return fWidth;
}


float
Header::MinWidth() const
{
	return fMinWidth;
}


float
Header::MaxWidth() const
{
	return fMaxWidth;
}


float
Header::PreferredWidth() const
{
	return fPreferredWidth;
}


void
Header::SetWidth(float width)
{
	if (width != fWidth) {
		fWidth = width;
		NotifyWidthChanged();
	}
}


void
Header::SetMinWidth(float width)
{
	if (width != fMinWidth) {
		fMinWidth = width;
		NotifyWidthRestrictionsChanged();
	}
}


void
Header::SetMaxWidth(float width)
{
	if (width != fMaxWidth) {
		fMaxWidth = width;
		NotifyWidthRestrictionsChanged();
	}
}


void
Header::SetPreferredWidth(float width)
{
	if (width != fPreferredWidth) {
		fPreferredWidth = width;
		NotifyWidthRestrictionsChanged();
	}
}


bool
Header::IsResizable() const
{
	return fResizable;
}


void
Header::SetResizable(bool resizable)
{
	if (resizable != fResizable) {
		fResizable = resizable;
		NotifyWidthRestrictionsChanged();
	}
}


bool
Header::GetValue(BVariant& _value) const
{
	_value = fValue;
	return true;
}


void
Header::SetValue(const BVariant& value)
{
	fValue = value;
	NotifyValueChanged();
}


int32
Header::ModelIndex() const
{
	return fModelIndex;
}


void
Header::SetModelIndex(int32 index)
{
	fModelIndex = index;
}


HeaderRenderer*
Header::GetHeaderRenderer() const
{
	return fRenderer;
}


void
Header::SetHeaderRenderer(HeaderRenderer* renderer)
{
	if (renderer != fRenderer) {
		fRenderer = renderer;
		NotifyRendererChanged();
	}
}


void
Header::AddListener(HeaderListener* listener)
{
	fListeners.AddItem(listener);
}


void
Header::RemoveListener(HeaderListener* listener)
{
	fListeners.RemoveItem(listener);
}


void
Header::NotifyWidthChanged()
{
	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
		HeaderListener* listener = fListeners.ItemAt(i);
		listener->HeaderWidthChanged(this);
	}
}


void
Header::NotifyWidthRestrictionsChanged()
{
	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
		HeaderListener* listener = fListeners.ItemAt(i);
		listener->HeaderWidthRestrictionsChanged(this);
	}
}


void
Header::NotifyValueChanged()
{
	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
		HeaderListener* listener = fListeners.ItemAt(i);
		listener->HeaderValueChanged(this);
	}
}


void
Header::NotifyRendererChanged()
{
	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
		HeaderListener* listener = fListeners.ItemAt(i);
		listener->HeaderRendererChanged(this);
	}
}


// #pragma mark - HeaderModelListener


HeaderModelListener::~HeaderModelListener()
{
}


void
HeaderModelListener::HeaderAdded(HeaderModel* model, int32 index)
{
}


void
HeaderModelListener::HeaderRemoved(HeaderModel* model, int32 index)
{
}


void
HeaderModelListener::HeaderMoved(HeaderModel* model, int32 fromIndex,
	int32 toIndex)
{
}


// #pragma mark - HeaderModel


HeaderModel::HeaderModel()
{
}


HeaderModel::~HeaderModel()
{
}


int32
HeaderModel::CountHeaders() const
{
	return fHeaders.CountItems();
}


Header*
HeaderModel::HeaderAt(int32 index) const
{
	return fHeaders.ItemAt(index);
}


int32
HeaderModel::IndexOfHeader(Header* header) const
{
	return fHeaders.IndexOf(header);
}


bool
HeaderModel::AddHeader(Header* header)
{
	if (!fHeaders.AddItem(header))
		return false;

	NotifyHeaderAdded(fHeaders.CountItems() - 1);

	return true;
}


Header*
HeaderModel::RemoveHeader(int32 index)
{
	Header* header = fHeaders.RemoveItemAt(index);
	if (header != NULL)
		return NULL;

	NotifyHeaderRemoved(index);

	return header;
}


void
HeaderModel::RemoveHeader(Header* header)
{
	RemoveHeader(fHeaders.IndexOf(header));
}


bool
HeaderModel::MoveHeader(int32 fromIndex, int32 toIndex)
{
	int32 headerCount = fHeaders.CountItems();
	if (fromIndex < 0 || fromIndex >= headerCount
		|| toIndex < 0 || toIndex >= headerCount) {
		return false;
	}

	if (fromIndex == toIndex)
		return true;

	Header* header = fHeaders.RemoveItemAt(fromIndex);
	fHeaders.AddItem(header, toIndex);
		// TODO: Might fail.

	NotifyHeaderMoved(fromIndex, toIndex);

	return true;
}


void
HeaderModel::AddListener(HeaderModelListener* listener)
{
	fListeners.AddItem(listener);
}


void
HeaderModel::RemoveListener(HeaderModelListener* listener)
{
	fListeners.RemoveItem(listener);
}


void
HeaderModel::NotifyHeaderAdded(int32 index)
{
	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
		HeaderModelListener* listener = fListeners.ItemAt(i);
		listener->HeaderAdded(this, index);
	}
}


void
HeaderModel::NotifyHeaderRemoved(int32 index)
{
	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
		HeaderModelListener* listener = fListeners.ItemAt(i);
		listener->HeaderRemoved(this, index);
	}
}


void
HeaderModel::NotifyHeaderMoved(int32 fromIndex, int32 toIndex)
{
	for (int32 i = fListeners.CountItems() - 1; i >= 0; i--) {
		HeaderModelListener* listener = fListeners.ItemAt(i);
		listener->HeaderMoved(this, fromIndex, toIndex);
	}
}


// #pragma mark - HeaderEntry


struct HeaderView::HeaderEntry {
	Header*	header;
	float	position;
	float	width;

	HeaderEntry(Header* header)
		:
		header(header)
	{
	}
};


// #pragma mark - States


class HeaderView::State {
public:
	State(HeaderView* parent)
		:
		fParent(parent)
	{
	}

	virtual ~State()
	{
	}

	virtual void Entering(State* previousState)
	{
	}

	virtual void Leaving(State* nextState)
	{
	}

	virtual void MouseDown(BPoint where, uint32 buttons, uint32 modifiers)
	{
	}

	virtual void MouseUp(BPoint where, uint32 buttons, uint32 modifiers)
	{
	}

	virtual void MouseMoved(BPoint where, uint32 transit,
		const BMessage* dragMessage)
	{
	}

protected:
	HeaderView*	fParent;
};


class HeaderView::DefaultState : public State {
public:
								DefaultState(HeaderView* parent);

	virtual	void				MouseDown(BPoint where, uint32 buttons,
									uint32 modifiers);
	virtual	void				MouseMoved(BPoint where, uint32 transit,
									const BMessage* dragMessage);
};


class HeaderView::ResizeState : public State {
public:
	virtual	void				Entering(State* previousState);
	virtual	void				Leaving(State* nextState);

								ResizeState(HeaderView* parent,
									int32 headerIndex, BPoint startPoint);

	virtual	void				MouseUp(BPoint where, uint32 buttons,
									uint32 modifiers);
	virtual	void				MouseMoved(BPoint where, uint32 transit,
									const BMessage* dragMessage);

private:
			Header*				fHeader;
			float				fStartX;
			float				fStartWidth;
};


// #pragma mark - DefaultState


HeaderView::DefaultState::DefaultState(HeaderView* parent)
	:
	State(parent)
{
}


void
HeaderView::DefaultState::MouseDown(BPoint where, uint32 buttons,
	uint32 modifiers)
{
	HeaderModel* model = fParent->Model();
	if (model == NULL)
		return;

	if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
		return;

	int32 headerIndex = fParent->HeaderIndexAt(where);
	if (headerIndex < 0) {
		int32 headerCount = model->CountHeaders();
		if (headerCount == 0)
			return;

		headerIndex = headerCount - 1;
	}

	// Check whether the mouse is close to the left or the right side of the
	// header.
	BRect headerFrame = fParent->HeaderFrame(headerIndex);
	if (fabs(headerFrame.left - where.x) <= 3) {
		if (headerIndex == 0)
			return;
		headerIndex--;
	} else if (fabs(headerFrame.right + 1 - where.x) > 3)
		return;

	// start resizing the header
	fParent->_SwitchState(new ResizeState(fParent, headerIndex, where));
}


void
HeaderView::DefaultState::MouseMoved(BPoint where, uint32 transit,
	const BMessage* dragMessage)
{
}


// #pragma mark - ResizeState


void
HeaderView::ResizeState::Entering(State* previousState)
{
	fParent->SetEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
}


void
HeaderView::ResizeState::Leaving(State* nextState)
{
	fParent->SetEventMask(0, 0);
}


HeaderView::ResizeState::ResizeState(HeaderView* parent, int32 headerIndex,
	BPoint startPoint)
	:
	State(parent),
	fHeader(parent->Model()->HeaderAt(headerIndex)),
	fStartX(startPoint.x),
	fStartWidth(fHeader->Width())
{
}


void
HeaderView::ResizeState::MouseUp(BPoint where, uint32 buttons,
	uint32 modifiers)
{
	if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
		fParent->_SwitchState(NULL);
}


void
HeaderView::ResizeState::MouseMoved(BPoint where, uint32 transit,
	const BMessage* dragMessage)
{
	float width = fStartWidth + where.x - fStartX;
	width = std::max(width, fHeader->MinWidth());
	width = std::min(width, fHeader->MaxWidth());
	fHeader->SetWidth(width);
}


// #pragma mark - HeaderView


HeaderView::HeaderView()
 	:
 	BView("header view", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
 	fModel(NULL),
	fHeaderEntries(10),
 	fLayoutValid(false),
 	fDefaultState(new DefaultState(this)),
 	fState(fDefaultState)
{
 	HeaderModel* model = new(std::nothrow) HeaderModel;
 	BReference<HeaderModel> modelReference(model, true);
 	SetModel(model);

	SetViewColor(B_TRANSPARENT_32_BIT);
}


HeaderView::~HeaderView()
{
	SetModel(NULL);

	_SwitchState(NULL);
	delete fDefaultState;
}


void
HeaderView::Draw(BRect updateRect)
{
	if (fModel == NULL)
		return;

	_ValidateHeadersLayout();

	DefaultHeaderRenderer defaultRenderer;
	float bottom = Bounds().Height();
	float left = 0.0f;

	for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++) {
		if (Header* header = fModel->HeaderAt(i)) {
			HeaderRenderer* renderer = header->GetHeaderRenderer();
			if (renderer == NULL)
				renderer = &defaultRenderer;

			BRect frame(entry->position, 0, entry->position + entry->width - 1,
				bottom);
			renderer->DrawHeader(this, frame, updateRect, header, 0);
				// TODO: flags!

			left = entry->position + entry->width;
		}
	}

	// TODO: We are not using any custom renderer here.
	defaultRenderer.DrawHeaderBackground(this,
		BRect(left, 0, Bounds().right + 1, bottom), updateRect, 0);
}


BSize
HeaderView::MinSize()
{
	_ValidateHeadersLayout();

	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
		BSize(100, fPreferredHeight - 1));
}


BSize
HeaderView::MaxSize()
{
	_ValidateHeadersLayout();

	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
		BSize(B_SIZE_UNLIMITED, fPreferredHeight - 1));
}


BSize
HeaderView::PreferredSize()
{
	_ValidateHeadersLayout();

	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
		BSize(fPreferredWidth - 1, fPreferredHeight - 1));
}


HeaderModel*
HeaderView::Model() const
{
	return fModel;
}


void
HeaderView::MouseDown(BPoint where)
{
	// get buttons and modifiers
	BMessage* message = Looper()->CurrentMessage();
	int32 buttons;
	if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
		buttons = 0;
	int32 modifiers;
	if (message == NULL || message->FindInt32("modifiers", &modifiers) != B_OK)
		modifiers = 0;

	fState->MouseDown(where, buttons, modifiers);
}


void
HeaderView::MouseUp(BPoint where)
{
	// get buttons and modifiers
	BMessage* message = Looper()->CurrentMessage();
	int32 buttons;
	if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
		buttons = 0;
	int32 modifiers;
	if (message == NULL || message->FindInt32("modifiers", &modifiers) != B_OK)
		modifiers = 0;

	fState->MouseUp(where, buttons, modifiers);
}


void
HeaderView::MouseMoved(BPoint where, uint32 transit,
	const BMessage* dragMessage)
{
	fState->MouseMoved(where, transit, dragMessage);
}


status_t
HeaderView::SetModel(HeaderModel* model)
{
	if (model == fModel)
		return B_OK;

	_SwitchState(NULL);

	if (fModel != NULL) {
		// remove all headers
		for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++)
			entry->header->RemoveListener(this);
		fHeaderEntries.MakeEmpty();

		fModel->RemoveListener(this);
		fModel->ReleaseReference();
	}

	fModel = model;

	if (fModel != NULL) {
		fModel->AcquireReference();
		fModel->AddListener(this);

		// create header entries
		int32 headerCount = fModel->CountHeaders();
		for (int32 i = 0; i < headerCount; i++) {
			HeaderEntry* entry = new(std::nothrow) HeaderEntry(
				fModel->HeaderAt(i));
			if (entry == NULL || !fHeaderEntries.AddItem(entry)) {
				delete entry;
				return B_NO_MEMORY;
			}

			entry->header->AddListener(this);
		}
	}

	_InvalidateHeadersLayout(0);
	Invalidate();

	return B_OK;
}


BRect
HeaderView::HeaderFrame(int32 index) const
{
	float bottom = Bounds().Height();

	if (HeaderEntry* entry = fHeaderEntries.ItemAt(index)) {
		return BRect(entry->position, 0, entry->position + entry->width - 1,
			bottom);
	}

	return BRect();
}


int32
HeaderView::HeaderIndexAt(BPoint point) const
{
	float x = point.x;

	for (int32 i = 0; HeaderEntry* entry = fHeaderEntries.ItemAt(i); i++) {
		if (x >= entry->position && x < entry->position + entry->width)
			return i;
	}

	return -1;
}


void
HeaderView::AddListener(HeaderViewListener* listener)
{
	fListeners.AddItem(listener);
}


void
HeaderView::RemoveListener(HeaderViewListener* listener)
{
	fListeners.RemoveItem(listener);
}


void
HeaderView::HeaderAdded(HeaderModel* model, int32 index)
{
	if (Header* header = fModel->HeaderAt(index)) {
		HeaderEntry* entry = new(std::nothrow) HeaderEntry(
			fModel->HeaderAt(index));
		if (entry == NULL || !fHeaderEntries.AddItem(entry)) {
			delete entry;
			return;
		}

		header->AddListener(this);
		_InvalidateHeadersLayout(index);
	}
}


void
HeaderView::HeaderRemoved(HeaderModel* model, int32 index)
{
	if (HeaderEntry* entry = fHeaderEntries.RemoveItemAt(index)) {
		entry->header->RemoveListener(this);
		_InvalidateHeadersLayout(index);
	}
}


void
HeaderView::HeaderMoved(HeaderModel* model, int32 fromIndex, int32 toIndex)
{
	_InvalidateHeadersLayout(std::min(fromIndex, toIndex));
}


void
HeaderView::HeaderWidthChanged(Header* header)
{
	_HeaderPropertiesChanged(header, true, true);
}


void
HeaderView::HeaderWidthRestrictionsChanged(Header* header)
{
	// TODO:...
}


void
HeaderView::HeaderValueChanged(Header* header)
{
	_HeaderPropertiesChanged(header, true, false);
}


void
HeaderView::HeaderRendererChanged(Header* header)
{
	_HeaderPropertiesChanged(header, true, true);
}


void
HeaderView::_HeaderPropertiesChanged(Header* header, bool redrawNeeded,
	bool relayoutNeeded)
{
	if (!redrawNeeded && !relayoutNeeded)
		return;

	int32 index = fModel->IndexOfHeader(header);

	if (relayoutNeeded)
		_InvalidateHeadersLayout(index);
	else if (redrawNeeded)
		_InvalidateHeaders(index, index + 1);
}


void
HeaderView::_InvalidateHeadersLayout(int32 firstIndex)
{
	if (!fLayoutValid)
		return;

	fLayoutValid = false;
	InvalidateLayout();
	Invalidate();
}


void
HeaderView::_InvalidateHeaders(int32 firstIndex, int32 endIndex)
{
	Invalidate();
		// TODO: Be less lazy!
}


void
HeaderView::_ValidateHeadersLayout()
{
	if (fLayoutValid)
		return;

	DefaultHeaderRenderer defaultRenderer;

	int32 headerCount = fHeaderEntries.CountItems();
	float position = 0;
	fPreferredWidth = 0;
	fPreferredHeight = 0;

	for (int32 i = 0; i < headerCount; i++) {
		HeaderEntry* entry = fHeaderEntries.ItemAt(i);
		entry->position = position;
		if (Header* header = fModel->HeaderAt(i)) {
			entry->width = header->Width();
			fPreferredWidth += header->PreferredWidth();
		} else
			entry->width = 0;

		position = entry->position + entry->width;

		if (Header* header = fModel->HeaderAt(i)) {
			HeaderRenderer* renderer = header->GetHeaderRenderer();
			if (renderer == NULL)
				renderer = &defaultRenderer;

			float height = renderer->HeaderHeight(this, header);
			if (height > fPreferredHeight)
				fPreferredHeight = height;
		}
	}

	fLayoutValid = true;
}


void
HeaderView::_SwitchState(State* newState)
{
	if (newState == NULL)
		newState = fDefaultState;

	fState->Leaving(newState);
	newState->Entering(fState);

	if (fState != fDefaultState)
		delete fState;

	fState = newState;
}


// #pragma mark - HeaderViewListener


HeaderViewListener::~HeaderViewListener()
{
}