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 367 fAudioView = new AudioSettingsView(); 368 fContentLayout->AddView(fAudioView); 369 370 fVideoView = new VideoSettingsView(); 371 fContentLayout->AddView(fVideoView); 372 373 // Layout all views 374 BLayoutBuilder::Group<>(this, B_HORIZONTAL) 375 .SetInsets(B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING, 376 B_USE_DEFAULT_SPACING, B_USE_DEFAULT_SPACING) 377 .Add(scrollView, 0.0f) 378 .AddGroup(B_VERTICAL) 379 .SetInsets(0, 0, 0, 0) 380 .Add(fTitleView) 381 .Add(fContentLayout); 382 383 // Start the window 384 fInitCheck = _InitMedia(true); 385 if (fInitCheck != B_OK) 386 PostMessage(B_QUIT_REQUESTED); 387 else if (IsHidden()) 388 Show(); 389 } 390 391 392 status_t 393 MediaWindow::_InitMedia(bool first) 394 { 395 status_t err = B_OK; 396 BMediaRoster* roster = BMediaRoster::Roster(&err); 397 398 if (first && err != B_OK) { 399 BAlert* alert = new BAlert("start_media_server", 400 B_TRANSLATE("Could not connect to the media server.\n" 401 "Would you like to start it ?"), 402 B_TRANSLATE("Quit"), 403 B_TRANSLATE("Start media server"), NULL, 404 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 405 alert->SetShortcut(0, B_ESCAPE); 406 if (alert->Go() == 0) 407 return B_ERROR; 408 409 fAlert = new MediaAlert(BRect(0, 0, 300, 60), "restart_alert", 410 B_TRANSLATE("Restarting media services\nStarting media server" 411 B_UTF8_ELLIPSIS "\n")); 412 fAlert->Show(); 413 414 Show(); 415 416 launch_media_server(); 417 } 418 419 Lock(); 420 421 bool isVideoSelected = true; 422 if (!first && fListView->ItemAt(0) != NULL 423 && fListView->ItemAt(0)->IsSelected()) 424 isVideoSelected = false; 425 426 if ((!first || (first && err) ) && fAlert) { 427 BAutolock locker(fAlert); 428 if (locker.IsLocked()) 429 fAlert->TextView()->SetText( 430 B_TRANSLATE("Ready for use" B_UTF8_ELLIPSIS)); 431 } 432 433 while (fListView->CountItems() > 0) 434 delete fListView->RemoveItem((int32)0); 435 _EmptyNodeLists(); 436 437 // Grab Media Info 438 _FindNodes(); 439 440 // Add video nodes first. They might have an additional audio 441 // output or input, but still should be listed as video node. 442 _AddNodeItems(fVideoOutputs, MediaListItem::VIDEO_TYPE); 443 _AddNodeItems(fVideoInputs, MediaListItem::VIDEO_TYPE); 444 _AddNodeItems(fAudioOutputs, MediaListItem::AUDIO_TYPE); 445 _AddNodeItems(fAudioInputs, MediaListItem::AUDIO_TYPE); 446 447 fAudioView->AddOutputNodes(fAudioOutputs); 448 fAudioView->AddInputNodes(fAudioInputs); 449 fVideoView->AddOutputNodes(fVideoOutputs); 450 fVideoView->AddInputNodes(fVideoInputs); 451 452 // build our list view 453 DeviceListItem* audio = new DeviceListItem(B_TRANSLATE("Audio settings"), 454 MediaListItem::AUDIO_TYPE); 455 fListView->AddItem(audio); 456 457 MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"), 458 MediaListItem::VIDEO_TYPE); 459 fListView->AddItem(video); 460 461 MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer")); 462 fListView->AddItem(mixer); 463 464 fListView->SortItems(&MediaListItem::Compare); 465 _UpdateListViewMinWidth(); 466 467 // Set default nodes for our setting views 468 media_node defaultNode; 469 dormant_node_info nodeInfo; 470 int32 outputID; 471 BString outputName; 472 473 if (roster->GetAudioInput(&defaultNode) == B_OK) { 474 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 475 fAudioView->SetDefaultInput(&nodeInfo); 476 // this causes our listview to be updated as well 477 } 478 479 if (roster->GetAudioOutput(&defaultNode, &outputID, &outputName) == B_OK) { 480 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 481 fAudioView->SetDefaultOutput(&nodeInfo); 482 fAudioView->SetDefaultChannel(outputID); 483 // this causes our listview to be updated as well 484 } 485 486 if (roster->GetVideoInput(&defaultNode) == B_OK) { 487 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 488 fVideoView->SetDefaultInput(&nodeInfo); 489 // this causes our listview to be updated as well 490 } 491 492 if (roster->GetVideoOutput(&defaultNode) == B_OK) { 493 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 494 fVideoView->SetDefaultOutput(&nodeInfo); 495 // this causes our listview to be updated as well 496 } 497 498 if (first) 499 fListView->Select(fListView->IndexOf(mixer)); 500 else if (isVideoSelected) 501 fListView->Select(fListView->IndexOf(video)); 502 else 503 fListView->Select(fListView->IndexOf(audio)); 504 505 if (fAlert != NULL) { 506 snooze(800000); 507 fAlert->PostMessage(B_QUIT_REQUESTED); 508 } 509 fAlert = NULL; 510 511 Unlock(); 512 513 return B_OK; 514 } 515 516 517 void 518 MediaWindow::_FindNodes() 519 { 520 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 521 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 522 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 523 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 524 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 525 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 526 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 527 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 528 } 529 530 531 void 532 MediaWindow::_FindNodes(media_type type, uint64 kind, NodeList& into) 533 { 534 dormant_node_info nodeInfo[64]; 535 int32 nodeInfoCount = 64; 536 537 media_format format; 538 media_format* nodeInputFormat = NULL; 539 media_format* nodeOutputFormat = NULL; 540 format.type = type; 541 542 // output nodes must be BBufferConsumers => they have an input format 543 // input nodes must be BBufferProducers => they have an output format 544 if ((kind & B_PHYSICAL_OUTPUT) != 0) 545 nodeInputFormat = &format; 546 else if ((kind & B_PHYSICAL_INPUT) != 0) 547 nodeOutputFormat = &format; 548 else 549 return; 550 551 BMediaRoster* roster = BMediaRoster::Roster(); 552 553 if (roster->GetDormantNodes(nodeInfo, &nodeInfoCount, nodeInputFormat, 554 nodeOutputFormat, NULL, kind) != B_OK) { 555 // TODO: better error reporting! 556 fprintf(stderr, "error\n"); 557 return; 558 } 559 560 for (int32 i = 0; i < nodeInfoCount; i++) { 561 PRINT(("node : %s, media_addon %i, flavor_id %i\n", 562 nodeInfo[i].name, (int)nodeInfo[i].addon, 563 (int)nodeInfo[i].flavor_id)); 564 565 dormant_node_info* info = new dormant_node_info(); 566 strncpy(info->name, nodeInfo[i].name, B_MEDIA_NAME_LENGTH); 567 info->flavor_id = nodeInfo[i].flavor_id; 568 info->addon = nodeInfo[i].addon; 569 into.AddItem(info); 570 } 571 } 572 573 574 void 575 MediaWindow::_AddNodeItems(NodeList& list, MediaListItem::media_type type) 576 { 577 int32 count = list.CountItems(); 578 for (int32 i = 0; i < count; i++) { 579 dormant_node_info* info = list.ItemAt(i); 580 if (_FindNodeListItem(info) == NULL) 581 fListView->AddItem(new NodeListItem(info, type)); 582 } 583 } 584 585 586 void 587 MediaWindow::_EmptyNodeLists() 588 { 589 fAudioOutputs.MakeEmpty(); 590 fAudioInputs.MakeEmpty(); 591 fVideoOutputs.MakeEmpty(); 592 fVideoInputs.MakeEmpty(); 593 } 594 595 596 NodeListItem* 597 MediaWindow::_FindNodeListItem(dormant_node_info* info) 598 { 599 NodeListItem audioItem(info, MediaListItem::AUDIO_TYPE); 600 NodeListItem videoItem(info, MediaListItem::VIDEO_TYPE); 601 602 NodeListItem::Comparator audioComparator(&audioItem); 603 NodeListItem::Comparator videoComparator(&videoItem); 604 605 for (int32 i = 0; i < fListView->CountItems(); i++) { 606 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 607 item->Accept(audioComparator); 608 if (audioComparator.result == 0) 609 return static_cast<NodeListItem*>(item); 610 611 item->Accept(videoComparator); 612 if (videoComparator.result == 0) 613 return static_cast<NodeListItem*>(item); 614 } 615 return NULL; 616 } 617 618 619 void 620 MediaWindow::_UpdateListViewMinWidth() 621 { 622 float width = 0; 623 for (int32 i = 0; i < fListView->CountItems(); i++) { 624 BListItem* item = fListView->ItemAt(i); 625 width = max_c(width, item->Width()); 626 } 627 fListView->SetExplicitMinSize(BSize(width, B_SIZE_UNSET)); 628 fListView->InvalidateLayout(); 629 } 630 631 632 status_t 633 MediaWindow::_RestartMediaServices(void* data) 634 { 635 MediaWindow* window = (MediaWindow*)data; 636 window->fAlert = new MediaAlert(BRect(0, 0, 300, 60), 637 "restart_alert", B_TRANSLATE( 638 "Restarting media services\nShutting down media server\n")); 639 640 window->fAlert->Show(); 641 642 shutdown_media_server(B_INFINITE_TIMEOUT, MediaWindow::_UpdateProgress, 643 window->fAlert); 644 645 { 646 BAutolock locker(window->fAlert); 647 if (locker.IsLocked()) 648 window->fAlert->TextView()->SetText( 649 B_TRANSLATE("Starting media server" B_UTF8_ELLIPSIS)); 650 } 651 launch_media_server(); 652 653 return window->PostMessage(ML_INIT_MEDIA); 654 } 655 656 657 bool 658 MediaWindow::_UpdateProgress(int stage, const char* message, void* cookie) 659 { 660 MediaAlert* alert = static_cast<MediaAlert*>(cookie); 661 PRINT(("stage : %i\n", stage)); 662 const char* string = "Unknown stage"; 663 switch (stage) { 664 case 10: 665 string = B_TRANSLATE("Stopping media server" B_UTF8_ELLIPSIS); 666 break; 667 case 20: 668 string = B_TRANSLATE("Telling media_addon_server to quit."); 669 break; 670 case 40: 671 string = B_TRANSLATE("Waiting for media_server to quit."); 672 break; 673 case 70: 674 string = B_TRANSLATE("Cleaning up."); 675 break; 676 case 100: 677 string = B_TRANSLATE("Done shutting down."); 678 break; 679 } 680 681 BAutolock locker(alert); 682 if (locker.IsLocked()) 683 alert->TextView()->SetText(string); 684 return true; 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) { 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