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