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