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, light1, dark, dark1, dark2, dark4; 863 if (enabled) { 864 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 865 light1 = tint_color(normal, B_LIGHTEN_1_TINT); 866 dark = tint_color(normal, B_DARKEN_3_TINT); 867 dark1 = tint_color(normal, B_DARKEN_1_TINT); 868 dark2 = tint_color(normal, B_DARKEN_2_TINT); 869 dark4 = tint_color(normal, B_DARKEN_4_TINT); 870 } else { 871 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 872 light1 = normal; 873 dark = tint_color(normal, B_DARKEN_2_TINT); 874 dark1 = tint_color(normal, B_LIGHTEN_2_TINT); 875 dark2 = tint_color(normal, B_LIGHTEN_1_TINT); 876 dark4 = tint_color(normal, B_DARKEN_3_TINT); 877 } 878 879 SetDrawingMode(B_OP_OVER); 880 881 BRect thumbBG = bounds; 882 bool doubleArrows = _DoubleArrows(); 883 884 // Draw arrows 885 if (fOrientation == B_HORIZONTAL) { 886 BRect buttonFrame(bounds.left, bounds.top, 887 bounds.left + bounds.Height(), bounds.bottom); 888 889 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 890 enabled, fPrivateData->fButtonDown == ARROW1); 891 892 if (doubleArrows) { 893 buttonFrame.OffsetBy(bounds.Height() + 1, 0.0); 894 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 895 enabled, fPrivateData->fButtonDown == ARROW2); 896 897 buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1), 898 bounds.top); 899 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 900 enabled, fPrivateData->fButtonDown == ARROW3); 901 902 thumbBG.left += bounds.Height() * 2 + 2; 903 thumbBG.right -= bounds.Height() * 2 + 2; 904 } else { 905 thumbBG.left += bounds.Height() + 1; 906 thumbBG.right -= bounds.Height() + 1; 907 } 908 909 buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top); 910 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 911 enabled, fPrivateData->fButtonDown == ARROW4); 912 } else { 913 BRect buttonFrame(bounds.left, bounds.top, bounds.right, 914 bounds.top + bounds.Width()); 915 916 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 917 enabled, fPrivateData->fButtonDown == ARROW1); 918 919 if (doubleArrows) { 920 buttonFrame.OffsetBy(0.0, bounds.Width() + 1); 921 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 922 enabled, fPrivateData->fButtonDown == ARROW2); 923 924 buttonFrame.OffsetTo(bounds.left, bounds.bottom 925 - ((bounds.Width() * 2) + 1)); 926 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 927 enabled, fPrivateData->fButtonDown == ARROW3); 928 929 thumbBG.top += bounds.Width() * 2 + 2; 930 thumbBG.bottom -= bounds.Width() * 2 + 2; 931 } else { 932 thumbBG.top += bounds.Width() + 1; 933 thumbBG.bottom -= bounds.Width() + 1; 934 } 935 936 buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width()); 937 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 938 enabled, fPrivateData->fButtonDown == ARROW4); 939 } 940 941 SetDrawingMode(B_OP_COPY); 942 943 // background for thumb area 944 BRect rect(fPrivateData->fThumbFrame); 945 946 if (be_control_look == NULL) { 947 if (fOrientation == B_HORIZONTAL) { 948 BeginLineArray(8); 949 950 if (rect.left > thumbBG.left) { 951 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 952 BPoint(thumbBG.left, thumbBG.top), 953 rect.left > thumbBG.left + 1 ? dark4 : dark); 954 } 955 if (rect.left > thumbBG.left + 1) { 956 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 957 BPoint(thumbBG.left + 1, thumbBG.bottom), dark2); 958 AddLine(BPoint(thumbBG.left + 1, thumbBG.top), 959 BPoint(rect.left - 1, thumbBG.top), dark2); 960 AddLine(BPoint(rect.left - 1, thumbBG.bottom), 961 BPoint(thumbBG.left + 2, thumbBG.bottom), normal); 962 } 963 964 if (rect.right < thumbBG.right - 1) { 965 AddLine(BPoint(rect.right + 2, thumbBG.top + 1), 966 BPoint(rect.right + 2, thumbBG.bottom), dark2); 967 AddLine(BPoint(rect.right + 1, thumbBG.top), 968 BPoint(thumbBG.right, thumbBG.top), dark2); 969 AddLine(BPoint(thumbBG.right - 1, thumbBG.bottom), 970 BPoint(rect.right + 3, thumbBG.bottom), normal); 971 } 972 if (rect.right < thumbBG.right) { 973 AddLine(BPoint(thumbBG.right, thumbBG.top), 974 BPoint(thumbBG.right, thumbBG.bottom), dark); 975 } 976 977 EndLineArray(); 978 } else { 979 BeginLineArray(8); 980 981 if (rect.top > thumbBG.top) { 982 AddLine(BPoint(thumbBG.left, thumbBG.top), 983 BPoint(thumbBG.right, thumbBG.top), 984 rect.top > thumbBG.top + 1 ? dark4 : dark); 985 } 986 if (rect.top > thumbBG.top + 1) { 987 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 988 BPoint(thumbBG.right, thumbBG.top + 1), dark2); 989 AddLine(BPoint(thumbBG.left, rect.top - 1), 990 BPoint(thumbBG.left, thumbBG.top + 1), dark2); 991 AddLine(BPoint(thumbBG.right, rect.top - 1), 992 BPoint(thumbBG.right, thumbBG.top + 2), normal); 993 } 994 995 if (rect.bottom < thumbBG.bottom - 1) { 996 AddLine(BPoint(thumbBG.left + 1, rect.bottom + 2), 997 BPoint(thumbBG.right, rect.bottom + 2), dark2); 998 AddLine(BPoint(thumbBG.left, rect.bottom + 1), 999 BPoint(thumbBG.left, thumbBG.bottom - 1), dark2); 1000 AddLine(BPoint(thumbBG.right, rect.bottom + 3), 1001 BPoint(thumbBG.right, thumbBG.bottom - 1), normal); 1002 } 1003 if (rect.bottom < thumbBG.bottom) { 1004 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 1005 BPoint(thumbBG.right, thumbBG.bottom), dark); 1006 } 1007 1008 EndLineArray(); 1009 } 1010 } 1011 SetHighColor(dark1); 1012 1013 if (be_control_look != NULL) { 1014 uint32 flags = 0; 1015 if (!enabled) 1016 flags |= BControlLook::B_DISABLED; 1017 1018 // fill background besides the thumb 1019 if (fOrientation == B_HORIZONTAL) { 1020 BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1, 1021 thumbBG.bottom); 1022 BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right, 1023 thumbBG.bottom); 1024 1025 be_control_look->DrawScrollBarBackground(this, leftOfThumb, 1026 rightOfThumb, updateRect, normal, flags, fOrientation); 1027 } else { 1028 BRect topOfThumb(thumbBG.left, thumbBG.top, 1029 thumbBG.right, rect.top - 1); 1030 1031 BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, 1032 thumbBG.right, thumbBG.bottom); 1033 1034 be_control_look->DrawScrollBarBackground(this, topOfThumb, 1035 bottomOfThumb, updateRect, normal, flags, fOrientation); 1036 } 1037 } 1038 1039 // Draw scroll thumb 1040 if (enabled) { 1041 if (be_control_look == NULL) { 1042 // fill and additional dark lines 1043 thumbBG.InsetBy(1.0, 1.0); 1044 if (fOrientation == B_HORIZONTAL) { 1045 BRect leftOfThumb(thumbBG.left + 1, thumbBG.top, rect.left - 1, 1046 thumbBG.bottom); 1047 if (leftOfThumb.IsValid()) 1048 FillRect(leftOfThumb); 1049 1050 BRect rightOfThumb(rect.right + 3, thumbBG.top, thumbBG.right, 1051 thumbBG.bottom); 1052 if (rightOfThumb.IsValid()) 1053 FillRect(rightOfThumb); 1054 1055 // dark lines before and after thumb 1056 if (rect.left > thumbBG.left) { 1057 SetHighColor(dark); 1058 StrokeLine(BPoint(rect.left - 1, rect.top), 1059 BPoint(rect.left - 1, rect.bottom)); 1060 } 1061 if (rect.right < thumbBG.right) { 1062 SetHighColor(dark4); 1063 StrokeLine(BPoint(rect.right + 1, rect.top), 1064 BPoint(rect.right + 1, rect.bottom)); 1065 } 1066 } else { 1067 BRect topOfThumb(thumbBG.left, thumbBG.top + 1, 1068 thumbBG.right, rect.top - 1); 1069 if (topOfThumb.IsValid()) 1070 FillRect(topOfThumb); 1071 1072 BRect bottomOfThumb(thumbBG.left, rect.bottom + 3, 1073 thumbBG.right, thumbBG.bottom); 1074 if (bottomOfThumb.IsValid()) 1075 FillRect(bottomOfThumb); 1076 1077 // dark lines before and after thumb 1078 if (rect.top > thumbBG.top) { 1079 SetHighColor(dark); 1080 StrokeLine(BPoint(rect.left, rect.top - 1), 1081 BPoint(rect.right, rect.top - 1)); 1082 } 1083 if (rect.bottom < thumbBG.bottom) { 1084 SetHighColor(dark4); 1085 StrokeLine(BPoint(rect.left, rect.bottom + 1), 1086 BPoint(rect.right, rect.bottom + 1)); 1087 } 1088 } 1089 } 1090 1091 // fill the clickable surface of the thumb 1092 if (be_control_look != NULL) { 1093 be_control_look->DrawButtonBackground(this, rect, updateRect, 1094 normal, 0, BControlLook::B_ALL_BORDERS, fOrientation); 1095 } else { 1096 BeginLineArray(4); 1097 AddLine(BPoint(rect.left, rect.bottom), 1098 BPoint(rect.left, rect.top), light); 1099 AddLine(BPoint(rect.left + 1, rect.top), 1100 BPoint(rect.right, rect.top), light); 1101 AddLine(BPoint(rect.right, rect.top + 1), 1102 BPoint(rect.right, rect.bottom), dark1); 1103 AddLine(BPoint(rect.right - 1, rect.bottom), 1104 BPoint(rect.left + 1, rect.bottom), dark1); 1105 EndLineArray(); 1106 1107 // fill 1108 rect.InsetBy(1.0, 1.0); 1109 /*if (fPrivateData->fButtonDown == THUMB) 1110 SetHighColor(tint_color(normal, (B_NO_TINT + B_DARKEN_1_TINT) / 2)); 1111 else*/ 1112 SetHighColor(normal); 1113 1114 FillRect(rect); 1115 } 1116 // TODO: Add the other thumb styles - dots and lines 1117 } else { 1118 if (fMin >= fMax || fProportion >= 1.0 || fProportion < 0.0) { 1119 // we cannot scroll at all 1120 _DrawDisabledBackground(thumbBG, light, dark, dark1); 1121 } else { 1122 // we could scroll, but we're simply disabled 1123 float bgTint = 1.06; 1124 rgb_color bgLight = tint_color(light, bgTint * 3); 1125 rgb_color bgShadow = tint_color(dark, bgTint); 1126 rgb_color bgFill = tint_color(dark1, bgTint); 1127 if (fOrientation == B_HORIZONTAL) { 1128 // left of thumb 1129 BRect besidesThumb(thumbBG); 1130 besidesThumb.right = rect.left - 1; 1131 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1132 // right of thumb 1133 besidesThumb.left = rect.right + 1; 1134 besidesThumb.right = thumbBG.right; 1135 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1136 } else { 1137 // above thumb 1138 BRect besidesThumb(thumbBG); 1139 besidesThumb.bottom = rect.top - 1; 1140 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1141 // below thumb 1142 besidesThumb.top = rect.bottom + 1; 1143 besidesThumb.bottom = thumbBG.bottom; 1144 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1145 } 1146 // thumb bevel 1147 BeginLineArray(4); 1148 AddLine(BPoint(rect.left, rect.bottom), 1149 BPoint(rect.left, rect.top), light); 1150 AddLine(BPoint(rect.left + 1, rect.top), 1151 BPoint(rect.right, rect.top), light); 1152 AddLine(BPoint(rect.right, rect.top + 1), 1153 BPoint(rect.right, rect.bottom), dark2); 1154 AddLine(BPoint(rect.right - 1, rect.bottom), 1155 BPoint(rect.left + 1, rect.bottom), dark2); 1156 EndLineArray(); 1157 // thumb fill 1158 rect.InsetBy(1.0, 1.0); 1159 SetHighColor(dark1); 1160 FillRect(rect); 1161 } 1162 } 1163 } 1164 1165 1166 void 1167 BScrollBar::FrameMoved(BPoint newPosition) 1168 { 1169 BView::FrameMoved(newPosition); 1170 } 1171 1172 1173 void 1174 BScrollBar::FrameResized(float newWidth, float newHeight) 1175 { 1176 _UpdateThumbFrame(); 1177 } 1178 1179 1180 BHandler* 1181 BScrollBar::ResolveSpecifier(BMessage* message, int32 index, 1182 BMessage* specifier, int32 form, const char *property) 1183 { 1184 return BView::ResolveSpecifier(message, index, specifier, form, property); 1185 } 1186 1187 1188 void 1189 BScrollBar::ResizeToPreferred() 1190 { 1191 BView::ResizeToPreferred(); 1192 } 1193 1194 1195 void 1196 BScrollBar::GetPreferredSize(float* _width, float* _height) 1197 { 1198 if (fOrientation == B_VERTICAL) { 1199 if (_width) 1200 *_width = B_V_SCROLL_BAR_WIDTH; 1201 if (_height) 1202 *_height = Bounds().Height(); 1203 } else if (fOrientation == B_HORIZONTAL) { 1204 if (_width) 1205 *_width = Bounds().Width(); 1206 if (_height) 1207 *_height = B_H_SCROLL_BAR_HEIGHT; 1208 } 1209 } 1210 1211 1212 void 1213 BScrollBar::MakeFocus(bool state) 1214 { 1215 BView::MakeFocus(state); 1216 } 1217 1218 1219 void 1220 BScrollBar::AllAttached() 1221 { 1222 BView::AllAttached(); 1223 } 1224 1225 1226 void 1227 BScrollBar::AllDetached() 1228 { 1229 BView::AllDetached(); 1230 } 1231 1232 1233 status_t 1234 BScrollBar::GetSupportedSuites(BMessage *message) 1235 { 1236 return BView::GetSupportedSuites(message); 1237 } 1238 1239 1240 BSize 1241 BScrollBar::MinSize() 1242 { 1243 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); 1244 } 1245 1246 1247 BSize 1248 BScrollBar::MaxSize() 1249 { 1250 BSize maxSize = _MinSize(); 1251 if (fOrientation == B_HORIZONTAL) 1252 maxSize.width = B_SIZE_UNLIMITED; 1253 else 1254 maxSize.height = B_SIZE_UNLIMITED; 1255 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); 1256 } 1257 1258 1259 BSize 1260 BScrollBar::PreferredSize() 1261 { 1262 BSize preferredSize = _MinSize(); 1263 if (fOrientation == B_HORIZONTAL) 1264 preferredSize.width *= 2; 1265 else 1266 preferredSize.height *= 2; 1267 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); 1268 } 1269 1270 1271 status_t 1272 BScrollBar::Perform(perform_code code, void* _data) 1273 { 1274 switch (code) { 1275 case PERFORM_CODE_MIN_SIZE: 1276 ((perform_data_min_size*)_data)->return_value 1277 = BScrollBar::MinSize(); 1278 return B_OK; 1279 case PERFORM_CODE_MAX_SIZE: 1280 ((perform_data_max_size*)_data)->return_value 1281 = BScrollBar::MaxSize(); 1282 return B_OK; 1283 case PERFORM_CODE_PREFERRED_SIZE: 1284 ((perform_data_preferred_size*)_data)->return_value 1285 = BScrollBar::PreferredSize(); 1286 return B_OK; 1287 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1288 ((perform_data_layout_alignment*)_data)->return_value 1289 = BScrollBar::LayoutAlignment(); 1290 return B_OK; 1291 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1292 ((perform_data_has_height_for_width*)_data)->return_value 1293 = BScrollBar::HasHeightForWidth(); 1294 return B_OK; 1295 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1296 { 1297 perform_data_get_height_for_width* data 1298 = (perform_data_get_height_for_width*)_data; 1299 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, 1300 &data->preferred); 1301 return B_OK; 1302 } 1303 case PERFORM_CODE_SET_LAYOUT: 1304 { 1305 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1306 BScrollBar::SetLayout(data->layout); 1307 return B_OK; 1308 } 1309 case PERFORM_CODE_LAYOUT_INVALIDATED: 1310 { 1311 perform_data_layout_invalidated* data 1312 = (perform_data_layout_invalidated*)_data; 1313 BScrollBar::LayoutInvalidated(data->descendants); 1314 return B_OK; 1315 } 1316 case PERFORM_CODE_DO_LAYOUT: 1317 { 1318 BScrollBar::DoLayout(); 1319 return B_OK; 1320 } 1321 } 1322 1323 return BView::Perform(code, _data); 1324 } 1325 1326 1327 #if DISABLES_ON_WINDOW_DEACTIVATION 1328 void 1329 BScrollBar::WindowActivated(bool active) 1330 { 1331 fPrivateData->fEnabled = active; 1332 Invalidate(); 1333 } 1334 #endif // DISABLES_ON_WINDOW_DEACTIVATION 1335 1336 1337 void BScrollBar::_ReservedScrollBar1() {} 1338 void BScrollBar::_ReservedScrollBar2() {} 1339 void BScrollBar::_ReservedScrollBar3() {} 1340 void BScrollBar::_ReservedScrollBar4() {} 1341 1342 1343 1344 BScrollBar& 1345 BScrollBar::operator=(const BScrollBar&) 1346 { 1347 return *this; 1348 } 1349 1350 1351 bool 1352 BScrollBar::_DoubleArrows() const 1353 { 1354 if (!fPrivateData->fScrollBarInfo.double_arrows) 1355 return false; 1356 1357 // if there is not enough room, switch to single arrows even though 1358 // double arrows is specified 1359 if (fOrientation == B_HORIZONTAL) { 1360 return Bounds().Width() > (Bounds().Height() + 1) * 4 1361 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1362 } else { 1363 return Bounds().Height() > (Bounds().Width() + 1) * 4 1364 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1365 } 1366 } 1367 1368 1369 void 1370 BScrollBar::_UpdateThumbFrame() 1371 { 1372 BRect bounds = Bounds(); 1373 bounds.InsetBy(1.0, 1.0); 1374 1375 BRect oldFrame = fPrivateData->fThumbFrame; 1376 fPrivateData->fThumbFrame = bounds; 1377 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1378 float maxSize; 1379 float buttonSize; 1380 1381 // assume square buttons 1382 if (fOrientation == B_VERTICAL) { 1383 maxSize = bounds.Height(); 1384 buttonSize = bounds.Width() + 1.0; 1385 } else { 1386 maxSize = bounds.Width(); 1387 buttonSize = bounds.Height() + 1.0; 1388 } 1389 1390 if (_DoubleArrows()) { 1391 // subtract the size of four buttons 1392 maxSize -= buttonSize * 4; 1393 } else { 1394 // subtract the size of two buttons 1395 maxSize -= buttonSize * 2; 1396 } 1397 // visual adjustments (room for darker line between thumb and buttons) 1398 maxSize--; 1399 1400 float thumbSize; 1401 if (fPrivateData->fScrollBarInfo.proportional) { 1402 float proportion = fProportion; 1403 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) 1404 proportion = 1.0; 1405 if (proportion == 0.0) { 1406 // Special case a proportion of 0.0, use the large step value 1407 // in that case (NOTE: fMin == fMax already handled above) 1408 // This calculation is based on the assumption that "large step" 1409 // scrolls by one "page size". 1410 proportion = fLargeStep / (2 * (fMax - fMin)); 1411 if (proportion > 1.0) 1412 proportion = 1.0; 1413 } 1414 thumbSize = maxSize * proportion; 1415 if (thumbSize < minSize) 1416 thumbSize = minSize; 1417 } else 1418 thumbSize = minSize; 1419 1420 thumbSize = floorf(thumbSize + 0.5); 1421 thumbSize--; 1422 1423 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" 1424 float offset = 0.0; 1425 if (fMax > fMin) { 1426 offset = floorf(((fValue - fMin) / (fMax - fMin)) 1427 * (maxSize - thumbSize - 1.0)); 1428 } 1429 1430 if (_DoubleArrows()) { 1431 offset += buttonSize * 2; 1432 } else { 1433 offset += buttonSize; 1434 } 1435 // visual adjustments (room for darker line between thumb and buttons) 1436 offset++; 1437 1438 if (fOrientation == B_VERTICAL) { 1439 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top 1440 + thumbSize; 1441 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1442 } else { 1443 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left 1444 + thumbSize; 1445 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1446 } 1447 1448 if (Window()) { 1449 BRect invalid = oldFrame.IsValid() ? 1450 oldFrame | fPrivateData->fThumbFrame 1451 : fPrivateData->fThumbFrame; 1452 // account for those two dark lines 1453 if (fOrientation == B_HORIZONTAL) 1454 invalid.InsetBy(-2.0, 0.0); 1455 else 1456 invalid.InsetBy(0.0, -2.0); 1457 Invalidate(invalid); 1458 } 1459 } 1460 1461 1462 float 1463 BScrollBar::_ValueFor(BPoint where) const 1464 { 1465 BRect bounds = Bounds(); 1466 bounds.InsetBy(1.0, 1.0); 1467 1468 float offset; 1469 float thumbSize; 1470 float maxSize; 1471 float buttonSize; 1472 1473 if (fOrientation == B_VERTICAL) { 1474 offset = where.y; 1475 thumbSize = fPrivateData->fThumbFrame.Height(); 1476 maxSize = bounds.Height(); 1477 buttonSize = bounds.Width() + 1.0; 1478 } else { 1479 offset = where.x; 1480 thumbSize = fPrivateData->fThumbFrame.Width(); 1481 maxSize = bounds.Width(); 1482 buttonSize = bounds.Height() + 1.0; 1483 } 1484 1485 if (_DoubleArrows()) { 1486 // subtract the size of four buttons 1487 maxSize -= buttonSize * 4; 1488 // convert point to inside of area between buttons 1489 offset -= buttonSize * 2; 1490 } else { 1491 // subtract the size of two buttons 1492 maxSize -= buttonSize * 2; 1493 // convert point to inside of area between buttons 1494 offset -= buttonSize; 1495 } 1496 // visual adjustments (room for darker line between thumb and buttons) 1497 maxSize--; 1498 offset++; 1499 1500 float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0)); 1501 if (value >= 0.0) 1502 return floorf(value + 0.5); 1503 else 1504 return ceilf(value - 0.5); 1505 } 1506 1507 1508 int32 1509 BScrollBar::_ButtonFor(BPoint where) const 1510 { 1511 BRect bounds = Bounds(); 1512 bounds.InsetBy(1.0, 1.0); 1513 1514 float buttonSize; 1515 if (fOrientation == B_VERTICAL) { 1516 buttonSize = bounds.Width() + 1.0; 1517 } else { 1518 buttonSize = bounds.Height() + 1.0; 1519 } 1520 1521 BRect rect(bounds.left, bounds.top, 1522 bounds.left + buttonSize, bounds.top + buttonSize); 1523 1524 if (fOrientation == B_VERTICAL) { 1525 if (rect.Contains(where)) 1526 return ARROW1; 1527 if (_DoubleArrows()) { 1528 rect.OffsetBy(0.0, buttonSize); 1529 if (rect.Contains(where)) 1530 return ARROW2; 1531 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1532 if (rect.Contains(where)) 1533 return ARROW3; 1534 } 1535 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1536 if (rect.Contains(where)) 1537 return ARROW4; 1538 } else { 1539 if (rect.Contains(where)) 1540 return ARROW1; 1541 if (_DoubleArrows()) { 1542 rect.OffsetBy(buttonSize, 0.0); 1543 if (rect.Contains(where)) 1544 return ARROW2; 1545 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1546 if (rect.Contains(where)) 1547 return ARROW3; 1548 } 1549 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1550 if (rect.Contains(where)) 1551 return ARROW4; 1552 } 1553 1554 return NOARROW; 1555 } 1556 1557 1558 BRect 1559 BScrollBar::_ButtonRectFor(int32 button) const 1560 { 1561 BRect bounds = Bounds(); 1562 bounds.InsetBy(1.0, 1.0); 1563 1564 float buttonSize; 1565 if (fOrientation == B_VERTICAL) { 1566 buttonSize = bounds.Width() + 1.0; 1567 } else { 1568 buttonSize = bounds.Height() + 1.0; 1569 } 1570 1571 BRect rect(bounds.left, bounds.top, 1572 bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0); 1573 1574 if (fOrientation == B_VERTICAL) { 1575 switch (button) { 1576 case ARROW1: 1577 break; 1578 case ARROW2: 1579 rect.OffsetBy(0.0, buttonSize); 1580 break; 1581 case ARROW3: 1582 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1583 break; 1584 case ARROW4: 1585 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1586 break; 1587 } 1588 } else { 1589 switch (button) { 1590 case ARROW1: 1591 break; 1592 case ARROW2: 1593 rect.OffsetBy(buttonSize, 0.0); 1594 break; 1595 case ARROW3: 1596 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1597 break; 1598 case ARROW4: 1599 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1600 break; 1601 } 1602 } 1603 1604 return rect; 1605 } 1606 1607 1608 void 1609 BScrollBar::_UpdateTargetValue(BPoint where) 1610 { 1611 if (fOrientation == B_VERTICAL) { 1612 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y 1613 - fPrivateData->fThumbFrame.Height() / 2.0)); 1614 } else { 1615 fPrivateData->fStopValue = _ValueFor(BPoint(where.x 1616 - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1617 } 1618 } 1619 1620 1621 void 1622 BScrollBar::_UpdateArrowButtons() 1623 { 1624 bool upEnabled = fValue > fMin; 1625 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1626 fPrivateData->fUpArrowsEnabled = upEnabled; 1627 Invalidate(_ButtonRectFor(ARROW1)); 1628 if (_DoubleArrows()) 1629 Invalidate(_ButtonRectFor(ARROW3)); 1630 } 1631 1632 bool downEnabled = fValue < fMax; 1633 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1634 fPrivateData->fDownArrowsEnabled = downEnabled; 1635 Invalidate(_ButtonRectFor(ARROW4)); 1636 if (_DoubleArrows()) 1637 Invalidate(_ButtonRectFor(ARROW2)); 1638 } 1639 } 1640 1641 1642 status_t 1643 control_scrollbar(scroll_bar_info *info, BScrollBar *bar) 1644 { 1645 if (!bar || !info) 1646 return B_BAD_VALUE; 1647 1648 if (bar->fPrivateData->fScrollBarInfo.double_arrows 1649 != info->double_arrows) { 1650 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1651 1652 int8 multiplier = (info->double_arrows) ? 1 : -1; 1653 1654 if (bar->fOrientation == B_VERTICAL) { 1655 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier 1656 * B_H_SCROLL_BAR_HEIGHT); 1657 } else { 1658 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier 1659 * B_V_SCROLL_BAR_WIDTH, 0); 1660 } 1661 } 1662 1663 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1664 1665 // TODO: Figure out how proportional relates to the size of the thumb 1666 1667 // TODO: Add redraw code to reflect the changes 1668 1669 if (info->knob >= 0 && info->knob <= 2) 1670 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1671 else 1672 return B_BAD_VALUE; 1673 1674 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE 1675 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) 1676 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1677 else 1678 return B_BAD_VALUE; 1679 1680 return B_OK; 1681 } 1682 1683 1684 void 1685 BScrollBar::_DrawDisabledBackground(BRect area, 1686 const rgb_color& light, 1687 const rgb_color& dark, 1688 const rgb_color& fill) 1689 { 1690 if (!area.IsValid()) 1691 return; 1692 1693 if (fOrientation == B_VERTICAL) { 1694 int32 height = area.IntegerHeight(); 1695 if (height == 0) { 1696 SetHighColor(dark); 1697 StrokeLine(area.LeftTop(), area.RightTop()); 1698 } else if (height == 1) { 1699 SetHighColor(dark); 1700 FillRect(area); 1701 } else { 1702 BeginLineArray(4); 1703 AddLine(BPoint(area.left, area.top), 1704 BPoint(area.right, area.top), dark); 1705 AddLine(BPoint(area.left, area.bottom - 1), 1706 BPoint(area.left, area.top + 1), light); 1707 AddLine(BPoint(area.left + 1, area.top + 1), 1708 BPoint(area.right, area.top + 1), light); 1709 AddLine(BPoint(area.right, area.bottom), 1710 BPoint(area.left, area.bottom), dark); 1711 EndLineArray(); 1712 area.left++; 1713 area.top += 2; 1714 area.bottom--; 1715 if (area.IsValid()) { 1716 SetHighColor(fill); 1717 FillRect(area); 1718 } 1719 } 1720 } else { 1721 int32 width = area.IntegerWidth(); 1722 if (width == 0) { 1723 SetHighColor(dark); 1724 StrokeLine(area.LeftBottom(), area.LeftTop()); 1725 } else if (width == 1) { 1726 SetHighColor(dark); 1727 FillRect(area); 1728 } else { 1729 BeginLineArray(4); 1730 AddLine(BPoint(area.left, area.bottom), 1731 BPoint(area.left, area.top), dark); 1732 AddLine(BPoint(area.left + 1, area.bottom), 1733 BPoint(area.left + 1, area.top + 1), light); 1734 AddLine(BPoint(area.left + 1, area.top), 1735 BPoint(area.right - 1, area.top), light); 1736 AddLine(BPoint(area.right, area.top), 1737 BPoint(area.right, area.bottom), dark); 1738 EndLineArray(); 1739 area.left += 2; 1740 area.top ++; 1741 area.right--; 1742 if (area.IsValid()) { 1743 SetHighColor(fill); 1744 FillRect(area); 1745 } 1746 } 1747 } 1748 } 1749 1750 1751 void 1752 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect r, 1753 const BRect& updateRect, bool enabled, bool down) 1754 { 1755 if (!updateRect.Intersects(r)) 1756 return; 1757 1758 rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR); 1759 rgb_color light, dark, darker, normal, arrow; 1760 1761 if (down && fPrivateData->fDoRepeat) { 1762 light = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0); 1763 dark = darker = c; 1764 normal = tint_color(c, B_DARKEN_1_TINT); 1765 arrow = tint_color(c, B_DARKEN_MAX_TINT); 1766 1767 } else { 1768 // Add a usability perk - disable buttons if they would not do anything 1769 // - like a left arrow if the value == fMin 1770 // NOTE: disabled because of too much visual noise/distraction 1771 /* if ((direction == ARROW_LEFT || direction == ARROW_UP) 1772 && (fValue == fMin)) { 1773 use_enabled_colors = false; 1774 } else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN) 1775 && (fValue == fMax)) { 1776 use_enabled_colors = false; 1777 }*/ 1778 1779 if (enabled) { 1780 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1781 dark = tint_color(c, B_DARKEN_1_TINT); 1782 darker = tint_color(c, B_DARKEN_2_TINT); 1783 normal = c; 1784 arrow = tint_color(c, (B_DARKEN_MAX_TINT + B_DARKEN_4_TINT) / 2.0); 1785 } else { 1786 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1787 dark = tint_color(c, B_LIGHTEN_1_TINT); 1788 darker = tint_color(c, B_DARKEN_2_TINT); 1789 normal = tint_color(c, B_LIGHTEN_2_TINT); 1790 arrow = tint_color(c, B_DARKEN_1_TINT); 1791 } 1792 } 1793 1794 BPoint tri1, tri2, tri3; 1795 float hInset = r.Width() / 3; 1796 float vInset = r.Height() / 3; 1797 r.InsetBy(hInset, vInset); 1798 1799 switch (direction) { 1800 case ARROW_LEFT: 1801 tri1.Set(r.right, r.top); 1802 tri2.Set(r.right - r.Width() / 1.33, (r.top + r.bottom + 1) /2 ); 1803 tri3.Set(r.right, r.bottom + 1); 1804 break; 1805 case ARROW_RIGHT: 1806 tri1.Set(r.left, r.bottom + 1); 1807 tri2.Set(r.left + r.Width() / 1.33, (r.top + r.bottom + 1) / 2); 1808 tri3.Set(r.left, r.top); 1809 break; 1810 case ARROW_UP: 1811 tri1.Set(r.left, r.bottom); 1812 tri2.Set((r.left + r.right + 1) / 2, r.bottom - r.Height() / 1.33); 1813 tri3.Set(r.right + 1, r.bottom); 1814 break; 1815 default: 1816 tri1.Set(r.left, r.top); 1817 tri2.Set((r.left + r.right + 1) / 2, r.top + r.Height() / 1.33); 1818 tri3.Set(r.right + 1, r.top); 1819 break; 1820 } 1821 // offset triangle if down 1822 if (down && fPrivateData->fDoRepeat) { 1823 BPoint offset(1.0, 1.0); 1824 tri1 = tri1 + offset; 1825 tri2 = tri2 + offset; 1826 tri3 = tri3 + offset; 1827 } 1828 1829 r.InsetBy(-(hInset - 1), -(vInset - 1)); 1830 if (be_control_look != NULL) { 1831 BRect temp(r.InsetByCopy(-1, -1)); 1832 uint32 flags = 0; 1833 if (down) 1834 flags |= BControlLook::B_ACTIVATED; 1835 be_control_look->DrawButtonBackground(this, temp, updateRect, 1836 down ? c : normal, flags, BControlLook::B_ALL_BORDERS, 1837 fOrientation); 1838 } else { 1839 SetHighColor(normal); 1840 FillRect(r); 1841 } 1842 1843 BShape arrowShape; 1844 arrowShape.MoveTo(tri1); 1845 arrowShape.LineTo(tri2); 1846 arrowShape.LineTo(tri3); 1847 1848 SetHighColor(arrow); 1849 SetPenSize(ceilf(hInset / 2.0)); 1850 StrokeShape(&arrowShape); 1851 SetPenSize(1.0); 1852 1853 if (be_control_look != NULL) 1854 return; 1855 1856 r.InsetBy(-1, -1); 1857 BeginLineArray(4); 1858 if (direction == ARROW_LEFT || direction == ARROW_RIGHT) { 1859 // horizontal 1860 if (doubleArrows && direction == ARROW_LEFT) { 1861 // draw in such a way that the arrows are 1862 // more visually separated 1863 AddLine(BPoint(r.left + 1, r.top), 1864 BPoint(r.right - 1, r.top), light); 1865 AddLine(BPoint(r.right, r.top), 1866 BPoint(r.right, r.bottom), darker); 1867 } else { 1868 AddLine(BPoint(r.left + 1, r.top), 1869 BPoint(r.right, r.top), light); 1870 AddLine(BPoint(r.right, r.top + 1), 1871 BPoint(r.right, r.bottom), dark); 1872 } 1873 AddLine(BPoint(r.left, r.bottom), 1874 BPoint(r.left, r.top), light); 1875 AddLine(BPoint(r.right - 1, r.bottom), 1876 BPoint(r.left + 1, r.bottom), dark); 1877 } else { 1878 // vertical 1879 if (doubleArrows && direction == ARROW_UP) { 1880 // draw in such a way that the arrows are 1881 // more visually separated 1882 AddLine(BPoint(r.left, r.bottom - 1), 1883 BPoint(r.left, r.top), light); 1884 AddLine(BPoint(r.right, r.bottom), 1885 BPoint(r.left, r.bottom), darker); 1886 } else { 1887 AddLine(BPoint(r.left, r.bottom), 1888 BPoint(r.left, r.top), light); 1889 AddLine(BPoint(r.right, r.bottom), 1890 BPoint(r.left + 1, r.bottom), dark); 1891 } 1892 AddLine(BPoint(r.left + 1, r.top), 1893 BPoint(r.right, r.top), light); 1894 AddLine(BPoint(r.right, r.top + 1), 1895 BPoint(r.right, r.bottom - 1), dark); 1896 } 1897 EndLineArray(); 1898 } 1899 1900 1901 BSize 1902 BScrollBar::_MinSize() const 1903 { 1904 BSize minSize; 1905 if (fOrientation == B_HORIZONTAL) { 1906 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH 1907 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1908 minSize.height = B_H_SCROLL_BAR_HEIGHT; 1909 } else { 1910 minSize.width = B_V_SCROLL_BAR_WIDTH; 1911 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT 1912 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1913 } 1914 return minSize; 1915 } 1916 1917