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