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 | (fPrivateData->fButtonDown == ARROW1 415 ? BControlLook::B_ACTIVATED : 0), 416 BControlLook::B_LEFT_ARROW, fOrientation, 417 fPrivateData->fButtonDown == ARROW1); 418 419 if (doubleArrows) { 420 buttonFrame.OffsetBy(rect.Height() + 1, 0.0f); 421 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 422 base, flags | (fPrivateData->fButtonDown == ARROW2 423 ? BControlLook::B_ACTIVATED : 0), 424 BControlLook::B_RIGHT_ARROW, fOrientation, 425 fPrivateData->fButtonDown == ARROW2); 426 427 buttonFrame.OffsetTo(rect.right - ((rect.Height() * 2) + 1), 428 rect.top); 429 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 430 base, flags | (fPrivateData->fButtonDown == ARROW3 431 ? BControlLook::B_ACTIVATED : 0), 432 BControlLook::B_LEFT_ARROW, fOrientation, 433 fPrivateData->fButtonDown == ARROW3); 434 435 thumbBG.left += rect.Height() * 2 + 2; 436 thumbBG.right -= rect.Height() * 2 + 2; 437 } else { 438 thumbBG.left += rect.Height() + 1; 439 thumbBG.right -= rect.Height() + 1; 440 } 441 442 buttonFrame.OffsetTo(rect.right - rect.Height(), rect.top); 443 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 444 base, flags | (fPrivateData->fButtonDown == ARROW4 445 ? BControlLook::B_ACTIVATED : 0), 446 BControlLook::B_RIGHT_ARROW, fOrientation, 447 fPrivateData->fButtonDown == ARROW4); 448 } else { 449 BRect buttonFrame(rect.left, rect.top, rect.right, 450 rect.top + rect.Width()); 451 452 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 453 base, flags | (fPrivateData->fButtonDown == ARROW1 454 ? BControlLook::B_ACTIVATED : 0), 455 BControlLook::B_UP_ARROW, fOrientation, 456 fPrivateData->fButtonDown == ARROW1); 457 458 if (doubleArrows) { 459 buttonFrame.OffsetBy(0, rect.Width() + 1); 460 be_control_look->DrawScrollBarButton(this, buttonFrame, 461 updateRect, base, flags | (fPrivateData->fButtonDown == ARROW2 462 ? BControlLook::B_ACTIVATED : 0), 463 BControlLook::B_DOWN_ARROW, fOrientation, 464 fPrivateData->fButtonDown == ARROW2); 465 466 buttonFrame.OffsetTo(rect.left, rect.bottom 467 - ((rect.Width() * 2) + 1)); 468 be_control_look->DrawScrollBarButton(this, buttonFrame, 469 updateRect, base, flags | (fPrivateData->fButtonDown == ARROW3 470 ? BControlLook::B_ACTIVATED : 0), 471 BControlLook::B_UP_ARROW, fOrientation, 472 fPrivateData->fButtonDown == ARROW3); 473 474 thumbBG.top += rect.Width() * 2 + 2; 475 thumbBG.bottom -= rect.Width() * 2 + 2; 476 } else { 477 thumbBG.top += rect.Width() + 1; 478 thumbBG.bottom -= rect.Width() + 1; 479 } 480 481 buttonFrame.OffsetTo(rect.left, rect.bottom - rect.Width()); 482 be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, 483 base, flags | (fPrivateData->fButtonDown == ARROW4 484 ? BControlLook::B_ACTIVATED : 0), 485 BControlLook::B_DOWN_ARROW, fOrientation, 486 fPrivateData->fButtonDown == ARROW4); 487 } 488 489 // fill background besides the thumb 490 rect = fPrivateData->fThumbFrame; 491 if (fOrientation == B_HORIZONTAL) { 492 BRect leftOfThumb(thumbBG.left, thumbBG.top, 493 rect.left - 1, thumbBG.bottom); 494 BRect rightOfThumb(rect.right + 1, thumbBG.top, 495 thumbBG.right, thumbBG.bottom); 496 be_control_look->DrawScrollBarBackground(this, leftOfThumb, 497 rightOfThumb, updateRect, base, flags, fOrientation); 498 } else { 499 BRect topOfThumb(thumbBG.left, thumbBG.top, 500 thumbBG.right, rect.top - 1); 501 BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, 502 thumbBG.right, thumbBG.bottom); 503 be_control_look->DrawScrollBarBackground(this, topOfThumb, 504 bottomOfThumb, updateRect, base, flags, fOrientation); 505 } 506 507 // draw thumb 508 rect = fPrivateData->fThumbFrame; 509 be_control_look->DrawScrollBarThumb(this, rect, updateRect, 510 ui_color(B_SCROLL_BAR_THUMB_COLOR), flags, fOrientation, 511 fPrivateData->fScrollBarInfo.knob); 512 } 513 514 515 void 516 BScrollBar::FrameMoved(BPoint newPosition) 517 { 518 BView::FrameMoved(newPosition); 519 } 520 521 522 void 523 BScrollBar::FrameResized(float newWidth, float newHeight) 524 { 525 _UpdateThumbFrame(); 526 } 527 528 529 void 530 BScrollBar::MessageReceived(BMessage* message) 531 { 532 switch(message->what) { 533 case B_VALUE_CHANGED: 534 { 535 int32 value; 536 if (message->FindInt32("value", &value) == B_OK) 537 ValueChanged(value); 538 539 break; 540 } 541 542 case B_MOUSE_WHEEL_CHANGED: 543 { 544 // Must handle this here since BView checks for the existence of 545 // scrollbars, which a scrollbar itself does not have 546 float deltaX = 0.0f; 547 float deltaY = 0.0f; 548 message->FindFloat("be:wheel_delta_x", &deltaX); 549 message->FindFloat("be:wheel_delta_y", &deltaY); 550 551 if (deltaX == 0.0f && deltaY == 0.0f) 552 break; 553 554 if (deltaX != 0.0f && deltaY == 0.0f) 555 deltaY = deltaX; 556 557 ScrollWithMouseWheelDelta(this, deltaY); 558 } 559 560 default: 561 BView::MessageReceived(message); 562 } 563 } 564 565 566 void 567 BScrollBar::MouseDown(BPoint where) 568 { 569 if (!fPrivateData->fEnabled || fMin == fMax) 570 return; 571 572 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 573 574 int32 buttons; 575 if (Looper() == NULL || Looper()->CurrentMessage() == NULL 576 || Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) { 577 buttons = B_PRIMARY_MOUSE_BUTTON; 578 } 579 580 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 581 // special absolute scrolling: move thumb to where we clicked 582 fPrivateData->fButtonDown = THUMB; 583 fPrivateData->fClickOffset 584 = fPrivateData->fThumbFrame.LeftTop() - where; 585 if (Orientation() == B_HORIZONTAL) { 586 fPrivateData->fClickOffset.x 587 = -fPrivateData->fThumbFrame.Width() / 2; 588 } else { 589 fPrivateData->fClickOffset.y 590 = -fPrivateData->fThumbFrame.Height() / 2; 591 } 592 593 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 594 return; 595 } 596 597 // hit test for the thumb 598 if (fPrivateData->fThumbFrame.Contains(where)) { 599 fPrivateData->fButtonDown = THUMB; 600 fPrivateData->fClickOffset 601 = fPrivateData->fThumbFrame.LeftTop() - where; 602 Invalidate(fPrivateData->fThumbFrame); 603 return; 604 } 605 606 // hit test for arrows or empty area 607 float scrollValue = 0.0; 608 609 // pressing the shift key scrolls faster 610 float buttonStepSize 611 = (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep; 612 613 fPrivateData->fButtonDown = _ButtonFor(where); 614 switch (fPrivateData->fButtonDown) { 615 case ARROW1: 616 scrollValue = -buttonStepSize; 617 break; 618 619 case ARROW2: 620 scrollValue = buttonStepSize; 621 break; 622 623 case ARROW3: 624 scrollValue = -buttonStepSize; 625 break; 626 627 case ARROW4: 628 scrollValue = buttonStepSize; 629 break; 630 631 case NOARROW: 632 // we hit the empty area, figure out which side of the thumb 633 if (fOrientation == B_VERTICAL) { 634 if (where.y < fPrivateData->fThumbFrame.top) 635 scrollValue = -fLargeStep; 636 else 637 scrollValue = fLargeStep; 638 } else { 639 if (where.x < fPrivateData->fThumbFrame.left) 640 scrollValue = -fLargeStep; 641 else 642 scrollValue = fLargeStep; 643 } 644 _UpdateTargetValue(where); 645 break; 646 } 647 if (scrollValue != 0.0) { 648 SetValue(fValue + scrollValue); 649 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 650 651 // launch the repeat thread 652 if (fPrivateData->fRepeaterThread == -1) { 653 fPrivateData->fExitRepeater = false; 654 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 655 fPrivateData->fThumbInc = scrollValue; 656 fPrivateData->fDoRepeat = true; 657 fPrivateData->fRepeaterThread = spawn_thread( 658 fPrivateData->button_repeater_thread, "scroll repeater", 659 B_NORMAL_PRIORITY, fPrivateData); 660 resume_thread(fPrivateData->fRepeaterThread); 661 } else { 662 fPrivateData->fExitRepeater = false; 663 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 664 fPrivateData->fDoRepeat = true; 665 } 666 } 667 } 668 669 670 void 671 BScrollBar::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) 672 { 673 if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f 674 || fProportion < 0.0f) { 675 return; 676 } 677 678 if (fPrivateData->fButtonDown != NOARROW) { 679 if (fPrivateData->fButtonDown == THUMB) { 680 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 681 } else { 682 // suspend the repeating if the mouse is not over the button 683 bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains( 684 where); 685 if (fPrivateData->fDoRepeat != repeat) { 686 fPrivateData->fDoRepeat = repeat; 687 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 688 } 689 } 690 } else { 691 // update the value at which we want to stop repeating 692 if (fPrivateData->fDoRepeat) { 693 _UpdateTargetValue(where); 694 // we might have to turn arround 695 if ((fValue < fPrivateData->fStopValue 696 && fPrivateData->fThumbInc < 0) 697 || (fValue > fPrivateData->fStopValue 698 && fPrivateData->fThumbInc > 0)) { 699 fPrivateData->fThumbInc = -fPrivateData->fThumbInc; 700 } 701 } 702 } 703 } 704 705 706 void 707 BScrollBar::MouseUp(BPoint where) 708 { 709 if (fPrivateData->fButtonDown == THUMB) 710 Invalidate(fPrivateData->fThumbFrame); 711 else 712 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 713 714 fPrivateData->fButtonDown = NOARROW; 715 fPrivateData->fExitRepeater = true; 716 fPrivateData->fDoRepeat = false; 717 } 718 719 720 #if DISABLES_ON_WINDOW_DEACTIVATION 721 void 722 BScrollBar::WindowActivated(bool active) 723 { 724 fPrivateData->fEnabled = active; 725 Invalidate(); 726 } 727 #endif // DISABLES_ON_WINDOW_DEACTIVATION 728 729 730 void 731 BScrollBar::SetValue(float value) 732 { 733 if (value > fMax) 734 value = fMax; 735 else if (value < fMin) 736 value = fMin; 737 else if (isnan(value) || isinf(value)) 738 return; 739 740 value = roundf(value); 741 if (value == fValue) 742 return; 743 744 TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value); 745 746 fValue = value; 747 748 _UpdateThumbFrame(); 749 _UpdateArrowButtons(); 750 751 ValueChanged(fValue); 752 } 753 754 755 float 756 BScrollBar::Value() const 757 { 758 return fValue; 759 } 760 761 762 void 763 BScrollBar::ValueChanged(float newValue) 764 { 765 TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue); 766 767 if (fTarget != NULL) { 768 // cache target bounds 769 BRect targetBounds = fTarget->Bounds(); 770 // if vertical, check bounds top and scroll if different from newValue 771 if (fOrientation == B_VERTICAL && targetBounds.top != newValue) 772 fTarget->ScrollBy(0.0, newValue - targetBounds.top); 773 774 // if horizontal, check bounds left and scroll if different from newValue 775 if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue) 776 fTarget->ScrollBy(newValue - targetBounds.left, 0.0); 777 } 778 779 TRACE(" -> %.1f\n", newValue); 780 781 SetValue(newValue); 782 } 783 784 785 void 786 BScrollBar::SetProportion(float value) 787 { 788 if (value < 0.0f) 789 value = 0.0f; 790 else if (value > 1.0f) 791 value = 1.0f; 792 793 if (value == fProportion) 794 return; 795 796 TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value); 797 798 bool oldEnabled = fPrivateData->fEnabled && fMin < fMax 799 && fProportion < 1.0f && fProportion >= 0.0f; 800 801 fProportion = value; 802 803 bool newEnabled = fPrivateData->fEnabled && fMin < fMax 804 && fProportion < 1.0f && fProportion >= 0.0f; 805 806 _UpdateThumbFrame(); 807 808 if (oldEnabled != newEnabled) 809 Invalidate(); 810 } 811 812 813 float 814 BScrollBar::Proportion() const 815 { 816 return fProportion; 817 } 818 819 820 void 821 BScrollBar::SetRange(float min, float max) 822 { 823 if (min > max || isnanf(min) || isnanf(max) 824 || isinff(min) || isinff(max)) { 825 min = 0.0f; 826 max = 0.0f; 827 } 828 829 min = roundf(min); 830 max = roundf(max); 831 832 if (fMin == min && fMax == max) 833 return; 834 835 TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max); 836 837 fMin = min; 838 fMax = max; 839 840 if (fValue < fMin || fValue > fMax) 841 SetValue(fValue); 842 else { 843 _UpdateThumbFrame(); 844 Invalidate(); 845 } 846 } 847 848 849 void 850 BScrollBar::GetRange(float* min, float* max) const 851 { 852 if (min != NULL) 853 *min = fMin; 854 855 if (max != NULL) 856 *max = fMax; 857 } 858 859 860 void 861 BScrollBar::SetSteps(float smallStep, float largeStep) 862 { 863 // Under R5, steps can be set only after being attached to a window, 864 // probably because the data is kept server-side. We'll just remove 865 // that limitation... :P 866 867 // The BeBook also says that we need to specify an integer value even 868 // though the step values are floats. For the moment, we'll just make 869 // sure that they are integers 870 smallStep = roundf(smallStep); 871 largeStep = roundf(largeStep); 872 if (fSmallStep == smallStep && fLargeStep == largeStep) 873 return; 874 875 TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(), 876 smallStep, largeStep); 877 878 fSmallStep = smallStep; 879 fLargeStep = largeStep; 880 881 if (fProportion == 0.0) { 882 // special case, proportion is based on fLargeStep if it was never 883 // set, so it means we need to invalidate here 884 _UpdateThumbFrame(); 885 Invalidate(); 886 } 887 888 // TODO: test use of fractional values and make them work properly if 889 // they don't 890 } 891 892 893 void 894 BScrollBar::GetSteps(float* smallStep, float* largeStep) const 895 { 896 if (smallStep != NULL) 897 *smallStep = fSmallStep; 898 899 if (largeStep != NULL) 900 *largeStep = fLargeStep; 901 } 902 903 904 void 905 BScrollBar::SetTarget(BView* target) 906 { 907 if (fTarget) { 908 // unset the previous target's scrollbar pointer 909 if (fOrientation == B_VERTICAL) 910 fTarget->fVerScroller = NULL; 911 else 912 fTarget->fHorScroller = NULL; 913 } 914 915 fTarget = target; 916 if (fTarget) { 917 if (fOrientation == B_VERTICAL) 918 fTarget->fVerScroller = this; 919 else 920 fTarget->fHorScroller = this; 921 } 922 } 923 924 925 void 926 BScrollBar::SetTarget(const char* targetName) 927 { 928 // NOTE 1: BeOS implementation crashes for targetName == NULL 929 // NOTE 2: BeOS implementation also does not modify the target 930 // if it can't be found 931 if (targetName == NULL) 932 return; 933 934 if (Window() == NULL) 935 debugger("Method requires window and doesn't have one"); 936 937 BView* target = Window()->FindView(targetName); 938 if (target != NULL) 939 SetTarget(target); 940 } 941 942 943 BView* 944 BScrollBar::Target() const 945 { 946 return fTarget; 947 } 948 949 950 void 951 BScrollBar::SetOrientation(orientation direction) 952 { 953 if (fOrientation == direction) 954 return; 955 956 fOrientation = direction; 957 InvalidateLayout(); 958 Invalidate(); 959 } 960 961 962 orientation 963 BScrollBar::Orientation() const 964 { 965 return fOrientation; 966 } 967 968 969 status_t 970 BScrollBar::SetBorderHighlighted(bool highlight) 971 { 972 if (fPrivateData->fBorderHighlighted == highlight) 973 return B_OK; 974 975 fPrivateData->fBorderHighlighted = highlight; 976 977 BRect dirty(Bounds()); 978 if (fOrientation == B_HORIZONTAL) 979 dirty.bottom = dirty.top; 980 else 981 dirty.right = dirty.left; 982 983 Invalidate(dirty); 984 985 return B_OK; 986 } 987 988 989 void 990 BScrollBar::GetPreferredSize(float* _width, float* _height) 991 { 992 if (fOrientation == B_VERTICAL) { 993 if (_width) 994 *_width = B_V_SCROLL_BAR_WIDTH; 995 996 if (_height) 997 *_height = Bounds().Height(); 998 } else if (fOrientation == B_HORIZONTAL) { 999 if (_width) 1000 *_width = Bounds().Width(); 1001 1002 if (_height) 1003 *_height = B_H_SCROLL_BAR_HEIGHT; 1004 } 1005 } 1006 1007 1008 void 1009 BScrollBar::ResizeToPreferred() 1010 { 1011 BView::ResizeToPreferred(); 1012 } 1013 1014 1015 1016 void 1017 BScrollBar::MakeFocus(bool focus) 1018 { 1019 BView::MakeFocus(focus); 1020 } 1021 1022 1023 BSize 1024 BScrollBar::MinSize() 1025 { 1026 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); 1027 } 1028 1029 1030 BSize 1031 BScrollBar::MaxSize() 1032 { 1033 BSize maxSize = _MinSize(); 1034 if (fOrientation == B_HORIZONTAL) 1035 maxSize.width = B_SIZE_UNLIMITED; 1036 else 1037 maxSize.height = B_SIZE_UNLIMITED; 1038 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); 1039 } 1040 1041 1042 BSize 1043 BScrollBar::PreferredSize() 1044 { 1045 BSize preferredSize = _MinSize(); 1046 if (fOrientation == B_HORIZONTAL) 1047 preferredSize.width *= 2; 1048 else 1049 preferredSize.height *= 2; 1050 1051 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); 1052 } 1053 1054 1055 status_t 1056 BScrollBar::GetSupportedSuites(BMessage* message) 1057 { 1058 return BView::GetSupportedSuites(message); 1059 } 1060 1061 1062 BHandler* 1063 BScrollBar::ResolveSpecifier(BMessage* message, int32 index, 1064 BMessage* specifier, int32 what, const char* property) 1065 { 1066 return BView::ResolveSpecifier(message, index, specifier, what, property); 1067 } 1068 1069 1070 status_t 1071 BScrollBar::Perform(perform_code code, void* _data) 1072 { 1073 switch (code) { 1074 case PERFORM_CODE_MIN_SIZE: 1075 ((perform_data_min_size*)_data)->return_value 1076 = BScrollBar::MinSize(); 1077 1078 return B_OK; 1079 1080 case PERFORM_CODE_MAX_SIZE: 1081 ((perform_data_max_size*)_data)->return_value 1082 = BScrollBar::MaxSize(); 1083 1084 return B_OK; 1085 1086 case PERFORM_CODE_PREFERRED_SIZE: 1087 ((perform_data_preferred_size*)_data)->return_value 1088 = BScrollBar::PreferredSize(); 1089 1090 return B_OK; 1091 1092 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1093 ((perform_data_layout_alignment*)_data)->return_value 1094 = BScrollBar::LayoutAlignment(); 1095 1096 return B_OK; 1097 1098 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1099 ((perform_data_has_height_for_width*)_data)->return_value 1100 = BScrollBar::HasHeightForWidth(); 1101 1102 return B_OK; 1103 1104 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1105 { 1106 perform_data_get_height_for_width* data 1107 = (perform_data_get_height_for_width*)_data; 1108 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, 1109 &data->preferred); 1110 1111 return B_OK; 1112 } 1113 1114 case PERFORM_CODE_SET_LAYOUT: 1115 { 1116 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1117 BScrollBar::SetLayout(data->layout); 1118 1119 return B_OK; 1120 } 1121 1122 case PERFORM_CODE_LAYOUT_INVALIDATED: 1123 { 1124 perform_data_layout_invalidated* data 1125 = (perform_data_layout_invalidated*)_data; 1126 BScrollBar::LayoutInvalidated(data->descendants); 1127 1128 return B_OK; 1129 } 1130 1131 case PERFORM_CODE_DO_LAYOUT: 1132 { 1133 BScrollBar::DoLayout(); 1134 1135 return B_OK; 1136 } 1137 } 1138 1139 return BView::Perform(code, _data); 1140 } 1141 1142 1143 void BScrollBar::_ReservedScrollBar1() {} 1144 void BScrollBar::_ReservedScrollBar2() {} 1145 void BScrollBar::_ReservedScrollBar3() {} 1146 void BScrollBar::_ReservedScrollBar4() {} 1147 1148 1149 BScrollBar& 1150 BScrollBar::operator=(const BScrollBar&) 1151 { 1152 return *this; 1153 } 1154 1155 1156 bool 1157 BScrollBar::_DoubleArrows() const 1158 { 1159 if (!fPrivateData->fScrollBarInfo.double_arrows) 1160 return false; 1161 1162 // if there is not enough room, switch to single arrows even though 1163 // double arrows is specified 1164 if (fOrientation == B_HORIZONTAL) { 1165 return Bounds().Width() > (Bounds().Height() + 1) * 4 1166 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1167 } else { 1168 return Bounds().Height() > (Bounds().Width() + 1) * 4 1169 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1170 } 1171 } 1172 1173 1174 void 1175 BScrollBar::_UpdateThumbFrame() 1176 { 1177 BRect bounds = Bounds(); 1178 bounds.InsetBy(1.0, 1.0); 1179 1180 BRect oldFrame = fPrivateData->fThumbFrame; 1181 fPrivateData->fThumbFrame = bounds; 1182 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1183 float maxSize; 1184 float buttonSize; 1185 1186 // assume square buttons 1187 if (fOrientation == B_VERTICAL) { 1188 maxSize = bounds.Height(); 1189 buttonSize = bounds.Width() + 1.0; 1190 } else { 1191 maxSize = bounds.Width(); 1192 buttonSize = bounds.Height() + 1.0; 1193 } 1194 1195 if (_DoubleArrows()) { 1196 // subtract the size of four buttons 1197 maxSize -= buttonSize * 4; 1198 } else { 1199 // subtract the size of two buttons 1200 maxSize -= buttonSize * 2; 1201 } 1202 // visual adjustments (room for darker line between thumb and buttons) 1203 maxSize--; 1204 1205 float thumbSize; 1206 if (fPrivateData->fScrollBarInfo.proportional) { 1207 float proportion = fProportion; 1208 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) 1209 proportion = 1.0; 1210 1211 if (proportion == 0.0) { 1212 // Special case a proportion of 0.0, use the large step value 1213 // in that case (NOTE: fMin == fMax already handled above) 1214 // This calculation is based on the assumption that "large step" 1215 // scrolls by one "page size". 1216 proportion = fLargeStep / (2 * (fMax - fMin)); 1217 if (proportion > 1.0) 1218 proportion = 1.0; 1219 } 1220 thumbSize = maxSize * proportion; 1221 if (thumbSize < minSize) 1222 thumbSize = minSize; 1223 } else 1224 thumbSize = minSize; 1225 1226 thumbSize = floorf(thumbSize + 0.5); 1227 thumbSize--; 1228 1229 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" 1230 float offset = 0.0; 1231 if (fMax > fMin) { 1232 offset = floorf(((fValue - fMin) / (fMax - fMin)) 1233 * (maxSize - thumbSize - 1.0)); 1234 } 1235 1236 if (_DoubleArrows()) { 1237 offset += buttonSize * 2; 1238 } else 1239 offset += buttonSize; 1240 1241 // visual adjustments (room for darker line between thumb and buttons) 1242 offset++; 1243 1244 if (fOrientation == B_VERTICAL) { 1245 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top 1246 + thumbSize; 1247 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1248 } else { 1249 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left 1250 + thumbSize; 1251 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1252 } 1253 1254 if (Window() != NULL) { 1255 BRect invalid = oldFrame.IsValid() 1256 ? oldFrame | fPrivateData->fThumbFrame 1257 : fPrivateData->fThumbFrame; 1258 // account for those two dark lines 1259 if (fOrientation == B_HORIZONTAL) 1260 invalid.InsetBy(-2.0, 0.0); 1261 else 1262 invalid.InsetBy(0.0, -2.0); 1263 1264 Invalidate(invalid); 1265 } 1266 } 1267 1268 1269 float 1270 BScrollBar::_ValueFor(BPoint where) const 1271 { 1272 BRect bounds = Bounds(); 1273 bounds.InsetBy(1.0f, 1.0f); 1274 1275 float offset; 1276 float thumbSize; 1277 float maxSize; 1278 float buttonSize; 1279 1280 if (fOrientation == B_VERTICAL) { 1281 offset = where.y; 1282 thumbSize = fPrivateData->fThumbFrame.Height(); 1283 maxSize = bounds.Height(); 1284 buttonSize = bounds.Width() + 1.0f; 1285 } else { 1286 offset = where.x; 1287 thumbSize = fPrivateData->fThumbFrame.Width(); 1288 maxSize = bounds.Width(); 1289 buttonSize = bounds.Height() + 1.0f; 1290 } 1291 1292 if (_DoubleArrows()) { 1293 // subtract the size of four buttons 1294 maxSize -= buttonSize * 4; 1295 // convert point to inside of area between buttons 1296 offset -= buttonSize * 2; 1297 } else { 1298 // subtract the size of two buttons 1299 maxSize -= buttonSize * 2; 1300 // convert point to inside of area between buttons 1301 offset -= buttonSize; 1302 } 1303 // visual adjustments (room for darker line between thumb and buttons) 1304 maxSize--; 1305 offset++; 1306 1307 return roundf(fMin + (offset / (maxSize - thumbSize) 1308 * (fMax - fMin + 1.0f))); 1309 } 1310 1311 1312 int32 1313 BScrollBar::_ButtonFor(BPoint where) const 1314 { 1315 BRect bounds = Bounds(); 1316 bounds.InsetBy(1.0f, 1.0f); 1317 1318 float buttonSize = fOrientation == B_VERTICAL 1319 ? bounds.Width() + 1.0f 1320 : bounds.Height() + 1.0f; 1321 1322 BRect rect(bounds.left, bounds.top, 1323 bounds.left + buttonSize, bounds.top + buttonSize); 1324 1325 if (fOrientation == B_VERTICAL) { 1326 if (rect.Contains(where)) 1327 return ARROW1; 1328 1329 if (_DoubleArrows()) { 1330 rect.OffsetBy(0.0, buttonSize); 1331 if (rect.Contains(where)) 1332 return ARROW2; 1333 1334 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1335 if (rect.Contains(where)) 1336 return ARROW3; 1337 } 1338 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1339 if (rect.Contains(where)) 1340 return ARROW4; 1341 } else { 1342 if (rect.Contains(where)) 1343 return ARROW1; 1344 1345 if (_DoubleArrows()) { 1346 rect.OffsetBy(buttonSize, 0.0); 1347 if (rect.Contains(where)) 1348 return ARROW2; 1349 1350 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1351 if (rect.Contains(where)) 1352 return ARROW3; 1353 } 1354 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1355 if (rect.Contains(where)) 1356 return ARROW4; 1357 } 1358 1359 return NOARROW; 1360 } 1361 1362 1363 BRect 1364 BScrollBar::_ButtonRectFor(int32 button) const 1365 { 1366 BRect bounds = Bounds(); 1367 bounds.InsetBy(1.0f, 1.0f); 1368 1369 float buttonSize = fOrientation == B_VERTICAL 1370 ? bounds.Width() + 1.0f 1371 : bounds.Height() + 1.0f; 1372 1373 BRect rect(bounds.left, bounds.top, 1374 bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f); 1375 1376 if (fOrientation == B_VERTICAL) { 1377 switch (button) { 1378 case ARROW1: 1379 break; 1380 1381 case ARROW2: 1382 rect.OffsetBy(0.0, buttonSize); 1383 break; 1384 1385 case ARROW3: 1386 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1387 break; 1388 1389 case ARROW4: 1390 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1391 break; 1392 } 1393 } else { 1394 switch (button) { 1395 case ARROW1: 1396 break; 1397 1398 case ARROW2: 1399 rect.OffsetBy(buttonSize, 0.0); 1400 break; 1401 1402 case ARROW3: 1403 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1404 break; 1405 1406 case ARROW4: 1407 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1408 break; 1409 } 1410 } 1411 1412 return rect; 1413 } 1414 1415 1416 void 1417 BScrollBar::_UpdateTargetValue(BPoint where) 1418 { 1419 if (fOrientation == B_VERTICAL) { 1420 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y 1421 - fPrivateData->fThumbFrame.Height() / 2.0)); 1422 } else { 1423 fPrivateData->fStopValue = _ValueFor(BPoint(where.x 1424 - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1425 } 1426 } 1427 1428 1429 void 1430 BScrollBar::_UpdateArrowButtons() 1431 { 1432 bool upEnabled = fValue > fMin; 1433 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1434 fPrivateData->fUpArrowsEnabled = upEnabled; 1435 Invalidate(_ButtonRectFor(ARROW1)); 1436 if (_DoubleArrows()) 1437 Invalidate(_ButtonRectFor(ARROW3)); 1438 } 1439 1440 bool downEnabled = fValue < fMax; 1441 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1442 fPrivateData->fDownArrowsEnabled = downEnabled; 1443 Invalidate(_ButtonRectFor(ARROW4)); 1444 if (_DoubleArrows()) 1445 Invalidate(_ButtonRectFor(ARROW2)); 1446 } 1447 } 1448 1449 1450 status_t 1451 control_scrollbar(scroll_bar_info* info, BScrollBar* bar) 1452 { 1453 if (bar == NULL || info == NULL) 1454 return B_BAD_VALUE; 1455 1456 if (bar->fPrivateData->fScrollBarInfo.double_arrows 1457 != info->double_arrows) { 1458 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1459 1460 int8 multiplier = (info->double_arrows) ? 1 : -1; 1461 1462 if (bar->fOrientation == B_VERTICAL) { 1463 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier 1464 * B_H_SCROLL_BAR_HEIGHT); 1465 } else { 1466 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier 1467 * B_V_SCROLL_BAR_WIDTH, 0); 1468 } 1469 } 1470 1471 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1472 1473 // TODO: Figure out how proportional relates to the size of the thumb 1474 1475 // TODO: Add redraw code to reflect the changes 1476 1477 if (info->knob >= 0 && info->knob <= 2) 1478 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1479 else 1480 return B_BAD_VALUE; 1481 1482 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE 1483 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) { 1484 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1485 } else 1486 return B_BAD_VALUE; 1487 1488 return B_OK; 1489 } 1490 1491 1492 BSize 1493 BScrollBar::_MinSize() const 1494 { 1495 BSize minSize; 1496 if (fOrientation == B_HORIZONTAL) { 1497 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH 1498 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1499 minSize.height = B_H_SCROLL_BAR_HEIGHT; 1500 } else { 1501 minSize.width = B_V_SCROLL_BAR_WIDTH; 1502 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT 1503 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1504 } 1505 1506 return minSize; 1507 } 1508