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