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(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 rgb_color thumbColor = ui_color(B_SCROLL_BAR_THUMB_COLOR); 969 970 // Draw scroll thumb 971 if (enabled) { 972 // fill the clickable surface of the thumb 973 be_control_look->DrawButtonBackground(this, rect, updateRect, 974 thumbColor, 0, BControlLook::B_ALL_BORDERS, fOrientation); 975 // TODO: Add the other thumb styles - dots and lines 976 } else { 977 if (fMin >= fMax || fProportion >= 1.0 || fProportion < 0.0) { 978 // we cannot scroll at all 979 _DrawDisabledBackground(thumbBG, light, dark, dark1); 980 } else { 981 // we could scroll, but we're simply disabled 982 float bgTint = 1.06; 983 rgb_color bgLight = tint_color(light, bgTint * 3); 984 rgb_color bgShadow = tint_color(dark, bgTint); 985 rgb_color bgFill = tint_color(dark1, bgTint); 986 if (fOrientation == B_HORIZONTAL) { 987 // left of thumb 988 BRect besidesThumb(thumbBG); 989 besidesThumb.right = rect.left - 1; 990 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 991 // right of thumb 992 besidesThumb.left = rect.right + 1; 993 besidesThumb.right = thumbBG.right; 994 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 995 } else { 996 // above thumb 997 BRect besidesThumb(thumbBG); 998 besidesThumb.bottom = rect.top - 1; 999 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1000 // below thumb 1001 besidesThumb.top = rect.bottom + 1; 1002 besidesThumb.bottom = thumbBG.bottom; 1003 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1004 } 1005 // thumb bevel 1006 BeginLineArray(4); 1007 AddLine(BPoint(rect.left, rect.bottom), 1008 BPoint(rect.left, rect.top), light); 1009 AddLine(BPoint(rect.left + 1, rect.top), 1010 BPoint(rect.right, rect.top), light); 1011 AddLine(BPoint(rect.right, rect.top + 1), 1012 BPoint(rect.right, rect.bottom), dark2); 1013 AddLine(BPoint(rect.right - 1, rect.bottom), 1014 BPoint(rect.left + 1, rect.bottom), dark2); 1015 EndLineArray(); 1016 // thumb fill 1017 rect.InsetBy(1.0, 1.0); 1018 SetHighColor(dark1); 1019 FillRect(rect); 1020 } 1021 } 1022 } 1023 1024 1025 void 1026 BScrollBar::FrameMoved(BPoint newPosition) 1027 { 1028 BView::FrameMoved(newPosition); 1029 } 1030 1031 1032 void 1033 BScrollBar::FrameResized(float newWidth, float newHeight) 1034 { 1035 _UpdateThumbFrame(); 1036 } 1037 1038 1039 BHandler* 1040 BScrollBar::ResolveSpecifier(BMessage* message, int32 index, 1041 BMessage* specifier, int32 form, const char *property) 1042 { 1043 return BView::ResolveSpecifier(message, index, specifier, form, property); 1044 } 1045 1046 1047 void 1048 BScrollBar::ResizeToPreferred() 1049 { 1050 BView::ResizeToPreferred(); 1051 } 1052 1053 1054 void 1055 BScrollBar::GetPreferredSize(float* _width, float* _height) 1056 { 1057 if (fOrientation == B_VERTICAL) { 1058 if (_width) 1059 *_width = B_V_SCROLL_BAR_WIDTH; 1060 if (_height) 1061 *_height = Bounds().Height(); 1062 } else if (fOrientation == B_HORIZONTAL) { 1063 if (_width) 1064 *_width = Bounds().Width(); 1065 if (_height) 1066 *_height = B_H_SCROLL_BAR_HEIGHT; 1067 } 1068 } 1069 1070 1071 void 1072 BScrollBar::MakeFocus(bool state) 1073 { 1074 BView::MakeFocus(state); 1075 } 1076 1077 1078 void 1079 BScrollBar::AllAttached() 1080 { 1081 BView::AllAttached(); 1082 } 1083 1084 1085 void 1086 BScrollBar::AllDetached() 1087 { 1088 BView::AllDetached(); 1089 } 1090 1091 1092 status_t 1093 BScrollBar::GetSupportedSuites(BMessage *message) 1094 { 1095 return BView::GetSupportedSuites(message); 1096 } 1097 1098 1099 BSize 1100 BScrollBar::MinSize() 1101 { 1102 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); 1103 } 1104 1105 1106 BSize 1107 BScrollBar::MaxSize() 1108 { 1109 BSize maxSize = _MinSize(); 1110 if (fOrientation == B_HORIZONTAL) 1111 maxSize.width = B_SIZE_UNLIMITED; 1112 else 1113 maxSize.height = B_SIZE_UNLIMITED; 1114 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); 1115 } 1116 1117 1118 BSize 1119 BScrollBar::PreferredSize() 1120 { 1121 BSize preferredSize = _MinSize(); 1122 if (fOrientation == B_HORIZONTAL) 1123 preferredSize.width *= 2; 1124 else 1125 preferredSize.height *= 2; 1126 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); 1127 } 1128 1129 1130 status_t 1131 BScrollBar::Perform(perform_code code, void* _data) 1132 { 1133 switch (code) { 1134 case PERFORM_CODE_MIN_SIZE: 1135 ((perform_data_min_size*)_data)->return_value 1136 = BScrollBar::MinSize(); 1137 return B_OK; 1138 case PERFORM_CODE_MAX_SIZE: 1139 ((perform_data_max_size*)_data)->return_value 1140 = BScrollBar::MaxSize(); 1141 return B_OK; 1142 case PERFORM_CODE_PREFERRED_SIZE: 1143 ((perform_data_preferred_size*)_data)->return_value 1144 = BScrollBar::PreferredSize(); 1145 return B_OK; 1146 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1147 ((perform_data_layout_alignment*)_data)->return_value 1148 = BScrollBar::LayoutAlignment(); 1149 return B_OK; 1150 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1151 ((perform_data_has_height_for_width*)_data)->return_value 1152 = BScrollBar::HasHeightForWidth(); 1153 return B_OK; 1154 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1155 { 1156 perform_data_get_height_for_width* data 1157 = (perform_data_get_height_for_width*)_data; 1158 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, 1159 &data->preferred); 1160 return B_OK; 1161 } 1162 case PERFORM_CODE_SET_LAYOUT: 1163 { 1164 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1165 BScrollBar::SetLayout(data->layout); 1166 return B_OK; 1167 } 1168 case PERFORM_CODE_LAYOUT_INVALIDATED: 1169 { 1170 perform_data_layout_invalidated* data 1171 = (perform_data_layout_invalidated*)_data; 1172 BScrollBar::LayoutInvalidated(data->descendants); 1173 return B_OK; 1174 } 1175 case PERFORM_CODE_DO_LAYOUT: 1176 { 1177 BScrollBar::DoLayout(); 1178 return B_OK; 1179 } 1180 } 1181 1182 return BView::Perform(code, _data); 1183 } 1184 1185 1186 #if DISABLES_ON_WINDOW_DEACTIVATION 1187 void 1188 BScrollBar::WindowActivated(bool active) 1189 { 1190 fPrivateData->fEnabled = active; 1191 Invalidate(); 1192 } 1193 #endif // DISABLES_ON_WINDOW_DEACTIVATION 1194 1195 1196 void BScrollBar::_ReservedScrollBar1() {} 1197 void BScrollBar::_ReservedScrollBar2() {} 1198 void BScrollBar::_ReservedScrollBar3() {} 1199 void BScrollBar::_ReservedScrollBar4() {} 1200 1201 1202 1203 BScrollBar& 1204 BScrollBar::operator=(const BScrollBar&) 1205 { 1206 return *this; 1207 } 1208 1209 1210 bool 1211 BScrollBar::_DoubleArrows() const 1212 { 1213 if (!fPrivateData->fScrollBarInfo.double_arrows) 1214 return false; 1215 1216 // if there is not enough room, switch to single arrows even though 1217 // double arrows is specified 1218 if (fOrientation == B_HORIZONTAL) { 1219 return Bounds().Width() > (Bounds().Height() + 1) * 4 1220 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1221 } else { 1222 return Bounds().Height() > (Bounds().Width() + 1) * 4 1223 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1224 } 1225 } 1226 1227 1228 void 1229 BScrollBar::_UpdateThumbFrame() 1230 { 1231 BRect bounds = Bounds(); 1232 bounds.InsetBy(1.0, 1.0); 1233 1234 BRect oldFrame = fPrivateData->fThumbFrame; 1235 fPrivateData->fThumbFrame = bounds; 1236 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1237 float maxSize; 1238 float buttonSize; 1239 1240 // assume square buttons 1241 if (fOrientation == B_VERTICAL) { 1242 maxSize = bounds.Height(); 1243 buttonSize = bounds.Width() + 1.0; 1244 } else { 1245 maxSize = bounds.Width(); 1246 buttonSize = bounds.Height() + 1.0; 1247 } 1248 1249 if (_DoubleArrows()) { 1250 // subtract the size of four buttons 1251 maxSize -= buttonSize * 4; 1252 } else { 1253 // subtract the size of two buttons 1254 maxSize -= buttonSize * 2; 1255 } 1256 // visual adjustments (room for darker line between thumb and buttons) 1257 maxSize--; 1258 1259 float thumbSize; 1260 if (fPrivateData->fScrollBarInfo.proportional) { 1261 float proportion = fProportion; 1262 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) 1263 proportion = 1.0; 1264 if (proportion == 0.0) { 1265 // Special case a proportion of 0.0, use the large step value 1266 // in that case (NOTE: fMin == fMax already handled above) 1267 // This calculation is based on the assumption that "large step" 1268 // scrolls by one "page size". 1269 proportion = fLargeStep / (2 * (fMax - fMin)); 1270 if (proportion > 1.0) 1271 proportion = 1.0; 1272 } 1273 thumbSize = maxSize * proportion; 1274 if (thumbSize < minSize) 1275 thumbSize = minSize; 1276 } else 1277 thumbSize = minSize; 1278 1279 thumbSize = floorf(thumbSize + 0.5); 1280 thumbSize--; 1281 1282 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" 1283 float offset = 0.0; 1284 if (fMax > fMin) { 1285 offset = floorf(((fValue - fMin) / (fMax - fMin)) 1286 * (maxSize - thumbSize - 1.0)); 1287 } 1288 1289 if (_DoubleArrows()) { 1290 offset += buttonSize * 2; 1291 } else { 1292 offset += buttonSize; 1293 } 1294 // visual adjustments (room for darker line between thumb and buttons) 1295 offset++; 1296 1297 if (fOrientation == B_VERTICAL) { 1298 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top 1299 + thumbSize; 1300 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1301 } else { 1302 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left 1303 + thumbSize; 1304 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1305 } 1306 1307 if (Window()) { 1308 BRect invalid = oldFrame.IsValid() ? 1309 oldFrame | fPrivateData->fThumbFrame 1310 : fPrivateData->fThumbFrame; 1311 // account for those two dark lines 1312 if (fOrientation == B_HORIZONTAL) 1313 invalid.InsetBy(-2.0, 0.0); 1314 else 1315 invalid.InsetBy(0.0, -2.0); 1316 Invalidate(invalid); 1317 } 1318 } 1319 1320 1321 float 1322 BScrollBar::_ValueFor(BPoint where) const 1323 { 1324 BRect bounds = Bounds(); 1325 bounds.InsetBy(1.0, 1.0); 1326 1327 float offset; 1328 float thumbSize; 1329 float maxSize; 1330 float buttonSize; 1331 1332 if (fOrientation == B_VERTICAL) { 1333 offset = where.y; 1334 thumbSize = fPrivateData->fThumbFrame.Height(); 1335 maxSize = bounds.Height(); 1336 buttonSize = bounds.Width() + 1.0; 1337 } else { 1338 offset = where.x; 1339 thumbSize = fPrivateData->fThumbFrame.Width(); 1340 maxSize = bounds.Width(); 1341 buttonSize = bounds.Height() + 1.0; 1342 } 1343 1344 if (_DoubleArrows()) { 1345 // subtract the size of four buttons 1346 maxSize -= buttonSize * 4; 1347 // convert point to inside of area between buttons 1348 offset -= buttonSize * 2; 1349 } else { 1350 // subtract the size of two buttons 1351 maxSize -= buttonSize * 2; 1352 // convert point to inside of area between buttons 1353 offset -= buttonSize; 1354 } 1355 // visual adjustments (room for darker line between thumb and buttons) 1356 maxSize--; 1357 offset++; 1358 1359 float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0)); 1360 if (value >= 0.0) 1361 return floorf(value + 0.5); 1362 else 1363 return ceilf(value - 0.5); 1364 } 1365 1366 1367 int32 1368 BScrollBar::_ButtonFor(BPoint where) const 1369 { 1370 BRect bounds = Bounds(); 1371 bounds.InsetBy(1.0, 1.0); 1372 1373 float buttonSize; 1374 if (fOrientation == B_VERTICAL) { 1375 buttonSize = bounds.Width() + 1.0; 1376 } else { 1377 buttonSize = bounds.Height() + 1.0; 1378 } 1379 1380 BRect rect(bounds.left, bounds.top, 1381 bounds.left + buttonSize, bounds.top + buttonSize); 1382 1383 if (fOrientation == B_VERTICAL) { 1384 if (rect.Contains(where)) 1385 return ARROW1; 1386 if (_DoubleArrows()) { 1387 rect.OffsetBy(0.0, buttonSize); 1388 if (rect.Contains(where)) 1389 return ARROW2; 1390 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1391 if (rect.Contains(where)) 1392 return ARROW3; 1393 } 1394 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1395 if (rect.Contains(where)) 1396 return ARROW4; 1397 } else { 1398 if (rect.Contains(where)) 1399 return ARROW1; 1400 if (_DoubleArrows()) { 1401 rect.OffsetBy(buttonSize, 0.0); 1402 if (rect.Contains(where)) 1403 return ARROW2; 1404 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1405 if (rect.Contains(where)) 1406 return ARROW3; 1407 } 1408 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1409 if (rect.Contains(where)) 1410 return ARROW4; 1411 } 1412 1413 return NOARROW; 1414 } 1415 1416 1417 BRect 1418 BScrollBar::_ButtonRectFor(int32 button) const 1419 { 1420 BRect bounds = Bounds(); 1421 bounds.InsetBy(1.0, 1.0); 1422 1423 float buttonSize; 1424 if (fOrientation == B_VERTICAL) { 1425 buttonSize = bounds.Width() + 1.0; 1426 } else { 1427 buttonSize = bounds.Height() + 1.0; 1428 } 1429 1430 BRect rect(bounds.left, bounds.top, 1431 bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0); 1432 1433 if (fOrientation == B_VERTICAL) { 1434 switch (button) { 1435 case ARROW1: 1436 break; 1437 case ARROW2: 1438 rect.OffsetBy(0.0, buttonSize); 1439 break; 1440 case ARROW3: 1441 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1442 break; 1443 case ARROW4: 1444 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1445 break; 1446 } 1447 } else { 1448 switch (button) { 1449 case ARROW1: 1450 break; 1451 case ARROW2: 1452 rect.OffsetBy(buttonSize, 0.0); 1453 break; 1454 case ARROW3: 1455 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1456 break; 1457 case ARROW4: 1458 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1459 break; 1460 } 1461 } 1462 1463 return rect; 1464 } 1465 1466 1467 void 1468 BScrollBar::_UpdateTargetValue(BPoint where) 1469 { 1470 if (fOrientation == B_VERTICAL) { 1471 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y 1472 - fPrivateData->fThumbFrame.Height() / 2.0)); 1473 } else { 1474 fPrivateData->fStopValue = _ValueFor(BPoint(where.x 1475 - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1476 } 1477 } 1478 1479 1480 void 1481 BScrollBar::_UpdateArrowButtons() 1482 { 1483 bool upEnabled = fValue > fMin; 1484 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1485 fPrivateData->fUpArrowsEnabled = upEnabled; 1486 Invalidate(_ButtonRectFor(ARROW1)); 1487 if (_DoubleArrows()) 1488 Invalidate(_ButtonRectFor(ARROW3)); 1489 } 1490 1491 bool downEnabled = fValue < fMax; 1492 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1493 fPrivateData->fDownArrowsEnabled = downEnabled; 1494 Invalidate(_ButtonRectFor(ARROW4)); 1495 if (_DoubleArrows()) 1496 Invalidate(_ButtonRectFor(ARROW2)); 1497 } 1498 } 1499 1500 1501 status_t 1502 control_scrollbar(scroll_bar_info *info, BScrollBar *bar) 1503 { 1504 if (!bar || !info) 1505 return B_BAD_VALUE; 1506 1507 if (bar->fPrivateData->fScrollBarInfo.double_arrows 1508 != info->double_arrows) { 1509 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1510 1511 int8 multiplier = (info->double_arrows) ? 1 : -1; 1512 1513 if (bar->fOrientation == B_VERTICAL) { 1514 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier 1515 * B_H_SCROLL_BAR_HEIGHT); 1516 } else { 1517 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier 1518 * B_V_SCROLL_BAR_WIDTH, 0); 1519 } 1520 } 1521 1522 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1523 1524 // TODO: Figure out how proportional relates to the size of the thumb 1525 1526 // TODO: Add redraw code to reflect the changes 1527 1528 if (info->knob >= 0 && info->knob <= 2) 1529 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1530 else 1531 return B_BAD_VALUE; 1532 1533 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE 1534 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) 1535 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1536 else 1537 return B_BAD_VALUE; 1538 1539 return B_OK; 1540 } 1541 1542 1543 void 1544 BScrollBar::_DrawDisabledBackground(BRect area, 1545 const rgb_color& light, 1546 const rgb_color& dark, 1547 const rgb_color& fill) 1548 { 1549 if (!area.IsValid()) 1550 return; 1551 1552 if (fOrientation == B_VERTICAL) { 1553 int32 height = area.IntegerHeight(); 1554 if (height == 0) { 1555 SetHighColor(dark); 1556 StrokeLine(area.LeftTop(), area.RightTop()); 1557 } else if (height == 1) { 1558 SetHighColor(dark); 1559 FillRect(area); 1560 } else { 1561 BeginLineArray(4); 1562 AddLine(BPoint(area.left, area.top), 1563 BPoint(area.right, area.top), dark); 1564 AddLine(BPoint(area.left, area.bottom - 1), 1565 BPoint(area.left, area.top + 1), light); 1566 AddLine(BPoint(area.left + 1, area.top + 1), 1567 BPoint(area.right, area.top + 1), light); 1568 AddLine(BPoint(area.right, area.bottom), 1569 BPoint(area.left, area.bottom), dark); 1570 EndLineArray(); 1571 area.left++; 1572 area.top += 2; 1573 area.bottom--; 1574 if (area.IsValid()) { 1575 SetHighColor(fill); 1576 FillRect(area); 1577 } 1578 } 1579 } else { 1580 int32 width = area.IntegerWidth(); 1581 if (width == 0) { 1582 SetHighColor(dark); 1583 StrokeLine(area.LeftBottom(), area.LeftTop()); 1584 } else if (width == 1) { 1585 SetHighColor(dark); 1586 FillRect(area); 1587 } else { 1588 BeginLineArray(4); 1589 AddLine(BPoint(area.left, area.bottom), 1590 BPoint(area.left, area.top), dark); 1591 AddLine(BPoint(area.left + 1, area.bottom), 1592 BPoint(area.left + 1, area.top + 1), light); 1593 AddLine(BPoint(area.left + 1, area.top), 1594 BPoint(area.right - 1, area.top), light); 1595 AddLine(BPoint(area.right, area.top), 1596 BPoint(area.right, area.bottom), dark); 1597 EndLineArray(); 1598 area.left += 2; 1599 area.top ++; 1600 area.right--; 1601 if (area.IsValid()) { 1602 SetHighColor(fill); 1603 FillRect(area); 1604 } 1605 } 1606 } 1607 } 1608 1609 1610 void 1611 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect rect, 1612 const BRect& updateRect, bool enabled, bool down) 1613 { 1614 if (!updateRect.Intersects(rect)) 1615 return; 1616 1617 uint32 flags = 0; 1618 if (!enabled) 1619 flags |= BControlLook::B_DISABLED; 1620 if (down && fPrivateData->fDoRepeat) 1621 flags |= BControlLook::B_ACTIVATED; 1622 1623 // TODO: Why does BControlLook need this as the base color for the 1624 // scrollbar to look right? 1625 rgb_color baseColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1626 B_LIGHTEN_1_TINT); 1627 1628 be_control_look->DrawButtonBackground(this, rect, updateRect, baseColor, 1629 flags, BControlLook::B_ALL_BORDERS, fOrientation); 1630 1631 // TODO: Why does BControlLook need this negative inset for the arrow to 1632 // look right? 1633 rect.InsetBy(-1, -1); 1634 be_control_look->DrawArrowShape(this, rect, updateRect, 1635 baseColor, direction, flags, B_DARKEN_MAX_TINT); 1636 } 1637 1638 1639 BSize 1640 BScrollBar::_MinSize() const 1641 { 1642 BSize minSize; 1643 if (fOrientation == B_HORIZONTAL) { 1644 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH 1645 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1646 minSize.height = B_H_SCROLL_BAR_HEIGHT; 1647 } else { 1648 minSize.width = B_V_SCROLL_BAR_WIDTH; 1649 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT 1650 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1651 } 1652 return minSize; 1653 } 1654 1655