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