xref: /haiku/src/preferences/media/MediaWindow.cpp (revision 922d2034b2a2818465589969b7d682661553a9fd)
1 /*
2  * Copyright 2003-2012, Haiku, Inc.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Sikosis, Jérôme Duval
7  *		yourpalal, Alex Wilson
8  */
9 
10 
11 #include "MediaWindow.h"
12 
13 #include <stdio.h>
14 
15 #include <Alert.h>
16 #include <Application.h>
17 #include <Autolock.h>
18 #include <Button.h>
19 #include <CardLayout.h>
20 #include <Catalog.h>
21 #include <Debug.h>
22 #include <Deskbar.h>
23 #include <IconUtils.h>
24 #include <LayoutBuilder.h>
25 #include <Locale.h>
26 #include <MediaRoster.h>
27 #include <MediaTheme.h>
28 #include <Resources.h>
29 #include <Roster.h>
30 #include <Screen.h>
31 #include <ScrollView.h>
32 #include <SeparatorView.h>
33 #include <SpaceLayoutItem.h>
34 #include <StorageKit.h>
35 #include <String.h>
36 #include <TextView.h>
37 
38 #include "Media.h"
39 #include "MediaIcons.h"
40 #include "MidiSettingsView.h"
41 
42 #undef B_TRANSLATION_CONTEXT
43 #define B_TRANSLATION_CONTEXT "Media Window"
44 
45 
46 const uint32 ML_SELECTED_NODE = 'MlSN';
47 const uint32 ML_RESTART_THREAD_FINISHED = 'MlRF';
48 
49 
50 class NodeListItemUpdater : public MediaListItem::Visitor {
51 public:
52 	typedef void (NodeListItem::*UpdateMethod)(bool);
53 
54 	NodeListItemUpdater(NodeListItem* target, UpdateMethod action)
55 		:
56 		fComparator(target),
57 		fAction(action)
58 	{
59 	}
60 
61 
62 	virtual	void	Visit(AudioMixerListItem*){}
63 	virtual	void	Visit(DeviceListItem*){}
64 	virtual	void	Visit(MidiListItem*){}
65 	virtual void	Visit(NodeListItem* item)
66 	{
67 		item->Accept(fComparator);
68 		(item->*(fAction))(fComparator.result == 0);
69 	}
70 
71 private:
72 
73 			NodeListItem::Comparator		fComparator;
74 			UpdateMethod					fAction;
75 };
76 
77 
78 MediaWindow::SmartNode::SmartNode(const BMessenger& notifyHandler)
79 	:
80 	fNode(NULL),
81 	fMessenger(notifyHandler)
82 {
83 }
84 
85 
86 MediaWindow::SmartNode::~SmartNode()
87 {
88 	_FreeNode();
89 }
90 
91 
92 void
93 MediaWindow::SmartNode::SetTo(const dormant_node_info* info)
94 {
95 	_FreeNode();
96 	if (!info)
97 		return;
98 
99 	fNode = new media_node();
100 	BMediaRoster* roster = BMediaRoster::Roster();
101 
102 	status_t status = B_OK;
103 	media_node_id node_id;
104 	if (roster->GetInstancesFor(info->addon, info->flavor_id, &node_id) == B_OK)
105 		status = roster->GetNodeFor(node_id, fNode);
106 	else
107 		status = roster->InstantiateDormantNode(*info, fNode, B_FLAVOR_IS_GLOBAL);
108 
109 	if (status != B_OK) {
110 		fprintf(stderr, "SmartNode::SetTo error with node %" B_PRId32
111 			": %s\n", fNode->node, strerror(status));
112 	}
113 
114 	status = roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD);
115 	if (status != B_OK) {
116 		fprintf(stderr, "SmartNode::SetTo can't start watching for"
117 			" node %" B_PRId32 "\n", fNode->node);
118 	}
119 }
120 
121 
122 void
123 MediaWindow::SmartNode::SetTo(const media_node& node)
124 {
125 	_FreeNode();
126 	fNode = new media_node(node);
127 	BMediaRoster* roster = BMediaRoster::Roster();
128 	roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD);
129 }
130 
131 
132 bool
133 MediaWindow::SmartNode::IsSet()
134 {
135 	return fNode != NULL;
136 }
137 
138 
139 MediaWindow::SmartNode::operator media_node()
140 {
141 	if (fNode)
142 		return *fNode;
143 	media_node node;
144 	return node;
145 }
146 
147 
148 void
149 MediaWindow::SmartNode::_FreeNode()
150 {
151 	if (!IsSet())
152 		return;
153 
154 	BMediaRoster* roster = BMediaRoster::Roster();
155 	if (roster != NULL) {
156 		status_t status = roster->StopWatching(fMessenger,
157 			*fNode, B_MEDIA_WILDCARD);
158 		if (status != B_OK) {
159 			fprintf(stderr, "SmartNode::_FreeNode can't unwatch"
160 				" media services for node %" B_PRId32 "\n", fNode->node);
161 		}
162 
163 		roster->ReleaseNode(*fNode);
164 		if (status != B_OK) {
165 			fprintf(stderr, "SmartNode::_FreeNode can't release"
166 				" node %" B_PRId32 "\n", fNode->node);
167 		}
168 	}
169 	delete fNode;
170 	fNode = NULL;
171 }
172 
173 
174 // #pragma mark -
175 
176 
177 MediaWindow::MediaWindow(BRect frame)
178 	:
179 	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Media"), B_TITLED_WINDOW,
180 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
181 	fCurrentNode(BMessenger(this)),
182 	fParamWeb(NULL),
183 	fAudioInputs(5, true),
184 	fAudioOutputs(5, true),
185 	fVideoInputs(5, true),
186 	fVideoOutputs(5, true),
187 	fInitCheck(B_OK),
188 	fRestartThread(-1)
189 {
190 	_InitWindow();
191 
192 	BMediaRoster* roster = BMediaRoster::Roster();
193 	roster->StartWatching(BMessenger(this, this),
194 		B_MEDIA_SERVER_STARTED);
195 }
196 
197 
198 MediaWindow::~MediaWindow()
199 {
200 	_EmptyNodeLists();
201 	_ClearParamView();
202 
203 	char buffer[512];
204 	BRect rect = Frame();
205 	PRINT_OBJECT(rect);
206 	snprintf(buffer, 512, "# MediaPrefs Settings\n rect = %i,%i,%i,%i\n",
207 		int(rect.left), int(rect.top), int(rect.right), int(rect.bottom));
208 
209 	BPath path;
210 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
211 		path.Append(SETTINGS_FILE);
212 		BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
213 		if (file.InitCheck() == B_OK)
214 			file.Write(buffer, strlen(buffer));
215 	}
216 
217 	BMediaRoster* roster = BMediaRoster::CurrentRoster();
218 	roster->StopWatching(BMessenger(this, this),
219 		B_MEDIA_SERVER_STARTED);
220 }
221 
222 
223 status_t
224 MediaWindow::InitCheck()
225 {
226 	return fInitCheck;
227 }
228 
229 
230 void
231 MediaWindow::SelectNode(const dormant_node_info* node)
232 {
233 	fCurrentNode.SetTo(node);
234 	_MakeParamView();
235 	fTitleView->SetLabel(node->name);
236 }
237 
238 
239 void
240 MediaWindow::SelectAudioSettings(const char* title)
241 {
242 	fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fAudioView));
243 	fTitleView->SetLabel(title);
244 }
245 
246 
247 void
248 MediaWindow::SelectVideoSettings(const char* title)
249 {
250 	fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fVideoView));
251 	fTitleView->SetLabel(title);
252 }
253 
254 
255 void
256 MediaWindow::SelectAudioMixer(const char* title)
257 {
258 	media_node mixerNode;
259 	BMediaRoster* roster = BMediaRoster::Roster();
260 	roster->GetAudioMixer(&mixerNode);
261 	fCurrentNode.SetTo(mixerNode);
262 	_MakeParamView();
263 	fTitleView->SetLabel(title);
264 }
265 
266 
267 void
268 MediaWindow::SelectMidiSettings(const char* title)
269 {
270 	fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fMidiView));
271 	fTitleView->SetLabel(title);
272 }
273 
274 
275 void
276 MediaWindow::UpdateInputListItem(MediaListItem::media_type type,
277 	const dormant_node_info* node)
278 {
279 	NodeListItem compareTo(node, type);
280 	NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultInput);
281 	for (int32 i = 0; i < fListView->CountItems(); i++) {
282 		MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i));
283 		item->Accept(updater);
284 	}
285 	fListView->Invalidate();
286 }
287 
288 
289 void
290 MediaWindow::UpdateOutputListItem(MediaListItem::media_type type,
291 	const dormant_node_info* node)
292 {
293 	NodeListItem compareTo(node, type);
294 	NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultOutput);
295 	for (int32 i = 0; i < fListView->CountItems(); i++) {
296 		MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i));
297 		item->Accept(updater);
298 	}
299 	fListView->Invalidate();
300 }
301 
302 
303 bool
304 MediaWindow::QuitRequested()
305 {
306 	status_t exit = B_OK;
307 	if (fRestartThread > 0) {
308 		wait_for_thread(fRestartThread, &exit);
309 		if (exit != B_OK) {
310 			fprintf(stderr, "MediaWindow::QuitRequested wait_for_thread"
311 				" returned with an error: %s\n", strerror(exit));
312 		}
313 	}
314 	// Stop watching the MediaRoster
315 	fCurrentNode.SetTo(NULL);
316 	be_app->PostMessage(B_QUIT_REQUESTED);
317 	return true;
318 }
319 
320 
321 void
322 MediaWindow::MessageReceived(BMessage* message)
323 {
324 	switch (message->what) {
325 		case ML_RESTART_THREAD_FINISHED:
326 			fRestartThread = -1;
327 			_InitMedia(false);
328 			break;
329 
330 		case ML_RESTART_MEDIA_SERVER:
331 		{
332 			fRestartThread = spawn_thread(&MediaWindow::_RestartMediaServices,
333 				"restart_thread", B_NORMAL_PRIORITY, this);
334 			if (fRestartThread < 0)
335 				fprintf(stderr, "couldn't create restart thread\n");
336 			else
337 				resume_thread(fRestartThread);
338 			break;
339 		}
340 		case B_MEDIA_WEB_CHANGED:
341 		case ML_SELECTED_NODE:
342 		{
343 			PRINT_OBJECT(*message);
344 
345 			MediaListItem* item = static_cast<MediaListItem*>(
346 					fListView->ItemAt(fListView->CurrentSelection()));
347 			if (item == NULL)
348 				break;
349 
350 			fCurrentNode.SetTo(NULL);
351 			_ClearParamView();
352 			item->AlterWindow(this);
353 			break;
354 		}
355 		case B_MEDIA_SERVER_STARTED:
356 		{
357 			PRINT_OBJECT(*message);
358 			_InitMedia(false);
359 			break;
360 		}
361 		default:
362 			BWindow::MessageReceived(message);
363 			break;
364 	}
365 }
366 
367 
368 // #pragma mark - private
369 
370 
371 void
372 MediaWindow::_InitWindow()
373 {
374 	fListView = new BListView("media_list_view");
375 	fListView->SetSelectionMessage(new BMessage(ML_SELECTED_NODE));
376 	fListView->SetExplicitMinSize(BSize(140, B_SIZE_UNSET));
377 
378 	// Add ScrollView to Media Menu for pretty border
379 	BScrollView* scrollView = new BScrollView("listscroller",
380 		fListView, 0, false, false, B_FANCY_BORDER);
381 
382 	// Create the Views
383 	fTitleView = new BSeparatorView(B_HORIZONTAL, B_FANCY_BORDER);
384 	fTitleView->SetLabel(B_TRANSLATE("Audio settings"));
385 	fTitleView->SetFont(be_bold_font);
386 
387 	fContentLayout = new BCardLayout();
388 	new BView("content view", 0, fContentLayout);
389 	fContentLayout->Owner()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
390 	fContentLayout->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
391 
392 	fAudioView = new AudioSettingsView();
393 	fContentLayout->AddView(fAudioView);
394 
395 	fVideoView = new VideoSettingsView();
396 	fContentLayout->AddView(fVideoView);
397 
398 	fMidiView = new MidiSettingsView();
399 	fContentLayout->AddView(fMidiView);
400 
401 	// Layout all views
402 	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
403 		.SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING,
404 			B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING)
405 		.Add(scrollView, 0.0f)
406 		.AddGroup(B_VERTICAL)
407 			.SetInsets(0, 0, 0, 0)
408 			.Add(fTitleView)
409 			.Add(fContentLayout);
410 
411 	// Start the window
412 	fInitCheck = _InitMedia(true);
413 	if (fInitCheck != B_OK)
414 		PostMessage(B_QUIT_REQUESTED);
415 	else if (IsHidden())
416 		Show();
417 }
418 
419 
420 status_t
421 MediaWindow::_InitMedia(bool first)
422 {
423 	status_t err = B_OK;
424 	BMediaRoster* roster = BMediaRoster::Roster(&err);
425 
426 	if (first && err != B_OK) {
427 		BAlert* alert = new BAlert("start_media_server",
428 			B_TRANSLATE("Could not connect to the media server.\n"
429 				"Would you like to start it ?"),
430 			B_TRANSLATE("Quit"),
431 			B_TRANSLATE("Start media server"), NULL,
432 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
433 		alert->SetShortcut(0, B_ESCAPE);
434 		if (alert->Go() == 0)
435 			return B_ERROR;
436 
437 		Show();
438 
439 		launch_media_server();
440 	}
441 
442 	Lock();
443 
444 	bool isVideoSelected = true;
445 	if (!first && fListView->ItemAt(0) != NULL
446 		&& fListView->ItemAt(0)->IsSelected())
447 		isVideoSelected = false;
448 
449 	while (fListView->CountItems() > 0)
450 		delete fListView->RemoveItem((int32)0);
451 	_EmptyNodeLists();
452 
453 	// Grab Media Info
454 	_FindNodes();
455 
456 	// Add video nodes first. They might have an additional audio
457 	// output or input, but still should be listed as video node.
458 	_AddNodeItems(fVideoOutputs, MediaListItem::VIDEO_TYPE);
459 	_AddNodeItems(fVideoInputs, MediaListItem::VIDEO_TYPE);
460 	_AddNodeItems(fAudioOutputs, MediaListItem::AUDIO_TYPE);
461 	_AddNodeItems(fAudioInputs, MediaListItem::AUDIO_TYPE);
462 
463 	fAudioView->AddOutputNodes(fAudioOutputs);
464 	fAudioView->AddInputNodes(fAudioInputs);
465 	fVideoView->AddOutputNodes(fVideoOutputs);
466 	fVideoView->AddInputNodes(fVideoInputs);
467 
468 	// build our list view
469 	DeviceListItem* audio = new DeviceListItem(B_TRANSLATE("Audio settings"),
470 		MediaListItem::AUDIO_TYPE);
471 	fListView->AddItem(audio);
472 
473 	MidiListItem* midi = new MidiListItem(B_TRANSLATE("MIDI Settings"));
474 	fListView->AddItem(midi);
475 
476 	MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"),
477 		MediaListItem::VIDEO_TYPE);
478 	fListView->AddItem(video);
479 
480 	MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer"));
481 	fListView->AddItem(mixer);
482 
483 	fListView->SortItems(&MediaListItem::Compare);
484 	_UpdateListViewMinWidth();
485 
486 	// Set default nodes for our setting views
487 	media_node defaultNode;
488 	dormant_node_info nodeInfo;
489 	int32 outputID;
490 	BString outputName;
491 
492 	if (roster->GetAudioInput(&defaultNode) == B_OK) {
493 		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
494 		fAudioView->SetDefaultInput(&nodeInfo);
495 			// this causes our listview to be updated as well
496 	}
497 
498 	if (roster->GetAudioOutput(&defaultNode, &outputID, &outputName) == B_OK) {
499 		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
500 		fAudioView->SetDefaultOutput(&nodeInfo);
501 		fAudioView->SetDefaultChannel(outputID);
502 			// this causes our listview to be updated as well
503 	}
504 
505 	if (roster->GetVideoInput(&defaultNode) == B_OK) {
506 		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
507 		fVideoView->SetDefaultInput(&nodeInfo);
508 			// this causes our listview to be updated as well
509 	}
510 
511 	if (roster->GetVideoOutput(&defaultNode) == B_OK) {
512 		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
513 		fVideoView->SetDefaultOutput(&nodeInfo);
514 			// this causes our listview to be updated as well
515 	}
516 
517 	if (first)
518 		fListView->Select(fListView->IndexOf(mixer));
519 	else if (isVideoSelected)
520 		fListView->Select(fListView->IndexOf(video));
521 	else
522 		fListView->Select(fListView->IndexOf(audio));
523 
524 	Unlock();
525 
526 	return B_OK;
527 }
528 
529 
530 void
531 MediaWindow::_FindNodes()
532 {
533 	_FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs);
534 	_FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_INPUT, fAudioInputs);
535 	_FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs);
536 	_FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_INPUT, fAudioInputs);
537 	_FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs);
538 	_FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_INPUT, fVideoInputs);
539 	_FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs);
540 	_FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_INPUT, fVideoInputs);
541 }
542 
543 
544 void
545 MediaWindow::_FindNodes(media_type type, uint64 kind, NodeList& into)
546 {
547 	dormant_node_info nodeInfo[64];
548 	int32 nodeInfoCount = 64;
549 
550 	media_format format;
551 	media_format* nodeInputFormat = NULL;
552 	media_format* nodeOutputFormat = NULL;
553 	format.type = type;
554 
555 	// output nodes must be BBufferConsumers => they have an input format
556 	// input nodes must be BBufferProducers => they have an output format
557 	if ((kind & B_PHYSICAL_OUTPUT) != 0)
558 		nodeInputFormat = &format;
559 	else if ((kind & B_PHYSICAL_INPUT) != 0)
560 		nodeOutputFormat = &format;
561 	else
562 		return;
563 
564 	BMediaRoster* roster = BMediaRoster::Roster();
565 
566 	if (roster->GetDormantNodes(nodeInfo, &nodeInfoCount, nodeInputFormat,
567 			nodeOutputFormat, NULL, kind) != B_OK) {
568 		// TODO: better error reporting!
569 		fprintf(stderr, "error\n");
570 		return;
571 	}
572 
573 	for (int32 i = 0; i < nodeInfoCount; i++) {
574 		PRINT(("node : %s, media_addon %i, flavor_id %i\n",
575 			nodeInfo[i].name, (int)nodeInfo[i].addon,
576 			(int)nodeInfo[i].flavor_id));
577 
578 		dormant_node_info* info = new dormant_node_info();
579 		strncpy(info->name, nodeInfo[i].name, B_MEDIA_NAME_LENGTH);
580 		info->flavor_id = nodeInfo[i].flavor_id;
581 		info->addon = nodeInfo[i].addon;
582 		into.AddItem(info);
583 	}
584 }
585 
586 
587 void
588 MediaWindow::_AddNodeItems(NodeList& list, MediaListItem::media_type type)
589 {
590 	int32 count = list.CountItems();
591 	for (int32 i = 0; i < count; i++) {
592 		dormant_node_info* info = list.ItemAt(i);
593 		if (_FindNodeListItem(info) == NULL)
594 			fListView->AddItem(new NodeListItem(info, type));
595 	}
596 }
597 
598 
599 void
600 MediaWindow::_EmptyNodeLists()
601 {
602 	fAudioOutputs.MakeEmpty();
603 	fAudioInputs.MakeEmpty();
604 	fVideoOutputs.MakeEmpty();
605 	fVideoInputs.MakeEmpty();
606 }
607 
608 
609 NodeListItem*
610 MediaWindow::_FindNodeListItem(dormant_node_info* info)
611 {
612 	NodeListItem audioItem(info, MediaListItem::AUDIO_TYPE);
613 	NodeListItem videoItem(info, MediaListItem::VIDEO_TYPE);
614 
615 	NodeListItem::Comparator audioComparator(&audioItem);
616 	NodeListItem::Comparator videoComparator(&videoItem);
617 
618 	for (int32 i = 0; i < fListView->CountItems(); i++) {
619 		MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i));
620 		item->Accept(audioComparator);
621 		if (audioComparator.result == 0)
622 			return static_cast<NodeListItem*>(item);
623 
624 		item->Accept(videoComparator);
625 		if (videoComparator.result == 0)
626 			return static_cast<NodeListItem*>(item);
627 	}
628 	return NULL;
629 }
630 
631 
632 void
633 MediaWindow::_UpdateListViewMinWidth()
634 {
635 	float width = 0;
636 	for (int32 i = 0; i < fListView->CountItems(); i++) {
637 		BListItem* item = fListView->ItemAt(i);
638 		width = max_c(width, item->Width());
639 	}
640 	fListView->SetExplicitMinSize(BSize(width, B_SIZE_UNSET));
641 	fListView->InvalidateLayout();
642 }
643 
644 
645 status_t
646 MediaWindow::_RestartMediaServices(void* data)
647 {
648 	MediaWindow* window = (MediaWindow*)data;
649 
650 	shutdown_media_server();
651 	launch_media_server();
652 
653 	return window->PostMessage(ML_RESTART_THREAD_FINISHED);
654 }
655 
656 
657 void
658 MediaWindow::_ClearParamView()
659 {
660 	BLayoutItem* item = fContentLayout->VisibleItem();
661 	if (!item)
662 		return;
663 
664 	BView* view = item->View();
665 	if (view != fVideoView && view != fAudioView && view != fMidiView) {
666 		fContentLayout->RemoveItem(item);
667 		delete item;
668 		delete view;
669 		delete fParamWeb;
670 		fParamWeb = NULL;
671 	}
672 }
673 
674 
675 void
676 MediaWindow::_MakeParamView()
677 {
678 	if (!fCurrentNode.IsSet())
679 		return;
680 
681 	fParamWeb = NULL;
682 	BMediaRoster* roster = BMediaRoster::Roster();
683 	if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) {
684 		BRect hint(fContentLayout->Frame());
685 		BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint);
686 		if (paramView) {
687 			fContentLayout->AddView(paramView);
688 			fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1);
689 			return;
690 		}
691 	}
692 
693 	_MakeEmptyParamView();
694 }
695 
696 
697 void
698 MediaWindow::_MakeEmptyParamView()
699 {
700 	fParamWeb = NULL;
701 
702 	BStringView* stringView = new BStringView("noControls",
703 		B_TRANSLATE("This hardware has no controls."));
704 
705 	BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
706 	stringView->SetExplicitMaxSize(unlimited);
707 
708 	BAlignment centered(B_ALIGN_HORIZONTAL_CENTER,
709 		B_ALIGN_VERTICAL_CENTER);
710 	stringView->SetExplicitAlignment(centered);
711 	stringView->SetAlignment(B_ALIGN_CENTER);
712 
713 	fContentLayout->AddView(stringView);
714 	fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1);
715 
716 	rgb_color panel = stringView->LowColor();
717 	stringView->SetHighColor(tint_color(panel,
718 		B_DISABLED_LABEL_TINT));
719 }
720 
721