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