⛏️ index : haiku.git

/*
 * Copyright 2003-2009, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2019, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "DefaultMediaTheme.h"

#include <Box.h>
#include <Button.h>
#include <ChannelSlider.h>
#include <CheckBox.h>
#include <GroupView.h>
#include <MediaRoster.h>
#include <MenuField.h>
#include <MessageFilter.h>
#include <OptionPopUp.h>
#include <ParameterWeb.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <Slider.h>
#include <SpaceLayoutItem.h>
#include <StringView.h>
#include <TabView.h>
#include <TextControl.h>
#include <Window.h>

#include "MediaDebug.h"


using namespace BPrivate;


namespace BPrivate {

namespace DefaultMediaControls {

class SeparatorView : public BView {
	public:
		SeparatorView(orientation orientation);
		virtual ~SeparatorView();

		virtual void Draw(BRect updateRect);

	private:
		bool	fVertical;
};

class TitleView : public BView {
	public:
		TitleView(const char *title);
		virtual ~TitleView();

		virtual void Draw(BRect updateRect);
		virtual void GetPreferredSize(float *width, float *height);

	private:
		BString fTitle;
};

class CheckBox : public BCheckBox {
	public:
		CheckBox(const char* name, const char* label,
			BDiscreteParameter &parameter);
		virtual ~CheckBox();

		virtual void AttachedToWindow();
		virtual void DetachedFromWindow();
	private:
		BDiscreteParameter &fParameter;
};

class OptionPopUp : public BOptionPopUp {
	public:
		OptionPopUp(const char* name, const char* label,
			BDiscreteParameter &parameter);
		virtual ~OptionPopUp();

		virtual void AttachedToWindow();
		virtual void DetachedFromWindow();
	private:
		BDiscreteParameter &fParameter;
};

class Slider : public BSlider {
	public:
		Slider(const char* name, const char*label, int32 minValue,
			int32 maxValue, BContinuousParameter &parameter);
		virtual ~Slider();

		virtual void AttachedToWindow();
		virtual void DetachedFromWindow();
	private:
		BContinuousParameter &fParameter;
};

class ChannelSlider : public BChannelSlider {
	public:
		ChannelSlider(const char* name, const char* label,
			orientation orientation, int32 channels,
			BContinuousParameter &parameter);
		virtual ~ChannelSlider();

		virtual void AttachedToWindow();
		virtual void DetachedFromWindow();
		virtual void UpdateToolTip(int32 currentValue);
	private:
		BContinuousParameter &fParameter;
};

class TextControl : public BTextControl {
	public:
		TextControl(const char* name, const char* label,
			BTextParameter &parameter);
		virtual ~TextControl();

		virtual void AttachedToWindow();
		virtual void DetachedFromWindow();
	private:
		BTextParameter &fParameter;
};

class MessageFilter : public BMessageFilter {
	public:
		static MessageFilter *FilterFor(BView *view, BParameter &parameter);

	protected:
		MessageFilter();
};

class ContinuousMessageFilter : public MessageFilter {
	public:
		ContinuousMessageFilter(BControl *control,
			BContinuousParameter &parameter);
		virtual ~ContinuousMessageFilter();

		virtual filter_result Filter(BMessage *message, BHandler **target);

	private:
		void _UpdateControl();

		BControl				*fControl;
		BContinuousParameter	&fParameter;
};

class DiscreteMessageFilter : public MessageFilter {
	public:
		DiscreteMessageFilter(BControl *control, BDiscreteParameter &parameter);
		virtual ~DiscreteMessageFilter();

		virtual filter_result Filter(BMessage *message, BHandler **target);

	private:
		BDiscreteParameter	&fParameter;
};

class TextMessageFilter : public MessageFilter {
	public:
		TextMessageFilter(BControl *control, BTextParameter &parameter);
		virtual ~TextMessageFilter();

		virtual filter_result Filter(BMessage *message, BHandler **target);

	private:
		BTextParameter	&fParameter;
};

};

using namespace DefaultMediaControls;

}	// namespace BPrivate


const uint32 kMsgParameterChanged = '_mPC';


static bool
parameter_should_be_hidden(BParameter &parameter)
{
	// ToDo: note, this is probably completely stupid, but it's the only
	// way I could safely remove the null parameters that are not shown
	// by the R5 media theme
	if (parameter.Type() != BParameter::B_NULL_PARAMETER
		|| strcmp(parameter.Kind(), B_WEB_PHYSICAL_INPUT))
		return false;

	for (int32 i = 0; i < parameter.CountOutputs(); i++) {
		if (!strcmp(parameter.OutputAt(0)->Kind(), B_INPUT_MUX))
			return true;
	}

	return false;
}


static void
start_watching_for_parameter_changes(BControl* control, BParameter &parameter)
{
	BMediaRoster* roster = BMediaRoster::CurrentRoster();
	if (roster == NULL)
		return;

	if (roster->StartWatching(control, parameter.Web()->Node(),
			B_MEDIA_NEW_PARAMETER_VALUE) != B_OK) {
		fprintf(stderr, "DefaultMediaTheme: Failed to start watching parameter"
			"\"%s\"\n", parameter.Name());
		return;
	}
}


static void
stop_watching_for_parameter_changes(BControl* control, BParameter &parameter)
{
	BMediaRoster* roster = BMediaRoster::CurrentRoster();
	if (roster == NULL)
		return;

	roster->StopWatching(control, parameter.Web()->Node(),
		B_MEDIA_NEW_PARAMETER_VALUE);
}


//	#pragma mark -


SeparatorView::SeparatorView(orientation orientation)
	: BView("-", B_WILL_DRAW),
	fVertical(orientation == B_VERTICAL)
{
	if (fVertical) {
		SetExplicitMinSize(BSize(5, 0));
		SetExplicitMaxSize(BSize(5, MaxSize().height));
	} else {
		SetExplicitMinSize(BSize(0, 5));
		SetExplicitMaxSize(BSize(MaxSize().width, 5));
	}
}


SeparatorView::~SeparatorView()
{
}


void
SeparatorView::Draw(BRect updateRect)
{
	rgb_color color = ui_color(B_PANEL_BACKGROUND_COLOR);
	BRect rect = updateRect & Bounds();

	SetHighColor(tint_color(color, B_DARKEN_1_TINT));
	if (fVertical)
		StrokeLine(BPoint(0, rect.top), BPoint(0, rect.bottom));
	else
		StrokeLine(BPoint(rect.left, 0), BPoint(rect.right, 0));

	SetHighColor(tint_color(color, B_LIGHTEN_1_TINT));
	if (fVertical)
		StrokeLine(BPoint(1, rect.top), BPoint(1, rect.bottom));
	else
		StrokeLine(BPoint(rect.left, 1), BPoint(rect.right, 1));
}


//	#pragma mark -


TitleView::TitleView(const char *title)
	: BView(title, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
	fTitle(title)
{
	AdoptSystemColors();
}


TitleView::~TitleView()
{
}


void
TitleView::Draw(BRect updateRect)
{
	BRect rect(Bounds());
	rect.left = (rect.Width() - StringWidth(fTitle)) / 2;

	SetDrawingMode(B_OP_OVER);
	SetHighColor(mix_color(ui_color(B_PANEL_TEXT_COLOR), make_color(255, 0, 0), 100));
	DrawString(fTitle, BPoint(rect.left, rect.bottom - 9));
}


void
TitleView::GetPreferredSize(float *_width, float *_height)
{
	if (_width)
		*_width = StringWidth(fTitle) + 2;

	if (_height) {
		font_height fontHeight;
		GetFontHeight(&fontHeight);

		*_height = fontHeight.ascent + fontHeight.descent + fontHeight.leading + 8;
	}
}


//	#pragma mark -


CheckBox::CheckBox(const char* name, const char* label,
	BDiscreteParameter &parameter)
	: BCheckBox(name, label, NULL),
	fParameter(parameter)
{
}


CheckBox::~CheckBox()
{
}


void
CheckBox::AttachedToWindow()
{
	BCheckBox::AttachedToWindow();

	SetTarget(this);
	start_watching_for_parameter_changes(this, fParameter);
}


void
CheckBox::DetachedFromWindow()
{
	stop_watching_for_parameter_changes(this, fParameter);
}


OptionPopUp::OptionPopUp(const char* name, const char* label,
	BDiscreteParameter &parameter)
	: BOptionPopUp(name, label, NULL),
	fParameter(parameter)
{
}


OptionPopUp::~OptionPopUp()
{
}


void
OptionPopUp::AttachedToWindow()
{
	BOptionPopUp::AttachedToWindow();

	SetTarget(this);
	start_watching_for_parameter_changes(this, fParameter);
}


void
OptionPopUp::DetachedFromWindow()
{
	stop_watching_for_parameter_changes(this, fParameter);
}


Slider::Slider(const char* name, const char* label, int32 minValue,
	int32 maxValue, BContinuousParameter &parameter)
	: BSlider(name, label, NULL, minValue, maxValue, B_HORIZONTAL),
	fParameter(parameter)
{
}


Slider::~Slider()
{
}


void
Slider::AttachedToWindow()
{
	BSlider::AttachedToWindow();

	SetTarget(this);
	start_watching_for_parameter_changes(this, fParameter);
}


void
Slider::DetachedFromWindow()
{
	stop_watching_for_parameter_changes(this, fParameter);
}


ChannelSlider::ChannelSlider(const char* name, const char* label,
	orientation orientation, int32 channels, BContinuousParameter &parameter)
	: BChannelSlider(name, label, NULL, orientation, channels),
	fParameter(parameter)
{
}


ChannelSlider::~ChannelSlider()
{
}


void
ChannelSlider::AttachedToWindow()
{
	BChannelSlider::AttachedToWindow();

	SetTarget(this);
	start_watching_for_parameter_changes(this, fParameter);
}


void
ChannelSlider::DetachedFromWindow()
{
	stop_watching_for_parameter_changes(this, fParameter);

	BChannelSlider::DetachedFromWindow();
}


void
ChannelSlider::UpdateToolTip(int32 currentValue)
{
	BString valueString;
	valueString.SetToFormat("%.1f", currentValue / 1000.0);
	SetToolTip(valueString);
}


TextControl::TextControl(const char* name, const char* label,
	BTextParameter &parameter)
	: BTextControl(name, label, "", NULL),
	fParameter(parameter)
{
}


TextControl::~TextControl()
{
}


void
TextControl::AttachedToWindow()
{
	BTextControl::AttachedToWindow();

	SetTarget(this);
	start_watching_for_parameter_changes(this, fParameter);
}


void
TextControl::DetachedFromWindow()
{
	stop_watching_for_parameter_changes(this, fParameter);
}


//	#pragma mark -


MessageFilter::MessageFilter()
	: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE)
{
}


MessageFilter *
MessageFilter::FilterFor(BView *view, BParameter &parameter)
{
	BControl *control = dynamic_cast<BControl *>(view);
	if (control == NULL)
		return NULL;

	switch (parameter.Type()) {
		case BParameter::B_CONTINUOUS_PARAMETER:
			return new ContinuousMessageFilter(control,
				static_cast<BContinuousParameter &>(parameter));

		case BParameter::B_DISCRETE_PARAMETER:
			return new DiscreteMessageFilter(control,
				static_cast<BDiscreteParameter &>(parameter));

		case BParameter::B_TEXT_PARAMETER:
			return new TextMessageFilter(control,
				static_cast<BTextParameter &>(parameter));

		case BParameter::B_NULL_PARAMETER: /* fall through */
		default:
			return NULL;
	}
}


//	#pragma mark -


ContinuousMessageFilter::ContinuousMessageFilter(BControl *control,
		BContinuousParameter &parameter)
	: MessageFilter(),
	fControl(control),
	fParameter(parameter)
{
	// initialize view for us
	control->SetMessage(new BMessage(kMsgParameterChanged));

	if (BSlider *slider = dynamic_cast<BSlider *>(fControl))
		slider->SetModificationMessage(new BMessage(kMsgParameterChanged));
	else if (BChannelSlider *slider = dynamic_cast<BChannelSlider *>(fControl))
		slider->SetModificationMessage(new BMessage(kMsgParameterChanged));
	else
		ERROR("ContinuousMessageFilter: unknown continuous parameter view\n");

	// set initial value
	_UpdateControl();
}


ContinuousMessageFilter::~ContinuousMessageFilter()
{
}


filter_result
ContinuousMessageFilter::Filter(BMessage *message, BHandler **target)
{
	if (*target != fControl)
		return B_DISPATCH_MESSAGE;

	if (message->what == kMsgParameterChanged) {
		// update parameter from control
		// TODO: support for response!

		float value[fParameter.CountChannels()];

		if (BSlider *slider = dynamic_cast<BSlider *>(fControl)) {
			value[0] = (float)(slider->Value() / 1000.0);
		} else if (BChannelSlider *slider
				= dynamic_cast<BChannelSlider *>(fControl)) {
			for (int32 i = 0; i < fParameter.CountChannels(); i++)
				value[i] = (float)(slider->ValueFor(i) / 1000.0);
		}

		TRACE("ContinuousMessageFilter::Filter: update view %s, %" B_PRId32
			" channels\n", fControl->Name(), fParameter.CountChannels());

		if (fParameter.SetValue((void *)value, sizeof(value),
				-1) < B_OK) {
			ERROR("ContinuousMessageFilter::Filter: Could not set parameter "
				"value for %p\n", &fParameter);
			return B_DISPATCH_MESSAGE;
		}
		return B_SKIP_MESSAGE;
	}
	if (message->what == B_MEDIA_NEW_PARAMETER_VALUE) {
		// update view from parameter -- if the message concerns us
		const media_node* node;
		int32 parameterID;
		ssize_t size;
		if (message->FindInt32("parameter", &parameterID) != B_OK
			|| fParameter.ID() != parameterID
			|| message->FindData("node", B_RAW_TYPE, (const void**)&node,
					&size) != B_OK
			|| fParameter.Web()->Node() != *node)
			return B_DISPATCH_MESSAGE;

		_UpdateControl();
		return B_SKIP_MESSAGE;
	}

	return B_DISPATCH_MESSAGE;
}


void
ContinuousMessageFilter::_UpdateControl()
{
	// TODO: response support!

	float value[fParameter.CountChannels()];
	size_t size = sizeof(value);
	if (fParameter.GetValue((void *)&value, &size, NULL) < B_OK) {
		ERROR("ContinuousMessageFilter: Could not get value for continuous "
			"parameter %p (name '%s', node %d)\n", &fParameter,
			fParameter.Name(), (int)fParameter.Web()->Node().node);
		return;
	}

	if (BSlider *slider = dynamic_cast<BSlider *>(fControl)) {
		slider->SetValue((int32) (1000 * value[0]));
		slider->SetModificationMessage(new BMessage(kMsgParameterChanged));
	} else if (BChannelSlider *slider
			= dynamic_cast<BChannelSlider *>(fControl)) {
		for (int32 i = 0; i < fParameter.CountChannels(); i++) {
			slider->SetValueFor(i, (int32) (1000 * value[i]));
		}
	}
}


//	#pragma mark -


DiscreteMessageFilter::DiscreteMessageFilter(BControl *control,
		BDiscreteParameter &parameter)
	: MessageFilter(),
	fParameter(parameter)
{
	// initialize view for us
	control->SetMessage(new BMessage(kMsgParameterChanged));

	// set initial value
	size_t size = sizeof(int32);
	int32 value;
	if (parameter.GetValue((void *)&value, &size, NULL) < B_OK) {
		ERROR("DiscreteMessageFilter: Could not get value for discrete "
			"parameter %p (name '%s', node %d)\n", &parameter,
			parameter.Name(), (int)(parameter.Web()->Node().node));
		return;
	}

	if (BCheckBox *checkBox = dynamic_cast<BCheckBox *>(control)) {
		checkBox->SetValue(value);
	} else if (BOptionPopUp *popUp = dynamic_cast<BOptionPopUp *>(control)) {
		popUp->SelectOptionFor(value);
	} else
		ERROR("DiscreteMessageFilter: unknown discrete parameter view\n");
}


DiscreteMessageFilter::~DiscreteMessageFilter()
{
}


filter_result
DiscreteMessageFilter::Filter(BMessage *message, BHandler **target)
{
	BControl *control;

	if ((control = dynamic_cast<BControl *>(*target)) == NULL)
		return B_DISPATCH_MESSAGE;

	if (message->what == B_MEDIA_NEW_PARAMETER_VALUE) {
		TRACE("DiscreteMessageFilter::Filter: Got a new parameter value\n");
		const media_node* node;
		int32 parameterID;
		ssize_t size;
		if (message->FindInt32("parameter", &parameterID) != B_OK
			|| fParameter.ID() != parameterID
			|| message->FindData("node", B_RAW_TYPE, (const void**)&node,
					&size) != B_OK
			|| fParameter.Web()->Node() != *node)
			return B_DISPATCH_MESSAGE;

		int32 value = 0;
		size_t valueSize = sizeof(int32);
		if (fParameter.GetValue((void*)&value, &valueSize, NULL) < B_OK) {
			ERROR("DiscreteMessageFilter: Could not get value for continuous "
			"parameter %p (name '%s', node %d)\n", &fParameter,
			fParameter.Name(), (int)fParameter.Web()->Node().node);
			return B_SKIP_MESSAGE;
		}
		if (BCheckBox* checkBox = dynamic_cast<BCheckBox*>(control)) {
			checkBox->SetValue(value);
		} else if (BOptionPopUp* popUp = dynamic_cast<BOptionPopUp*>(control)) {
			popUp->SetValue(value);
		}

		return B_SKIP_MESSAGE;
	}

	if (message->what != kMsgParameterChanged)
		return B_DISPATCH_MESSAGE;

	// update view

	int32 value = 0;

	if (BCheckBox *checkBox = dynamic_cast<BCheckBox *>(control)) {
		value = checkBox->Value();
	} else if (BOptionPopUp *popUp = dynamic_cast<BOptionPopUp *>(control)) {
		popUp->SelectedOption(NULL, &value);
	}

	TRACE("DiscreteMessageFilter::Filter: update view %s, value = %"
		B_PRId32 "\n", control->Name(), value);

	if (fParameter.SetValue((void *)&value, sizeof(value), -1) < B_OK) {
		ERROR("DiscreteMessageFilter::Filter: Could not set parameter value for %p\n", &fParameter);
		return B_DISPATCH_MESSAGE;
	}

	return B_SKIP_MESSAGE;
}


//	#pragma mark -


TextMessageFilter::TextMessageFilter(BControl *control,
		BTextParameter &parameter)
	: MessageFilter(),
	fParameter(parameter)
{
	// initialize view for us
	control->SetMessage(new BMessage(kMsgParameterChanged));

	// set initial value
	if (BTextControl *textControl = dynamic_cast<BTextControl *>(control)) {
		size_t valueSize = parameter.MaxBytes();
		char* value = new char[valueSize + 1];

		if (parameter.GetValue((void *)value, &valueSize, NULL) < B_OK) {
			ERROR("TextMessageFilter: Could not get value for text "
				"parameter %p (name '%s', node %d)\n", &parameter,
				parameter.Name(), (int)(parameter.Web()->Node().node));
		} else {
			textControl->SetText(value);
		}

		delete[] value;
	}

	ERROR("TextMessageFilter: unknown text parameter view\n");
}


TextMessageFilter::~TextMessageFilter()
{
}


filter_result
TextMessageFilter::Filter(BMessage *message, BHandler **target)
{
	BControl *control;

	if ((control = dynamic_cast<BControl *>(*target)) == NULL)
		return B_DISPATCH_MESSAGE;

	if (message->what == B_MEDIA_NEW_PARAMETER_VALUE) {
		TRACE("TextMessageFilter::Filter: Got a new parameter value\n");
		const media_node* node;
		int32 parameterID;
		ssize_t size;
		if (message->FindInt32("parameter", &parameterID) != B_OK
			|| fParameter.ID() != parameterID
			|| message->FindData("node", B_RAW_TYPE, (const void**)&node,
					&size) != B_OK
			|| fParameter.Web()->Node() != *node)
			return B_DISPATCH_MESSAGE;

		if (BTextControl *textControl = dynamic_cast<BTextControl *>(control)) {
			size_t valueSize = fParameter.MaxBytes();
			char* value = new char[valueSize + 1];
			if (fParameter.GetValue((void *)value, &valueSize, NULL) < B_OK) {
				ERROR("TextMessageFilter: Could not get value for text "
					"parameter %p (name '%s', node %d)\n", &fParameter,
					fParameter.Name(), (int)(fParameter.Web()->Node().node));
			} else {
				textControl->SetText(value);
			}

			delete[] value;

			return B_SKIP_MESSAGE;
		}

		return B_DISPATCH_MESSAGE;
	}

	if (message->what != kMsgParameterChanged)
		return B_DISPATCH_MESSAGE;

	// update parameter value

	if (BTextControl *textControl = dynamic_cast<BTextControl *>(control)) {
		BString value = textControl->Text();
		TRACE("TextMessageFilter::Filter: update view %s, value = %s\n",
			control->Name(), value.String());
		if (fParameter.SetValue((void *)value.String(), value.Length() + 1, -1) < B_OK) {
			ERROR("TextMessageFilter::Filter: Could not set parameter value for %p\n", &fParameter);
			return B_DISPATCH_MESSAGE;
		}
	}

	return B_SKIP_MESSAGE;
}


//	#pragma mark -


DefaultMediaTheme::DefaultMediaTheme()
	: BMediaTheme("Haiku theme", "Haiku built-in theme version 0.1")
{
	CALLED();
}


BControl *
DefaultMediaTheme::MakeControlFor(BParameter *parameter)
{
	CALLED();

	return MakeViewFor(parameter);
}


BView *
DefaultMediaTheme::MakeViewFor(BParameterWeb *web, const BRect *hintRect)
{
	CALLED();

	if (web == NULL)
		return NULL;

	// do we have more than one attached parameter group?
	// if so, use a tabbed view with a tab for each group

	BTabView *tabView = NULL;
	if (web->CountGroups() > 1)
		tabView = new BTabView("web");

	for (int32 i = 0; i < web->CountGroups(); i++) {
		BParameterGroup *group = web->GroupAt(i);
		if (group == NULL)
			continue;

		BView *groupView = MakeViewFor(*group);
		if (groupView == NULL)
			continue;

		BScrollView *scrollView = new BScrollView(groupView->Name(), groupView, 0,
			true, true, B_NO_BORDER);
		scrollView->SetExplicitMinSize(BSize(B_V_SCROLL_BAR_WIDTH,
			B_H_SCROLL_BAR_HEIGHT));
		if (tabView == NULL) {
			if (hintRect != NULL) {
				scrollView->MoveTo(hintRect->LeftTop());
				scrollView->ResizeTo(hintRect->Size());
			} else {
				scrollView->ResizeTo(600, 400);
					// See comment below.
			}
			return scrollView;
		}
		tabView->AddTab(scrollView);
	}

	if (hintRect != NULL) {
		tabView->MoveTo(hintRect->LeftTop());
		tabView->ResizeTo(hintRect->Size());
	} else {
		// Apps not using layouted views may expect PreferredSize() to return
		// a sane value right away, and use this to set the maximum size of
		// things. Layouted views return their current size until the view has
		// been attached to the window, so in order to prevent breakage, we set
		// a default view size here.
		tabView->ResizeTo(600, 400);
	}
	return tabView;
}


BView *
DefaultMediaTheme::MakeViewFor(BParameterGroup& group)
{
	CALLED();

	if (group.Flags() & B_HIDDEN_PARAMETER)
		return NULL;

	BGroupView *view = new BGroupView(group.Name(), B_HORIZONTAL,
		B_USE_HALF_ITEM_SPACING);
	BGroupLayout *layout = view->GroupLayout();
	layout->SetInsets(B_USE_HALF_ITEM_INSETS);

	// Create and add the parameter views
	if (group.CountParameters() > 0) {
		BGroupView *paramView = new BGroupView(group.Name(), B_VERTICAL,
			B_USE_HALF_ITEM_SPACING);
		BGroupLayout *paramLayout = paramView->GroupLayout();
		paramLayout->SetInsets(0);

		for (int32 i = 0; i < group.CountParameters(); i++) {
			BParameter *parameter = group.ParameterAt(i);
			if (parameter == NULL)
				continue;

			BView *parameterView = MakeSelfHostingViewFor(*parameter);
			if (parameterView == NULL)
				continue;

			paramLayout->AddView(parameterView);
		}
		paramLayout->AddItem(BSpaceLayoutItem::CreateHorizontalStrut(10));
		layout->AddView(paramView);
	}

	// Add the sub-group views
	for (int32 i = 0; i < group.CountGroups(); i++) {
		BParameterGroup *subGroup = group.GroupAt(i);
		if (subGroup == NULL)
			continue;

		BView *groupView = MakeViewFor(*subGroup);
		if (groupView == NULL)
			continue;

		if (i > 0)
			layout->AddView(new SeparatorView(B_VERTICAL));

		layout->AddView(groupView);
	}

	layout->AddItem(BSpaceLayoutItem::CreateGlue());
	return view;
}


/*!	This creates a view that handles all incoming messages itself - that's
	what is meant with self-hosting.
*/
BView *
DefaultMediaTheme::MakeSelfHostingViewFor(BParameter& parameter)
{
	if (parameter.Flags() & B_HIDDEN_PARAMETER
		|| parameter_should_be_hidden(parameter))
		return NULL;

	BView *view = MakeViewFor(&parameter);
	if (view == NULL) {
		// The MakeViewFor() method above returns a BControl - which we
		// don't need for a null parameter; that's why it returns NULL.
		// But we want to see something anyway, so we add a string view
		// here.
		if (parameter.Type() == BParameter::B_NULL_PARAMETER) {
			if (parameter.Group()->ParameterAt(0) == &parameter) {
				// this is the first parameter in this group, so
				// let's use a nice title view
				return new TitleView(parameter.Name());
			}
			BStringView *stringView = new BStringView(parameter.Name(),
				parameter.Name());
			stringView->SetAlignment(B_ALIGN_CENTER);

			return stringView;
		}

		return NULL;
	}

	MessageFilter *filter = MessageFilter::FilterFor(view, parameter);
	if (filter != NULL)
		view->AddFilter(filter);

	return view;
}


BControl *
DefaultMediaTheme::MakeViewFor(BParameter *parameter)
{
	switch (parameter->Type()) {
		case BParameter::B_NULL_PARAMETER:
			// there is no default view for a null parameter
			return NULL;

		case BParameter::B_DISCRETE_PARAMETER:
		{
			BDiscreteParameter &discrete
				= static_cast<BDiscreteParameter &>(*parameter);

			if (!strcmp(discrete.Kind(), B_ENABLE)
				|| !strcmp(discrete.Kind(), B_MUTE)
				|| discrete.CountItems() == 0) {
				return new CheckBox(discrete.Name(), discrete.Name(), discrete);
			} else {
				BOptionPopUp *popUp = new OptionPopUp(discrete.Name(),
					discrete.Name(), discrete);

				for (int32 i = 0; i < discrete.CountItems(); i++) {
					popUp->AddOption(discrete.ItemNameAt(i),
						discrete.ItemValueAt(i));
				}

				return popUp;
			}
		}

		case BParameter::B_CONTINUOUS_PARAMETER:
		{
			BContinuousParameter &continuous
				= static_cast<BContinuousParameter &>(*parameter);

			if (!strcmp(continuous.Kind(), B_MASTER_GAIN)
				|| !strcmp(continuous.Kind(), B_GAIN)) {
				BChannelSlider *slider = new ChannelSlider(
					continuous.Name(), continuous.Name(), B_VERTICAL,
					continuous.CountChannels(), continuous);

				BString minLabel, maxLabel;
				const char *unit = continuous.Unit();
				if (unit[0]) {
					// if we have a unit, print it next to the limit values
					minLabel.SetToFormat("%g %s", continuous.MinValue(), continuous.Unit());
					maxLabel.SetToFormat("%g %s", continuous.MaxValue(), continuous.Unit());
				} else {
					minLabel.SetToFormat("%g", continuous.MinValue());
					maxLabel.SetToFormat("%g", continuous.MaxValue());
				}
				slider->SetLimitLabels(minLabel, maxLabel);

				// ToDo: take BContinuousParameter::GetResponse() & ValueStep() into account!

				for (int32 i = 0; i < continuous.CountChannels(); i++) {
					slider->SetLimitsFor(i, int32(continuous.MinValue() * 1000),
						int32(continuous.MaxValue() * 1000));
				}

				return slider;
			}

			BSlider *slider = new Slider(parameter->Name(),
				parameter->Name(), int32(continuous.MinValue() * 1000),
				int32(continuous.MaxValue() * 1000), continuous);

			return slider;
		}

		case BParameter::B_TEXT_PARAMETER:
		{
			BTextParameter &text
				= static_cast<BTextParameter &>(*parameter);
			return new TextControl(text.Name(), text.Name(), text);
		}

		default:
			ERROR("BMediaTheme: Don't know parameter type: 0x%x\n",
				parameter->Type());
	}
	return NULL;
}