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_TRANSLATE_CONTEXT 42 #define B_TRANSLATE_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) 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 424 // #pragma mark - 425 426 427 status_t 428 MediaWindow::InitMedia(bool first) 429 { 430 status_t err = B_OK; 431 BMediaRoster* roster = BMediaRoster::Roster(&err); 432 433 if (first && err != B_OK) { 434 BAlert* alert = new BAlert("start_media_server", 435 B_TRANSLATE("Could not connect to the media server.\n" 436 "Would you like to start it ?"), 437 B_TRANSLATE("Quit"), 438 B_TRANSLATE("Start media server"), NULL, 439 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 440 if (alert->Go()==0) 441 return B_ERROR; 442 443 fAlert = new MediaAlert(BRect(0, 0, 300, 60), 444 "restart_alert", B_TRANSLATE( 445 "Restarting media services\nStarting media server" 446 B_UTF8_ELLIPSIS "\n")); 447 fAlert->Show(); 448 449 Show(); 450 451 launch_media_server(); 452 } 453 454 Lock(); 455 456 bool isVideoSelected = true; 457 if (!first && fListView->ItemAt(0) && fListView->ItemAt(0)->IsSelected()) 458 isVideoSelected = false; 459 460 if ((!first || (first && err) ) && fAlert) { 461 BAutolock locker(fAlert); 462 if (locker.IsLocked()) 463 fAlert->TextView()->SetText( 464 B_TRANSLATE("Ready for use" B_UTF8_ELLIPSIS)); 465 } 466 467 while (fListView->CountItems() > 0) 468 delete fListView->RemoveItem((int32)0); 469 _EmptyNodeLists(); 470 471 // Grab Media Info 472 _FindNodes(); 473 474 // Add video nodes first. They might have an additional audio 475 // output or input, but still should be listed as video node. 476 _AddNodeItems(fVideoOutputs, MediaListItem::VIDEO_TYPE); 477 _AddNodeItems(fVideoInputs, MediaListItem::VIDEO_TYPE); 478 _AddNodeItems(fAudioOutputs, MediaListItem::AUDIO_TYPE); 479 _AddNodeItems(fAudioInputs, MediaListItem::AUDIO_TYPE); 480 481 fAudioView->AddOutputNodes(fAudioOutputs); 482 fAudioView->AddInputNodes(fAudioInputs); 483 fVideoView->AddOutputNodes(fVideoOutputs); 484 fVideoView->AddInputNodes(fVideoInputs); 485 486 // build our list view 487 DeviceListItem* audio = new DeviceListItem(B_TRANSLATE("Audio settings"), 488 MediaListItem::AUDIO_TYPE); 489 fListView->AddItem(audio); 490 491 MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"), 492 MediaListItem::VIDEO_TYPE); 493 fListView->AddItem(video); 494 495 MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer")); 496 fListView->AddItem(mixer); 497 498 fListView->SortItems(&MediaListItem::Compare); 499 _UpdateListViewMinWidth(); 500 501 // Set default nodes for our setting views 502 media_node default_node; 503 dormant_node_info node_info; 504 int32 outputID; 505 BString outputName; 506 507 if (roster->GetAudioInput(&default_node) == B_OK) { 508 roster->GetDormantNodeFor(default_node, &node_info); 509 fAudioView->SetDefaultInput(&node_info); 510 // this causes our listview to be updated as well 511 } 512 513 if (roster->GetAudioOutput(&default_node, &outputID, &outputName)==B_OK) { 514 roster->GetDormantNodeFor(default_node, &node_info); 515 fAudioView->SetDefaultOutput(&node_info); 516 fAudioView->SetDefaultChannel(outputID); 517 // this causes our listview to be updated as well 518 } 519 520 if (roster->GetVideoInput(&default_node)==B_OK) { 521 roster->GetDormantNodeFor(default_node, &node_info); 522 fVideoView->SetDefaultInput(&node_info); 523 // this causes our listview to be updated as well 524 } 525 526 if (roster->GetVideoOutput(&default_node)==B_OK) { 527 roster->GetDormantNodeFor(default_node, &node_info); 528 fVideoView->SetDefaultOutput(&node_info); 529 // this causes our listview to be updated as well 530 } 531 532 if (first) { 533 fListView->Select(fListView->IndexOf(mixer)); 534 } else { 535 if (isVideoSelected) 536 fListView->Select(fListView->IndexOf(video)); 537 else 538 fListView->Select(fListView->IndexOf(audio)); 539 } 540 541 if (fAlert) { 542 snooze(800000); 543 fAlert->PostMessage(B_QUIT_REQUESTED); 544 } 545 fAlert = NULL; 546 547 Unlock(); 548 549 return B_OK; 550 } 551 552 553 bool 554 MediaWindow::QuitRequested() 555 { 556 // stop watching the MediaRoster 557 fCurrentNode.SetTo(NULL); 558 be_app->PostMessage(B_QUIT_REQUESTED); 559 return true; 560 } 561 562 563 // ErrorAlert -- Displays a BAlert Box with a Custom Error or Debug Message 564 void 565 ErrorAlert(char* errorMessage) { 566 printf("%s\n", errorMessage); 567 BAlert* alert = new BAlert("BAlert", errorMessage, B_TRANSLATE("OK"), 568 NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 569 alert->Go(); 570 exit(1); 571 } 572 573 574 void 575 MediaWindow::MessageReceived(BMessage* message) 576 { 577 switch(message->what) 578 { 579 case ML_INIT_MEDIA: 580 InitMedia(false); 581 break; 582 case ML_RESTART_MEDIA_SERVER: 583 { 584 thread_id thread = spawn_thread(&MediaWindow::RestartMediaServices, 585 "restart_thread", B_NORMAL_PRIORITY, this); 586 if (thread < B_OK) 587 fprintf(stderr, "couldn't create restart thread\n"); 588 else 589 resume_thread(thread); 590 break; 591 } 592 case B_MEDIA_WEB_CHANGED: 593 case ML_SELECTED_NODE: 594 { 595 PRINT_OBJECT(*message); 596 597 MediaListItem* item = static_cast<MediaListItem*>( 598 fListView->ItemAt(fListView->CurrentSelection())); 599 if (!item) 600 break; 601 602 fCurrentNode.SetTo(NULL); 603 _ClearParamView(); 604 item->AlterWindow(this); 605 break; 606 } 607 case B_SOME_APP_LAUNCHED: 608 { 609 PRINT_OBJECT(*message); 610 if (!fAlert) 611 break; 612 613 BString mimeSig; 614 if (message->FindString("be:signature", &mimeSig) == B_OK 615 && (mimeSig == "application/x-vnd.Be.addon-host" 616 || mimeSig == "application/x-vnd.Be.media-server")) { 617 fAlert->Lock(); 618 fAlert->TextView()->SetText( 619 B_TRANSLATE("Starting media server" B_UTF8_ELLIPSIS)); 620 fAlert->Unlock(); 621 } 622 } 623 break; 624 case B_SOME_APP_QUIT: 625 { 626 PRINT_OBJECT(*message); 627 BString mimeSig; 628 if (message->FindString("be:signature", &mimeSig) == B_OK) { 629 if (mimeSig == "application/x-vnd.Be.addon-host" 630 || mimeSig == "application/x-vnd.Be.media-server") { 631 BMediaRoster* roster = BMediaRoster::CurrentRoster(); 632 if (roster && roster->Lock()) 633 roster->Quit(); 634 } 635 } 636 637 } 638 break; 639 default: 640 BWindow::MessageReceived(message); 641 break; 642 } 643 } 644 645 status_t 646 MediaWindow::RestartMediaServices(void* data) 647 { 648 MediaWindow* window = (MediaWindow*)data; 649 window->fAlert = new MediaAlert(BRect(0, 0, 300, 60), 650 "restart_alert", B_TRANSLATE( 651 "Restarting media services\nShutting down media server\n")); 652 653 window->fAlert->Show(); 654 655 shutdown_media_server(B_INFINITE_TIMEOUT, MediaWindow::UpdateProgress, 656 window->fAlert); 657 658 { 659 BAutolock locker(window->fAlert); 660 if (locker.IsLocked()) 661 window->fAlert->TextView()->SetText( 662 B_TRANSLATE("Starting media server" B_UTF8_ELLIPSIS)); 663 } 664 launch_media_server(); 665 666 return window->PostMessage(ML_INIT_MEDIA); 667 } 668 669 670 bool 671 MediaWindow::UpdateProgress(int stage, const char * message, void * cookie) 672 { 673 MediaAlert* alert = static_cast<MediaAlert*>(cookie); 674 PRINT(("stage : %i\n", stage)); 675 const char* string = "Unknown stage"; 676 switch (stage) { 677 case 10: 678 string = B_TRANSLATE("Stopping media server" B_UTF8_ELLIPSIS); 679 break; 680 case 20: 681 string = B_TRANSLATE("Telling media_addon_server to quit."); 682 break; 683 case 40: 684 string = B_TRANSLATE("Waiting for media_server to quit."); 685 break; 686 case 70: 687 string = B_TRANSLATE("Cleaning up."); 688 break; 689 case 100: 690 string = B_TRANSLATE("Done shutting down."); 691 break; 692 } 693 694 BAutolock locker(alert); 695 if (locker.IsLocked()) 696 alert->TextView()->SetText(string); 697 return true; 698 } 699 700 701 void 702 MediaWindow::_ClearParamView() 703 { 704 BLayoutItem* item = fContentLayout->VisibleItem(); 705 if (!item) 706 return; 707 708 BView* view = item->View(); 709 if (view != fVideoView && view != fAudioView) { 710 fContentLayout->RemoveItem(item); 711 delete item; 712 delete view; 713 delete fParamWeb; 714 fParamWeb = NULL; 715 } 716 } 717 718 719 void 720 MediaWindow::_MakeParamView() 721 { 722 if (!fCurrentNode.IsSet()) 723 return; 724 725 fParamWeb = NULL; 726 BMediaRoster* roster = BMediaRoster::Roster(); 727 if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) { 728 BRect hint(fContentLayout->Frame()); 729 BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint); 730 if (paramView) { 731 fContentLayout->AddView(paramView); 732 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 733 return; 734 } 735 } 736 737 _MakeEmptyParamView(); 738 } 739 740 741 void 742 MediaWindow::_MakeEmptyParamView() 743 { 744 fParamWeb = NULL; 745 746 BStringView* stringView = new BStringView("noControls", 747 B_TRANSLATE("This hardware has no controls.")); 748 749 BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 750 stringView->SetExplicitMaxSize(unlimited); 751 752 BAlignment centered(B_ALIGN_HORIZONTAL_CENTER, 753 B_ALIGN_VERTICAL_CENTER); 754 stringView->SetExplicitAlignment(centered); 755 stringView->SetAlignment(B_ALIGN_CENTER); 756 757 fContentLayout->AddView(stringView); 758 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 759 760 rgb_color panel = stringView->LowColor(); 761 stringView->SetHighColor(tint_color(panel, 762 B_DISABLED_LABEL_TINT)); 763 } 764 765