1 /* 2 * Copyright 2001-2020 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Stefano Ceccherini, burton666@libero.it 8 * DarkWyrm, bpmagic@columbus.rr.com 9 * Marc Flerackers, mflerackers@androme.be 10 * John Scipione, jscipione@gmail.com 11 */ 12 13 14 #include <ScrollBar.h> 15 16 #include <math.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 21 #include <ControlLook.h> 22 #include <LayoutUtils.h> 23 #include <Message.h> 24 #include <OS.h> 25 #include <Shape.h> 26 #include <Window.h> 27 28 #include <binary_compatibility/Interface.h> 29 30 31 //#define TRACE_SCROLLBAR 32 #ifdef TRACE_SCROLLBAR 33 # define TRACE(x...) printf(x) 34 #else 35 # define TRACE(x...) 36 #endif 37 38 39 typedef enum { 40 ARROW_LEFT = 0, 41 ARROW_RIGHT, 42 ARROW_UP, 43 ARROW_DOWN, 44 ARROW_NONE 45 } arrow_direction; 46 47 48 #define SBC_SCROLLBYVALUE 0 49 #define SBC_SETDOUBLE 1 50 #define SBC_SETPROPORTIONAL 2 51 #define SBC_SETSTYLE 3 52 53 // Quick constants for determining which arrow is down and are defined with 54 // respect to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of 55 // arrows and ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up 56 // and ARROW4 points right/down. 57 #define ARROW1 0 58 #define ARROW2 1 59 #define ARROW3 2 60 #define ARROW4 3 61 #define THUMB 4 62 #define NOARROW -1 63 64 65 static const bigtime_t kRepeatDelay = 300000; 66 67 68 // Because the R5 version kept a lot of data on server-side, we need to kludge 69 // our way into binary compatibility 70 class BScrollBar::Private { 71 public: 72 Private(BScrollBar* scrollBar) 73 : 74 fScrollBar(scrollBar), 75 fEnabled(true), 76 fRepeaterThread(-1), 77 fExitRepeater(false), 78 fRepeaterDelay(0), 79 fThumbFrame(0.0, 0.0, -1.0, -1.0), 80 fDoRepeat(false), 81 fClickOffset(0.0, 0.0), 82 fThumbInc(0.0), 83 fStopValue(0.0), 84 fUpArrowsEnabled(true), 85 fDownArrowsEnabled(true), 86 fBorderHighlighted(false), 87 fButtonDown(NOARROW) 88 { 89 #ifdef TEST_MODE 90 fScrollBarInfo.proportional = true; 91 fScrollBarInfo.double_arrows = true; 92 fScrollBarInfo.knob = 0; 93 fScrollBarInfo.min_knob_size = 15; 94 #else 95 get_scroll_bar_info(&fScrollBarInfo); 96 #endif 97 } 98 99 ~Private() 100 { 101 if (fRepeaterThread >= 0) { 102 status_t dummy; 103 fExitRepeater = true; 104 wait_for_thread(fRepeaterThread, &dummy); 105 } 106 } 107 108 void DrawScrollBarButton(BScrollBar* owner, arrow_direction direction, 109 BRect frame, bool down = false); 110 111 static int32 button_repeater_thread(void* data); 112 113 int32 ButtonRepeaterThread(); 114 115 BScrollBar* fScrollBar; 116 bool fEnabled; 117 118 // TODO: This should be a static, initialized by 119 // _init_interface_kit() at application startup-time, 120 // like BMenu::sMenuInfo 121 scroll_bar_info fScrollBarInfo; 122 123 thread_id fRepeaterThread; 124 volatile bool fExitRepeater; 125 bigtime_t fRepeaterDelay; 126 127 BRect fThumbFrame; 128 volatile bool fDoRepeat; 129 BPoint fClickOffset; 130 131 float fThumbInc; 132 float fStopValue; 133 134 bool fUpArrowsEnabled; 135 bool fDownArrowsEnabled; 136 137 bool fBorderHighlighted; 138 139 int8 fButtonDown; 140 }; 141 142 143 // This thread is spawned when a button is initially pushed and repeatedly scrolls 144 // the scrollbar by a little bit after a short delay 145 int32 146 BScrollBar::Private::button_repeater_thread(void* data) 147 { 148 BScrollBar::Private* privateData = (BScrollBar::Private*)data; 149 return privateData->ButtonRepeaterThread(); 150 } 151 152 153 int32 154 BScrollBar::Private::ButtonRepeaterThread() 155 { 156 // Wait a bit before auto scrolling starts. As long as the user releases 157 // and presses the button again while the repeat delay has not yet 158 // triggered, the value is pushed into the future, so we need to loop such 159 // that repeating starts at exactly the correct delay after the last 160 // button press. 161 while (fRepeaterDelay > system_time() && !fExitRepeater) 162 snooze_until(fRepeaterDelay, B_SYSTEM_TIMEBASE); 163 164 // repeat loop 165 while (!fExitRepeater) { 166 if (fScrollBar->LockLooper()) { 167 if (fDoRepeat) { 168 float value = fScrollBar->Value() + fThumbInc; 169 if (fButtonDown == NOARROW) { 170 // in this case we want to stop when we're under the mouse 171 if (fThumbInc > 0.0 && value <= fStopValue) 172 fScrollBar->SetValue(value); 173 if (fThumbInc < 0.0 && value >= fStopValue) 174 fScrollBar->SetValue(value); 175 } else 176 fScrollBar->SetValue(value); 177 } 178 179 fScrollBar->UnlockLooper(); 180 } 181 182 snooze(25000); 183 } 184 185 // tell scrollbar we're gone 186 if (fScrollBar->LockLooper()) { 187 fRepeaterThread = -1; 188 fScrollBar->UnlockLooper(); 189 } 190 191 return 0; 192 } 193 194 195 // #pragma mark - BScrollBar 196 197 198 BScrollBar::BScrollBar(BRect frame, const char* name, BView* target, 199 float min, float max, orientation direction) 200 : 201 BView(frame, name, B_FOLLOW_NONE, 202 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 203 fMin(min), 204 fMax(max), 205 fSmallStep(1.0f), 206 fLargeStep(10.0f), 207 fValue(0), 208 fProportion(0.0f), 209 fTarget(NULL), 210 fOrientation(direction) 211 { 212 SetViewColor(B_TRANSPARENT_COLOR); 213 214 fPrivateData = new BScrollBar::Private(this); 215 216 SetTarget(target); 217 SetEventMask(B_NO_POINTER_HISTORY); 218 219 _UpdateThumbFrame(); 220 _UpdateArrowButtons(); 221 222 SetResizingMode(direction == B_VERTICAL 223 ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT 224 : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 225 } 226 227 228 BScrollBar::BScrollBar(const char* name, BView* target, 229 float min, float max, orientation direction) 230 : 231 BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 232 fMin(min), 233 fMax(max), 234 fSmallStep(1.0f), 235 fLargeStep(10.0f), 236 fValue(0), 237 fProportion(0.0f), 238 fTarget(NULL), 239 fOrientation(direction) 240 { 241 SetViewColor(B_TRANSPARENT_COLOR); 242 243 fPrivateData = new BScrollBar::Private(this); 244 245 SetTarget(target); 246 SetEventMask(B_NO_POINTER_HISTORY); 247 248 _UpdateThumbFrame(); 249 _UpdateArrowButtons(); 250 } 251 252 253 BScrollBar::BScrollBar(BMessage* data) 254 : 255 BView(data), 256 fTarget(NULL) 257 { 258 fPrivateData = new BScrollBar::Private(this); 259 260 // TODO: Does the BeOS implementation try to find the target 261 // by name again? Does it archive the name at all? 262 if (data->FindFloat("_range", 0, &fMin) < B_OK) 263 fMin = 0.0f; 264 265 if (data->FindFloat("_range", 1, &fMax) < B_OK) 266 fMax = 0.0f; 267 268 if (data->FindFloat("_steps", 0, &fSmallStep) < B_OK) 269 fSmallStep = 1.0f; 270 271 if (data->FindFloat("_steps", 1, &fLargeStep) < B_OK) 272 fLargeStep = 10.0f; 273 274 if (data->FindFloat("_val", &fValue) < B_OK) 275 fValue = 0.0; 276 277 int32 orientation; 278 if (data->FindInt32("_orient", &orientation) < B_OK) { 279 fOrientation = B_VERTICAL; 280 } else 281 fOrientation = (enum orientation)orientation; 282 283 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) { 284 // just to make sure 285 SetResizingMode(fOrientation == B_VERTICAL 286 ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT 287 : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 288 } 289 290 if (data->FindFloat("_prop", &fProportion) < B_OK) 291 fProportion = 0.0; 292 293 _UpdateThumbFrame(); 294 _UpdateArrowButtons(); 295 } 296 297 298 BScrollBar::~BScrollBar() 299 { 300 SetTarget((BView*)NULL); 301 delete fPrivateData; 302 } 303 304 305 BArchivable* 306 BScrollBar::Instantiate(BMessage* data) 307 { 308 if (validate_instantiation(data, "BScrollBar")) 309 return new BScrollBar(data); 310 return NULL; 311 } 312 313 314 status_t 315 BScrollBar::Archive(BMessage* data, bool deep) const 316 { 317 status_t err = BView::Archive(data, deep); 318 if (err != B_OK) 319 return err; 320 321 err = data->AddFloat("_range", fMin); 322 if (err != B_OK) 323 return err; 324 325 err = data->AddFloat("_range", fMax); 326 if (err != B_OK) 327 return err; 328 329 err = data->AddFloat("_steps", fSmallStep); 330 if (err != B_OK) 331 return err; 332 333 err = data->AddFloat("_steps", fLargeStep); 334 if (err != B_OK) 335 return err; 336 337 err = data->AddFloat("_val", fValue); 338 if (err != B_OK) 339 return err; 340 341 err = data->AddInt32("_orient", (int32)fOrientation); 342 if (err != B_OK) 343 return err; 344 345 err = data->AddFloat("_prop", fProportion); 346 347 return err; 348 } 349 350 351 void 352 BScrollBar::AllAttached() 353 { 354 BView::AllAttached(); 355 } 356 357 358 void 359 BScrollBar::AllDetached() 360 { 361 BView::AllDetached(); 362 } 363 364 365 void 366 BScrollBar::AttachedToWindow() 367 { 368 BView::AttachedToWindow(); 369 } 370 371 372 void 373 BScrollBar::DetachedFromWindow() 374 { 375 BView::DetachedFromWindow(); 376 } 377 378 379 void 380 BScrollBar::Draw(BRect updateRect) 381 { 382 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 383 384 uint32 flags = 0; 385 bool scrollingEnabled = fMin < fMax 386 && fProportion >= 0.0f && fProportion < 1.0f; 387 if (scrollingEnabled) 388 flags |= BControlLook::B_PARTIALLY_ACTIVATED; 389 390 if (!fPrivateData->fEnabled || !scrollingEnabled) 391 flags |= BControlLook::B_DISABLED; 392 393 bool isFocused = fPrivateData->fBorderHighlighted; 394 if (isFocused) 395 flags |= BControlLook::B_FOCUSED; 396 397 bool doubleArrows = _DoubleArrows(); 398 399 // draw border 400 BRect rect = Bounds(); 401 be_control_look->DrawScrollBarBorder(this, rect, updateRect, base, flags, 402 fOrientation); 403 404 // inset past border 405 rect.InsetBy(1, 1); 406 407 // draw arrows buttons 408 BRect thumbBG = rect; 409 if (fOrientation == B_HORIZONTAL) { 410 BRect buttonFrame(rect.left, rect.top, 411 rect.left + rect.Height(), rect.bottom); 412 413 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 414 base, flags, BControlLook::B_LEFT_ARROW, fOrientation, 415 fPrivateData->fButtonDown == ARROW1); 416 417 if (doubleArrows) { 418 buttonFrame.OffsetBy(rect.Height() + 1, 0.0f); 419 be_control_look->DrawScrollBarButton(this, buttonFrame, 420 updateRect, base, flags, BControlLook::B_RIGHT_ARROW, 421 fOrientation, fPrivateData->fButtonDown == ARROW2); 422 423 buttonFrame.OffsetTo(rect.right - ((rect.Height() * 2) + 1), 424 rect.top); 425 be_control_look->DrawScrollBarButton(this, buttonFrame, 426 updateRect, base, flags, BControlLook::B_LEFT_ARROW, 427 fOrientation, fPrivateData->fButtonDown == ARROW3); 428 429 thumbBG.left += rect.Height() * 2 + 2; 430 thumbBG.right -= rect.Height() * 2 + 2; 431 } else { 432 thumbBG.left += rect.Height() + 1; 433 thumbBG.right -= rect.Height() + 1; 434 } 435 436 buttonFrame.OffsetTo(rect.right - rect.Height(), rect.top); 437 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 438 base, flags, BControlLook::B_RIGHT_ARROW, fOrientation, 439 fPrivateData->fButtonDown == ARROW4); 440 } else { 441 BRect buttonFrame(rect.left, rect.top, rect.right, 442 rect.top + rect.Width()); 443 444 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 445 base, flags, BControlLook::B_UP_ARROW, fOrientation, 446 fPrivateData->fButtonDown == ARROW1); 447 448 if (doubleArrows) { 449 buttonFrame.OffsetBy(0, rect.Width() + 1); 450 be_control_look->DrawScrollBarButton(this, buttonFrame, 451 updateRect, base, flags, BControlLook::B_DOWN_ARROW, 452 fOrientation, fPrivateData->fButtonDown == ARROW2); 453 454 buttonFrame.OffsetTo(rect.left, rect.bottom 455 - ((rect.Width() * 2) + 1)); 456 be_control_look->DrawScrollBarButton(this, buttonFrame, 457 updateRect, base, flags, BControlLook::B_UP_ARROW, 458 fOrientation, fPrivateData->fButtonDown == ARROW3); 459 460 thumbBG.top += rect.Width() * 2 + 2; 461 thumbBG.bottom -= rect.Width() * 2 + 2; 462 } else { 463 thumbBG.top += rect.Width() + 1; 464 thumbBG.bottom -= rect.Width() + 1; 465 } 466 467 buttonFrame.OffsetTo(rect.left, rect.bottom - rect.Width()); 468 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 469 base, flags, BControlLook::B_DOWN_ARROW, fOrientation, 470 fPrivateData->fButtonDown == ARROW4); 471 } 472 473 // fill background besides the thumb 474 rect = fPrivateData->fThumbFrame; 475 if (fOrientation == B_HORIZONTAL) { 476 BRect leftOfThumb(thumbBG.left, thumbBG.top, 477 rect.left - 1, thumbBG.bottom); 478 BRect rightOfThumb(rect.right + 1, thumbBG.top, 479 thumbBG.right, thumbBG.bottom); 480 be_control_look->DrawScrollBarBackground(this, leftOfThumb, 481 rightOfThumb, updateRect, base, flags, fOrientation); 482 } else { 483 BRect topOfThumb(thumbBG.left, thumbBG.top, 484 thumbBG.right, rect.top - 1); 485 BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, 486 thumbBG.right, thumbBG.bottom); 487 be_control_look->DrawScrollBarBackground(this, topOfThumb, 488 bottomOfThumb, updateRect, base, flags, fOrientation); 489 } 490 491 // draw thumb 492 rect = fPrivateData->fThumbFrame; 493 be_control_look->DrawScrollBarThumb(this, rect, updateRect, 494 ui_color(B_SCROLL_BAR_THUMB_COLOR), flags, fOrientation, 495 fPrivateData->fScrollBarInfo.knob); 496 } 497 498 499 void 500 BScrollBar::FrameMoved(BPoint newPosition) 501 { 502 BView::FrameMoved(newPosition); 503 } 504 505 506 void 507 BScrollBar::FrameResized(float newWidth, float newHeight) 508 { 509 _UpdateThumbFrame(); 510 } 511 512 513 void 514 BScrollBar::MessageReceived(BMessage* message) 515 { 516 switch(message->what) { 517 case B_VALUE_CHANGED: 518 { 519 int32 value; 520 if (message->FindInt32("value", &value) == B_OK) 521 ValueChanged(value); 522 523 break; 524 } 525 526 case B_MOUSE_WHEEL_CHANGED: 527 { 528 // Must handle this here since BView checks for the existence of 529 // scrollbars, which a scrollbar itself does not have 530 float deltaX = 0.0f; 531 float deltaY = 0.0f; 532 message->FindFloat("be:wheel_delta_x", &deltaX); 533 message->FindFloat("be:wheel_delta_y", &deltaY); 534 535 if (deltaX == 0.0f && deltaY == 0.0f) 536 break; 537 538 if (deltaX != 0.0f && deltaY == 0.0f) 539 deltaY = deltaX; 540 541 ScrollWithMouseWheelDelta(this, deltaY); 542 } 543 544 default: 545 BView::MessageReceived(message); 546 } 547 } 548 549 550 void 551 BScrollBar::MouseDown(BPoint where) 552 { 553 if (!fPrivateData->fEnabled || fMin == fMax) 554 return; 555 556 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 557 558 int32 buttons; 559 if (Looper() == NULL || Looper()->CurrentMessage() == NULL 560 || Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) { 561 buttons = B_PRIMARY_MOUSE_BUTTON; 562 } 563 564 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 565 // special absolute scrolling: move thumb to where we clicked 566 fPrivateData->fButtonDown = THUMB; 567 fPrivateData->fClickOffset 568 = fPrivateData->fThumbFrame.LeftTop() - where; 569 if (Orientation() == B_HORIZONTAL) { 570 fPrivateData->fClickOffset.x 571 = -fPrivateData->fThumbFrame.Width() / 2; 572 } else { 573 fPrivateData->fClickOffset.y 574 = -fPrivateData->fThumbFrame.Height() / 2; 575 } 576 577 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 578 return; 579 } 580 581 // hit test for the thumb 582 if (fPrivateData->fThumbFrame.Contains(where)) { 583 fPrivateData->fButtonDown = THUMB; 584 fPrivateData->fClickOffset 585 = fPrivateData->fThumbFrame.LeftTop() - where; 586 Invalidate(fPrivateData->fThumbFrame); 587 return; 588 } 589 590 // hit test for arrows or empty area 591 float scrollValue = 0.0; 592 593 // pressing the shift key scrolls faster 594 float buttonStepSize 595 = (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep; 596 597 fPrivateData->fButtonDown = _ButtonFor(where); 598 switch (fPrivateData->fButtonDown) { 599 case ARROW1: 600 scrollValue = -buttonStepSize; 601 break; 602 603 case ARROW2: 604 scrollValue = buttonStepSize; 605 break; 606 607 case ARROW3: 608 scrollValue = -buttonStepSize; 609 break; 610 611 case ARROW4: 612 scrollValue = buttonStepSize; 613 break; 614 615 case NOARROW: 616 // we hit the empty area, figure out which side of the thumb 617 if (fOrientation == B_VERTICAL) { 618 if (where.y < fPrivateData->fThumbFrame.top) 619 scrollValue = -fLargeStep; 620 else 621 scrollValue = fLargeStep; 622 } else { 623 if (where.x < fPrivateData->fThumbFrame.left) 624 scrollValue = -fLargeStep; 625 else 626 scrollValue = fLargeStep; 627 } 628 _UpdateTargetValue(where); 629 break; 630 } 631 if (scrollValue != 0.0) { 632 SetValue(fValue + scrollValue); 633 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 634 635 // launch the repeat thread 636 if (fPrivateData->fRepeaterThread == -1) { 637 fPrivateData->fExitRepeater = false; 638 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 639 fPrivateData->fThumbInc = scrollValue; 640 fPrivateData->fDoRepeat = true; 641 fPrivateData->fRepeaterThread = spawn_thread( 642 fPrivateData->button_repeater_thread, "scroll repeater", 643 B_NORMAL_PRIORITY, fPrivateData); 644 resume_thread(fPrivateData->fRepeaterThread); 645 } else { 646 fPrivateData->fExitRepeater = false; 647 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 648 fPrivateData->fDoRepeat = true; 649 } 650 } 651 } 652 653 654 void 655 BScrollBar::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 656 { 657 if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f 658 || fProportion < 0.0f) { 659 return; 660 } 661 662 if (fPrivateData->fButtonDown != NOARROW) { 663 if (fPrivateData->fButtonDown == THUMB) { 664 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 665 } else { 666 // suspend the repeating if the mouse is not over the button 667 bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains( 668 where); 669 if (fPrivateData->fDoRepeat != repeat) { 670 fPrivateData->fDoRepeat = repeat; 671 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 672 } 673 } 674 } else { 675 // update the value at which we want to stop repeating 676 if (fPrivateData->fDoRepeat) { 677 _UpdateTargetValue(where); 678 // we might have to turn arround 679 if ((fValue < fPrivateData->fStopValue 680 && fPrivateData->fThumbInc < 0) 681 || (fValue > fPrivateData->fStopValue 682 && fPrivateData->fThumbInc > 0)) { 683 fPrivateData->fThumbInc = -fPrivateData->fThumbInc; 684 } 685 } 686 } 687 } 688 689 690 void 691 BScrollBar::MouseUp(BPoint where) 692 { 693 if (fPrivateData->fButtonDown == THUMB) 694 Invalidate(fPrivateData->fThumbFrame); 695 else 696 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 697 698 fPrivateData->fButtonDown = NOARROW; 699 fPrivateData->fExitRepeater = true; 700 fPrivateData->fDoRepeat = false; 701 } 702 703 704 #if DISABLES_ON_WINDOW_DEACTIVATION 705 void 706 BScrollBar::WindowActivated(bool active) 707 { 708 fPrivateData->fEnabled = active; 709 Invalidate(); 710 } 711 #endif // DISABLES_ON_WINDOW_DEACTIVATION 712 713 714 void 715 BScrollBar::SetValue(float value) 716 { 717 if (value > fMax) 718 value = fMax; 719 else if (value < fMin) 720 value = fMin; 721 else if (isnan(value) || isinf(value)) 722 return; 723 724 value = roundf(value); 725 if (value == fValue) 726 return; 727 728 TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value); 729 730 fValue = value; 731 732 _UpdateThumbFrame(); 733 _UpdateArrowButtons(); 734 735 ValueChanged(fValue); 736 } 737 738 739 float 740 BScrollBar::Value() const 741 { 742 return fValue; 743 } 744 745 746 void 747 BScrollBar::ValueChanged(float newValue) 748 { 749 TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue); 750 751 if (fTarget != NULL) { 752 // cache target bounds 753 BRect targetBounds = fTarget->Bounds(); 754 // if vertical, check bounds top and scroll if different from newValue 755 if (fOrientation == B_VERTICAL && targetBounds.top != newValue) 756 fTarget->ScrollBy(0.0, newValue - targetBounds.top); 757 758 // if horizontal, check bounds left and scroll if different from newValue 759 if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue) 760 fTarget->ScrollBy(newValue - targetBounds.left, 0.0); 761 } 762 763 TRACE(" -> %.1f\n", newValue); 764 765 SetValue(newValue); 766 } 767 768 769 void 770 BScrollBar::SetProportion(float value) 771 { 772 if (value < 0.0f) 773 value = 0.0f; 774 else if (value > 1.0f) 775 value = 1.0f; 776 777 if (value == fProportion) 778 return; 779 780 TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value); 781 782 bool oldEnabled = fPrivateData->fEnabled && fMin < fMax 783 && fProportion < 1.0f && fProportion >= 0.0f; 784 785 fProportion = value; 786 787 bool newEnabled = fPrivateData->fEnabled && fMin < fMax 788 && fProportion < 1.0f && fProportion >= 0.0f; 789 790 _UpdateThumbFrame(); 791 792 if (oldEnabled != newEnabled) 793 Invalidate(); 794 } 795 796 797 float 798 BScrollBar::Proportion() const 799 { 800 return fProportion; 801 } 802 803 804 void 805 BScrollBar::SetRange(float min, float max) 806 { 807 if (min > max || isnanf(min) || isnanf(max) 808 || isinff(min) || isinff(max)) { 809 min = 0.0f; 810 max = 0.0f; 811 } 812 813 min = roundf(min); 814 max = roundf(max); 815 816 if (fMin == min && fMax == max) 817 return; 818 819 TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max); 820 821 fMin = min; 822 fMax = max; 823 824 if (fValue < fMin || fValue > fMax) 825 SetValue(fValue); 826 else { 827 _UpdateThumbFrame(); 828 Invalidate(); 829 } 830 } 831 832 833 void 834 BScrollBar::GetRange(float* min, float* max) const 835 { 836 if (min != NULL) 837 *min = fMin; 838 839 if (max != NULL) 840 *max = fMax; 841 } 842 843 844 void 845 BScrollBar::SetSteps(float smallStep, float largeStep) 846 { 847 // Under R5, steps can be set only after being attached to a window, 848 // probably because the data is kept server-side. We'll just remove 849 // that limitation... :P 850 851 // The BeBook also says that we need to specify an integer value even 852 // though the step values are floats. For the moment, we'll just make 853 // sure that they are integers 854 smallStep = roundf(smallStep); 855 largeStep = roundf(largeStep); 856 if (fSmallStep == smallStep && fLargeStep == largeStep) 857 return; 858 859 TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(), 860 smallStep, largeStep); 861 862 fSmallStep = smallStep; 863 fLargeStep = largeStep; 864 865 if (fProportion == 0.0) { 866 // special case, proportion is based on fLargeStep if it was never 867 // set, so it means we need to invalidate here 868 _UpdateThumbFrame(); 869 Invalidate(); 870 } 871 872 // TODO: test use of fractional values and make them work properly if 873 // they don't 874 } 875 876 877 void 878 BScrollBar::GetSteps(float* smallStep, float* largeStep) const 879 { 880 if (smallStep != NULL) 881 *smallStep = fSmallStep; 882 883 if (largeStep != NULL) 884 *largeStep = fLargeStep; 885 } 886 887 888 void 889 BScrollBar::SetTarget(BView* target) 890 { 891 if (fTarget) { 892 // unset the previous target's scrollbar pointer 893 if (fOrientation == B_VERTICAL) 894 fTarget->fVerScroller = NULL; 895 else 896 fTarget->fHorScroller = NULL; 897 } 898 899 fTarget = target; 900 if (fTarget) { 901 if (fOrientation == B_VERTICAL) 902 fTarget->fVerScroller = this; 903 else 904 fTarget->fHorScroller = this; 905 } 906 } 907 908 909 void 910 BScrollBar::SetTarget(const char* targetName) 911 { 912 // NOTE 1: BeOS implementation crashes for targetName == NULL 913 // NOTE 2: BeOS implementation also does not modify the target 914 // if it can't be found 915 if (targetName == NULL) 916 return; 917 918 if (Window() == NULL) 919 debugger("Method requires window and doesn't have one"); 920 921 BView* target = Window()->FindView(targetName); 922 if (target != NULL) 923 SetTarget(target); 924 } 925 926 927 BView* 928 BScrollBar::Target() const 929 { 930 return fTarget; 931 } 932 933 934 void 935 BScrollBar::SetOrientation(orientation direction) 936 { 937 if (fOrientation == direction) 938 return; 939 940 fOrientation = direction; 941 InvalidateLayout(); 942 Invalidate(); 943 } 944 945 946 orientation 947 BScrollBar::Orientation() const 948 { 949 return fOrientation; 950 } 951 952 953 status_t 954 BScrollBar::SetBorderHighlighted(bool highlight) 955 { 956 if (fPrivateData->fBorderHighlighted == highlight) 957 return B_OK; 958 959 fPrivateData->fBorderHighlighted = highlight; 960 961 BRect dirty(Bounds()); 962 if (fOrientation == B_HORIZONTAL) 963 dirty.bottom = dirty.top; 964 else 965 dirty.right = dirty.left; 966 967 Invalidate(dirty); 968 969 return B_OK; 970 } 971 972 973 void 974 BScrollBar::GetPreferredSize(float* _width, float* _height) 975 { 976 if (fOrientation == B_VERTICAL) { 977 if (_width) 978 *_width = B_V_SCROLL_BAR_WIDTH; 979 980 if (_height) 981 *_height = Bounds().Height(); 982 } else if (fOrientation == B_HORIZONTAL) { 983 if (_width) 984 *_width = Bounds().Width(); 985 986 if (_height) 987 *_height = B_H_SCROLL_BAR_HEIGHT; 988 } 989 } 990 991 992 void 993 BScrollBar::ResizeToPreferred() 994 { 995 BView::ResizeToPreferred(); 996 } 997 998 999 1000 void 1001 BScrollBar::MakeFocus(bool focus) 1002 { 1003 BView::MakeFocus(focus); 1004 } 1005 1006 1007 BSize 1008 BScrollBar::MinSize() 1009 { 1010 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); 1011 } 1012 1013 1014 BSize 1015 BScrollBar::MaxSize() 1016 { 1017 BSize maxSize = _MinSize(); 1018 if (fOrientation == B_HORIZONTAL) 1019 maxSize.width = B_SIZE_UNLIMITED; 1020 else 1021 maxSize.height = B_SIZE_UNLIMITED; 1022 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); 1023 } 1024 1025 1026 BSize 1027 BScrollBar::PreferredSize() 1028 { 1029 BSize preferredSize = _MinSize(); 1030 if (fOrientation == B_HORIZONTAL) 1031 preferredSize.width *= 2; 1032 else 1033 preferredSize.height *= 2; 1034 1035 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); 1036 } 1037 1038 1039 status_t 1040 BScrollBar::GetSupportedSuites(BMessage* message) 1041 { 1042 return BView::GetSupportedSuites(message); 1043 } 1044 1045 1046 BHandler* 1047 BScrollBar::ResolveSpecifier(BMessage* message, int32 index, 1048 BMessage* specifier, int32 what, const char* property) 1049 { 1050 return BView::ResolveSpecifier(message, index, specifier, what, property); 1051 } 1052 1053 1054 status_t 1055 BScrollBar::Perform(perform_code code, void* _data) 1056 { 1057 switch (code) { 1058 case PERFORM_CODE_MIN_SIZE: 1059 ((perform_data_min_size*)_data)->return_value 1060 = BScrollBar::MinSize(); 1061 1062 return B_OK; 1063 1064 case PERFORM_CODE_MAX_SIZE: 1065 ((perform_data_max_size*)_data)->return_value 1066 = BScrollBar::MaxSize(); 1067 1068 return B_OK; 1069 1070 case PERFORM_CODE_PREFERRED_SIZE: 1071 ((perform_data_preferred_size*)_data)->return_value 1072 = BScrollBar::PreferredSize(); 1073 1074 return B_OK; 1075 1076 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1077 ((perform_data_layout_alignment*)_data)->return_value 1078 = BScrollBar::LayoutAlignment(); 1079 1080 return B_OK; 1081 1082 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1083 ((perform_data_has_height_for_width*)_data)->return_value 1084 = BScrollBar::HasHeightForWidth(); 1085 1086 return B_OK; 1087 1088 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1089 { 1090 perform_data_get_height_for_width* data 1091 = (perform_data_get_height_for_width*)_data; 1092 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, 1093 &data->preferred); 1094 1095 return B_OK; 1096 } 1097 1098 case PERFORM_CODE_SET_LAYOUT: 1099 { 1100 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1101 BScrollBar::SetLayout(data->layout); 1102 1103 return B_OK; 1104 } 1105 1106 case PERFORM_CODE_LAYOUT_INVALIDATED: 1107 { 1108 perform_data_layout_invalidated* data 1109 = (perform_data_layout_invalidated*)_data; 1110 BScrollBar::LayoutInvalidated(data->descendants); 1111 1112 return B_OK; 1113 } 1114 1115 case PERFORM_CODE_DO_LAYOUT: 1116 { 1117 BScrollBar::DoLayout(); 1118 1119 return B_OK; 1120 } 1121 } 1122 1123 return BView::Perform(code, _data); 1124 } 1125 1126 1127 void BScrollBar::_ReservedScrollBar1() {} 1128 void BScrollBar::_ReservedScrollBar2() {} 1129 void BScrollBar::_ReservedScrollBar3() {} 1130 void BScrollBar::_ReservedScrollBar4() {} 1131 1132 1133 BScrollBar& 1134 BScrollBar::operator=(const BScrollBar&) 1135 { 1136 return *this; 1137 } 1138 1139 1140 bool 1141 BScrollBar::_DoubleArrows() const 1142 { 1143 if (!fPrivateData->fScrollBarInfo.double_arrows) 1144 return false; 1145 1146 // if there is not enough room, switch to single arrows even though 1147 // double arrows is specified 1148 if (fOrientation == B_HORIZONTAL) { 1149 return Bounds().Width() > (Bounds().Height() + 1) * 4 1150 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1151 } else { 1152 return Bounds().Height() > (Bounds().Width() + 1) * 4 1153 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1154 } 1155 } 1156 1157 1158 void 1159 BScrollBar::_UpdateThumbFrame() 1160 { 1161 BRect bounds = Bounds(); 1162 bounds.InsetBy(1.0, 1.0); 1163 1164 BRect oldFrame = fPrivateData->fThumbFrame; 1165 fPrivateData->fThumbFrame = bounds; 1166 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1167 float maxSize; 1168 float buttonSize; 1169 1170 // assume square buttons 1171 if (fOrientation == B_VERTICAL) { 1172 maxSize = bounds.Height(); 1173 buttonSize = bounds.Width() + 1.0; 1174 } else { 1175 maxSize = bounds.Width(); 1176 buttonSize = bounds.Height() + 1.0; 1177 } 1178 1179 if (_DoubleArrows()) { 1180 // subtract the size of four buttons 1181 maxSize -= buttonSize * 4; 1182 } else { 1183 // subtract the size of two buttons 1184 maxSize -= buttonSize * 2; 1185 } 1186 // visual adjustments (room for darker line between thumb and buttons) 1187 maxSize--; 1188 1189 float thumbSize; 1190 if (fPrivateData->fScrollBarInfo.proportional) { 1191 float proportion = fProportion; 1192 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) 1193 proportion = 1.0; 1194 1195 if (proportion == 0.0) { 1196 // Special case a proportion of 0.0, use the large step value 1197 // in that case (NOTE: fMin == fMax already handled above) 1198 // This calculation is based on the assumption that "large step" 1199 // scrolls by one "page size". 1200 proportion = fLargeStep / (2 * (fMax - fMin)); 1201 if (proportion > 1.0) 1202 proportion = 1.0; 1203 } 1204 thumbSize = maxSize * proportion; 1205 if (thumbSize < minSize) 1206 thumbSize = minSize; 1207 } else 1208 thumbSize = minSize; 1209 1210 thumbSize = floorf(thumbSize + 0.5); 1211 thumbSize--; 1212 1213 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" 1214 float offset = 0.0; 1215 if (fMax > fMin) { 1216 offset = floorf(((fValue - fMin) / (fMax - fMin)) 1217 * (maxSize - thumbSize - 1.0)); 1218 } 1219 1220 if (_DoubleArrows()) { 1221 offset += buttonSize * 2; 1222 } else 1223 offset += buttonSize; 1224 1225 // visual adjustments (room for darker line between thumb and buttons) 1226 offset++; 1227 1228 if (fOrientation == B_VERTICAL) { 1229 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top 1230 + thumbSize; 1231 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1232 } else { 1233 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left 1234 + thumbSize; 1235 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1236 } 1237 1238 if (Window() != NULL) { 1239 BRect invalid = oldFrame.IsValid() 1240 ? oldFrame | fPrivateData->fThumbFrame 1241 : fPrivateData->fThumbFrame; 1242 // account for those two dark lines 1243 if (fOrientation == B_HORIZONTAL) 1244 invalid.InsetBy(-2.0, 0.0); 1245 else 1246 invalid.InsetBy(0.0, -2.0); 1247 1248 Invalidate(invalid); 1249 } 1250 } 1251 1252 1253 float 1254 BScrollBar::_ValueFor(BPoint where) const 1255 { 1256 BRect bounds = Bounds(); 1257 bounds.InsetBy(1.0f, 1.0f); 1258 1259 float offset; 1260 float thumbSize; 1261 float maxSize; 1262 float buttonSize; 1263 1264 if (fOrientation == B_VERTICAL) { 1265 offset = where.y; 1266 thumbSize = fPrivateData->fThumbFrame.Height(); 1267 maxSize = bounds.Height(); 1268 buttonSize = bounds.Width() + 1.0f; 1269 } else { 1270 offset = where.x; 1271 thumbSize = fPrivateData->fThumbFrame.Width(); 1272 maxSize = bounds.Width(); 1273 buttonSize = bounds.Height() + 1.0f; 1274 } 1275 1276 if (_DoubleArrows()) { 1277 // subtract the size of four buttons 1278 maxSize -= buttonSize * 4; 1279 // convert point to inside of area between buttons 1280 offset -= buttonSize * 2; 1281 } else { 1282 // subtract the size of two buttons 1283 maxSize -= buttonSize * 2; 1284 // convert point to inside of area between buttons 1285 offset -= buttonSize; 1286 } 1287 // visual adjustments (room for darker line between thumb and buttons) 1288 maxSize--; 1289 offset++; 1290 1291 return roundf(fMin + (offset / (maxSize - thumbSize) 1292 * (fMax - fMin + 1.0f))); 1293 } 1294 1295 1296 int32 1297 BScrollBar::_ButtonFor(BPoint where) const 1298 { 1299 BRect bounds = Bounds(); 1300 bounds.InsetBy(1.0f, 1.0f); 1301 1302 float buttonSize = fOrientation == B_VERTICAL 1303 ? bounds.Width() + 1.0f 1304 : bounds.Height() + 1.0f; 1305 1306 BRect rect(bounds.left, bounds.top, 1307 bounds.left + buttonSize, bounds.top + buttonSize); 1308 1309 if (fOrientation == B_VERTICAL) { 1310 if (rect.Contains(where)) 1311 return ARROW1; 1312 1313 if (_DoubleArrows()) { 1314 rect.OffsetBy(0.0, buttonSize); 1315 if (rect.Contains(where)) 1316 return ARROW2; 1317 1318 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1319 if (rect.Contains(where)) 1320 return ARROW3; 1321 } 1322 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1323 if (rect.Contains(where)) 1324 return ARROW4; 1325 } else { 1326 if (rect.Contains(where)) 1327 return ARROW1; 1328 1329 if (_DoubleArrows()) { 1330 rect.OffsetBy(buttonSize, 0.0); 1331 if (rect.Contains(where)) 1332 return ARROW2; 1333 1334 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1335 if (rect.Contains(where)) 1336 return ARROW3; 1337 } 1338 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1339 if (rect.Contains(where)) 1340 return ARROW4; 1341 } 1342 1343 return NOARROW; 1344 } 1345 1346 1347 BRect 1348 BScrollBar::_ButtonRectFor(int32 button) const 1349 { 1350 BRect bounds = Bounds(); 1351 bounds.InsetBy(1.0f, 1.0f); 1352 1353 float buttonSize = fOrientation == B_VERTICAL 1354 ? bounds.Width() + 1.0f 1355 : bounds.Height() + 1.0f; 1356 1357 BRect rect(bounds.left, bounds.top, 1358 bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f); 1359 1360 if (fOrientation == B_VERTICAL) { 1361 switch (button) { 1362 case ARROW1: 1363 break; 1364 1365 case ARROW2: 1366 rect.OffsetBy(0.0, buttonSize); 1367 break; 1368 1369 case ARROW3: 1370 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1371 break; 1372 1373 case ARROW4: 1374 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1375 break; 1376 } 1377 } else { 1378 switch (button) { 1379 case ARROW1: 1380 break; 1381 1382 case ARROW2: 1383 rect.OffsetBy(buttonSize, 0.0); 1384 break; 1385 1386 case ARROW3: 1387 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1388 break; 1389 1390 case ARROW4: 1391 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1392 break; 1393 } 1394 } 1395 1396 return rect; 1397 } 1398 1399 1400 void 1401 BScrollBar::_UpdateTargetValue(BPoint where) 1402 { 1403 if (fOrientation == B_VERTICAL) { 1404 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y 1405 - fPrivateData->fThumbFrame.Height() / 2.0)); 1406 } else { 1407 fPrivateData->fStopValue = _ValueFor(BPoint(where.x 1408 - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1409 } 1410 } 1411 1412 1413 void 1414 BScrollBar::_UpdateArrowButtons() 1415 { 1416 bool upEnabled = fValue > fMin; 1417 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1418 fPrivateData->fUpArrowsEnabled = upEnabled; 1419 Invalidate(_ButtonRectFor(ARROW1)); 1420 if (_DoubleArrows()) 1421 Invalidate(_ButtonRectFor(ARROW3)); 1422 } 1423 1424 bool downEnabled = fValue < fMax; 1425 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1426 fPrivateData->fDownArrowsEnabled = downEnabled; 1427 Invalidate(_ButtonRectFor(ARROW4)); 1428 if (_DoubleArrows()) 1429 Invalidate(_ButtonRectFor(ARROW2)); 1430 } 1431 } 1432 1433 1434 status_t 1435 control_scrollbar(scroll_bar_info* info, BScrollBar* bar) 1436 { 1437 if (bar == NULL || info == NULL) 1438 return B_BAD_VALUE; 1439 1440 if (bar->fPrivateData->fScrollBarInfo.double_arrows 1441 != info->double_arrows) { 1442 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1443 1444 int8 multiplier = (info->double_arrows) ? 1 : -1; 1445 1446 if (bar->fOrientation == B_VERTICAL) { 1447 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier 1448 * B_H_SCROLL_BAR_HEIGHT); 1449 } else { 1450 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier 1451 * B_V_SCROLL_BAR_WIDTH, 0); 1452 } 1453 } 1454 1455 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1456 1457 // TODO: Figure out how proportional relates to the size of the thumb 1458 1459 // TODO: Add redraw code to reflect the changes 1460 1461 if (info->knob >= 0 && info->knob <= 2) 1462 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1463 else 1464 return B_BAD_VALUE; 1465 1466 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE 1467 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) { 1468 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1469 } else 1470 return B_BAD_VALUE; 1471 1472 return B_OK; 1473 } 1474 1475 1476 BSize 1477 BScrollBar::_MinSize() const 1478 { 1479 BSize minSize; 1480 if (fOrientation == B_HORIZONTAL) { 1481 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH 1482 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1483 minSize.height = B_H_SCROLL_BAR_HEIGHT; 1484 } else { 1485 minSize.width = B_V_SCROLL_BAR_WIDTH; 1486 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT 1487 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1488 } 1489 1490 return minSize; 1491 } 1492