1 /* 2 * Copyright 2003-2015, 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()->SetViewUIColor(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_WINDOW_SPACING) 415 .Add(scrollView, 0.0f) 416 .AddGroup(B_VERTICAL) 417 .SetInsets(0, 0, 0, 0) 418 .Add(fTitleView) 419 .Add(fContentLayout); 420 421 // Start the window 422 fInitCheck = _InitMedia(true); 423 if (fInitCheck != B_OK) 424 PostMessage(B_QUIT_REQUESTED); 425 else if (IsHidden()) 426 Show(); 427 } 428 429 430 status_t 431 MediaWindow::_InitMedia(bool first) 432 { 433 status_t err = B_OK; 434 BMediaRoster* roster = BMediaRoster::Roster(&err); 435 436 if (first && err != B_OK) { 437 BAlert* alert = new BAlert("start_media_server", 438 B_TRANSLATE("Could not connect to the media server.\n" 439 "Would you like to start it ?"), 440 B_TRANSLATE("Quit"), 441 B_TRANSLATE("Start media server"), NULL, 442 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 443 alert->SetShortcut(0, B_ESCAPE); 444 if (alert->Go() == 0) 445 return B_ERROR; 446 447 Show(); 448 449 launch_media_server(); 450 } 451 452 Lock(); 453 454 bool isVideoSelected = true; 455 if (!first && fListView->ItemAt(0) != NULL 456 && fListView->ItemAt(0)->IsSelected()) 457 isVideoSelected = false; 458 459 while (fListView->CountItems() > 0) 460 delete fListView->RemoveItem((int32)0); 461 _EmptyNodeLists(); 462 463 // Grab Media Info 464 _FindNodes(); 465 466 // Add video nodes first. They might have an additional audio 467 // output or input, but still should be listed as video node. 468 _AddNodeItems(fVideoOutputs, MediaListItem::VIDEO_TYPE); 469 _AddNodeItems(fVideoInputs, MediaListItem::VIDEO_TYPE); 470 _AddNodeItems(fAudioOutputs, MediaListItem::AUDIO_TYPE); 471 _AddNodeItems(fAudioInputs, MediaListItem::AUDIO_TYPE); 472 473 fAudioView->AddOutputNodes(fAudioOutputs); 474 fAudioView->AddInputNodes(fAudioInputs); 475 fVideoView->AddOutputNodes(fVideoOutputs); 476 fVideoView->AddInputNodes(fVideoInputs); 477 478 // build our list view 479 DeviceListItem* audio = new DeviceListItem(B_TRANSLATE("Audio settings"), 480 MediaListItem::AUDIO_TYPE); 481 fListView->AddItem(audio); 482 483 MidiListItem* midi = new MidiListItem(B_TRANSLATE("MIDI Settings")); 484 fListView->AddItem(midi); 485 486 MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"), 487 MediaListItem::VIDEO_TYPE); 488 fListView->AddItem(video); 489 490 MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer")); 491 fListView->AddItem(mixer); 492 493 fListView->SortItems(&MediaListItem::Compare); 494 _UpdateListViewMinWidth(); 495 496 // Set default nodes for our setting views 497 media_node defaultNode; 498 dormant_node_info nodeInfo; 499 int32 outputID; 500 BString outputName; 501 502 if (roster->GetAudioInput(&defaultNode) == B_OK) { 503 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 504 fAudioView->SetDefaultInput(&nodeInfo); 505 // this causes our listview to be updated as well 506 } 507 508 if (roster->GetAudioOutput(&defaultNode, &outputID, &outputName) == B_OK) { 509 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 510 fAudioView->SetDefaultOutput(&nodeInfo); 511 fAudioView->SetDefaultChannel(outputID); 512 // this causes our listview to be updated as well 513 } 514 515 if (roster->GetVideoInput(&defaultNode) == B_OK) { 516 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 517 fVideoView->SetDefaultInput(&nodeInfo); 518 // this causes our listview to be updated as well 519 } 520 521 if (roster->GetVideoOutput(&defaultNode) == B_OK) { 522 roster->GetDormantNodeFor(defaultNode, &nodeInfo); 523 fVideoView->SetDefaultOutput(&nodeInfo); 524 // this causes our listview to be updated as well 525 } 526 527 if (first) 528 fListView->Select(fListView->IndexOf(mixer)); 529 else if (isVideoSelected) 530 fListView->Select(fListView->IndexOf(video)); 531 else 532 fListView->Select(fListView->IndexOf(audio)); 533 534 Unlock(); 535 536 return B_OK; 537 } 538 539 540 void 541 MediaWindow::_FindNodes() 542 { 543 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 544 _FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 545 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs); 546 _FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_INPUT, fAudioInputs); 547 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 548 _FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 549 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs); 550 _FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_INPUT, fVideoInputs); 551 } 552 553 554 void 555 MediaWindow::_FindNodes(media_type type, uint64 kind, NodeList& into) 556 { 557 dormant_node_info nodeInfo[64]; 558 int32 nodeInfoCount = 64; 559 560 media_format format; 561 media_format* nodeInputFormat = NULL; 562 media_format* nodeOutputFormat = NULL; 563 format.type = type; 564 565 // output nodes must be BBufferConsumers => they have an input format 566 // input nodes must be BBufferProducers => they have an output format 567 if ((kind & B_PHYSICAL_OUTPUT) != 0) 568 nodeInputFormat = &format; 569 else if ((kind & B_PHYSICAL_INPUT) != 0) 570 nodeOutputFormat = &format; 571 else 572 return; 573 574 BMediaRoster* roster = BMediaRoster::Roster(); 575 576 if (roster->GetDormantNodes(nodeInfo, &nodeInfoCount, nodeInputFormat, 577 nodeOutputFormat, NULL, kind) != B_OK) { 578 // TODO: better error reporting! 579 fprintf(stderr, "error\n"); 580 return; 581 } 582 583 for (int32 i = 0; i < nodeInfoCount; i++) { 584 PRINT(("node : %s, media_addon %i, flavor_id %i\n", 585 nodeInfo[i].name, (int)nodeInfo[i].addon, 586 (int)nodeInfo[i].flavor_id)); 587 588 dormant_node_info* info = new dormant_node_info(); 589 strlcpy(info->name, nodeInfo[i].name, B_MEDIA_NAME_LENGTH); 590 info->flavor_id = nodeInfo[i].flavor_id; 591 info->addon = nodeInfo[i].addon; 592 into.AddItem(info); 593 } 594 } 595 596 597 void 598 MediaWindow::_AddNodeItems(NodeList& list, MediaListItem::media_type type) 599 { 600 int32 count = list.CountItems(); 601 for (int32 i = 0; i < count; i++) { 602 dormant_node_info* info = list.ItemAt(i); 603 if (_FindNodeListItem(info) == NULL) 604 fListView->AddItem(new NodeListItem(info, type)); 605 } 606 } 607 608 609 void 610 MediaWindow::_EmptyNodeLists() 611 { 612 fAudioOutputs.MakeEmpty(); 613 fAudioInputs.MakeEmpty(); 614 fVideoOutputs.MakeEmpty(); 615 fVideoInputs.MakeEmpty(); 616 } 617 618 619 NodeListItem* 620 MediaWindow::_FindNodeListItem(dormant_node_info* info) 621 { 622 NodeListItem audioItem(info, MediaListItem::AUDIO_TYPE); 623 NodeListItem videoItem(info, MediaListItem::VIDEO_TYPE); 624 625 NodeListItem::Comparator audioComparator(&audioItem); 626 NodeListItem::Comparator videoComparator(&videoItem); 627 628 for (int32 i = 0; i < fListView->CountItems(); i++) { 629 MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i)); 630 item->Accept(audioComparator); 631 if (audioComparator.result == 0) 632 return static_cast<NodeListItem*>(item); 633 634 item->Accept(videoComparator); 635 if (videoComparator.result == 0) 636 return static_cast<NodeListItem*>(item); 637 } 638 return NULL; 639 } 640 641 642 void 643 MediaWindow::_UpdateListViewMinWidth() 644 { 645 float width = 0; 646 for (int32 i = 0; i < fListView->CountItems(); i++) { 647 BListItem* item = fListView->ItemAt(i); 648 width = max_c(width, item->Width()); 649 } 650 fListView->SetExplicitMinSize(BSize(width, B_SIZE_UNSET)); 651 fListView->InvalidateLayout(); 652 } 653 654 655 status_t 656 MediaWindow::_RestartMediaServices(void* data) 657 { 658 MediaWindow* window = (MediaWindow*)data; 659 660 shutdown_media_server(); 661 662 if (window->fRestartAlert != NULL 663 && window->fRestartAlert->Lock()) { 664 window->fRestartAlert->Quit(); 665 } 666 667 return window->PostMessage(ML_RESTART_THREAD_FINISHED); 668 } 669 670 671 void 672 MediaWindow::_ClearParamView() 673 { 674 BLayoutItem* item = fContentLayout->VisibleItem(); 675 if (!item) 676 return; 677 678 BView* view = item->View(); 679 if (view != fVideoView && view != fAudioView && view != fMidiView) { 680 fContentLayout->RemoveItem(item); 681 delete item; 682 delete view; 683 delete fParamWeb; 684 fParamWeb = NULL; 685 } 686 } 687 688 689 void 690 MediaWindow::_MakeParamView() 691 { 692 if (!fCurrentNode.IsSet()) 693 return; 694 695 fParamWeb = NULL; 696 BMediaRoster* roster = BMediaRoster::Roster(); 697 if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) { 698 BRect hint(fContentLayout->Frame()); 699 BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint); 700 if (paramView) { 701 fContentLayout->AddView(paramView); 702 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 703 return; 704 } 705 } 706 707 _MakeEmptyParamView(); 708 } 709 710 711 void 712 MediaWindow::_MakeEmptyParamView() 713 { 714 fParamWeb = NULL; 715 716 BStringView* stringView = new BStringView("noControls", 717 B_TRANSLATE("This hardware has no controls.")); 718 719 BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 720 stringView->SetExplicitMaxSize(unlimited); 721 722 BAlignment centered(B_ALIGN_HORIZONTAL_CENTER, 723 B_ALIGN_VERTICAL_CENTER); 724 stringView->SetExplicitAlignment(centered); 725 stringView->SetAlignment(B_ALIGN_CENTER); 726 727 fContentLayout->AddView(stringView); 728 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 729 730 rgb_color panel = stringView->LowColor(); 731 stringView->SetHighColor(tint_color(panel, 732 B_DISABLED_LABEL_TINT)); 733 } 734 735