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 <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 "MediaIcons.h" 39 40 41 #undef B_TRANSLATION_CONTEXT 42 #define B_TRANSLATION_CONTEXT "Media Window" 43 44 45 const uint32 ML_SELECTED_NODE = 'MlSN'; 46 const uint32 ML_INIT_MEDIA = 'MlIM'; 47 48 49 class NodeListItemUpdater : public MediaListItem::Visitor { 50 public: 51 typedef void (NodeListItem::*UpdateMethod)(bool); 52 53 NodeListItemUpdater(NodeListItem* target, UpdateMethod action) 54 : 55 fComparator(target), 56 fAction(action) 57 { 58 } 59 60 61 virtual void Visit(AudioMixerListItem*){} 62 virtual void Visit(DeviceListItem*){} 63 64 virtual void Visit(NodeListItem* item) 65 { 66 item->Accept(fComparator); 67 (item->*(fAction))(fComparator.result == 0); 68 } 69 70 private: 71 72 NodeListItem::Comparator fComparator; 73 UpdateMethod fAction; 74 }; 75 76 77 MediaWindow::SmartNode::SmartNode(const BMessenger& notifyHandler) 78 : 79 fNode(NULL), 80 fMessenger(notifyHandler) 81 { 82 } 83 84 85 MediaWindow::SmartNode::~SmartNode() 86 { 87 _FreeNode(); 88 } 89 90 91 void 92 MediaWindow::SmartNode::SetTo(const dormant_node_info* info) 93 { 94 _FreeNode(); 95 if (!info) 96 return; 97 98 fNode = new media_node(); 99 BMediaRoster* roster = BMediaRoster::Roster(); 100 101 // TODO: error codes 102 media_node_id node_id; 103 if (roster->GetInstancesFor(info->addon, info->flavor_id, &node_id) == B_OK) 104 roster->GetNodeFor(node_id, fNode); 105 else 106 roster->InstantiateDormantNode(*info, fNode, B_FLAVOR_IS_GLOBAL); 107 roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD); 108 } 109 110 111 void 112 MediaWindow::SmartNode::SetTo(const media_node& node) 113 { 114 _FreeNode(); 115 fNode = new media_node(node); 116 BMediaRoster* roster = BMediaRoster::Roster(); 117 roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD); 118 } 119 120 121 bool 122 MediaWindow::SmartNode::IsSet() 123 { 124 return fNode != NULL; 125 } 126 127 128 MediaWindow::SmartNode::operator media_node() 129 { 130 if (fNode) 131 return *fNode; 132 media_node node; 133 return node; 134 } 135 136 137 void 138 MediaWindow::SmartNode::_FreeNode() 139 { 140 if (!IsSet()) 141 return; 142 // TODO: check error codes 143 BMediaRoster* roster = BMediaRoster::Roster(); 144 roster->StopWatching(fMessenger, *fNode, B_MEDIA_WILDCARD); 145 roster->ReleaseNode(*fNode); 146 delete fNode; 147 fNode = NULL; 148 } 149 150 151 // #pragma mark - 152 153 154 MediaWindow::MediaWindow(BRect frame) 155 : 156 BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Media"), B_TITLED_WINDOW, 157 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 158 fCurrentNode(BMessenger(this)), 159 fParamWeb(NULL), 160 fAudioInputs(5, true), 161 fAudioOutputs(5, true), 162 fVideoInputs(5, true), 163 fVideoOutputs(5, true), 164 fAlert(NULL), 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::UpdateInputListItem(MediaListItem::media_type type, 238 const dormant_node_info* node) 239 { 240 NodeListItem compareTo(node, type); 241 NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultInput); 242 for (int32 i = 0; i < fListView->CountItems(); i++) { 243 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 244 item->Accept(updater); 245 } 246 fListView->Invalidate(); 247 } 248 249 250 void 251 MediaWindow::UpdateOutputListItem(MediaListItem::media_type type, 252 const dormant_node_info* node) 253 { 254 NodeListItem compareTo(node, type); 255 NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultOutput); 256 for (int32 i = 0; i < fListView->CountItems(); i++) { 257 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 258 item->Accept(updater); 259 } 260 fListView->Invalidate(); 261 } 262 263 264 bool 265 MediaWindow::QuitRequested() 266 { 267 // stop watching the MediaRoster 268 fCurrentNode.SetTo(NULL); 269 be_app->PostMessage(B_QUIT_REQUESTED); 270 return true; 271 } 272 273 274 void 275 MediaWindow::MessageReceived(BMessage* message) 276 { 277 switch (message->what) { 278 case ML_INIT_MEDIA: 279 _InitMedia(false); 280 break; 281 case ML_RESTART_MEDIA_SERVER: 282 { 283 thread_id thread = spawn_thread(&MediaWindow::_RestartMediaServices, 284 "restart_thread", B_NORMAL_PRIORITY, this); 285 if (thread < 0) 286 fprintf(stderr, "couldn't create restart thread\n"); 287 else 288 resume_thread(thread); 289 break; 290 } 291 case B_MEDIA_WEB_CHANGED: 292 case ML_SELECTED_NODE: 293 { 294 PRINT_OBJECT(*message); 295 296 MediaListItem* item = static_cast<MediaListItem*>( 297 fListView->ItemAt(fListView->CurrentSelection())); 298 if (item == NULL) 299 break; 300 301 fCurrentNode.SetTo(NULL); 302 _ClearParamView(); 303 item->AlterWindow(this); 304 break; 305 } 306 case B_SOME_APP_LAUNCHED: 307 { 308 PRINT_OBJECT(*message); 309 if (fAlert == NULL) 310 break; 311 312 BString mimeSig; 313 if (message->FindString("be:signature", &mimeSig) == B_OK 314 && (mimeSig == "application/x-vnd.Be.addon-host" 315 || mimeSig == "application/x-vnd.Be.media-server")) { 316 fAlert->Lock(); 317 fAlert->TextView()->SetText( 318 B_TRANSLATE("Starting media server" B_UTF8_ELLIPSIS)); 319 fAlert->Unlock(); 320 } 321 break; 322 } 323 case B_SOME_APP_QUIT: 324 { 325 PRINT_OBJECT(*message); 326 BString mimeSig; 327 if (message->FindString("be:signature", &mimeSig) == B_OK) { 328 if (mimeSig == "application/x-vnd.Be.addon-host" 329 || mimeSig == "application/x-vnd.Be.media-server") { 330 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 331 if (roster != NULL && roster->Lock()) 332 roster->Quit(); 333 } 334 } 335 break; 336 } 337 default: 338 BWindow::MessageReceived(message); 339 break; 340 } 341 } 342 343 344 // #pragma mark - private 345 346 347 void 348 MediaWindow::_InitWindow() 349 { 350 fListView = new BListView("media_list_view"); 351 fListView->SetSelectionMessage(new BMessage(ML_SELECTED_NODE)); 352 fListView->SetExplicitMinSize(BSize(140, B_SIZE_UNSET)); 353 354 // Add ScrollView to Media Menu for pretty border 355 BScrollView* scrollView = new BScrollView("listscroller", 356 fListView, 0, false, false, B_FANCY_BORDER); 357 358 // Create the Views 359 fTitleView = new BSeparatorView(B_HORIZONTAL, B_FANCY_BORDER); 360 fTitleView->SetLabel(B_TRANSLATE("Audio settings")); 361 fTitleView->SetFont(be_bold_font); 362 363 fContentLayout = new BCardLayout(); 364 new BView("content view", 0, fContentLayout); 365 fContentLayout->Owner()->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 366 fContentLayout->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 367 368 fAudioView = new AudioSettingsView(); 369 fContentLayout->AddView(fAudioView); 370 371 fVideoView = new VideoSettingsView(); 372 fContentLayout->AddView(fVideoView); 373 374 // Layout all views 375 BLayoutBuilder::Group<>(this, B_HORIZONTAL) 376 .SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING, 377 B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING) 378 .Add(scrollView, 0.0f) 379 .AddGroup(B_VERTICAL) 380 .SetInsets(0, 0, 0, 0) 381 .Add(fTitleView) 382 .Add(fContentLayout); 383 384 // Start the window 385 fInitCheck = _InitMedia(true); 386 if (fInitCheck != B_OK) 387 PostMessage(B_QUIT_REQUESTED); 388 else if (IsHidden()) 389 Show(); 390 } 391 392 393 status_t 394 MediaWindow::_InitMedia(bool first) 395 { 396 status_t err = B_OK; 397 BMediaRoster* roster = BMediaRoster::Roster(&err); 398 399 if (first && err != B_OK) { 400 BAlert* alert = new BAlert("start_media_server", 401 B_TRANSLATE("Could not connect to the media server.\n" 402 "Would you like to start it ?"), 403 B_TRANSLATE("Quit"), 404 B_TRANSLATE("Start media server"), NULL, 405 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 406 alert->SetShortcut(0, B_ESCAPE); 407 if (alert->Go() == 0) 408 return B_ERROR; 409 410 fAlert = new MediaAlert(BRect(0, 0, 300, 60), "restart_alert", 411 B_TRANSLATE("Restarting media services\nStarting media server" 412 B_UTF8_ELLIPSIS "\n")); 413 fAlert->Show(); 414 415 Show(); 416 417 launch_media_server(); 418 } 419 420 Lock(); 421 422 bool isVideoSelected = true; 423 if (!first && fListView->ItemAt(0) != NULL 424 && fListView->ItemAt(0)->IsSelected()) 425 isVideoSelected = false; 426 427 if ((!first || (first && err) ) && fAlert) { 428 BAutolock locker(fAlert); 429 if (locker.IsLocked()) 430 fAlert->TextView()->SetText( 431 B_TRANSLATE("Ready for use" B_UTF8_ELLIPSIS)); 432 } 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 MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"), 459 MediaListItem::VIDEO_TYPE); 460 fListView->AddItem(video); 461 462 MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer")); 463 fListView->AddItem(mixer); 464 465 fListView->SortItems(&MediaListItem::Compare); 466 _UpdateListViewMinWidth(); 467 468 // Set default nodes for our setting views 469 media_node defaultNode; 470 dormant_node_info nodeInfo; 471 int32 outputID; 472 BString outputName; 473 474 if (roster->GetAudioInput(&defaultNode) == B_OK) { 475 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 476 fAudioView->SetDefaultInput(&nodeInfo); 477 // this causes our listview to be updated as well 478 } 479 480 if (roster->GetAudioOutput(&defaultNode, &outputID, &outputName) == B_OK) { 481 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 482 fAudioView->SetDefaultOutput(&nodeInfo); 483 fAudioView->SetDefaultChannel(outputID); 484 // this causes our listview to be updated as well 485 } 486 487 if (roster->GetVideoInput(&defaultNode) == B_OK) { 488 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 489 fVideoView->SetDefaultInput(&nodeInfo); 490 // this causes our listview to be updated as well 491 } 492 493 if (roster->GetVideoOutput(&defaultNode) == B_OK) { 494 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 495 fVideoView->SetDefaultOutput(&nodeInfo); 496 // this causes our listview to be updated as well 497 } 498 499 if (first) 500 fListView->Select(fListView->IndexOf(mixer)); 501 else if (isVideoSelected) 502 fListView->Select(fListView->IndexOf(video)); 503 else 504 fListView->Select(fListView->IndexOf(audio)); 505 506 if (fAlert != NULL) { 507 snooze(800000); 508 fAlert->PostMessage(B_QUIT_REQUESTED); 509 } 510 fAlert = NULL; 511 512 Unlock(); 513 514 return B_OK; 515 } 516 517 518 void 519 MediaWindow::_FindNodes() 520 { 521 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 522 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 523 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 524 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 525 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 526 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 527 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 528 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 529 } 530 531 532 void 533 MediaWindow::_FindNodes(media_type type, uint64 kind, NodeList& into) 534 { 535 dormant_node_info nodeInfo[64]; 536 int32 nodeInfoCount = 64; 537 538 media_format format; 539 media_format* nodeInputFormat = NULL; 540 media_format* nodeOutputFormat = NULL; 541 format.type = type; 542 543 // output nodes must be BBufferConsumers => they have an input format 544 // input nodes must be BBufferProducers => they have an output format 545 if ((kind & B_PHYSICAL_OUTPUT) != 0) 546 nodeInputFormat = &format; 547 else if ((kind & B_PHYSICAL_INPUT) != 0) 548 nodeOutputFormat = &format; 549 else 550 return; 551 552 BMediaRoster* roster = BMediaRoster::Roster(); 553 554 if (roster->GetDormantNodes(nodeInfo, &nodeInfoCount, nodeInputFormat, 555 nodeOutputFormat, NULL, kind) != B_OK) { 556 // TODO: better error reporting! 557 fprintf(stderr, "error\n"); 558 return; 559 } 560 561 for (int32 i = 0; i < nodeInfoCount; i++) { 562 PRINT(("node : %s, media_addon %i, flavor_id %i\n", 563 nodeInfo[i].name, (int)nodeInfo[i].addon, 564 (int)nodeInfo[i].flavor_id)); 565 566 dormant_node_info* info = new dormant_node_info(); 567 strncpy(info->name, nodeInfo[i].name, B_MEDIA_NAME_LENGTH); 568 info->flavor_id = nodeInfo[i].flavor_id; 569 info->addon = nodeInfo[i].addon; 570 into.AddItem(info); 571 } 572 } 573 574 575 void 576 MediaWindow::_AddNodeItems(NodeList& list, MediaListItem::media_type type) 577 { 578 int32 count = list.CountItems(); 579 for (int32 i = 0; i < count; i++) { 580 dormant_node_info* info = list.ItemAt(i); 581 if (_FindNodeListItem(info) == NULL) 582 fListView->AddItem(new NodeListItem(info, type)); 583 } 584 } 585 586 587 void 588 MediaWindow::_EmptyNodeLists() 589 { 590 fAudioOutputs.MakeEmpty(); 591 fAudioInputs.MakeEmpty(); 592 fVideoOutputs.MakeEmpty(); 593 fVideoInputs.MakeEmpty(); 594 } 595 596 597 NodeListItem* 598 MediaWindow::_FindNodeListItem(dormant_node_info* info) 599 { 600 NodeListItem audioItem(info, MediaListItem::AUDIO_TYPE); 601 NodeListItem videoItem(info, MediaListItem::VIDEO_TYPE); 602 603 NodeListItem::Comparator audioComparator(&audioItem); 604 NodeListItem::Comparator videoComparator(&videoItem); 605 606 for (int32 i = 0; i < fListView->CountItems(); i++) { 607 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 608 item->Accept(audioComparator); 609 if (audioComparator.result == 0) 610 return static_cast<NodeListItem*>(item); 611 612 item->Accept(videoComparator); 613 if (videoComparator.result == 0) 614 return static_cast<NodeListItem*>(item); 615 } 616 return NULL; 617 } 618 619 620 void 621 MediaWindow::_UpdateListViewMinWidth() 622 { 623 float width = 0; 624 for (int32 i = 0; i < fListView->CountItems(); i++) { 625 BListItem* item = fListView->ItemAt(i); 626 width = max_c(width, item->Width()); 627 } 628 fListView->SetExplicitMinSize(BSize(width, B_SIZE_UNSET)); 629 fListView->InvalidateLayout(); 630 } 631 632 633 status_t 634 MediaWindow::_RestartMediaServices(void* data) 635 { 636 MediaWindow* window = (MediaWindow*)data; 637 window->fAlert = new MediaAlert(BRect(0, 0, 300, 60), 638 "restart_alert", B_TRANSLATE( 639 "Restarting media services\nShutting down media server\n")); 640 641 window->fAlert->Show(); 642 643 shutdown_media_server(B_INFINITE_TIMEOUT, MediaWindow::_UpdateProgress, 644 window->fAlert); 645 646 { 647 BAutolock locker(window->fAlert); 648 if (locker.IsLocked()) 649 window->fAlert->TextView()->SetText( 650 B_TRANSLATE("Starting media server" B_UTF8_ELLIPSIS)); 651 } 652 launch_media_server(); 653 654 return window->PostMessage(ML_INIT_MEDIA); 655 } 656 657 658 bool 659 MediaWindow::_UpdateProgress(int stage, const char* message, void* cookie) 660 { 661 MediaAlert* alert = static_cast<MediaAlert*>(cookie); 662 PRINT(("stage : %i\n", stage)); 663 const char* string = "Unknown stage"; 664 switch (stage) { 665 case 10: 666 string = B_TRANSLATE("Stopping media server" B_UTF8_ELLIPSIS); 667 break; 668 case 20: 669 string = B_TRANSLATE("Telling media_addon_server to quit."); 670 break; 671 case 40: 672 string = B_TRANSLATE("Waiting for media_server to quit."); 673 break; 674 case 70: 675 string = B_TRANSLATE("Cleaning up."); 676 break; 677 case 100: 678 string = B_TRANSLATE("Done shutting down."); 679 break; 680 } 681 682 BAutolock locker(alert); 683 if (locker.IsLocked()) 684 alert->TextView()->SetText(string); 685 return true; 686 } 687 688 689 void 690 MediaWindow::_ClearParamView() 691 { 692 BLayoutItem* item = fContentLayout->VisibleItem(); 693 if (!item) 694 return; 695 696 BView* view = item->View(); 697 if (view != fVideoView && view != fAudioView) { 698 fContentLayout->RemoveItem(item); 699 delete item; 700 delete view; 701 delete fParamWeb; 702 fParamWeb = NULL; 703 } 704 } 705 706 707 void 708 MediaWindow::_MakeParamView() 709 { 710 if (!fCurrentNode.IsSet()) 711 return; 712 713 fParamWeb = NULL; 714 BMediaRoster* roster = BMediaRoster::Roster(); 715 if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) { 716 BRect hint(fContentLayout->Frame()); 717 BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint); 718 if (paramView) { 719 fContentLayout->AddView(paramView); 720 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 721 return; 722 } 723 } 724 725 _MakeEmptyParamView(); 726 } 727 728 729 void 730 MediaWindow::_MakeEmptyParamView() 731 { 732 fParamWeb = NULL; 733 734 BStringView* stringView = new BStringView("noControls", 735 B_TRANSLATE("This hardware has no controls.")); 736 737 BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 738 stringView->SetExplicitMaxSize(unlimited); 739 740 BAlignment centered(B_ALIGN_HORIZONTAL_CENTER, 741 B_ALIGN_VERTICAL_CENTER); 742 stringView->SetExplicitAlignment(centered); 743 stringView->SetAlignment(B_ALIGN_CENTER); 744 745 fContentLayout->AddView(stringView); 746 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 747 748 rgb_color panel = stringView->LowColor(); 749 stringView->SetHighColor(tint_color(panel, 750 B_DISABLED_LABEL_TINT)); 751 } 752 753