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 383 value = roundf(value); 384 385 if (value == fValue) 386 return; 387 388 TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value); 389 390 fValue = value; 391 392 _UpdateThumbFrame(); 393 _UpdateArrowButtons(); 394 395 ValueChanged(fValue); 396 } 397 398 399 float 400 BScrollBar::Value() const 401 { 402 return fValue; 403 } 404 405 406 void 407 BScrollBar::ValueChanged(float newValue) 408 { 409 TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue); 410 411 if (fTarget) { 412 // cache target bounds 413 BRect targetBounds = fTarget->Bounds(); 414 // if vertical, check bounds top and scroll if different from newValue 415 if (fOrientation == B_VERTICAL && targetBounds.top != newValue) { 416 fTarget->ScrollBy(0.0, newValue - targetBounds.top); 417 } 418 // if horizontal, check bounds left and scroll if different from newValue 419 if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue) { 420 fTarget->ScrollBy(newValue - targetBounds.left, 0.0); 421 } 422 } 423 424 TRACE(" -> %.1f\n", newValue); 425 426 SetValue(newValue); 427 } 428 429 430 void 431 BScrollBar::SetProportion(float value) 432 { 433 if (value < 0.0) 434 value = 0.0; 435 if (value > 1.0) 436 value = 1.0; 437 438 if (value == fProportion) 439 return; 440 TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value); 441 442 bool oldEnabled = fPrivateData->fEnabled && fMin < fMax 443 && fProportion < 1.0 && fProportion >= 0.0; 444 445 fProportion = value; 446 447 bool newEnabled = fPrivateData->fEnabled && fMin < fMax 448 && fProportion < 1.0 && fProportion >= 0.0; 449 450 _UpdateThumbFrame(); 451 452 if (oldEnabled != newEnabled) 453 Invalidate(); 454 455 } 456 457 458 float 459 BScrollBar::Proportion() const 460 { 461 return fProportion; 462 } 463 464 465 void 466 BScrollBar::SetRange(float min, float max) 467 { 468 if (min > max || isnanf(min) || isnanf(max) || isinff(min) || isinff(max)) { 469 min = 0; 470 max = 0; 471 } 472 473 min = roundf(min); 474 max = roundf(max); 475 476 if (fMin == min && fMax == max) 477 return; 478 TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max); 479 480 fMin = min; 481 fMax = max; 482 483 if (fValue < fMin || fValue > fMax) 484 SetValue(fValue); 485 else { 486 _UpdateThumbFrame(); 487 Invalidate(); 488 } 489 } 490 491 492 void 493 BScrollBar::GetRange(float *min, float *max) const 494 { 495 if (min != NULL) 496 *min = fMin; 497 if (max != NULL) 498 *max = fMax; 499 } 500 501 502 void 503 BScrollBar::SetSteps(float smallStep, float largeStep) 504 { 505 // Under R5, steps can be set only after being attached to a window, 506 // probably because the data is kept server-side. We'll just remove 507 // that limitation... :P 508 509 // The BeBook also says that we need to specify an integer value even 510 // though the step values are floats. For the moment, we'll just make 511 // sure that they are integers 512 smallStep = roundf(smallStep); 513 largeStep = roundf(largeStep); 514 if (fSmallStep == smallStep && fLargeStep == largeStep) 515 return; 516 TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(), 517 smallStep, largeStep); 518 519 fSmallStep = smallStep; 520 fLargeStep = largeStep; 521 522 if (fProportion == 0.0) { 523 // special case, proportion is based on fLargeStep if it was never 524 // set, so it means we need to invalidate here 525 _UpdateThumbFrame(); 526 Invalidate(); 527 } 528 529 // TODO: test use of fractional values and make them work properly if 530 // they don't 531 } 532 533 534 void 535 BScrollBar::GetSteps(float* smallStep, float* largeStep) const 536 { 537 if (smallStep) 538 *smallStep = fSmallStep; 539 if (largeStep) 540 *largeStep = fLargeStep; 541 } 542 543 544 void 545 BScrollBar::SetTarget(BView* target) 546 { 547 if (fTarget) { 548 // unset the previous target's scrollbar pointer 549 if (fOrientation == B_VERTICAL) 550 fTarget->fVerScroller = NULL; 551 else 552 fTarget->fHorScroller = NULL; 553 } 554 555 fTarget = target; 556 free(fTargetName); 557 558 if (fTarget) { 559 fTargetName = strdup(target->Name()); 560 561 if (fOrientation == B_VERTICAL) 562 fTarget->fVerScroller = this; 563 else 564 fTarget->fHorScroller = this; 565 } else 566 fTargetName = NULL; 567 } 568 569 570 void 571 BScrollBar::SetTarget(const char* targetName) 572 { 573 // NOTE 1: BeOS implementation crashes for targetName == NULL 574 // NOTE 2: BeOS implementation also does not modify the target 575 // if it can't be found 576 if (!targetName) 577 return; 578 579 if (!Window()) 580 debugger("Method requires window and doesn't have one"); 581 582 BView* target = Window()->FindView(targetName); 583 if (target) 584 SetTarget(target); 585 } 586 587 588 BView* 589 BScrollBar::Target() const 590 { 591 return fTarget; 592 } 593 594 595 void 596 BScrollBar::SetOrientation(enum orientation orientation) 597 { 598 if (fOrientation == orientation) 599 return; 600 601 fOrientation = orientation; 602 InvalidateLayout(); 603 Invalidate(); 604 } 605 606 607 orientation 608 BScrollBar::Orientation() const 609 { 610 return fOrientation; 611 } 612 613 614 status_t 615 BScrollBar::SetBorderHighlighted(bool state) 616 { 617 if (fPrivateData->fBorderHighlighted == state) 618 return B_OK; 619 620 fPrivateData->fBorderHighlighted = state; 621 622 BRect dirty(Bounds()); 623 if (fOrientation == B_HORIZONTAL) 624 dirty.bottom = dirty.top; 625 else 626 dirty.right = dirty.left; 627 628 Invalidate(dirty); 629 630 return B_OK; 631 } 632 633 634 void 635 BScrollBar::MessageReceived(BMessage* message) 636 { 637 switch(message->what) { 638 case B_VALUE_CHANGED: 639 { 640 int32 value; 641 if (message->FindInt32("value", &value) == B_OK) 642 ValueChanged(value); 643 break; 644 } 645 case B_MOUSE_WHEEL_CHANGED: 646 { 647 // Must handle this here since BView checks for the existence of 648 // scrollbars, which a scrollbar itself does not have 649 float deltaX = 0.0f, deltaY = 0.0f; 650 message->FindFloat("be:wheel_delta_x", &deltaX); 651 message->FindFloat("be:wheel_delta_y", &deltaY); 652 653 if (deltaX == 0.0f && deltaY == 0.0f) 654 break; 655 656 if (deltaX != 0.0f && deltaY == 0.0f) 657 deltaY = deltaX; 658 659 ScrollWithMouseWheelDelta(this, deltaY); 660 } 661 default: 662 BView::MessageReceived(message); 663 break; 664 } 665 } 666 667 668 void 669 BScrollBar::MouseDown(BPoint where) 670 { 671 if (!fPrivateData->fEnabled || fMin == fMax) 672 return; 673 674 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 675 676 int32 buttons; 677 if (Looper() == NULL || Looper()->CurrentMessage() == NULL 678 || Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) 679 buttons = B_PRIMARY_MOUSE_BUTTON; 680 681 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 682 // special absolute scrolling: move thumb to where we clicked 683 fPrivateData->fButtonDown = THUMB; 684 fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; 685 if (Orientation() == B_HORIZONTAL) 686 fPrivateData->fClickOffset.x = -fPrivateData->fThumbFrame.Width() / 2; 687 else 688 fPrivateData->fClickOffset.y = -fPrivateData->fThumbFrame.Height() / 2; 689 690 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 691 return; 692 } 693 694 // hit test for the thumb 695 if (fPrivateData->fThumbFrame.Contains(where)) { 696 fPrivateData->fButtonDown = THUMB; 697 fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; 698 Invalidate(fPrivateData->fThumbFrame); 699 return; 700 } 701 702 // hit test for arrows or empty area 703 float scrollValue = 0.0; 704 fPrivateData->fButtonDown = _ButtonFor(where); 705 switch (fPrivateData->fButtonDown) { 706 case ARROW1: 707 scrollValue = -fSmallStep; 708 break; 709 case ARROW2: 710 scrollValue = fSmallStep; 711 break; 712 case ARROW3: 713 scrollValue = -fSmallStep; 714 break; 715 case ARROW4: 716 scrollValue = fSmallStep; 717 break; 718 case NOARROW: 719 // we hit the empty area, figure out which side of the thumb 720 if (fOrientation == B_VERTICAL) { 721 if (where.y < fPrivateData->fThumbFrame.top) 722 scrollValue = -fLargeStep; 723 else 724 scrollValue = fLargeStep; 725 } else { 726 if (where.x < fPrivateData->fThumbFrame.left) 727 scrollValue = -fLargeStep; 728 else 729 scrollValue = fLargeStep; 730 } 731 _UpdateTargetValue(where); 732 break; 733 } 734 if (scrollValue != 0.0) { 735 SetValue(fValue + scrollValue); 736 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 737 738 // launch the repeat thread 739 if (fPrivateData->fRepeaterThread == -1) { 740 fPrivateData->fExitRepeater = false; 741 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 742 fPrivateData->fThumbInc = scrollValue; 743 fPrivateData->fDoRepeat = true; 744 fPrivateData->fRepeaterThread 745 = spawn_thread(fPrivateData->button_repeater_thread, 746 "scroll repeater", B_NORMAL_PRIORITY, fPrivateData); 747 resume_thread(fPrivateData->fRepeaterThread); 748 } else { 749 fPrivateData->fExitRepeater = false; 750 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 751 fPrivateData->fDoRepeat = true; 752 } 753 } 754 } 755 756 757 void 758 BScrollBar::MouseUp(BPoint pt) 759 { 760 if (fPrivateData->fButtonDown == THUMB) 761 Invalidate(fPrivateData->fThumbFrame); 762 else 763 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 764 765 fPrivateData->fButtonDown = NOARROW; 766 fPrivateData->fExitRepeater = true; 767 fPrivateData->fDoRepeat = false; 768 } 769 770 771 void 772 BScrollBar::MouseMoved(BPoint where, uint32 transit, const BMessage* message) 773 { 774 if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0 775 || fProportion < 0.0) 776 return; 777 778 if (fPrivateData->fButtonDown != NOARROW) { 779 if (fPrivateData->fButtonDown == THUMB) { 780 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 781 } else { 782 // suspend the repeating if the mouse is not over the button 783 bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains( 784 where); 785 if (fPrivateData->fDoRepeat != repeat) { 786 fPrivateData->fDoRepeat = repeat; 787 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 788 } 789 } 790 } else { 791 // update the value at which we want to stop repeating 792 if (fPrivateData->fDoRepeat) { 793 _UpdateTargetValue(where); 794 // we might have to turn arround 795 if ((fValue < fPrivateData->fStopValue 796 && fPrivateData->fThumbInc < 0) 797 || (fValue > fPrivateData->fStopValue 798 && fPrivateData->fThumbInc > 0)) { 799 fPrivateData->fThumbInc = -fPrivateData->fThumbInc; 800 } 801 } 802 } 803 } 804 805 806 void 807 BScrollBar::DetachedFromWindow() 808 { 809 BView::DetachedFromWindow(); 810 } 811 812 813 void 814 BScrollBar::Draw(BRect updateRect) 815 { 816 BRect bounds = Bounds(); 817 818 rgb_color normal = ui_color(B_PANEL_BACKGROUND_COLOR); 819 820 // stroke a dark frame arround the entire scrollbar 821 // (independent of enabled state) 822 // take care of border highlighting (scroll target is focus view) 823 SetHighColor(tint_color(normal, B_DARKEN_2_TINT)); 824 if (fPrivateData->fBorderHighlighted && fPrivateData->fEnabled) { 825 rgb_color borderColor = HighColor(); 826 rgb_color highlightColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); 827 BeginLineArray(4); 828 AddLine(BPoint(bounds.left + 1, bounds.bottom), 829 BPoint(bounds.right, bounds.bottom), borderColor); 830 AddLine(BPoint(bounds.right, bounds.top + 1), 831 BPoint(bounds.right, bounds.bottom - 1), borderColor); 832 if (fOrientation == B_HORIZONTAL) { 833 AddLine(BPoint(bounds.left, bounds.top + 1), 834 BPoint(bounds.left, bounds.bottom), borderColor); 835 } else { 836 AddLine(BPoint(bounds.left, bounds.top), 837 BPoint(bounds.left, bounds.bottom), highlightColor); 838 } 839 if (fOrientation == B_HORIZONTAL) { 840 AddLine(BPoint(bounds.left, bounds.top), 841 BPoint(bounds.right, bounds.top), highlightColor); 842 } else { 843 AddLine(BPoint(bounds.left + 1, bounds.top), 844 BPoint(bounds.right, bounds.top), borderColor); 845 } 846 EndLineArray(); 847 } else 848 StrokeRect(bounds); 849 850 bounds.InsetBy(1.0, 1.0); 851 852 bool enabled = fPrivateData->fEnabled && fMin < fMax 853 && fProportion < 1.0 && fProportion >= 0.0; 854 855 rgb_color light, light1, dark, dark1, dark2, dark4; 856 if (enabled) { 857 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 858 light1 = tint_color(normal, B_LIGHTEN_1_TINT); 859 dark = tint_color(normal, B_DARKEN_3_TINT); 860 dark1 = tint_color(normal, B_DARKEN_1_TINT); 861 dark2 = tint_color(normal, B_DARKEN_2_TINT); 862 dark4 = tint_color(normal, B_DARKEN_4_TINT); 863 } else { 864 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 865 light1 = normal; 866 dark = tint_color(normal, B_DARKEN_2_TINT); 867 dark1 = tint_color(normal, B_LIGHTEN_2_TINT); 868 dark2 = tint_color(normal, B_LIGHTEN_1_TINT); 869 dark4 = tint_color(normal, B_DARKEN_3_TINT); 870 } 871 872 SetDrawingMode(B_OP_OVER); 873 874 BRect thumbBG = bounds; 875 bool doubleArrows = _DoubleArrows(); 876 877 // Draw arrows 878 if (fOrientation == B_HORIZONTAL) { 879 BRect buttonFrame(bounds.left, bounds.top, 880 bounds.left + bounds.Height(), bounds.bottom); 881 882 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 883 enabled, fPrivateData->fButtonDown == ARROW1); 884 885 if (doubleArrows) { 886 buttonFrame.OffsetBy(bounds.Height() + 1, 0.0); 887 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 888 enabled, fPrivateData->fButtonDown == ARROW2); 889 890 buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1), 891 bounds.top); 892 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 893 enabled, fPrivateData->fButtonDown == ARROW3); 894 895 thumbBG.left += bounds.Height() * 2 + 2; 896 thumbBG.right -= bounds.Height() * 2 + 2; 897 } else { 898 thumbBG.left += bounds.Height() + 1; 899 thumbBG.right -= bounds.Height() + 1; 900 } 901 902 buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top); 903 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 904 enabled, fPrivateData->fButtonDown == ARROW4); 905 } else { 906 BRect buttonFrame(bounds.left, bounds.top, bounds.right, 907 bounds.top + bounds.Width()); 908 909 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 910 enabled, fPrivateData->fButtonDown == ARROW1); 911 912 if (doubleArrows) { 913 buttonFrame.OffsetBy(0.0, bounds.Width() + 1); 914 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 915 enabled, fPrivateData->fButtonDown == ARROW2); 916 917 buttonFrame.OffsetTo(bounds.left, bounds.bottom 918 - ((bounds.Width() * 2) + 1)); 919 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 920 enabled, fPrivateData->fButtonDown == ARROW3); 921 922 thumbBG.top += bounds.Width() * 2 + 2; 923 thumbBG.bottom -= bounds.Width() * 2 + 2; 924 } else { 925 thumbBG.top += bounds.Width() + 1; 926 thumbBG.bottom -= bounds.Width() + 1; 927 } 928 929 buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width()); 930 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 931 enabled, fPrivateData->fButtonDown == ARROW4); 932 } 933 934 SetDrawingMode(B_OP_COPY); 935 936 // background for thumb area 937 BRect rect(fPrivateData->fThumbFrame); 938 939 if (be_control_look == NULL) { 940 if (fOrientation == B_HORIZONTAL) { 941 BeginLineArray(8); 942 943 if (rect.left > thumbBG.left) { 944 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 945 BPoint(thumbBG.left, thumbBG.top), 946 rect.left > thumbBG.left + 1 ? dark4 : dark); 947 } 948 if (rect.left > thumbBG.left + 1) { 949 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 950 BPoint(thumbBG.left + 1, thumbBG.bottom), dark2); 951 AddLine(BPoint(thumbBG.left + 1, thumbBG.top), 952 BPoint(rect.left - 1, thumbBG.top), dark2); 953 AddLine(BPoint(rect.left - 1, thumbBG.bottom), 954 BPoint(thumbBG.left + 2, thumbBG.bottom), normal); 955 } 956 957 if (rect.right < thumbBG.right - 1) { 958 AddLine(BPoint(rect.right + 2, thumbBG.top + 1), 959 BPoint(rect.right + 2, thumbBG.bottom), dark2); 960 AddLine(BPoint(rect.right + 1, thumbBG.top), 961 BPoint(thumbBG.right, thumbBG.top), dark2); 962 AddLine(BPoint(thumbBG.right - 1, thumbBG.bottom), 963 BPoint(rect.right + 3, thumbBG.bottom), normal); 964 } 965 if (rect.right < thumbBG.right) { 966 AddLine(BPoint(thumbBG.right, thumbBG.top), 967 BPoint(thumbBG.right, thumbBG.bottom), dark); 968 } 969 970 EndLineArray(); 971 } else { 972 BeginLineArray(8); 973 974 if (rect.top > thumbBG.top) { 975 AddLine(BPoint(thumbBG.left, thumbBG.top), 976 BPoint(thumbBG.right, thumbBG.top), 977 rect.top > thumbBG.top + 1 ? dark4 : dark); 978 } 979 if (rect.top > thumbBG.top + 1) { 980 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 981 BPoint(thumbBG.right, thumbBG.top + 1), dark2); 982 AddLine(BPoint(thumbBG.left, rect.top - 1), 983 BPoint(thumbBG.left, thumbBG.top + 1), dark2); 984 AddLine(BPoint(thumbBG.right, rect.top - 1), 985 BPoint(thumbBG.right, thumbBG.top + 2), normal); 986 } 987 988 if (rect.bottom < thumbBG.bottom - 1) { 989 AddLine(BPoint(thumbBG.left + 1, rect.bottom + 2), 990 BPoint(thumbBG.right, rect.bottom + 2), dark2); 991 AddLine(BPoint(thumbBG.left, rect.bottom + 1), 992 BPoint(thumbBG.left, thumbBG.bottom - 1), dark2); 993 AddLine(BPoint(thumbBG.right, rect.bottom + 3), 994 BPoint(thumbBG.right, thumbBG.bottom - 1), normal); 995 } 996 if (rect.bottom < thumbBG.bottom) { 997 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 998 BPoint(thumbBG.right, thumbBG.bottom), dark); 999 } 1000 1001 EndLineArray(); 1002 } 1003 } 1004 SetHighColor(dark1); 1005 1006 if (be_control_look != NULL) { 1007 uint32 flags = 0; 1008 if (!enabled) 1009 flags |= BControlLook::B_DISABLED; 1010 1011 // fill background besides the thumb 1012 if (fOrientation == B_HORIZONTAL) { 1013 BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1, 1014 thumbBG.bottom); 1015 BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right, 1016 thumbBG.bottom); 1017 1018 be_control_look->DrawScrollBarBackground(this, leftOfThumb, 1019 rightOfThumb, updateRect, normal, flags, fOrientation); 1020 } else { 1021 BRect topOfThumb(thumbBG.left, thumbBG.top, 1022 thumbBG.right, rect.top - 1); 1023 1024 BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, 1025 thumbBG.right, thumbBG.bottom); 1026 1027 be_control_look->DrawScrollBarBackground(this, topOfThumb, 1028 bottomOfThumb, updateRect, normal, flags, fOrientation); 1029 } 1030 } 1031 1032 // Draw scroll thumb 1033 if (enabled) { 1034 if (be_control_look == NULL) { 1035 // fill and additional dark lines 1036 thumbBG.InsetBy(1.0, 1.0); 1037 if (fOrientation == B_HORIZONTAL) { 1038 BRect leftOfThumb(thumbBG.left + 1, thumbBG.top, rect.left - 1, 1039 thumbBG.bottom); 1040 if (leftOfThumb.IsValid()) 1041 FillRect(leftOfThumb); 1042 1043 BRect rightOfThumb(rect.right + 3, thumbBG.top, thumbBG.right, 1044 thumbBG.bottom); 1045 if (rightOfThumb.IsValid()) 1046 FillRect(rightOfThumb); 1047 1048 // dark lines before and after thumb 1049 if (rect.left > thumbBG.left) { 1050 SetHighColor(dark); 1051 StrokeLine(BPoint(rect.left - 1, rect.top), 1052 BPoint(rect.left - 1, rect.bottom)); 1053 } 1054 if (rect.right < thumbBG.right) { 1055 SetHighColor(dark4); 1056 StrokeLine(BPoint(rect.right + 1, rect.top), 1057 BPoint(rect.right + 1, rect.bottom)); 1058 } 1059 } else { 1060 BRect topOfThumb(thumbBG.left, thumbBG.top + 1, 1061 thumbBG.right, rect.top - 1); 1062 if (topOfThumb.IsValid()) 1063 FillRect(topOfThumb); 1064 1065 BRect bottomOfThumb(thumbBG.left, rect.bottom + 3, 1066 thumbBG.right, thumbBG.bottom); 1067 if (bottomOfThumb.IsValid()) 1068 FillRect(bottomOfThumb); 1069 1070 // dark lines before and after thumb 1071 if (rect.top > thumbBG.top) { 1072 SetHighColor(dark); 1073 StrokeLine(BPoint(rect.left, rect.top - 1), 1074 BPoint(rect.right, rect.top - 1)); 1075 } 1076 if (rect.bottom < thumbBG.bottom) { 1077 SetHighColor(dark4); 1078 StrokeLine(BPoint(rect.left, rect.bottom + 1), 1079 BPoint(rect.right, rect.bottom + 1)); 1080 } 1081 } 1082 } 1083 1084 // fill the clickable surface of the thumb 1085 if (be_control_look != NULL) { 1086 be_control_look->DrawButtonBackground(this, rect, updateRect, 1087 normal, 0, BControlLook::B_ALL_BORDERS, fOrientation); 1088 } else { 1089 BeginLineArray(4); 1090 AddLine(BPoint(rect.left, rect.bottom), 1091 BPoint(rect.left, rect.top), light); 1092 AddLine(BPoint(rect.left + 1, rect.top), 1093 BPoint(rect.right, rect.top), light); 1094 AddLine(BPoint(rect.right, rect.top + 1), 1095 BPoint(rect.right, rect.bottom), dark1); 1096 AddLine(BPoint(rect.right - 1, rect.bottom), 1097 BPoint(rect.left + 1, rect.bottom), dark1); 1098 EndLineArray(); 1099 1100 // fill 1101 rect.InsetBy(1.0, 1.0); 1102 /*if (fPrivateData->fButtonDown == THUMB) 1103 SetHighColor(tint_color(normal, (B_NO_TINT + B_DARKEN_1_TINT) / 2)); 1104 else*/ 1105 SetHighColor(normal); 1106 1107 FillRect(rect); 1108 } 1109 // TODO: Add the other thumb styles - dots and lines 1110 } else { 1111 if (fMin >= fMax || fProportion >= 1.0 || fProportion < 0.0) { 1112 // we cannot scroll at all 1113 _DrawDisabledBackground(thumbBG, light, dark, dark1); 1114 } else { 1115 // we could scroll, but we're simply disabled 1116 float bgTint = 1.06; 1117 rgb_color bgLight = tint_color(light, bgTint * 3); 1118 rgb_color bgShadow = tint_color(dark, bgTint); 1119 rgb_color bgFill = tint_color(dark1, bgTint); 1120 if (fOrientation == B_HORIZONTAL) { 1121 // left of thumb 1122 BRect besidesThumb(thumbBG); 1123 besidesThumb.right = rect.left - 1; 1124 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1125 // right of thumb 1126 besidesThumb.left = rect.right + 1; 1127 besidesThumb.right = thumbBG.right; 1128 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1129 } else { 1130 // above thumb 1131 BRect besidesThumb(thumbBG); 1132 besidesThumb.bottom = rect.top - 1; 1133 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1134 // below thumb 1135 besidesThumb.top = rect.bottom + 1; 1136 besidesThumb.bottom = thumbBG.bottom; 1137 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1138 } 1139 // thumb bevel 1140 BeginLineArray(4); 1141 AddLine(BPoint(rect.left, rect.bottom), 1142 BPoint(rect.left, rect.top), light); 1143 AddLine(BPoint(rect.left + 1, rect.top), 1144 BPoint(rect.right, rect.top), light); 1145 AddLine(BPoint(rect.right, rect.top + 1), 1146 BPoint(rect.right, rect.bottom), dark2); 1147 AddLine(BPoint(rect.right - 1, rect.bottom), 1148 BPoint(rect.left + 1, rect.bottom), dark2); 1149 EndLineArray(); 1150 // thumb fill 1151 rect.InsetBy(1.0, 1.0); 1152 SetHighColor(dark1); 1153 FillRect(rect); 1154 } 1155 } 1156 1157 if (fPrivateData->fScrollBarInfo.knob == B_KNOB_STYLE_NONE) 1158 return; 1159 1160 // draw the scrollbar thumb knobs 1161 bool square = fPrivateData->fScrollBarInfo.knob == B_KNOB_STYLE_DOTS; 1162 int32 knobWidth = 0; 1163 int32 knobHeight = 0; 1164 1165 if (square) { 1166 knobWidth = 2; 1167 knobHeight = 2; 1168 } else { 1169 knobWidth = 1; 1170 knobHeight = 3; 1171 } 1172 1173 int32 flags = 0; 1174 if (!enabled) 1175 flags |= BControlLook::B_DISABLED; 1176 1177 float hmiddle = rect.Width() / 2; 1178 float vmiddle = rect.Height() / 2; 1179 1180 BRect middleKnob = BRect( 1181 rect.left + hmiddle 1182 - (fOrientation == B_HORIZONTAL ? knobWidth : knobHeight), 1183 rect.top + vmiddle 1184 - (fOrientation == B_HORIZONTAL ? knobHeight : knobWidth), 1185 rect.left + hmiddle 1186 + (fOrientation == B_HORIZONTAL ? knobWidth : knobHeight), 1187 rect.top + vmiddle 1188 + (fOrientation == B_HORIZONTAL ? knobHeight : knobWidth)); 1189 1190 if (fOrientation == B_HORIZONTAL) { 1191 BRect leftKnob = middleKnob.OffsetByCopy(knobWidth * -4, 0); 1192 if (leftKnob.left > rect.left + knobWidth) { 1193 be_control_look->DrawButtonBackground(this, leftKnob, updateRect, 1194 normal, flags, BControlLook::B_ALL_BORDERS, fOrientation); 1195 } 1196 1197 BRect rightKnob = middleKnob.OffsetByCopy(knobWidth * 4, 0); 1198 if (rightKnob.right < rect.right - knobWidth) { 1199 be_control_look->DrawButtonBackground(this, rightKnob, updateRect, 1200 normal, flags, BControlLook::B_ALL_BORDERS, fOrientation); 1201 } 1202 } else { 1203 BRect topKnob = middleKnob.OffsetByCopy(0, knobWidth * -4); 1204 if (topKnob.top > rect.top + knobHeight) { 1205 be_control_look->DrawButtonBackground(this, topKnob, updateRect, 1206 normal, flags, BControlLook::B_ALL_BORDERS, fOrientation); 1207 } 1208 1209 BRect bottomKnob = middleKnob.OffsetByCopy(0, knobWidth * 4); 1210 if (bottomKnob.bottom < rect.bottom - knobHeight) { 1211 be_control_look->DrawButtonBackground(this, bottomKnob, updateRect, 1212 normal, flags, BControlLook::B_ALL_BORDERS, fOrientation); 1213 } 1214 } 1215 1216 // draw middle knob last because it modifies middleKnob 1217 be_control_look->DrawButtonBackground(this, middleKnob, updateRect, 1218 normal, flags, BControlLook::B_ALL_BORDERS, fOrientation); 1219 } 1220 1221 1222 void 1223 BScrollBar::FrameMoved(BPoint newPosition) 1224 { 1225 BView::FrameMoved(newPosition); 1226 } 1227 1228 1229 void 1230 BScrollBar::FrameResized(float newWidth, float newHeight) 1231 { 1232 _UpdateThumbFrame(); 1233 } 1234 1235 1236 BHandler* 1237 BScrollBar::ResolveSpecifier(BMessage* message, int32 index, 1238 BMessage* specifier, int32 form, const char *property) 1239 { 1240 return BView::ResolveSpecifier(message, index, specifier, form, property); 1241 } 1242 1243 1244 void 1245 BScrollBar::ResizeToPreferred() 1246 { 1247 BView::ResizeToPreferred(); 1248 } 1249 1250 1251 void 1252 BScrollBar::GetPreferredSize(float* _width, float* _height) 1253 { 1254 if (fOrientation == B_VERTICAL) { 1255 if (_width) 1256 *_width = B_V_SCROLL_BAR_WIDTH; 1257 if (_height) 1258 *_height = Bounds().Height(); 1259 } else if (fOrientation == B_HORIZONTAL) { 1260 if (_width) 1261 *_width = Bounds().Width(); 1262 if (_height) 1263 *_height = B_H_SCROLL_BAR_HEIGHT; 1264 } 1265 } 1266 1267 1268 void 1269 BScrollBar::MakeFocus(bool state) 1270 { 1271 BView::MakeFocus(state); 1272 } 1273 1274 1275 void 1276 BScrollBar::AllAttached() 1277 { 1278 BView::AllAttached(); 1279 } 1280 1281 1282 void 1283 BScrollBar::AllDetached() 1284 { 1285 BView::AllDetached(); 1286 } 1287 1288 1289 status_t 1290 BScrollBar::GetSupportedSuites(BMessage *message) 1291 { 1292 return BView::GetSupportedSuites(message); 1293 } 1294 1295 1296 BSize 1297 BScrollBar::MinSize() 1298 { 1299 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); 1300 } 1301 1302 1303 BSize 1304 BScrollBar::MaxSize() 1305 { 1306 BSize maxSize = _MinSize(); 1307 if (fOrientation == B_HORIZONTAL) 1308 maxSize.width = B_SIZE_UNLIMITED; 1309 else 1310 maxSize.height = B_SIZE_UNLIMITED; 1311 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); 1312 } 1313 1314 1315 BSize 1316 BScrollBar::PreferredSize() 1317 { 1318 BSize preferredSize = _MinSize(); 1319 if (fOrientation == B_HORIZONTAL) 1320 preferredSize.width *= 2; 1321 else 1322 preferredSize.height *= 2; 1323 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); 1324 } 1325 1326 1327 status_t 1328 BScrollBar::Perform(perform_code code, void* _data) 1329 { 1330 switch (code) { 1331 case PERFORM_CODE_MIN_SIZE: 1332 ((perform_data_min_size*)_data)->return_value 1333 = BScrollBar::MinSize(); 1334 return B_OK; 1335 case PERFORM_CODE_MAX_SIZE: 1336 ((perform_data_max_size*)_data)->return_value 1337 = BScrollBar::MaxSize(); 1338 return B_OK; 1339 case PERFORM_CODE_PREFERRED_SIZE: 1340 ((perform_data_preferred_size*)_data)->return_value 1341 = BScrollBar::PreferredSize(); 1342 return B_OK; 1343 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1344 ((perform_data_layout_alignment*)_data)->return_value 1345 = BScrollBar::LayoutAlignment(); 1346 return B_OK; 1347 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1348 ((perform_data_has_height_for_width*)_data)->return_value 1349 = BScrollBar::HasHeightForWidth(); 1350 return B_OK; 1351 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1352 { 1353 perform_data_get_height_for_width* data 1354 = (perform_data_get_height_for_width*)_data; 1355 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, 1356 &data->preferred); 1357 return B_OK; 1358 } 1359 case PERFORM_CODE_SET_LAYOUT: 1360 { 1361 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1362 BScrollBar::SetLayout(data->layout); 1363 return B_OK; 1364 } 1365 case PERFORM_CODE_LAYOUT_INVALIDATED: 1366 { 1367 perform_data_layout_invalidated* data 1368 = (perform_data_layout_invalidated*)_data; 1369 BScrollBar::LayoutInvalidated(data->descendants); 1370 return B_OK; 1371 } 1372 case PERFORM_CODE_DO_LAYOUT: 1373 { 1374 BScrollBar::DoLayout(); 1375 return B_OK; 1376 } 1377 } 1378 1379 return BView::Perform(code, _data); 1380 } 1381 1382 1383 #if DISABLES_ON_WINDOW_DEACTIVATION 1384 void 1385 BScrollBar::WindowActivated(bool active) 1386 { 1387 fPrivateData->fEnabled = active; 1388 Invalidate(); 1389 } 1390 #endif // DISABLES_ON_WINDOW_DEACTIVATION 1391 1392 1393 void BScrollBar::_ReservedScrollBar1() {} 1394 void BScrollBar::_ReservedScrollBar2() {} 1395 void BScrollBar::_ReservedScrollBar3() {} 1396 void BScrollBar::_ReservedScrollBar4() {} 1397 1398 1399 1400 BScrollBar& 1401 BScrollBar::operator=(const BScrollBar&) 1402 { 1403 return *this; 1404 } 1405 1406 1407 bool 1408 BScrollBar::_DoubleArrows() const 1409 { 1410 if (!fPrivateData->fScrollBarInfo.double_arrows) 1411 return false; 1412 1413 // if there is not enough room, switch to single arrows even though 1414 // double arrows is specified 1415 if (fOrientation == B_HORIZONTAL) { 1416 return Bounds().Width() > (Bounds().Height() + 1) * 4 1417 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1418 } else { 1419 return Bounds().Height() > (Bounds().Width() + 1) * 4 1420 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1421 } 1422 } 1423 1424 1425 void 1426 BScrollBar::_UpdateThumbFrame() 1427 { 1428 BRect bounds = Bounds(); 1429 bounds.InsetBy(1.0, 1.0); 1430 1431 BRect oldFrame = fPrivateData->fThumbFrame; 1432 fPrivateData->fThumbFrame = bounds; 1433 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1434 float maxSize; 1435 float buttonSize; 1436 1437 // assume square buttons 1438 if (fOrientation == B_VERTICAL) { 1439 maxSize = bounds.Height(); 1440 buttonSize = bounds.Width() + 1.0; 1441 } else { 1442 maxSize = bounds.Width(); 1443 buttonSize = bounds.Height() + 1.0; 1444 } 1445 1446 if (_DoubleArrows()) { 1447 // subtract the size of four buttons 1448 maxSize -= buttonSize * 4; 1449 } else { 1450 // subtract the size of two buttons 1451 maxSize -= buttonSize * 2; 1452 } 1453 // visual adjustments (room for darker line between thumb and buttons) 1454 maxSize--; 1455 1456 float thumbSize; 1457 if (fPrivateData->fScrollBarInfo.proportional) { 1458 float proportion = fProportion; 1459 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) 1460 proportion = 1.0; 1461 if (proportion == 0.0) { 1462 // Special case a proportion of 0.0, use the large step value 1463 // in that case (NOTE: fMin == fMax already handled above) 1464 // This calculation is based on the assumption that "large step" 1465 // scrolls by one "page size". 1466 proportion = fLargeStep / (2 * (fMax - fMin)); 1467 if (proportion > 1.0) 1468 proportion = 1.0; 1469 } 1470 thumbSize = maxSize * proportion; 1471 if (thumbSize < minSize) 1472 thumbSize = minSize; 1473 } else 1474 thumbSize = minSize; 1475 1476 thumbSize = floorf(thumbSize + 0.5); 1477 thumbSize--; 1478 1479 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" 1480 float offset = 0.0; 1481 if (fMax > fMin) { 1482 offset = floorf(((fValue - fMin) / (fMax - fMin)) 1483 * (maxSize - thumbSize - 1.0)); 1484 } 1485 1486 if (_DoubleArrows()) { 1487 offset += buttonSize * 2; 1488 } else { 1489 offset += buttonSize; 1490 } 1491 // visual adjustments (room for darker line between thumb and buttons) 1492 offset++; 1493 1494 if (fOrientation == B_VERTICAL) { 1495 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top 1496 + thumbSize; 1497 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1498 } else { 1499 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left 1500 + thumbSize; 1501 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1502 } 1503 1504 if (Window()) { 1505 BRect invalid = oldFrame.IsValid() ? 1506 oldFrame | fPrivateData->fThumbFrame 1507 : fPrivateData->fThumbFrame; 1508 // account for those two dark lines 1509 if (fOrientation == B_HORIZONTAL) 1510 invalid.InsetBy(-2.0, 0.0); 1511 else 1512 invalid.InsetBy(0.0, -2.0); 1513 Invalidate(invalid); 1514 } 1515 } 1516 1517 1518 float 1519 BScrollBar::_ValueFor(BPoint where) const 1520 { 1521 BRect bounds = Bounds(); 1522 bounds.InsetBy(1.0, 1.0); 1523 1524 float offset; 1525 float thumbSize; 1526 float maxSize; 1527 float buttonSize; 1528 1529 if (fOrientation == B_VERTICAL) { 1530 offset = where.y; 1531 thumbSize = fPrivateData->fThumbFrame.Height(); 1532 maxSize = bounds.Height(); 1533 buttonSize = bounds.Width() + 1.0; 1534 } else { 1535 offset = where.x; 1536 thumbSize = fPrivateData->fThumbFrame.Width(); 1537 maxSize = bounds.Width(); 1538 buttonSize = bounds.Height() + 1.0; 1539 } 1540 1541 if (_DoubleArrows()) { 1542 // subtract the size of four buttons 1543 maxSize -= buttonSize * 4; 1544 // convert point to inside of area between buttons 1545 offset -= buttonSize * 2; 1546 } else { 1547 // subtract the size of two buttons 1548 maxSize -= buttonSize * 2; 1549 // convert point to inside of area between buttons 1550 offset -= buttonSize; 1551 } 1552 // visual adjustments (room for darker line between thumb and buttons) 1553 maxSize--; 1554 offset++; 1555 1556 float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0)); 1557 if (value >= 0.0) 1558 return floorf(value + 0.5); 1559 else 1560 return ceilf(value - 0.5); 1561 } 1562 1563 1564 int32 1565 BScrollBar::_ButtonFor(BPoint where) const 1566 { 1567 BRect bounds = Bounds(); 1568 bounds.InsetBy(1.0, 1.0); 1569 1570 float buttonSize; 1571 if (fOrientation == B_VERTICAL) { 1572 buttonSize = bounds.Width() + 1.0; 1573 } else { 1574 buttonSize = bounds.Height() + 1.0; 1575 } 1576 1577 BRect rect(bounds.left, bounds.top, 1578 bounds.left + buttonSize, bounds.top + buttonSize); 1579 1580 if (fOrientation == B_VERTICAL) { 1581 if (rect.Contains(where)) 1582 return ARROW1; 1583 if (_DoubleArrows()) { 1584 rect.OffsetBy(0.0, buttonSize); 1585 if (rect.Contains(where)) 1586 return ARROW2; 1587 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1588 if (rect.Contains(where)) 1589 return ARROW3; 1590 } 1591 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1592 if (rect.Contains(where)) 1593 return ARROW4; 1594 } else { 1595 if (rect.Contains(where)) 1596 return ARROW1; 1597 if (_DoubleArrows()) { 1598 rect.OffsetBy(buttonSize, 0.0); 1599 if (rect.Contains(where)) 1600 return ARROW2; 1601 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1602 if (rect.Contains(where)) 1603 return ARROW3; 1604 } 1605 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1606 if (rect.Contains(where)) 1607 return ARROW4; 1608 } 1609 1610 return NOARROW; 1611 } 1612 1613 1614 BRect 1615 BScrollBar::_ButtonRectFor(int32 button) const 1616 { 1617 BRect bounds = Bounds(); 1618 bounds.InsetBy(1.0, 1.0); 1619 1620 float buttonSize; 1621 if (fOrientation == B_VERTICAL) { 1622 buttonSize = bounds.Width() + 1.0; 1623 } else { 1624 buttonSize = bounds.Height() + 1.0; 1625 } 1626 1627 BRect rect(bounds.left, bounds.top, 1628 bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0); 1629 1630 if (fOrientation == B_VERTICAL) { 1631 switch (button) { 1632 case ARROW1: 1633 break; 1634 case ARROW2: 1635 rect.OffsetBy(0.0, buttonSize); 1636 break; 1637 case ARROW3: 1638 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1639 break; 1640 case ARROW4: 1641 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1642 break; 1643 } 1644 } else { 1645 switch (button) { 1646 case ARROW1: 1647 break; 1648 case ARROW2: 1649 rect.OffsetBy(buttonSize, 0.0); 1650 break; 1651 case ARROW3: 1652 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1653 break; 1654 case ARROW4: 1655 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1656 break; 1657 } 1658 } 1659 1660 return rect; 1661 } 1662 1663 1664 void 1665 BScrollBar::_UpdateTargetValue(BPoint where) 1666 { 1667 if (fOrientation == B_VERTICAL) { 1668 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y 1669 - fPrivateData->fThumbFrame.Height() / 2.0)); 1670 } else { 1671 fPrivateData->fStopValue = _ValueFor(BPoint(where.x 1672 - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1673 } 1674 } 1675 1676 1677 void 1678 BScrollBar::_UpdateArrowButtons() 1679 { 1680 bool upEnabled = fValue > fMin; 1681 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1682 fPrivateData->fUpArrowsEnabled = upEnabled; 1683 Invalidate(_ButtonRectFor(ARROW1)); 1684 if (_DoubleArrows()) 1685 Invalidate(_ButtonRectFor(ARROW3)); 1686 } 1687 1688 bool downEnabled = fValue < fMax; 1689 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1690 fPrivateData->fDownArrowsEnabled = downEnabled; 1691 Invalidate(_ButtonRectFor(ARROW4)); 1692 if (_DoubleArrows()) 1693 Invalidate(_ButtonRectFor(ARROW2)); 1694 } 1695 } 1696 1697 1698 status_t 1699 control_scrollbar(scroll_bar_info *info, BScrollBar *bar) 1700 { 1701 if (!bar || !info) 1702 return B_BAD_VALUE; 1703 1704 if (bar->fPrivateData->fScrollBarInfo.double_arrows 1705 != info->double_arrows) { 1706 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1707 1708 int8 multiplier = (info->double_arrows) ? 1 : -1; 1709 1710 if (bar->fOrientation == B_VERTICAL) { 1711 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier 1712 * B_H_SCROLL_BAR_HEIGHT); 1713 } else { 1714 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier 1715 * B_V_SCROLL_BAR_WIDTH, 0); 1716 } 1717 } 1718 1719 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1720 1721 // TODO: Figure out how proportional relates to the size of the thumb 1722 1723 // TODO: Add redraw code to reflect the changes 1724 1725 if (info->knob >= 0 && info->knob <= 2) 1726 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1727 else 1728 return B_BAD_VALUE; 1729 1730 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE 1731 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) 1732 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1733 else 1734 return B_BAD_VALUE; 1735 1736 return B_OK; 1737 } 1738 1739 1740 void 1741 BScrollBar::_DrawDisabledBackground(BRect area, 1742 const rgb_color& light, 1743 const rgb_color& dark, 1744 const rgb_color& fill) 1745 { 1746 if (!area.IsValid()) 1747 return; 1748 1749 if (fOrientation == B_VERTICAL) { 1750 int32 height = area.IntegerHeight(); 1751 if (height == 0) { 1752 SetHighColor(dark); 1753 StrokeLine(area.LeftTop(), area.RightTop()); 1754 } else if (height == 1) { 1755 SetHighColor(dark); 1756 FillRect(area); 1757 } else { 1758 BeginLineArray(4); 1759 AddLine(BPoint(area.left, area.top), 1760 BPoint(area.right, area.top), dark); 1761 AddLine(BPoint(area.left, area.bottom - 1), 1762 BPoint(area.left, area.top + 1), light); 1763 AddLine(BPoint(area.left + 1, area.top + 1), 1764 BPoint(area.right, area.top + 1), light); 1765 AddLine(BPoint(area.right, area.bottom), 1766 BPoint(area.left, area.bottom), dark); 1767 EndLineArray(); 1768 area.left++; 1769 area.top += 2; 1770 area.bottom--; 1771 if (area.IsValid()) { 1772 SetHighColor(fill); 1773 FillRect(area); 1774 } 1775 } 1776 } else { 1777 int32 width = area.IntegerWidth(); 1778 if (width == 0) { 1779 SetHighColor(dark); 1780 StrokeLine(area.LeftBottom(), area.LeftTop()); 1781 } else if (width == 1) { 1782 SetHighColor(dark); 1783 FillRect(area); 1784 } else { 1785 BeginLineArray(4); 1786 AddLine(BPoint(area.left, area.bottom), 1787 BPoint(area.left, area.top), dark); 1788 AddLine(BPoint(area.left + 1, area.bottom), 1789 BPoint(area.left + 1, area.top + 1), light); 1790 AddLine(BPoint(area.left + 1, area.top), 1791 BPoint(area.right - 1, area.top), light); 1792 AddLine(BPoint(area.right, area.top), 1793 BPoint(area.right, area.bottom), dark); 1794 EndLineArray(); 1795 area.left += 2; 1796 area.top ++; 1797 area.right--; 1798 if (area.IsValid()) { 1799 SetHighColor(fill); 1800 FillRect(area); 1801 } 1802 } 1803 } 1804 } 1805 1806 1807 void 1808 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect r, 1809 const BRect& updateRect, bool enabled, bool down) 1810 { 1811 if (!updateRect.Intersects(r)) 1812 return; 1813 1814 rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR); 1815 rgb_color light, dark, darker, normal, arrow; 1816 1817 if (down && fPrivateData->fDoRepeat) { 1818 light = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0); 1819 dark = darker = c; 1820 normal = tint_color(c, B_DARKEN_1_TINT); 1821 arrow = tint_color(c, B_DARKEN_MAX_TINT); 1822 1823 } else { 1824 // Add a usability perk - disable buttons if they would not do anything 1825 // - like a left arrow if the value == fMin 1826 // NOTE: disabled because of too much visual noise/distraction 1827 /* if ((direction == ARROW_LEFT || direction == ARROW_UP) 1828 && (fValue == fMin)) { 1829 use_enabled_colors = false; 1830 } else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN) 1831 && (fValue == fMax)) { 1832 use_enabled_colors = false; 1833 }*/ 1834 1835 if (enabled) { 1836 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1837 dark = tint_color(c, B_DARKEN_1_TINT); 1838 darker = tint_color(c, B_DARKEN_2_TINT); 1839 normal = c; 1840 arrow = tint_color(c, (B_DARKEN_MAX_TINT + B_DARKEN_4_TINT) / 2.0); 1841 } else { 1842 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1843 dark = tint_color(c, B_LIGHTEN_1_TINT); 1844 darker = tint_color(c, B_DARKEN_2_TINT); 1845 normal = tint_color(c, B_LIGHTEN_2_TINT); 1846 arrow = tint_color(c, B_DARKEN_1_TINT); 1847 } 1848 } 1849 1850 BPoint tri1, tri2, tri3; 1851 float hInset = r.Width() / 3; 1852 float vInset = r.Height() / 3; 1853 r.InsetBy(hInset, vInset); 1854 1855 switch (direction) { 1856 case ARROW_LEFT: 1857 tri1.Set(r.right, r.top); 1858 tri2.Set(r.right - r.Width() / 1.33, (r.top + r.bottom + 1) /2 ); 1859 tri3.Set(r.right, r.bottom + 1); 1860 break; 1861 case ARROW_RIGHT: 1862 tri1.Set(r.left, r.bottom + 1); 1863 tri2.Set(r.left + r.Width() / 1.33, (r.top + r.bottom + 1) / 2); 1864 tri3.Set(r.left, r.top); 1865 break; 1866 case ARROW_UP: 1867 tri1.Set(r.left, r.bottom); 1868 tri2.Set((r.left + r.right + 1) / 2, r.bottom - r.Height() / 1.33); 1869 tri3.Set(r.right + 1, r.bottom); 1870 break; 1871 default: 1872 tri1.Set(r.left, r.top); 1873 tri2.Set((r.left + r.right + 1) / 2, r.top + r.Height() / 1.33); 1874 tri3.Set(r.right + 1, r.top); 1875 break; 1876 } 1877 // offset triangle if down 1878 if (down && fPrivateData->fDoRepeat) { 1879 BPoint offset(1.0, 1.0); 1880 tri1 = tri1 + offset; 1881 tri2 = tri2 + offset; 1882 tri3 = tri3 + offset; 1883 } 1884 1885 r.InsetBy(-(hInset - 1), -(vInset - 1)); 1886 if (be_control_look != NULL) { 1887 BRect temp(r.InsetByCopy(-1, -1)); 1888 uint32 flags = 0; 1889 if (down) 1890 flags |= BControlLook::B_ACTIVATED; 1891 be_control_look->DrawButtonBackground(this, temp, updateRect, 1892 down ? c : normal, flags, BControlLook::B_ALL_BORDERS, 1893 fOrientation); 1894 } else { 1895 SetHighColor(normal); 1896 FillRect(r); 1897 } 1898 1899 BShape arrowShape; 1900 arrowShape.MoveTo(tri1); 1901 arrowShape.LineTo(tri2); 1902 arrowShape.LineTo(tri3); 1903 1904 SetHighColor(arrow); 1905 SetPenSize(ceilf(hInset / 2.0)); 1906 StrokeShape(&arrowShape); 1907 SetPenSize(1.0); 1908 1909 if (be_control_look != NULL) 1910 return; 1911 1912 r.InsetBy(-1, -1); 1913 BeginLineArray(4); 1914 if (direction == ARROW_LEFT || direction == ARROW_RIGHT) { 1915 // horizontal 1916 if (doubleArrows && direction == ARROW_LEFT) { 1917 // draw in such a way that the arrows are 1918 // more visually separated 1919 AddLine(BPoint(r.left + 1, r.top), 1920 BPoint(r.right - 1, r.top), light); 1921 AddLine(BPoint(r.right, r.top), 1922 BPoint(r.right, r.bottom), darker); 1923 } else { 1924 AddLine(BPoint(r.left + 1, r.top), 1925 BPoint(r.right, r.top), light); 1926 AddLine(BPoint(r.right, r.top + 1), 1927 BPoint(r.right, r.bottom), dark); 1928 } 1929 AddLine(BPoint(r.left, r.bottom), 1930 BPoint(r.left, r.top), light); 1931 AddLine(BPoint(r.right - 1, r.bottom), 1932 BPoint(r.left + 1, r.bottom), dark); 1933 } else { 1934 // vertical 1935 if (doubleArrows && direction == ARROW_UP) { 1936 // draw in such a way that the arrows are 1937 // more visually separated 1938 AddLine(BPoint(r.left, r.bottom - 1), 1939 BPoint(r.left, r.top), light); 1940 AddLine(BPoint(r.right, r.bottom), 1941 BPoint(r.left, r.bottom), darker); 1942 } else { 1943 AddLine(BPoint(r.left, r.bottom), 1944 BPoint(r.left, r.top), light); 1945 AddLine(BPoint(r.right, r.bottom), 1946 BPoint(r.left + 1, r.bottom), dark); 1947 } 1948 AddLine(BPoint(r.left + 1, r.top), 1949 BPoint(r.right, r.top), light); 1950 AddLine(BPoint(r.right, r.top + 1), 1951 BPoint(r.right, r.bottom - 1), dark); 1952 } 1953 EndLineArray(); 1954 } 1955 1956 1957 BSize 1958 BScrollBar::_MinSize() const 1959 { 1960 BSize minSize; 1961 if (fOrientation == B_HORIZONTAL) { 1962 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH 1963 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1964 minSize.height = B_H_SCROLL_BAR_HEIGHT; 1965 } else { 1966 minSize.width = B_V_SCROLL_BAR_WIDTH; 1967 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT 1968 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1969 } 1970 return minSize; 1971 } 1972 1973