xref: /haiku/src/kits/media/DefaultMediaTheme.cpp (revision 4f00613311d0bd6b70fa82ce19931c41f071ea4e)
1 /*
2 ** Copyright 2003-2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3 ** Distributed under the terms of the OpenBeOS License.
4 */
5 
6 
7 #include "DefaultMediaTheme.h"
8 #include "debug.h"
9 
10 #include <ParameterWeb.h>
11 
12 #include <Slider.h>
13 #include <ScrollBar.h>
14 #include <StringView.h>
15 #include <Button.h>
16 #include <TextControl.h>
17 #include <OptionPopUp.h>
18 #include <ChannelSlider.h>
19 #include <Box.h>
20 #include <CheckBox.h>
21 #include <TabView.h>
22 #include <MenuField.h>
23 #include <MessageFilter.h>
24 #include <Window.h>
25 
26 
27 using namespace BPrivate;
28 
29 
30 namespace BPrivate {
31 
32 class DynamicScrollView : public BView {
33 	public:
34 		DynamicScrollView(const char *name, BView *target);
35 		virtual ~DynamicScrollView();
36 
37 		virtual void AttachedToWindow(void);
38 		virtual void FrameResized(float width, float height);
39 
40 		void SetContentBounds(BRect bounds);
41 		BRect ContentBounds() const { return fContentBounds; }
42 
43 	private:
44 		void UpdateBars();
45 
46 		BScrollBar	*fHorizontalScrollBar, *fVerticalScrollBar;
47 		BRect		fContentBounds;
48 		BView		*fTarget;
49 		bool		fIsDocumentScroller;
50 };
51 
52 class GroupView : public BView {
53 	public:
54 		GroupView(BRect frame, const char *name);
55 		virtual ~GroupView();
56 
57 		virtual void AttachedToWindow();
58 		virtual void AllAttached();
59 		virtual void GetPreferredSize(float *_width, float *_height);
60 
61 		void SetContentBounds(BRect bounds);
62 		BRect ContentBounds() const { return fContentBounds; }
63 
64 	private:
65 		BRect		fContentBounds;
66 };
67 
68 class TabView : public BTabView {
69 	public:
70 		TabView(BRect frame, const char *name, button_width width = B_WIDTH_AS_USUAL,
71 			uint32 resizingMode = B_FOLLOW_ALL, uint32 flags = B_FULL_UPDATE_ON_RESIZE
72 				| B_WILL_DRAW | B_NAVIGABLE_JUMP | B_FRAME_EVENTS | B_NAVIGABLE);
73 
74 		virtual void FrameResized(float width, float height);
75 		virtual void Select(int32 tab);
76 };
77 
78 class SeparatorView : public BView {
79 	public:
80 		SeparatorView(BRect frame);
81 		virtual ~SeparatorView();
82 
83 		virtual void Draw(BRect updateRect);
84 
85 	private:
86 		bool	fVertical;
87 };
88 
89 class TitleView : public BView {
90 	public:
91 		TitleView(BRect frame, const char *title);
92 		virtual ~TitleView();
93 
94 		virtual void Draw(BRect updateRect);
95 		virtual void GetPreferredSize(float *width, float *height);
96 
97 	private:
98 		const char *fTitle;
99 };
100 
101 class MessageFilter : public BMessageFilter {
102 	public:
103 		static MessageFilter *FilterFor(BView *view, BParameter &parameter);
104 
105 	protected:
106 		MessageFilter();
107 };
108 
109 class ContinuousMessageFilter : public MessageFilter {
110 	public:
111 		ContinuousMessageFilter(BControl *control, BContinuousParameter &parameter);
112 		virtual ~ContinuousMessageFilter();
113 
114 		virtual filter_result Filter(BMessage *message, BHandler **target);
115 
116 	private:
117 		BContinuousParameter	&fParameter;
118 };
119 
120 class DiscreteMessageFilter : public MessageFilter {
121 	public:
122 		DiscreteMessageFilter(BControl *control, BDiscreteParameter &parameter);
123 		virtual ~DiscreteMessageFilter();
124 
125 		virtual filter_result Filter(BMessage *message, BHandler **target);
126 
127 	private:
128 		BDiscreteParameter	&fParameter;
129 };
130 
131 }	// namespace BPrivate
132 
133 
134 const uint32 kMsgParameterChanged = '_mPC';
135 
136 
137 static bool
138 parameter_should_be_hidden(BParameter &parameter)
139 {
140 	// ToDo: note, this is probably completely stupid, but it's the only
141 	// way I could safely remove the null parameters that are not shown
142 	// by the R5 media theme
143 	if (parameter.Type() != BParameter::B_NULL_PARAMETER
144 		|| strcmp(parameter.Kind(), B_WEB_PHYSICAL_INPUT))
145 		return false;
146 
147 	for (int32 i = 0; i < parameter.CountOutputs(); i++) {
148 		if (!strcmp(parameter.OutputAt(0)->Kind(), B_INPUT_MUX))
149 			return true;
150 	}
151 
152 	return false;
153 }
154 
155 
156 //	#pragma mark -
157 
158 
159 DynamicScrollView::DynamicScrollView(const char *name, BView *target)
160 	: BView(target->Frame(), name, B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS),
161 	fHorizontalScrollBar(NULL),
162 	fVerticalScrollBar(NULL),
163 	fTarget(target),
164 	fIsDocumentScroller(false)
165 {
166 	fContentBounds.Set(-1, -1, -1, -1);
167 	SetViewColor(fTarget->ViewColor());
168 	target->MoveTo(B_ORIGIN);
169 	AddChild(target);
170 }
171 
172 
173 DynamicScrollView::~DynamicScrollView()
174 {
175 }
176 
177 
178 void
179 DynamicScrollView::AttachedToWindow(void)
180 {
181 	BRect frame = ConvertToScreen(Bounds());
182 	BRect windowFrame = Window()->Frame();
183 
184 	fIsDocumentScroller = Parent() == NULL
185 		&& Window() != NULL
186 		&& Window()->Look() == B_DOCUMENT_WINDOW_LOOK
187 		&& frame.right == windowFrame.right
188 		&& frame.bottom == windowFrame.bottom;
189 
190 	UpdateBars();
191 }
192 
193 
194 void
195 DynamicScrollView::FrameResized(float width, float height)
196 {
197 	UpdateBars();
198 }
199 
200 
201 void
202 DynamicScrollView::SetContentBounds(BRect bounds)
203 {
204 	fContentBounds = bounds;
205 	if (Window())
206 		UpdateBars();
207 }
208 
209 
210 void
211 DynamicScrollView::UpdateBars()
212 {
213 	// we need the size that the view wants to have, and the one
214 	// it could have (without the space for the scroll bars)
215 
216 	float width, height;
217 	if (fContentBounds == BRect(-1, -1, -1, -1))
218 		fTarget->GetPreferredSize(&width, &height);
219 	else {
220 		width = fContentBounds.Width();
221 		height = fContentBounds.Height();
222 	}
223 
224 	BRect bounds = Bounds();
225 
226 	// do we have to remove a scroll bar?
227 
228 	bool horizontal = width > Bounds().Width();
229 	bool vertical = height > Bounds().Height();
230 	bool horizontalChanged = false;
231 	bool verticalChanged = false;
232 
233 	if (!horizontal && fHorizontalScrollBar != NULL) {
234 		RemoveChild(fHorizontalScrollBar);
235 		delete fHorizontalScrollBar;
236 		fHorizontalScrollBar = NULL;
237 		fTarget->ResizeBy(0, B_H_SCROLL_BAR_HEIGHT);
238 		horizontalChanged = true;
239 	}
240 
241 	if (!vertical && fVerticalScrollBar != NULL) {
242 		RemoveChild(fVerticalScrollBar);
243 		delete fVerticalScrollBar;
244 		fVerticalScrollBar = NULL;
245 		fTarget->ResizeBy(B_V_SCROLL_BAR_WIDTH, 0);
246 		verticalChanged = true;
247 	}
248 
249 	// or do we have to add a scroll bar?
250 
251 	if (horizontal && fHorizontalScrollBar == NULL) {
252 		BRect rect = Frame();
253 		rect.top = rect.bottom + 1 - B_H_SCROLL_BAR_HEIGHT;
254 		if (vertical || fIsDocumentScroller)
255 			rect.right -= B_V_SCROLL_BAR_WIDTH;
256 
257 		fHorizontalScrollBar = new BScrollBar(rect, "horizontal", fTarget, 0, width, B_HORIZONTAL);
258 		fTarget->ResizeBy(0, -B_H_SCROLL_BAR_HEIGHT);
259 		AddChild(fHorizontalScrollBar);
260 		horizontalChanged = true;
261 	}
262 
263 	if (vertical && fVerticalScrollBar == NULL) {
264 		BRect rect = Frame();
265 		rect.left = rect.right + 1 - B_V_SCROLL_BAR_WIDTH;
266 		if (horizontal || fIsDocumentScroller)
267 			rect.bottom -= B_H_SCROLL_BAR_HEIGHT;
268 
269 		fVerticalScrollBar = new BScrollBar(rect, "vertical", fTarget, 0, height, B_VERTICAL);
270 		fTarget->ResizeBy(-B_V_SCROLL_BAR_WIDTH, 0);
271 		AddChild(fVerticalScrollBar);
272 		verticalChanged = true;
273 	}
274 
275 	// adapt the scroll bars, so that they don't overlap each other
276 	if (!fIsDocumentScroller) {
277 		if (horizontalChanged && !verticalChanged && vertical)
278 			fVerticalScrollBar->ResizeBy(0, (horizontal ? -1 : 1) * B_H_SCROLL_BAR_HEIGHT);
279 		if (verticalChanged && !horizontalChanged && horizontal)
280 			fHorizontalScrollBar->ResizeBy((vertical ? -1 : 1) * B_V_SCROLL_BAR_WIDTH, 0);
281 	}
282 
283 	// update the scroll bar range & proportions
284 
285 	bounds = Bounds();
286 	if (fHorizontalScrollBar != NULL)
287 		bounds.bottom -= B_H_SCROLL_BAR_HEIGHT;
288 	if (fVerticalScrollBar != NULL)
289 		bounds.right -= B_V_SCROLL_BAR_WIDTH;
290 
291 	if (fHorizontalScrollBar != NULL) {
292 		float delta = width - bounds.Width();
293 		if (delta < 0)
294 			delta = 0;
295 
296 		fHorizontalScrollBar->SetRange(0, delta);
297 		fHorizontalScrollBar->SetSteps(1, bounds.Width());
298 		fHorizontalScrollBar->SetProportion(bounds.Width() / width);
299 	}
300 	if (fVerticalScrollBar != NULL) {
301 		float delta = height - bounds.Height();
302 		if (delta < 0)
303 			delta = 0;
304 
305 		fVerticalScrollBar->SetRange(0, delta);
306 		fVerticalScrollBar->SetSteps(1, bounds.Height());
307 		fVerticalScrollBar->SetProportion(bounds.Height() / height);
308 	}
309 }
310 
311 
312 //	#pragma mark -
313 
314 
315 GroupView::GroupView(BRect frame, const char *name)
316 	: BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW)
317 {
318 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
319 }
320 
321 
322 GroupView::~GroupView()
323 {
324 }
325 
326 
327 void
328 GroupView::AttachedToWindow()
329 {
330 	for (int32 i = CountChildren(); i-- > 0;) {
331 		BControl *control = dynamic_cast<BControl *>(ChildAt(i));
332 		if (control == NULL)
333 			continue;
334 
335 		control->SetTarget(control);
336 	}
337 }
338 
339 
340 void
341 GroupView::AllAttached()
342 {
343 }
344 
345 
346 void
347 GroupView::GetPreferredSize(float *_width, float *_height)
348 {
349 	if (_width)
350 		*_width = fContentBounds.Width();
351 
352 	if (_height)
353 		*_height = fContentBounds.Height();
354 }
355 
356 
357 void
358 GroupView::SetContentBounds(BRect bounds)
359 {
360 	fContentBounds = bounds;
361 }
362 
363 
364 //	#pragma mark -
365 
366 
367 /** BTabView is really stupid - it doesn't even resize its content
368  *	view when it is resized itself.
369  *	This derived class fixes this issue, and also resizes all tab
370  *	content views to the size of the container view when they are
371  *	selected (does not take their resize flags into account, though).
372  */
373 
374 TabView::TabView(BRect frame, const char *name, button_width width,
375 	uint32 resizingMode, uint32 flags)
376 	: BTabView(frame, name, width, resizingMode, flags)
377 {
378 }
379 
380 
381 void
382 TabView::FrameResized(float width, float height)
383 {
384 	BRect rect = Bounds();
385 	rect.InsetBySelf(1, 1);
386 	rect.top += TabHeight();
387 
388 	ContainerView()->ResizeTo(rect.Width(), rect.Height());
389 }
390 
391 
392 void
393 TabView::Select(int32 tab)
394 {
395 	BTabView::Select(tab);
396 
397 	BView *view = ViewForTab(Selection());
398 	if (view != NULL) {
399 		BRect rect = ContainerView()->Bounds();
400 		view->ResizeTo(rect.Width(), rect.Height());
401 	}
402 }
403 
404 
405 //	#pragma mark -
406 
407 
408 SeparatorView::SeparatorView(BRect frame)
409 	: BView(frame, "-", B_FOLLOW_NONE, B_WILL_DRAW)
410 {
411 	fVertical = frame.Width() < frame.Height();
412 	SetViewColor(B_TRANSPARENT_COLOR);
413 }
414 
415 
416 SeparatorView::~SeparatorView()
417 {
418 }
419 
420 
421 void
422 SeparatorView::Draw(BRect updateRect)
423 {
424 	rgb_color color = ui_color(B_PANEL_BACKGROUND_COLOR);
425 	BRect rect = updateRect & Bounds();
426 
427 	SetHighColor(tint_color(color, B_DARKEN_1_TINT));
428 	if (fVertical)
429 		StrokeLine(BPoint(0, rect.top), BPoint(0, rect.bottom));
430 	else
431 		StrokeLine(BPoint(rect.left, 0), BPoint(rect.right, 0));
432 
433 	SetHighColor(tint_color(color, B_LIGHTEN_1_TINT));
434 	if (fVertical)
435 		StrokeLine(BPoint(1, rect.top), BPoint(1, rect.bottom));
436 	else
437 		StrokeLine(BPoint(rect.left, 1), BPoint(rect.right, 1));
438 }
439 
440 
441 //	#pragma mark -
442 
443 
444 TitleView::TitleView(BRect frame, const char *title)
445 	: BView(frame, title, B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW)
446 {
447 	fTitle = strdup(title);
448 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
449 	SetLowColor(ViewColor());
450 }
451 
452 
453 TitleView::~TitleView()
454 {
455 	free((char *)fTitle);
456 }
457 
458 
459 void
460 TitleView::Draw(BRect updateRect)
461 {
462 	BRect rect(Bounds());
463 
464 	SetDrawingMode(B_OP_COPY);
465 	SetHighColor(240, 240, 240);
466 	DrawString(fTitle, BPoint(rect.left + 1, rect.bottom - 9));
467 
468 	SetDrawingMode(B_OP_OVER);
469 	SetHighColor(80, 20, 20);
470 	DrawString(fTitle, BPoint(rect.left, rect.bottom - 8));
471 }
472 
473 
474 void
475 TitleView::GetPreferredSize(float *_width, float *_height)
476 {
477 	if (_width)
478 		*_width = StringWidth(fTitle) + 2;
479 
480 	if (_height) {
481 		font_height fontHeight;
482 		GetFontHeight(&fontHeight);
483 
484 		*_height = fontHeight.ascent + fontHeight.descent + fontHeight.leading + 8;
485 	}
486 }
487 
488 
489 //	#pragma mark -
490 
491 
492 MessageFilter::MessageFilter()
493 	: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE)
494 {
495 }
496 
497 
498 MessageFilter *
499 MessageFilter::FilterFor(BView *view, BParameter &parameter)
500 {
501 	BControl *control = dynamic_cast<BControl *>(view);
502 	if (control == NULL)
503 		return NULL;
504 
505 	switch (parameter.Type()) {
506 		case BParameter::B_CONTINUOUS_PARAMETER:
507 			return new ContinuousMessageFilter(control, static_cast<BContinuousParameter &>(parameter));
508 
509 		case BParameter::B_DISCRETE_PARAMETER:
510 			return new DiscreteMessageFilter(control, static_cast<BDiscreteParameter &>(parameter));
511 
512 		case BParameter::B_NULL_PARAMETER: /* fall through */
513 		default:
514 			return NULL;
515 	}
516 }
517 
518 
519 //	#pragma mark -
520 
521 
522 ContinuousMessageFilter::ContinuousMessageFilter(BControl *control, BContinuousParameter &parameter)
523 	: MessageFilter(),
524 	fParameter(parameter)
525 {
526 	// initialize view for us
527 	control->SetMessage(new BMessage(kMsgParameterChanged));
528 
529 	// set initial value
530 	// ToDo: response support!
531 
532 	float value[fParameter.CountChannels()];
533 	size_t size = sizeof(value);
534 	if (parameter.GetValue((void *)&value, &size, NULL) < B_OK) {
535 		ERROR("ContinuousMessageFilter: Could not get value for continuous parameter %p (name '%s', node %d)\n", &parameter, parameter.Name(), parameter.Web()->Node().node);
536 		return;
537 	}
538 
539 	if (BSlider *slider = dynamic_cast<BSlider *>(control)) {
540 		slider->SetValue((int32) (1000 * value[0]));
541 		slider->SetModificationMessage(new BMessage(kMsgParameterChanged));
542 	} else if (BChannelSlider *slider = dynamic_cast<BChannelSlider *>(control)) {
543 		for (int32 i = 0; i < fParameter.CountChannels(); i++)
544 			slider->SetValueFor(i, (int32) (1000 * value[i]));
545 
546 		slider->SetModificationMessage(new BMessage(kMsgParameterChanged));
547 	} else
548 		ERROR("ContinuousMessageFilter: unknown continuous parameter view\n");
549 }
550 
551 
552 ContinuousMessageFilter::~ContinuousMessageFilter()
553 {
554 }
555 
556 
557 filter_result
558 ContinuousMessageFilter::Filter(BMessage *message, BHandler **target)
559 {
560 	BControl *control;
561 
562 	if (message->what != kMsgParameterChanged
563 		|| (control = dynamic_cast<BControl *>(*target)) == NULL)
564 		return B_DISPATCH_MESSAGE;
565 
566 	// update view
567 	// ToDo: support for response!
568 
569 	float value[fParameter.CountChannels()];
570 
571 	if (BSlider *slider = dynamic_cast<BSlider *>(control)) {
572 		value[0] = (float)(slider->Value() / 1000.0);
573 	} else if (BChannelSlider *slider = dynamic_cast<BChannelSlider *>(control)) {
574 		for (int32 i = 0; i < fParameter.CountChannels(); i++)
575 			value[i] = (float)(slider->ValueFor(i) / 1000.0);
576 	}
577 
578 	TRACE("ContinuousMessageFilter::Filter: update view %s, %ld channels\n", control->Name(), fParameter.CountChannels());
579 
580 	if (fParameter.SetValue((void *)value, sizeof(value), system_time()) < B_OK) {
581 		ERROR("ContinuousMessageFilter::Filter: Could not set parameter value for %p\n", &fParameter);
582 		return B_DISPATCH_MESSAGE;
583 	}
584 
585 	return B_SKIP_MESSAGE;
586 }
587 
588 
589 //	#pragma mark -
590 
591 
592 DiscreteMessageFilter::DiscreteMessageFilter(BControl *control, BDiscreteParameter &parameter)
593 	: MessageFilter(),
594 	fParameter(parameter)
595 {
596 	// initialize view for us
597 	control->SetMessage(new BMessage(kMsgParameterChanged));
598 
599 	// set initial value
600 
601 	size_t size = sizeof(int32);
602 	int32 value;
603 	if (parameter.GetValue((void *)&value, &size, NULL) < B_OK) {
604 		ERROR("DiscreteMessageFilter: Could not get value for discrete parameter %p (name '%s', node %d)\n", &parameter, parameter.Name(), parameter.Web()->Node().node);
605 		return;
606 	}
607 
608 	if (BCheckBox *checkBox = dynamic_cast<BCheckBox *>(control)) {
609 		checkBox->SetValue(value);
610 	} else if (BOptionPopUp *popUp = dynamic_cast<BOptionPopUp *>(control)) {
611 		popUp->SelectOptionFor(value);
612 	} else
613 		ERROR("DiscreteMessageFilter: unknown discrete parameter view\n");
614 }
615 
616 
617 DiscreteMessageFilter::~DiscreteMessageFilter()
618 {
619 }
620 
621 
622 filter_result
623 DiscreteMessageFilter::Filter(BMessage *message, BHandler **target)
624 {
625 	BControl *control;
626 
627 	if (message->what != kMsgParameterChanged
628 		|| (control = dynamic_cast<BControl *>(*target)) == NULL)
629 		return B_DISPATCH_MESSAGE;
630 
631 	// update view
632 
633 	int32 value = 0;
634 
635 	if (BCheckBox *checkBox = dynamic_cast<BCheckBox *>(control)) {
636 		value = checkBox->Value();
637 	} else if (BOptionPopUp *popUp = dynamic_cast<BOptionPopUp *>(control)) {
638 		popUp->SelectedOption(NULL, &value);
639 	}
640 
641 	TRACE("DiscreteMessageFilter::Filter: update view %s, value = %ld\n", control->Name(), value);
642 
643 	if (fParameter.SetValue((void *)&value, sizeof(value), system_time()) < B_OK) {
644 		ERROR("DiscreteMessageFilter::Filter: Could not set parameter value for %p\n", &fParameter);
645 		return B_DISPATCH_MESSAGE;
646 	}
647 
648 	return B_SKIP_MESSAGE;
649 }
650 
651 
652 //	#pragma mark -
653 
654 
655 DefaultMediaTheme::DefaultMediaTheme()
656 	: BMediaTheme("BeOS Theme", "BeOS built-in theme version 0.1")
657 {
658 	CALLED();
659 }
660 
661 
662 BControl *
663 DefaultMediaTheme::MakeControlFor(BParameter *parameter)
664 {
665 	CALLED();
666 
667 	BRect rect(0, 0, 150, 100);
668 	return MakeViewFor(parameter, &rect);
669 }
670 
671 
672 BView *
673 DefaultMediaTheme::MakeViewFor(BParameterWeb *web, const BRect *hintRect)
674 {
675 	CALLED();
676 
677 	if (web == NULL)
678 		return NULL;
679 
680 	BRect rect;
681 	if (hintRect)
682 		rect = *hintRect;
683 	else
684 		rect.Set(0, 0, 80, 100);
685 
686 	// do we have more than one attached parameter group?
687 	// if so, use a tabbed view with a tab for each group
688 
689 	TabView *tabView = NULL;
690 
691 	if (web->CountGroups() > 1)
692 		tabView = new TabView(rect, "web");
693 
694 	rect.OffsetTo(B_ORIGIN);
695 
696 	for (int32 i = 0; i < web->CountGroups(); i++) {
697 		BParameterGroup *group = web->GroupAt(i);
698 		if (group == NULL)
699 			continue;
700 
701 		BView *groupView = MakeViewFor(*group, rect);
702 		if (groupView == NULL)
703 			continue;
704 
705 		if (GroupView *view = dynamic_cast<GroupView *>(groupView)) {
706 			// the top-level group views must not be larger than their hintRect,
707 			// but unlike their children, they should follow all sides when
708 			// their parent is resized
709 			view->ResizeTo(rect.Width() - 10, rect.Height() - 10);
710 			view->SetResizingMode(B_FOLLOW_ALL);
711 		}
712 
713 		if (tabView == NULL) {
714 			// if we don't need a container to put that view into,
715 			// we're done here (but the groupView may span over the
716 			// whole hintRect)
717 			groupView->MoveBy(-5, -5);
718 			groupView->ResizeBy(10, 10);
719 
720 			return new DynamicScrollView(groupView->Name(), groupView);
721 		}
722 
723 		tabView->AddTab(new DynamicScrollView(groupView->Name(), groupView));
724 	}
725 
726 	return tabView;
727 }
728 
729 
730 BView *
731 DefaultMediaTheme::MakeViewFor(BParameterGroup &group, const BRect &hintRect)
732 {
733 	CALLED();
734 
735 	if (group.Flags() & B_HIDDEN_PARAMETER)
736 		return NULL;
737 
738 	BRect rect(hintRect);
739 	GroupView *view = new GroupView(rect, group.Name());
740 
741 	// Create the parameter views - but don't add them yet
742 
743 	rect.OffsetTo(B_ORIGIN);
744 	rect.InsetBySelf(5, 5);
745 
746 	BList views;
747 	for (int32 i = 0; i < group.CountParameters(); i++) {
748 		BParameter *parameter = group.ParameterAt(i);
749 		if (parameter == NULL)
750 			continue;
751 
752 		BView *parameterView = MakeSelfHostingViewFor(*parameter, rect);
753 		if (parameterView == NULL)
754 			continue;
755 
756 		parameterView->SetViewColor(view->ViewColor());
757 			// ToDo: dunno why this is needed, but the controls
758 			// sometimes (!) have a white background without it
759 
760 		views.AddItem(parameterView);
761 	}
762 
763 	// Identify a title view, and add it at the top if present
764 
765 	TitleView *titleView = dynamic_cast<TitleView *>((BView *)views.ItemAt(0));
766 	if (titleView != NULL) {
767 		view->AddChild(titleView);
768 		rect.OffsetBy(0, titleView->Bounds().Height());
769 	}
770 
771 	// Add the sub-group views
772 
773 	rect.right = rect.left + 20;
774 	rect.bottom = rect.top + 20;
775 	float lastHeight = 0;
776 
777 	for (int32 i = 0; i < group.CountGroups(); i++) {
778 		BParameterGroup *subGroup = group.GroupAt(i);
779 		if (subGroup == NULL)
780 			continue;
781 
782 		BView *groupView = MakeViewFor(*subGroup, rect);
783 		if (groupView == NULL)
784 			continue;
785 
786 		if (i > 0) {
787 			// add separator view
788 			BRect separatorRect(groupView->Frame());
789 			separatorRect.left -= 3;
790 			separatorRect.right = separatorRect.left + 1;
791 			if (lastHeight > separatorRect.Height())
792 				separatorRect.bottom = separatorRect.top + lastHeight;
793 
794 			view->AddChild(new SeparatorView(separatorRect));
795 		}
796 
797 		view->AddChild(groupView);
798 
799 		rect.OffsetBy(groupView->Bounds().Width() + 5, 0);
800 
801 		lastHeight = groupView->Bounds().Height();
802 		if (lastHeight > rect.Height())
803 			rect.bottom = rect.top + lastHeight - 1;
804 	}
805 
806 	view->ResizeTo(rect.left + 10, rect.bottom + 5);
807 	view->SetContentBounds(view->Bounds());
808 
809 	if (group.CountParameters() == 0)
810 		return view;
811 
812 	// add the parameter views part of the group
813 
814 	if (group.CountGroups() > 0) {
815 		rect.top = rect.bottom + 10;
816 		rect.bottom = rect.top + 20;
817 	}
818 
819 	bool center = false;
820 
821 	for (int32 i = 0; i < views.CountItems(); i++) {
822 		BView *parameterView = static_cast<BView *>(views.ItemAt(i));
823 
824 		if (parameterView->Bounds().Width() + 5 > rect.Width())
825 			rect.right = parameterView->Bounds().Width() + rect.left + 5;
826 
827 		// we don't need to add the title view again
828 		if (parameterView == titleView)
829 			continue;
830 
831 		// if there is a BChannelSlider (ToDo: or any vertical slider?)
832 		// the views will be centered
833 		if (dynamic_cast<BChannelSlider *>(parameterView) != NULL)
834 			center = true;
835 
836 		parameterView->MoveTo(parameterView->Frame().left, rect.top);
837 		view->AddChild(parameterView);
838 
839 		rect.OffsetBy(0, parameterView->Bounds().Height() + 5);
840 	}
841 
842 	if (views.CountItems() > (titleView != NULL ? 1 : 0))
843 		view->ResizeTo(rect.right + 5, rect.top + 5);
844 
845 	// center the parameter views if needed, and tweak some views
846 
847 	float width = view->Bounds().Width();
848 
849 	for (int32 i = 0; i < views.CountItems(); i++) {
850 		BView *subView = static_cast<BView *>(views.ItemAt(i));
851 		BRect frame = subView->Frame();
852 
853 		if (center)
854 			subView->MoveTo((width - frame.Width()) / 2, frame.top);
855 		else {
856 			// tweak the PopUp views to look better
857 			if (dynamic_cast<BOptionPopUp *>(subView) != NULL)
858 				subView->ResizeTo(width, frame.Height());
859 		}
860 	}
861 
862 	view->SetContentBounds(view->Bounds());
863 	return view;
864 }
865 
866 
867 /** This creates a view that handles all incoming messages itself - that's
868  *	what is meant with self-hosting.
869  */
870 
871 BView *
872 DefaultMediaTheme::MakeSelfHostingViewFor(BParameter &parameter, const BRect &hintRect)
873 {
874 	if (parameter.Flags() & B_HIDDEN_PARAMETER
875 		|| parameter_should_be_hidden(parameter))
876 		return NULL;
877 
878 	BView *view = MakeViewFor(&parameter, &hintRect);
879 	if (view == NULL) {
880 		// The MakeViewFor() method above returns a BControl - which we
881 		// don't need for a null parameter; that's why it returns NULL.
882 		// But we want to see something anyway, so we add a string view
883 		// here.
884 		if (parameter.Type() == BParameter::B_NULL_PARAMETER) {
885 			if (parameter.Group()->ParameterAt(0) == &parameter) {
886 				// this is the first parameter in this group, so
887 				// let's use a nice title view
888 
889 				TitleView *titleView = new TitleView(hintRect, parameter.Name());
890 				titleView->ResizeToPreferred();
891 
892 				return titleView;
893 			}
894 			BStringView *stringView = new BStringView(hintRect, parameter.Name(), parameter.Name());
895 			stringView->SetAlignment(B_ALIGN_CENTER);
896 			stringView->ResizeToPreferred();
897 
898 			return stringView;
899 		}
900 
901 		return NULL;
902 	}
903 
904 	MessageFilter *filter = MessageFilter::FilterFor(view, parameter);
905 	if (filter != NULL)
906 		view->AddFilter(filter);
907 
908 	return view;
909 }
910 
911 
912 BControl *
913 DefaultMediaTheme::MakeViewFor(BParameter *parameter, const BRect *hintRect)
914 {
915 	BRect rect;
916 	if (hintRect)
917 		rect = *hintRect;
918 	else
919 		rect.Set(0, 0, 50, 100);
920 
921 	switch (parameter->Type()) {
922 		case BParameter::B_NULL_PARAMETER:
923 			// there is no default view for a null parameter
924 			return NULL;
925 
926 		case BParameter::B_DISCRETE_PARAMETER:
927 		{
928 			BDiscreteParameter &discrete = static_cast<BDiscreteParameter &>(*parameter);
929 
930 			if (!strcmp(discrete.Kind(), B_ENABLE)
931 				|| !strcmp(discrete.Kind(), B_MUTE)
932 				|| discrete.CountItems() == 0) {
933 				// create a checkbox item
934 
935 				BCheckBox *checkBox = new BCheckBox(rect, discrete.Name(), discrete.Name(), NULL);
936 				checkBox->ResizeToPreferred();
937 
938 				return checkBox;
939 			} else {
940 				// create a pop up menu field
941 
942 				// ToDo: replace BOptionPopUp (or fix it in OpenBeOS...)
943 				// this is a workaround for a bug in BOptionPopUp - you need to
944 				// know the actual width before creating the object - very nice...
945 
946 				BFont font;
947 				float width = 0;
948 				for (int32 i = 0; i < discrete.CountItems(); i++) {
949 					float labelWidth = font.StringWidth(discrete.ItemNameAt(i));
950 					if (labelWidth > width)
951 						width = labelWidth;
952 				}
953 				width += font.StringWidth(discrete.Name()) + 55;
954 				rect.right = rect.left + width;
955 
956 				BOptionPopUp *popUp = new BOptionPopUp(rect, discrete.Name(), discrete.Name(), NULL);
957 
958 				for (int32 i = 0; i < discrete.CountItems(); i++) {
959 					popUp->AddOption(discrete.ItemNameAt(i), discrete.ItemValueAt(i));
960 				}
961 
962 				popUp->ResizeToPreferred();
963 
964 				return popUp;
965 			}
966 		}
967 
968 		case BParameter::B_CONTINUOUS_PARAMETER:
969 		{
970 			BContinuousParameter &continuous = static_cast<BContinuousParameter &>(*parameter);
971 
972 			if (!strcmp(continuous.Kind(), B_MASTER_GAIN)
973 				|| !strcmp(continuous.Kind(), B_GAIN))
974 			{
975 				BChannelSlider *slider = new BChannelSlider(rect, continuous.Name(),
976 					continuous.Name(), NULL, B_VERTICAL, continuous.CountChannels());
977 
978 				char minLabel[64], maxLabel[64];
979 
980 				const char *unit = continuous.Unit();
981 				if (unit[0]) {
982 					// if we have a unit, print it next to the limit values
983 					sprintf(minLabel, "%g %s", continuous.MinValue(), continuous.Unit());
984 					sprintf(maxLabel, "%g %s", continuous.MaxValue(), continuous.Unit());
985 				} else {
986 					sprintf(minLabel, "%g", continuous.MinValue());
987 					sprintf(maxLabel, "%g", continuous.MaxValue());
988 				}
989 				slider->SetLimitLabels(minLabel, maxLabel);
990 
991 				float width, height;
992 				slider->GetPreferredSize(&width, &height);
993 				slider->ResizeTo(width, 190);
994 
995 				// ToDo: take BContinuousParameter::GetResponse() & ValueStep() into account!
996 
997 				for (int32 i = 0; i < continuous.CountChannels(); i++)
998 					slider->SetLimitsFor(i, continuous.MinValue() * 1000, continuous.MaxValue() * 1000);
999 
1000 				return slider;
1001 			}
1002 
1003 			BSlider *slider = new BSlider(rect, parameter->Name(), parameter->Name(),
1004 				NULL, 0, 100);
1005 
1006 			float width, height;
1007 			slider->GetPreferredSize(&width, &height);
1008 			slider->ResizeTo(100, height);
1009 
1010 			return slider;
1011 		}
1012 
1013 		default:
1014 			ERROR("BMediaTheme: Don't know parameter type: 0x%lx\n", parameter->Type());
1015 	}
1016 	return NULL;
1017 }
1018 
1019