1 /* 2 ** Copyright 2003, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 3 ** Distributed under the terms of the OpenBeOS License. 4 */ 5 6 7 #include "DefaultMediaTheme.h" 8 #include "debug.h" 9 10 #include <ParameterWeb.h> 11 12 #include <Slider.h> 13 #include <StringView.h> 14 #include <Button.h> 15 #include <TextControl.h> 16 #include <OptionPopUp.h> 17 #include <ChannelSlider.h> 18 #include <Box.h> 19 #include <CheckBox.h> 20 #include <TabView.h> 21 #include <MenuField.h> 22 #include <MessageFilter.h> 23 24 25 using namespace BPrivate; 26 27 28 namespace BPrivate { 29 30 class GroupView : public BView { 31 public: 32 GroupView(BRect frame, const char *name); 33 virtual ~GroupView(); 34 35 virtual void AttachedToWindow(); 36 37 private: 38 }; 39 40 class SeparatorView : public BView { 41 public: 42 SeparatorView(BRect frame); 43 virtual ~SeparatorView(); 44 45 virtual void Draw(BRect updateRect); 46 47 private: 48 bool fVertical; 49 }; 50 51 class TitleView : public BView { 52 public: 53 TitleView(BRect frame, const char *title); 54 virtual ~TitleView(); 55 56 virtual void Draw(BRect updateRect); 57 virtual void GetPreferredSize(float *width, float *height); 58 59 private: 60 const char *fTitle; 61 }; 62 63 class MessageFilter : public BMessageFilter { 64 public: 65 static MessageFilter *FilterFor(BView *view, BParameter ¶meter); 66 67 protected: 68 MessageFilter(); 69 }; 70 71 class ContinuousMessageFilter : public MessageFilter { 72 public: 73 ContinuousMessageFilter(BControl *control, BContinuousParameter ¶meter); 74 virtual ~ContinuousMessageFilter(); 75 76 virtual filter_result Filter(BMessage *message, BHandler **target); 77 78 private: 79 BContinuousParameter &fParameter; 80 }; 81 82 class DiscreteMessageFilter : public MessageFilter { 83 public: 84 DiscreteMessageFilter(BControl *control, BDiscreteParameter ¶meter); 85 virtual ~DiscreteMessageFilter(); 86 87 virtual filter_result Filter(BMessage *message, BHandler **target); 88 89 private: 90 BDiscreteParameter &fParameter; 91 }; 92 93 } // namespace BPrivate 94 95 96 const uint32 kMsgParameterChanged = '_mPC'; 97 98 99 static bool 100 parameter_should_be_hidden(BParameter ¶meter) 101 { 102 // ToDo: note, this is probably completely stupid, but it's the only 103 // way I could safely remove the null parameters that are not shown 104 // by the R5 media theme 105 if (parameter.Type() != BParameter::B_NULL_PARAMETER 106 || strcmp(parameter.Kind(), B_WEB_PHYSICAL_INPUT)) 107 return false; 108 109 for (int32 i = 0; i < parameter.CountOutputs(); i++) { 110 if (!strcmp(parameter.OutputAt(0)->Kind(), B_INPUT_MUX)) 111 return true; 112 } 113 114 return false; 115 } 116 117 118 // #pragma mark - 119 120 121 GroupView::GroupView(BRect frame, const char *name) 122 : BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW) 123 { 124 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 125 126 // ToDo: show scroll bars if necessary! 127 } 128 129 130 GroupView::~GroupView() 131 { 132 } 133 134 135 void 136 GroupView::AttachedToWindow() 137 { 138 for (int32 i = CountChildren(); i-- > 0;) { 139 BControl *control = dynamic_cast<BControl *>(ChildAt(i)); 140 if (control == NULL) 141 continue; 142 143 control->SetTarget(control); 144 } 145 } 146 147 148 // #pragma mark - 149 150 151 SeparatorView::SeparatorView(BRect frame) 152 : BView(frame, "-", B_FOLLOW_NONE, B_WILL_DRAW) 153 { 154 fVertical = frame.Width() < frame.Height(); 155 SetViewColor(B_TRANSPARENT_COLOR); 156 } 157 158 159 SeparatorView::~SeparatorView() 160 { 161 } 162 163 164 void 165 SeparatorView::Draw(BRect updateRect) 166 { 167 rgb_color color = ui_color(B_PANEL_BACKGROUND_COLOR); 168 BRect rect = updateRect & Bounds(); 169 170 SetHighColor(tint_color(color, B_DARKEN_1_TINT)); 171 if (fVertical) 172 StrokeLine(BPoint(0, rect.top), BPoint(0, rect.bottom)); 173 else 174 StrokeLine(BPoint(rect.left, 0), BPoint(rect.right, 0)); 175 176 SetHighColor(tint_color(color, B_LIGHTEN_1_TINT)); 177 if (fVertical) 178 StrokeLine(BPoint(1, rect.top), BPoint(1, rect.bottom)); 179 else 180 StrokeLine(BPoint(rect.left, 1), BPoint(rect.right, 1)); 181 } 182 183 184 // #pragma mark - 185 186 187 TitleView::TitleView(BRect frame, const char *title) 188 : BView(frame, title, B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW) 189 { 190 fTitle = strdup(title); 191 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 192 SetLowColor(ViewColor()); 193 } 194 195 196 TitleView::~TitleView() 197 { 198 free((char *)fTitle); 199 } 200 201 202 void 203 TitleView::Draw(BRect updateRect) 204 { 205 BRect rect(Bounds()); 206 207 SetDrawingMode(B_OP_COPY); 208 SetHighColor(240, 240, 240); 209 DrawString(fTitle, BPoint(rect.left + 1, rect.bottom - 9)); 210 211 SetDrawingMode(B_OP_OVER); 212 SetHighColor(80, 20, 20); 213 DrawString(fTitle, BPoint(rect.left, rect.bottom - 8)); 214 } 215 216 217 void 218 TitleView::GetPreferredSize(float *_width, float *_height) 219 { 220 if (_width) 221 *_width = StringWidth(fTitle) + 2; 222 223 if (_height) { 224 font_height fontHeight; 225 GetFontHeight(&fontHeight); 226 227 *_height = fontHeight.ascent + fontHeight.descent + fontHeight.leading + 8; 228 } 229 } 230 231 232 // #pragma mark - 233 234 235 MessageFilter::MessageFilter() 236 : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE) 237 { 238 } 239 240 241 MessageFilter * 242 MessageFilter::FilterFor(BView *view, BParameter ¶meter) 243 { 244 BControl *control = dynamic_cast<BControl *>(view); 245 if (control == NULL) 246 return NULL; 247 248 switch (parameter.Type()) { 249 case BParameter::B_CONTINUOUS_PARAMETER: 250 return new ContinuousMessageFilter(control, static_cast<BContinuousParameter &>(parameter)); 251 252 case BParameter::B_DISCRETE_PARAMETER: 253 return new DiscreteMessageFilter(control, static_cast<BDiscreteParameter &>(parameter)); 254 255 case BParameter::B_NULL_PARAMETER: /* fall through */ 256 default: 257 return NULL; 258 } 259 } 260 261 262 // #pragma mark - 263 264 265 ContinuousMessageFilter::ContinuousMessageFilter(BControl *control, BContinuousParameter ¶meter) 266 : MessageFilter(), 267 fParameter(parameter) 268 { 269 // initialize view for us 270 control->SetMessage(new BMessage(kMsgParameterChanged)); 271 272 // set initial value 273 // ToDo: response support! 274 275 float value[fParameter.CountChannels()]; 276 size_t size = sizeof(value); 277 if (parameter.GetValue((void *)&value, &size, NULL) < B_OK) { 278 ERROR("Could not get parameter value for %p\n", ¶meter); 279 return; 280 } 281 282 if (BSlider *slider = dynamic_cast<BSlider *>(control)) { 283 slider->SetValue((int32) (1000 * value[0])); 284 slider->SetModificationMessage(new BMessage(kMsgParameterChanged)); 285 } else if (BChannelSlider *slider = dynamic_cast<BChannelSlider *>(control)) { 286 for (int32 i = 0; i < fParameter.CountChannels(); i++) 287 slider->SetValueFor(i, (int32) (1000 * value[i])); 288 289 slider->SetModificationMessage(new BMessage(kMsgParameterChanged)); 290 } else 291 printf("unknown discrete parameter view\n"); 292 } 293 294 295 ContinuousMessageFilter::~ContinuousMessageFilter() 296 { 297 } 298 299 300 filter_result 301 ContinuousMessageFilter::Filter(BMessage *message, BHandler **target) 302 { 303 BControl *control; 304 305 if (message->what != kMsgParameterChanged 306 || (control = dynamic_cast<BControl *>(*target)) == NULL) 307 return B_DISPATCH_MESSAGE; 308 309 // update view 310 // ToDo: support for response! 311 312 float value[fParameter.CountChannels()]; 313 314 if (BSlider *slider = dynamic_cast<BSlider *>(control)) { 315 value[0] = (float)(slider->Value() / 1000.0); 316 } else if (BChannelSlider *slider = dynamic_cast<BChannelSlider *>(control)) { 317 for (int32 i = 0; i < fParameter.CountChannels(); i++) 318 value[i] = (float)(slider->ValueFor(i) / 1000.0); 319 } 320 321 printf("update view %s, %ld channels\n", control->Name(), fParameter.CountChannels()); 322 323 if (fParameter.SetValue((void *)value, sizeof(value), system_time()) < B_OK) { 324 ERROR("Could not set parameter value for %p\n", &fParameter); 325 return B_DISPATCH_MESSAGE; 326 } 327 328 return B_SKIP_MESSAGE; 329 } 330 331 332 // #pragma mark - 333 334 335 DiscreteMessageFilter::DiscreteMessageFilter(BControl *control, BDiscreteParameter ¶meter) 336 : MessageFilter(), 337 fParameter(parameter) 338 { 339 // initialize view for us 340 control->SetMessage(new BMessage(kMsgParameterChanged)); 341 342 // set initial value 343 344 size_t size = sizeof(int32); 345 int32 value; 346 if (parameter.GetValue((void *)&value, &size, NULL) < B_OK) { 347 ERROR("Could not get parameter value for %p\n", ¶meter); 348 return; 349 } 350 351 if (BCheckBox *checkBox = dynamic_cast<BCheckBox *>(control)) { 352 checkBox->SetValue(value); 353 } else if (BOptionPopUp *popUp = dynamic_cast<BOptionPopUp *>(control)) { 354 popUp->SelectOptionFor(value); 355 } else 356 printf("unknown discrete parameter view\n"); 357 } 358 359 360 DiscreteMessageFilter::~DiscreteMessageFilter() 361 { 362 } 363 364 365 filter_result 366 DiscreteMessageFilter::Filter(BMessage *message, BHandler **target) 367 { 368 BControl *control; 369 370 if (message->what != kMsgParameterChanged 371 || (control = dynamic_cast<BControl *>(*target)) == NULL) 372 return B_DISPATCH_MESSAGE; 373 374 // update view 375 376 int32 value = 0; 377 378 if (BCheckBox *checkBox = dynamic_cast<BCheckBox *>(control)) { 379 value = checkBox->Value(); 380 } else if (BOptionPopUp *popUp = dynamic_cast<BOptionPopUp *>(control)) { 381 popUp->SelectedOption(NULL, &value); 382 } 383 384 printf("update view %s, value = %ld\n", control->Name(), value); 385 386 if (fParameter.SetValue((void *)&value, sizeof(value), system_time()) < B_OK) { 387 ERROR("Could not set parameter value for %p\n", &fParameter); 388 return B_DISPATCH_MESSAGE; 389 } 390 391 return B_SKIP_MESSAGE; 392 } 393 394 395 // #pragma mark - 396 397 398 DefaultMediaTheme::DefaultMediaTheme() 399 : BMediaTheme("BeOS Theme", "BeOS built-in theme version 0.1") 400 { 401 CALLED(); 402 } 403 404 405 BControl * 406 DefaultMediaTheme::MakeControlFor(BParameter *parameter) 407 { 408 CALLED(); 409 410 BRect rect(0, 0, 150, 100); 411 return MakeViewFor(parameter, &rect); 412 } 413 414 415 BView * 416 DefaultMediaTheme::MakeViewFor(BParameterWeb *web, const BRect *hintRect) 417 { 418 CALLED(); 419 420 if (web == NULL) 421 return NULL; 422 423 BRect rect; 424 if (hintRect) 425 rect = *hintRect; 426 else 427 rect.Set(0, 0, 80, 100); 428 429 // do we have more than one attached parameter group? 430 // if so, use a tabbed view with a tab for each group 431 432 BTabView *tabView = NULL; 433 434 if (web->CountGroups() > 1) 435 tabView = new BTabView(rect, "web"); 436 437 rect.OffsetTo(B_ORIGIN); 438 439 for (int32 i = 0; i < web->CountGroups(); i++) { 440 BParameterGroup *group = web->GroupAt(i); 441 if (group == NULL) 442 continue; 443 444 BView *groupView = MakeViewFor(*group, rect); 445 if (groupView == NULL) 446 continue; 447 448 if (tabView == NULL) { 449 // if we don't need a container to put that view into, 450 // we're done here 451 return groupView; 452 } 453 454 tabView->AddTab(groupView); 455 456 // enlarge the bounding rectangle as needed 457 458 if (groupView->Bounds().Width() + 5 > rect.Width()) 459 rect.right = groupView->Bounds().Width() - 1; 460 461 if (groupView->Bounds().Height() > rect.Height()) 462 rect.bottom = groupView->Bounds().Height(); 463 } 464 465 if (tabView != NULL) { 466 tabView->ResizeTo(rect.right + 10, rect.bottom + tabView->TabHeight()); 467 468 rect = tabView->Bounds(); 469 rect.InsetBySelf(1, 1); 470 rect.top += tabView->TabHeight(); 471 472 tabView->ContainerView()->ResizeTo(rect.Width(), rect.Height()); 473 } 474 475 return tabView; 476 } 477 478 479 BView * 480 DefaultMediaTheme::MakeViewFor(BParameterGroup &group, const BRect &hintRect) 481 { 482 CALLED(); 483 484 if (group.Flags() & B_HIDDEN_PARAMETER) 485 return NULL; 486 487 BRect rect(hintRect); 488 BView *view = new GroupView(rect, group.Name()); 489 490 // Create the parameter views - but don't add them yet 491 492 rect.OffsetTo(B_ORIGIN); 493 rect.InsetBySelf(5, 5); 494 495 BList views; 496 for (int32 i = 0; i < group.CountParameters(); i++) { 497 BParameter *parameter = group.ParameterAt(i); 498 if (parameter == NULL) 499 continue; 500 501 BView *parameterView = MakeSelfHostingViewFor(*parameter, rect); 502 if (parameterView == NULL) 503 continue; 504 505 parameterView->SetViewColor(view->ViewColor()); 506 // ToDo: dunno why this is needed, but the controls 507 // sometimes (!) have a white background without it 508 509 views.AddItem(parameterView); 510 } 511 512 // Identify a title view, and add it at the top if present 513 514 TitleView *titleView = dynamic_cast<TitleView *>((BView *)views.ItemAt(0)); 515 if (titleView != NULL) { 516 view->AddChild(titleView); 517 rect.OffsetBy(0, titleView->Bounds().Height()); 518 } 519 520 // Add the sub-group views 521 522 rect.right = rect.left + 50; 523 rect.bottom = rect.top + 10; 524 float lastHeight = 0; 525 526 for (int32 i = 0; i < group.CountGroups(); i++) { 527 BParameterGroup *subGroup = group.GroupAt(i); 528 if (subGroup == NULL) 529 continue; 530 531 BView *groupView = MakeViewFor(*subGroup, rect); 532 if (groupView == NULL) 533 continue; 534 535 if (i > 0) { 536 // add separator view 537 BRect separatorRect(groupView->Frame()); 538 separatorRect.left -= 3; 539 separatorRect.right = separatorRect.left + 1; 540 if (lastHeight > separatorRect.Height()) 541 separatorRect.bottom = separatorRect.top + lastHeight; 542 543 view->AddChild(new SeparatorView(separatorRect)); 544 } 545 546 view->AddChild(groupView); 547 548 rect.OffsetBy(groupView->Bounds().Width() + 5, 0); 549 550 lastHeight = groupView->Bounds().Height(); 551 if (lastHeight > rect.Height()) 552 rect.bottom = rect.top + lastHeight - 1; 553 } 554 555 view->ResizeTo(rect.left + 10, rect.bottom + 5); 556 557 if (group.CountParameters() == 0) 558 return view; 559 560 // add the parameter views part of the group 561 562 if (group.CountGroups() > 0) { 563 rect.top = rect.bottom + 10; 564 rect.bottom = rect.top + 20; 565 } 566 567 bool center = false; 568 569 for (int32 i = 0; i < views.CountItems(); i++) { 570 BView *parameterView = static_cast<BView *>(views.ItemAt(i)); 571 572 if (parameterView->Bounds().Width() + 5 > rect.Width()) 573 rect.right = parameterView->Bounds().Width() + rect.left + 5; 574 575 // we don't need to add the title view again 576 if (parameterView == titleView) 577 continue; 578 579 // if there is a BChannelSlider (ToDo: or any vertical slider?) 580 // the views will be centered 581 if (dynamic_cast<BChannelSlider *>(parameterView) != NULL) 582 center = true; 583 584 parameterView->MoveTo(parameterView->Frame().left, rect.top); 585 view->AddChild(parameterView); 586 587 rect.OffsetBy(0, parameterView->Bounds().Height() + 5); 588 } 589 590 if (views.CountItems() > (titleView != NULL ? 1 : 0)) 591 view->ResizeTo(rect.right + 5, rect.top + 5); 592 593 // center the parameter views if needed, and tweak some views 594 595 float width = view->Bounds().Width(); 596 597 for (int32 i = 0; i < views.CountItems(); i++) { 598 BView *subView = static_cast<BView *>(views.ItemAt(i)); 599 BRect frame = subView->Frame(); 600 601 if (center) 602 subView->MoveTo((width - frame.Width()) / 2, frame.top); 603 else { 604 // tweak the PopUp views to look better 605 if (dynamic_cast<BOptionPopUp *>(subView) != NULL) 606 subView->ResizeTo(width, frame.Height()); 607 } 608 } 609 610 return view; 611 } 612 613 614 /** This creates a view that handles all incoming messages itself - that's 615 * what is meant with self-hosting. 616 */ 617 618 BView * 619 DefaultMediaTheme::MakeSelfHostingViewFor(BParameter ¶meter, const BRect &hintRect) 620 { 621 if (parameter.Flags() & B_HIDDEN_PARAMETER 622 || parameter_should_be_hidden(parameter)) 623 return NULL; 624 625 BView *view = MakeViewFor(¶meter, &hintRect); 626 if (view == NULL) { 627 // The MakeViewFor() method above returns a BControl - which we 628 // don't need for a null parameter; that's why it returns NULL. 629 // But we want to see something anyway, so we add a string view 630 // here. 631 if (parameter.Type() == BParameter::B_NULL_PARAMETER) { 632 if (parameter.Group()->ParameterAt(0) == ¶meter) { 633 // this is the first parameter in this group, so 634 // let's use a nice title view 635 636 TitleView *titleView = new TitleView(hintRect, parameter.Name()); 637 titleView->ResizeToPreferred(); 638 639 return titleView; 640 } 641 BStringView *stringView = new BStringView(hintRect, parameter.Name(), parameter.Name()); 642 stringView->SetAlignment(B_ALIGN_CENTER); 643 stringView->ResizeToPreferred(); 644 645 return stringView; 646 } 647 648 return NULL; 649 } 650 651 MessageFilter *filter = MessageFilter::FilterFor(view, parameter); 652 if (filter != NULL) 653 view->AddFilter(filter); 654 655 return view; 656 } 657 658 659 BControl * 660 DefaultMediaTheme::MakeViewFor(BParameter *parameter, const BRect *hintRect) 661 { 662 BRect rect; 663 if (hintRect) 664 rect = *hintRect; 665 else 666 rect.Set(0, 0, 50, 100); 667 668 switch (parameter->Type()) { 669 case BParameter::B_NULL_PARAMETER: 670 // there is no default view for a null parameter 671 return NULL; 672 673 case BParameter::B_DISCRETE_PARAMETER: 674 { 675 BDiscreteParameter &discrete = static_cast<BDiscreteParameter &>(*parameter); 676 677 if (!strcmp(discrete.Kind(), B_ENABLE) 678 || !strcmp(discrete.Kind(), B_MUTE) 679 || discrete.CountItems() == 0) { 680 // create a checkbox item 681 682 BCheckBox *checkBox = new BCheckBox(rect, discrete.Name(), discrete.Name(), NULL); 683 checkBox->ResizeToPreferred(); 684 685 return checkBox; 686 } else { 687 // create a pop up menu field 688 689 // ToDo: replace BOptionPopUp (or fix it in OpenBeOS...) 690 // this is a workaround for a bug in BOptionPopUp - you need to 691 // know the actual width before creating the object - very nice... 692 693 BFont font; 694 float width = 0; 695 for (int32 i = 0; i < discrete.CountItems(); i++) { 696 float labelWidth = font.StringWidth(discrete.ItemNameAt(i)); 697 if (labelWidth > width) 698 width = labelWidth; 699 } 700 width += font.StringWidth(discrete.Name()) + 55; 701 rect.right = rect.left + width; 702 703 BOptionPopUp *popUp = new BOptionPopUp(rect, discrete.Name(), discrete.Name(), NULL); 704 705 for (int32 i = 0; i < discrete.CountItems(); i++) { 706 popUp->AddOption(discrete.ItemNameAt(i), discrete.ItemValueAt(i)); 707 } 708 709 popUp->ResizeToPreferred(); 710 711 return popUp; 712 } 713 } 714 715 case BParameter::B_CONTINUOUS_PARAMETER: 716 { 717 BContinuousParameter &continuous = static_cast<BContinuousParameter &>(*parameter); 718 719 if (!strcmp(continuous.Kind(), B_MASTER_GAIN) 720 || !strcmp(continuous.Kind(), B_GAIN)) 721 { 722 BChannelSlider *slider = new BChannelSlider(rect, continuous.Name(), 723 continuous.Name(), NULL, B_VERTICAL, continuous.CountChannels()); 724 725 char minLabel[64], maxLabel[64]; 726 727 const char *unit = continuous.Unit(); 728 if (unit[0]) { 729 // if we have a unit, print it next to the limit values 730 sprintf(minLabel, "%g %s", continuous.MinValue(), continuous.Unit()); 731 sprintf(maxLabel, "%g %s", continuous.MaxValue(), continuous.Unit()); 732 } else { 733 sprintf(minLabel, "%g", continuous.MinValue()); 734 sprintf(maxLabel, "%g", continuous.MaxValue()); 735 } 736 slider->SetLimitLabels(minLabel, maxLabel); 737 738 float width, height; 739 slider->GetPreferredSize(&width, &height); 740 slider->ResizeTo(width, 190); 741 742 // ToDo: take BContinuousParameter::GetResponse() & ValueStep() into account! 743 744 for (int32 i = 0; i < continuous.CountChannels(); i++) 745 slider->SetLimitsFor(i, continuous.MinValue() * 1000, continuous.MaxValue() * 1000); 746 747 return slider; 748 } 749 750 BSlider *slider = new BSlider(rect, parameter->Name(), parameter->Name(), 751 NULL, 0, 100); 752 753 float width, height; 754 slider->GetPreferredSize(&width, &height); 755 slider->ResizeTo(100, height); 756 757 return slider; 758 } 759 760 default: 761 ERROR("BMediaTheme: Don't know parameter type: 0x%lx\n", parameter->Type()); 762 } 763 return NULL; 764 } 765 766