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