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