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