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 45const uint32 ML_SELECTED_NODE = 'MlSN'; 46const uint32 ML_RESTART_THREAD_FINISHED = 'MlRF'; 47 48 49class NodeListItemUpdater : public MediaListItem::Visitor { 50public: 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 70private: 71 72 NodeListItem::Comparator fComparator; 73 UpdateMethod fAction; 74}; 75 76 77MediaWindow::SmartNode::SmartNode(const BMessenger& notifyHandler) 78 : 79 fNode(NULL), 80 fMessenger(notifyHandler) 81{ 82} 83 84 85MediaWindow::SmartNode::~SmartNode() 86{ 87 _FreeNode(); 88} 89 90 91void 92MediaWindow::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 121void 122MediaWindow::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 131bool 132MediaWindow::SmartNode::IsSet() 133{ 134 return fNode != NULL; 135} 136 137 138MediaWindow::SmartNode::operator media_node() 139{ 140 if (fNode) 141 return *fNode; 142 media_node node; 143 return node; 144} 145 146 147void 148MediaWindow::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 176MediaWindow::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 200MediaWindow::~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 227status_t 228MediaWindow::InitCheck() 229{ 230 return fInitCheck; 231} 232 233 234void 235MediaWindow::SelectNode(const dormant_node_info* node) 236{ 237 fCurrentNode.SetTo(node); 238 _MakeParamView(); 239 fTitleView->SetLabel(node->name); 240} 241 242 243void 244MediaWindow::SelectAudioSettings(const char* title) 245{ 246 fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fAudioView)); 247 fTitleView->SetLabel(title); 248} 249 250 251void 252MediaWindow::SelectVideoSettings(const char* title) 253{ 254 fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fVideoView)); 255 fTitleView->SetLabel(title); 256} 257 258 259void 260MediaWindow::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 271void 272MediaWindow::SelectMidiSettings(const char* title) 273{ 274 fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fMidiView)); 275 fTitleView->SetLabel(title); 276} 277 278 279void 280MediaWindow::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 293void 294MediaWindow::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 307bool 308MediaWindow::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 328void 329MediaWindow::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 382void 383MediaWindow::_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 430status_t 431MediaWindow::_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 540void 541MediaWindow::_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 554void 555MediaWindow::_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 597void 598MediaWindow::_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 609void 610MediaWindow::_EmptyNodeLists() 611{ 612 fAudioOutputs.MakeEmpty(); 613 fAudioInputs.MakeEmpty(); 614 fVideoOutputs.MakeEmpty(); 615 fVideoInputs.MakeEmpty(); 616} 617 618 619NodeListItem* 620MediaWindow::_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 642void 643MediaWindow::_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 655status_t 656MediaWindow::_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 671void 672MediaWindow::_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 view; 682 delete fParamWeb; 683 fParamWeb = NULL; 684 } 685} 686 687 688void 689MediaWindow::_MakeParamView() 690{ 691 if (!fCurrentNode.IsSet()) 692 return; 693 694 fParamWeb = NULL; 695 BMediaRoster* roster = BMediaRoster::Roster(); 696 if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) { 697 BRect hint(fContentLayout->Frame()); 698 BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint); 699 if (paramView) { 700 fContentLayout->AddView(paramView); 701 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 702 return; 703 } 704 } 705 706 _MakeEmptyParamView(); 707} 708 709 710void 711MediaWindow::_MakeEmptyParamView() 712{ 713 fParamWeb = NULL; 714 715 BStringView* stringView = new BStringView("noControls", 716 B_TRANSLATE("This hardware has no controls.")); 717 718 BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 719 stringView->SetExplicitMaxSize(unlimited); 720 721 BAlignment centered(B_ALIGN_HORIZONTAL_CENTER, 722 B_ALIGN_VERTICAL_CENTER); 723 stringView->SetExplicitAlignment(centered); 724 stringView->SetAlignment(B_ALIGN_CENTER); 725 726 fContentLayout->AddView(stringView); 727 fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1); 728 729 rgb_color panel = stringView->LowColor(); 730 stringView->SetHighColor(tint_color(panel, 731 B_DISABLED_LABEL_TINT)); 732} 733 734