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