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