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