1/* 2 * Copyright 2006-2009, Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Ingo Weinhold <bonefish@cs.tu-berlin.de> 7 * Stephan A��mus <superstippi@gmx.de> 8 */ 9 10#include "ScrollView.h" 11 12#include <algorithm> 13#include <stdio.h> 14#include <string.h> 15 16#include <Bitmap.h> 17#ifdef __HAIKU__ 18# include <LayoutUtils.h> 19#endif 20#include <Message.h> 21#include <ScrollBar.h> 22#include <Window.h> 23 24#include "Scrollable.h" 25#include "ScrollCornerBitmaps.h" 26 27using namespace std; 28 29// #pragma mark - InternalScrollBar 30 31class InternalScrollBar : public BScrollBar { 32 public: 33 InternalScrollBar(ScrollView* scrollView, 34 BRect frame, 35 orientation posture); 36 virtual ~InternalScrollBar(); 37 38 virtual void ValueChanged(float value); 39 40 virtual void MouseDown(BPoint where); 41 virtual void MouseUp(BPoint where); 42 43 private: 44 ScrollView* fScrollView; 45}; 46 47// constructor 48InternalScrollBar::InternalScrollBar(ScrollView* scrollView, BRect frame, 49 orientation posture) 50 : BScrollBar(frame, NULL, NULL, 0, 0, posture), 51 fScrollView(scrollView) 52{ 53} 54 55// destructor 56InternalScrollBar::~InternalScrollBar() 57{ 58} 59 60// ValueChanged 61void 62InternalScrollBar::ValueChanged(float value) 63{ 64 // Notify our parent scroll view. Note: the value already has changed, 65 // so that we can't check, if it really has changed. 66 if (fScrollView) 67 fScrollView->_ScrollValueChanged(this, value); 68} 69 70// MouseDown 71void 72InternalScrollBar::MouseDown(BPoint where) 73{ 74 if (fScrollView) 75 fScrollView->_SetScrolling(true); 76 BScrollBar::MouseDown(where); 77} 78 79// MouseUp 80void 81InternalScrollBar::MouseUp(BPoint where) 82{ 83 BScrollBar::MouseUp(where); 84 if (fScrollView) 85 fScrollView->_SetScrolling(false); 86} 87 88 89 90// #pragma mark -ScrollCorner 91 92class ScrollCorner : public BView { 93 public: 94 ScrollCorner(ScrollView* scrollView); 95 virtual ~ScrollCorner(); 96 97 virtual void MouseDown(BPoint point); 98 virtual void MouseUp(BPoint point); 99 virtual void MouseMoved(BPoint point, uint32 transit, 100 const BMessage* message); 101 102 virtual void Draw(BRect updateRect); 103 virtual void WindowActivated(bool active); 104 105 void SetActive(bool active); 106 inline bool IsActive() const 107 { return fState & STATE_ACTIVE; } 108 109 private: 110 ScrollView* fScrollView; 111 uint32 fState; 112 BPoint fStartPoint; 113 BPoint fStartScrollOffset; 114 BBitmap* fBitmaps[3]; 115 116 inline bool IsEnabled() const 117 { return ((fState & STATE_ENABLED) == 118 STATE_ENABLED); } 119 120 void SetDragging(bool dragging); 121 inline bool IsDragging() const 122 { return (fState & STATE_DRAGGING); } 123 124 enum { 125 STATE_DRAGGING = 0x01, 126 STATE_WINDOW_ACTIVE = 0x02, 127 STATE_ACTIVE = 0x04, 128 STATE_ENABLED = STATE_WINDOW_ACTIVE | STATE_ACTIVE, 129 }; 130}; 131 132// constructor 133ScrollCorner::ScrollCorner(ScrollView* scrollView) 134 : BView(BRect(0.0, 0.0, B_V_SCROLL_BAR_WIDTH - 1.0f, B_H_SCROLL_BAR_HEIGHT - 1.0f), NULL, 135 0, B_WILL_DRAW), 136 fScrollView(scrollView), 137 fState(0), 138 fStartPoint(0, 0), 139 fStartScrollOffset(0, 0) 140{ 141 SetViewColor(B_TRANSPARENT_32_BIT); 142 143 fBitmaps[0] = new BBitmap(BRect(0.0f, 0.0f, sBitmapWidth - 1, 144 sBitmapHeight - 1), sColorSpace); 145 char* bits = (char*)fBitmaps[0]->Bits(); 146 int32 bpr = fBitmaps[0]->BytesPerRow(); 147 for (int i = 0; i < sBitmapHeight; i++, bits += bpr) { 148 memcpy(bits, &sScrollCornerNormalBits[i * sBitmapHeight * 4], 149 sBitmapWidth * 4); 150 } 151 152 fBitmaps[1] = new BBitmap(BRect(0.0f, 0.0f, sBitmapWidth - 1, 153 sBitmapHeight - 1), sColorSpace); 154 bits = (char*)fBitmaps[1]->Bits(); 155 bpr = fBitmaps[1]->BytesPerRow(); 156 for (int i = 0; i < sBitmapHeight; i++, bits += bpr) { 157 memcpy(bits, &sScrollCornerPushedBits[i * sBitmapHeight * 4], 158 sBitmapWidth * 4); 159 } 160 161 fBitmaps[2] = new BBitmap(BRect(0.0f, 0.0f, sBitmapWidth - 1, 162 sBitmapHeight - 1), sColorSpace); 163 bits = (char*)fBitmaps[2]->Bits(); 164 bpr = fBitmaps[2]->BytesPerRow(); 165 for (int i = 0; i < sBitmapHeight; i++, bits += bpr) { 166 memcpy(bits, &sScrollCornerDisabledBits[i * sBitmapHeight * 4], 167 sBitmapWidth * 4); 168 } 169} 170 171// destructor 172ScrollCorner::~ScrollCorner() 173{ 174 for (int i = 0; i < 3; i++) 175 delete fBitmaps[i]; 176} 177 178// MouseDown 179void 180ScrollCorner::MouseDown(BPoint point) 181{ 182 BView::MouseDown(point); 183 uint32 buttons = 0; 184 Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); 185 if (buttons & B_PRIMARY_MOUSE_BUTTON) { 186 SetMouseEventMask(B_POINTER_EVENTS); 187 if (fScrollView && IsEnabled() && Bounds().Contains(point)) { 188 SetDragging(true); 189 fStartPoint = point; 190 fStartScrollOffset = fScrollView->ScrollOffset(); 191 } 192 } 193} 194 195// MouseUp 196void 197ScrollCorner::MouseUp(BPoint point) 198{ 199 BView::MouseUp(point); 200 uint32 buttons = 0; 201 Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); 202 if (!(buttons & B_PRIMARY_MOUSE_BUTTON)) 203 SetDragging(false); 204} 205 206// MouseMoved 207void 208ScrollCorner::MouseMoved(BPoint point, uint32 transit, const BMessage* message) 209{ 210 BView::MouseMoved(point, transit, message); 211 if (IsDragging()) { 212 uint32 buttons = 0; 213 Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); 214 // This is a work-around for a BeOS bug: We sometimes don't get a 215 // MouseUp(), but fortunately it seems, that within the last 216 // MouseMoved() the button is not longer pressed. 217 if (buttons & B_PRIMARY_MOUSE_BUTTON) { 218 BPoint diff = point - fStartPoint; 219 if (fScrollView) { 220 fScrollView->_ScrollCornerValueChanged(fStartScrollOffset 221 - diff); 222// + diff); 223 } 224 } else 225 SetDragging(false); 226 } 227} 228 229// Draw 230void 231ScrollCorner::Draw(BRect updateRect) 232{ 233 if (IsEnabled()) { 234 if (IsDragging()) 235 DrawBitmap(fBitmaps[1], BPoint(0.0f, 0.0f)); 236 else 237 DrawBitmap(fBitmaps[0], BPoint(0.0f, 0.0f)); 238 } 239 else 240 DrawBitmap(fBitmaps[2], BPoint(0.0f, 0.0f)); 241} 242 243// WindowActivated 244void 245ScrollCorner::WindowActivated(bool active) 246{ 247 if (active != (fState & STATE_WINDOW_ACTIVE)) { 248 bool enabled = IsEnabled(); 249 if (active) 250 fState |= STATE_WINDOW_ACTIVE; 251 else 252 fState &= ~STATE_WINDOW_ACTIVE; 253 if (enabled != IsEnabled()) 254 Invalidate(); 255 } 256} 257 258// SetActive 259void 260ScrollCorner::SetActive(bool active) 261{ 262 if (active != IsActive()) { 263 bool enabled = IsEnabled(); 264 if (active) 265 fState |= STATE_ACTIVE; 266 else 267 fState &= ~STATE_ACTIVE; 268 if (enabled != IsEnabled()) 269 Invalidate(); 270 } 271} 272 273// SetDragging 274void 275ScrollCorner::SetDragging(bool dragging) 276{ 277 if (dragging != IsDragging()) { 278 if (dragging) 279 fState |= STATE_DRAGGING; 280 else 281 fState &= ~STATE_DRAGGING; 282 Invalidate(); 283 } 284} 285 286 287// #pragma mark - ScrollView 288 289 290// constructor 291ScrollView::ScrollView(BView* child, uint32 scrollingFlags, BRect frame, 292 const char* name, uint32 resizingMode, uint32 viewFlags, 293 uint32 borderStyle, uint32 borderFlags) 294 : BView(frame, name, resizingMode, 295 viewFlags | B_FRAME_EVENTS | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 296 Scroller() 297{ 298 _Init(child, scrollingFlags, borderStyle, borderFlags); 299} 300 301#ifdef __HAIKU__ 302 303// constructor 304ScrollView::ScrollView(BView* child, uint32 scrollingFlags, const char* name, 305 uint32 viewFlags, uint32 borderStyle, uint32 borderFlags) 306 : BView(name, viewFlags | B_FRAME_EVENTS | B_WILL_DRAW 307 | B_FULL_UPDATE_ON_RESIZE), 308 Scroller() 309{ 310 _Init(child, scrollingFlags, borderStyle, borderFlags); 311} 312 313#endif // __HAIKU__ 314 315// destructor 316ScrollView::~ScrollView() 317{ 318} 319 320// AllAttached 321void 322ScrollView::AllAttached() 323{ 324 // do a first layout 325 _Layout(_UpdateScrollBarVisibility()); 326} 327 328// Draw 329void ScrollView::Draw(BRect updateRect) 330{ 331 if (fBorderStyle == B_NO_BORDER) 332 return; 333 334 rgb_color keyboardFocus = keyboard_navigation_color(); 335 rgb_color light = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 336 B_LIGHTEN_MAX_TINT); 337 rgb_color shadow = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 338 B_DARKEN_1_TINT); 339 rgb_color darkShadow = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 340 B_DARKEN_2_TINT); 341 342 BRect r = Bounds(); 343 344 if (fChildFocused && fWindowActive) { 345 SetHighColor(keyboardFocus); 346 StrokeRect(r); 347 } else { 348 if (fBorderStyle == B_PLAIN_BORDER) { 349 SetHighColor(darkShadow); 350 StrokeRect(r); 351 } else { 352 BeginLineArray(4); 353 AddLine(BPoint(r.left, r.bottom), 354 BPoint(r.left, r.top), shadow); 355 AddLine(BPoint(r.left + 1.0, r.top), 356 BPoint(r.right, r.top), shadow); 357 AddLine(BPoint(r.right, r.top + 1.0), 358 BPoint(r.right, r.bottom), light); 359 AddLine(BPoint(r.right - 1.0, r.bottom), 360 BPoint(r.left + 1.0, r.bottom), light); 361 EndLineArray(); 362 } 363 } 364 if (fBorderStyle == B_PLAIN_BORDER) 365 return; 366 367 // The right and bottom lines will be hidden if the scroll views are 368 // visible. But that doesn't harm. 369 r.InsetBy(1, 1); 370 SetHighColor(darkShadow); 371 StrokeRect(r); 372} 373 374// FrameResized 375void 376ScrollView::FrameResized(float width, float height) 377{ 378 _Layout(0); 379} 380 381// WindowActivated 382void ScrollView::WindowActivated(bool activated) 383{ 384 fWindowActive = activated; 385 if (fChildFocused) 386 Invalidate(); 387} 388 389#ifdef __HAIKU__ 390 391// MinSize 392BSize 393ScrollView::MinSize() 394{ 395 BSize size = (fChild ? fChild->MinSize() : BSize(-1, -1)); 396 return _Size(size); 397} 398 399// PreferredSize 400BSize 401ScrollView::PreferredSize() 402{ 403 BSize size = (fChild ? fChild->PreferredSize() : BSize(-1, -1)); 404 return _Size(size); 405} 406 407#endif // __HAIKU__ 408 409// #pragma mark - 410 411// ScrollingFlags 412uint32 413ScrollView::ScrollingFlags() const 414{ 415 return fScrollingFlags; 416} 417 418// SetVisibleRectIsChildBounds 419void 420ScrollView::SetVisibleRectIsChildBounds(bool flag) 421{ 422 if (flag != VisibleRectIsChildBounds()) { 423 if (flag) 424 fScrollingFlags |= SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS; 425 else 426 fScrollingFlags &= ~SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS; 427 if (fChild && _UpdateScrollBarVisibility()) 428 _Layout(0); 429 } 430} 431 432// VisibleRectIsChildBounds 433bool 434ScrollView::VisibleRectIsChildBounds() const 435{ 436 return (fScrollingFlags & SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS); 437} 438 439// Child 440BView* 441ScrollView::Child() const 442{ 443 return fChild; 444} 445 446// ChildFocusChanged 447// 448// To be called by the scroll child, when its has got or lost the focus. 449// We need this to know, when to draw the blue focus frame. 450void 451ScrollView::ChildFocusChanged(bool focused) 452{ 453 if (fChildFocused != focused) { 454 fChildFocused = focused; 455 Invalidate(); 456 } 457} 458 459// HScrollBar 460BScrollBar* 461ScrollView::HScrollBar() const 462{ 463 return fHScrollBar; 464} 465 466// VScrollBar 467BScrollBar* 468ScrollView::VScrollBar() const 469{ 470 return fVScrollBar; 471} 472 473// HVScrollCorner 474BView* 475ScrollView::HVScrollCorner() const 476{ 477 return fScrollCorner; 478} 479 480// #pragma mark - 481 482// SetHSmallStep 483void 484ScrollView::SetHSmallStep(float hStep) 485{ 486 SetSmallSteps(hStep, fVSmallStep); 487} 488 489// SetVSmallStep 490void 491ScrollView::SetVSmallStep(float vStep) 492{ 493 SetSmallSteps(fHSmallStep, vStep); 494} 495 496// SetSmallSteps 497void 498ScrollView::SetSmallSteps(float hStep, float vStep) 499{ 500 if (fHSmallStep != hStep || fVSmallStep != vStep) { 501 fHSmallStep = hStep; 502 fVSmallStep = vStep; 503 _UpdateScrollBars(); 504 } 505} 506 507// GetSmallSteps 508void 509ScrollView::GetSmallSteps(float* hStep, float* vStep) const 510{ 511 *hStep = fHSmallStep; 512 *vStep = fVSmallStep; 513} 514 515// HSmallStep 516float 517ScrollView::HSmallStep() const 518{ 519 return fHSmallStep; 520} 521 522// VSmallStep 523float 524ScrollView::VSmallStep() const 525{ 526 return fVSmallStep; 527} 528 529// IsScrolling 530bool 531ScrollView::IsScrolling() const 532{ 533 return fScrolling; 534} 535 536void 537ScrollView::SetScrollingEnabled(bool enabled) 538{ 539 Scroller::SetScrollingEnabled(enabled); 540 if (IsScrollingEnabled()) 541 SetScrollOffset(ScrollOffset()); 542} 543 544// #pragma mark - 545 546// DataRectChanged 547void 548ScrollView::DataRectChanged(BRect /*oldDataRect*/, BRect /*newDataRect*/) 549{ 550 if (ScrollTarget()) { 551 if (_UpdateScrollBarVisibility()) 552 _Layout(0); 553 else 554 _UpdateScrollBars(); 555 } 556} 557 558// ScrollOffsetChanged 559void 560ScrollView::ScrollOffsetChanged(BPoint /*oldOffset*/, BPoint newOffset) 561{ 562 if (fHScrollBar && fHScrollBar->Value() != newOffset.x) 563 fHScrollBar->SetValue(newOffset.x); 564 if (fVScrollBar && fVScrollBar->Value() != newOffset.y) 565 fVScrollBar->SetValue(newOffset.y); 566} 567 568// VisibleSizeChanged 569void 570ScrollView::VisibleSizeChanged(float /*oldWidth*/, float /*oldHeight*/, 571 float /*newWidth*/, float /*newHeight*/) 572{ 573 if (ScrollTarget()) { 574 if (_UpdateScrollBarVisibility()) 575 _Layout(0); 576 else 577 _UpdateScrollBars(); 578 } 579} 580 581// ScrollTargetChanged 582void 583ScrollView::ScrollTargetChanged(Scrollable* /*oldTarget*/, 584 Scrollable* newTarget) 585{ 586/* // remove the old child 587 if (fChild) 588 RemoveChild(fChild); 589 // add the new child 590 BView* view = dynamic_cast<BView*>(newTarget); 591 fChild = view; 592 if (view) 593 AddChild(view); 594 else if (newTarget) // set the scroll target to NULL, if it isn't a BView 595 SetScrollTarget(NULL); 596*/ 597} 598 599// _Init 600void 601ScrollView::_Init(BView* child, uint32 scrollingFlags, uint32 borderStyle, 602 uint32 borderFlags) 603{ 604 fChild = NULL; 605 fScrollingFlags = scrollingFlags; 606 607 fHScrollBar = NULL; 608 fVScrollBar = NULL; 609 fScrollCorner = NULL; 610 611 fHVisible = true; 612 fVVisible = true; 613 fCornerVisible = true; 614 615 fWindowActive = false; 616 fChildFocused = false; 617 618 fScrolling = false; 619 620 fHSmallStep = 1; 621 fVSmallStep = 1; 622 623 fBorderStyle = borderStyle; 624 fBorderFlags = borderFlags; 625 626 // Set transparent view color -- our area is completely covered by 627 // our children. 628 SetViewColor(B_TRANSPARENT_32_BIT); 629 // create scroll bars 630 if (fScrollingFlags & (SCROLL_HORIZONTAL | SCROLL_HORIZONTAL_MAGIC)) { 631 fHScrollBar = new InternalScrollBar(this, 632 BRect(0.0, 0.0, 100.0, B_H_SCROLL_BAR_HEIGHT), B_HORIZONTAL); 633 AddChild(fHScrollBar); 634 } 635 if (fScrollingFlags & (SCROLL_VERTICAL | SCROLL_VERTICAL_MAGIC)) { 636 fVScrollBar = new InternalScrollBar(this, 637 BRect(0.0, 0.0, B_V_SCROLL_BAR_WIDTH, 100.0), B_VERTICAL); 638 AddChild(fVScrollBar); 639 } 640 // Create a scroll corner, if we can scroll into both direction. 641 if (fHScrollBar && fVScrollBar) { 642 fScrollCorner = new ScrollCorner(this); 643 AddChild(fScrollCorner); 644 } 645 // add child 646 if (child) { 647 fChild = child; 648 AddChild(child); 649 if (Scrollable* scrollable = dynamic_cast<Scrollable*>(child)) 650 SetScrollTarget(scrollable); 651 } 652} 653 654 655// _ScrollValueChanged 656void 657ScrollView::_ScrollValueChanged(InternalScrollBar* scrollBar, float value) 658{ 659 if (!IsScrollingEnabled()) 660 return; 661 662 switch (scrollBar->Orientation()) { 663 case B_HORIZONTAL: 664 if (fHScrollBar) 665 SetScrollOffset(BPoint(value, ScrollOffset().y)); 666 break; 667 case B_VERTICAL: 668 if (fVScrollBar) 669 SetScrollOffset(BPoint(ScrollOffset().x, value)); 670 break; 671 default: 672 break; 673 } 674} 675 676// _ScrollCornerValueChanged 677void 678ScrollView::_ScrollCornerValueChanged(BPoint offset) 679{ 680 // The logic in Scrollable::SetScrollOffset() handles offsets, that 681 // are out of range. 682 SetScrollOffset(offset); 683} 684 685// #pragma mark - 686 687// _Layout 688// 689// Relayouts all children (fChild, scroll bars). 690// flags indicates which scrollbars' visibility has changed. 691// May be overridden to do a custom layout -- the SCROLL_*_MAGIC must 692// be disabled in this case, or strange things happen. 693void 694ScrollView::_Layout(uint32 flags) 695{ 696 bool hbar = (fHScrollBar && fHVisible); 697 bool vbar = (fVScrollBar && fVVisible); 698 bool corner = (fScrollCorner && fCornerVisible); 699 BRect childRect(_ChildRect()); 700 float innerWidth = childRect.Width(); 701 float innerHeight = childRect.Height(); 702 BPoint scrollLT(_InnerRect().LeftTop()); 703 scrollLT.x--; 704 scrollLT.y--; 705 706 BPoint scrollRB(childRect.RightBottom() + BPoint(1.0f, 1.0f)); 707 708 // layout scroll bars and scroll corner 709 if (corner) { 710 // In this case the scrollbars overlap one pixel. 711 fHScrollBar->MoveTo(scrollLT.x, scrollRB.y); 712 fHScrollBar->ResizeTo(innerWidth + 2.0, B_H_SCROLL_BAR_HEIGHT); 713 fVScrollBar->MoveTo(scrollRB.x, scrollLT.y); 714 fVScrollBar->ResizeTo(B_V_SCROLL_BAR_WIDTH, innerHeight + 2.0); 715 fScrollCorner->MoveTo(childRect.right + 2.0, childRect.bottom + 2.0); 716 } else if (hbar) { 717 fHScrollBar->MoveTo(scrollLT.x, scrollRB.y); 718 fHScrollBar->ResizeTo(innerWidth + 2.0, B_H_SCROLL_BAR_HEIGHT); 719 } else if (vbar) { 720 fVScrollBar->MoveTo(scrollRB.x, scrollLT.y); 721 fVScrollBar->ResizeTo(B_V_SCROLL_BAR_WIDTH, innerHeight + 2.0); 722 } 723 // layout child 724 if (fChild) { 725 fChild->MoveTo(childRect.LeftTop()); 726 fChild->ResizeTo(innerWidth, innerHeight); 727 if (VisibleRectIsChildBounds()) 728 SetVisibleSize(innerWidth, innerHeight); 729 // Due to a BeOS bug sometimes the area under a recently hidden 730 // scroll bar isn't updated correctly. 731 // We force this manually: The position of hidden scroll bar isn't 732 // updated any longer, so we can't just invalidate it. 733 if (fChild->Window()) { 734 if (flags & SCROLL_HORIZONTAL && !fHVisible) 735 fChild->Invalidate(fHScrollBar->Frame()); 736 if (flags & SCROLL_VERTICAL && !fVVisible) 737 fChild->Invalidate(fVScrollBar->Frame()); 738 } 739 } 740} 741 742// _UpdateScrollBars 743// 744// Probably somewhat misnamed. This function updates the scroll bars' 745// proportion, range attributes and step widths according to the scroll 746// target's DataRect() and VisibleBounds(). May also be called, if there's 747// no scroll target -- then the scroll bars are disabled. 748void 749ScrollView::_UpdateScrollBars() 750{ 751 BRect dataRect = DataRect(); 752 BRect visibleBounds = VisibleBounds(); 753 if (!fScrollTarget) { 754 dataRect.Set(0.0, 0.0, 0.0, 0.0); 755 visibleBounds.Set(0.0, 0.0, 0.0, 0.0); 756 } 757 float hProportion = min_c(1.0f, (visibleBounds.Width() + 1.0f) 758 / (dataRect.Width() + 1.0f)); 759 float hMaxValue = max_c(dataRect.left, 760 dataRect.right - visibleBounds.Width()); 761 float vProportion = min_c(1.0f, (visibleBounds.Height() + 1.0f) 762 / (dataRect.Height() + 1.0f)); 763 float vMaxValue = max_c(dataRect.top, 764 dataRect.bottom - visibleBounds.Height()); 765 // update horizontal scroll bar 766 if (fHScrollBar) { 767 fHScrollBar->SetProportion(hProportion); 768 fHScrollBar->SetRange(dataRect.left, hMaxValue); 769 // This obviously ineffective line works around a BScrollBar bug: 770 // As documented the scrollbar's value is adjusted, if the range 771 // has been changed and it therefore falls out of the range. But if, 772 // after resetting the range to what it has been before, the user 773 // moves the scrollbar to the original value via one click 774 // it is failed to invoke BScrollBar::ValueChanged(). 775 fHScrollBar->SetValue(fHScrollBar->Value()); 776 fHScrollBar->SetSteps(fHSmallStep, visibleBounds.Width()); 777 } 778 // update vertical scroll bar 779 if (fVScrollBar) { 780 fVScrollBar->SetProportion(vProportion); 781 fVScrollBar->SetRange(dataRect.top, vMaxValue); 782 // This obviously ineffective line works around a BScrollBar bug. 783 fVScrollBar->SetValue(fVScrollBar->Value()); 784 fVScrollBar->SetSteps(fVSmallStep, visibleBounds.Height()); 785 } 786 // update scroll corner 787 if (fScrollCorner) { 788 fScrollCorner->SetActive(hProportion < 1.0f || vProportion < 1.0f); 789 } 790} 791 792// set_visible_state 793// 794// Convenience function: Sets a view's visibility state to /visible/. 795// Returns true, if the state was actually changed, false otherwise. 796// This function never calls Hide() on a hidden or Show() on a visible 797// view. /view/ must be valid. 798static inline 799bool 800set_visible_state(BView* view, bool visible, bool* currentlyVisible) 801{ 802 bool changed = false; 803 if (*currentlyVisible != visible) { 804 if (visible) 805 view->Show(); 806 else 807 view->Hide(); 808 *currentlyVisible = visible; 809 changed = true; 810 } 811 return changed; 812} 813 814// _UpdateScrollBarVisibility 815// 816// Checks which of scroll bars need to be visible according to 817// SCROLL_*_MAGIG and shows/hides them, if necessary. 818// Returns a bitwise combination of SCROLL_HORIZONTAL and SCROLL_VERTICAL 819// according to which scroll bar's visibility state has changed, 0 if none. 820// A return value != 0 usually means that the layout isn't valid any longer. 821uint32 822ScrollView::_UpdateScrollBarVisibility() 823{ 824 uint32 changed = 0; 825 BRect childRect(_MaxVisibleRect()); 826 float width = childRect.Width(); 827 float height = childRect.Height(); 828 BRect dataRect = DataRect(); // Invalid if !ScrollTarget(), 829 float dataWidth = dataRect.Width(); // but that doesn't harm. 830 float dataHeight = dataRect.Height(); // 831 bool hbar = (fScrollingFlags & SCROLL_HORIZONTAL_MAGIC); 832 bool vbar = (fScrollingFlags & SCROLL_VERTICAL_MAGIC); 833 if (!ScrollTarget()) { 834 if (hbar) { 835 if (set_visible_state(fHScrollBar, false, &fHVisible)) 836 changed |= SCROLL_HORIZONTAL; 837 } 838 if (vbar) { 839 if (set_visible_state(fVScrollBar, false, &fVVisible)) 840 changed |= SCROLL_VERTICAL; 841 } 842 } else if (hbar && width >= dataWidth && vbar && height >= dataHeight) { 843 // none 844 if (set_visible_state(fHScrollBar, false, &fHVisible)) 845 changed |= SCROLL_HORIZONTAL; 846 if (set_visible_state(fVScrollBar, false, &fVVisible)) 847 changed |= SCROLL_VERTICAL; 848 } else { 849 // The case, that both scroll bars are magic and invisible is catched, 850 // so that while checking one bar we can suppose, that the other one 851 // is visible (if it does exist at all). 852 BRect innerRect(_GuessVisibleRect(fHScrollBar, fVScrollBar)); 853 float innerWidth = innerRect.Width(); 854 float innerHeight = innerRect.Height(); 855 // the horizontal one? 856 if (hbar) { 857 if (innerWidth >= dataWidth) { 858 if (set_visible_state(fHScrollBar, false, &fHVisible)) 859 changed |= SCROLL_HORIZONTAL; 860 } else { 861 if (set_visible_state(fHScrollBar, true, &fHVisible)) 862 changed |= SCROLL_HORIZONTAL; 863 } 864 } 865 // the vertical one? 866 if (vbar) { 867 if (innerHeight >= dataHeight) { 868 if (set_visible_state(fVScrollBar, false, &fVVisible)) 869 changed |= SCROLL_VERTICAL; 870 } else { 871 if (set_visible_state(fVScrollBar, true, &fVVisible)) 872 changed |= SCROLL_VERTICAL; 873 } 874 } 875 } 876 // If anything has changed, update the scroll corner as well. 877 if (changed && fScrollCorner) 878 set_visible_state(fScrollCorner, fHVisible && fVVisible, &fCornerVisible); 879 return changed; 880} 881 882// _InnerRect 883// 884// Returns the rectangle that actually can be used for the child and the 885// scroll bars, i.e. the view's Bounds() subtracted the space for the 886// decorative frame. 887BRect 888ScrollView::_InnerRect() const 889{ 890 BRect r = Bounds(); 891 float borderWidth = 0; 892 switch (fBorderStyle) { 893 case B_NO_BORDER: 894 break; 895 case B_PLAIN_BORDER: 896 borderWidth = 1; 897 break; 898 case B_FANCY_BORDER: 899 default: 900 borderWidth = 2; 901 break; 902 } 903 if (fBorderFlags & BORDER_LEFT) 904 r.left += borderWidth; 905 if (fBorderFlags & BORDER_TOP) 906 r.top += borderWidth; 907 if (fBorderFlags & BORDER_RIGHT) 908 r.right -= borderWidth; 909 if (fBorderFlags & BORDER_BOTTOM) 910 r.bottom -= borderWidth; 911 return r; 912} 913 914// _ChildRect 915// 916// Returns the rectangle, that should be the current child frame. 917// `should' because 1. we might not have a child at all or 2. a 918// relayout is pending. 919BRect 920ScrollView::_ChildRect() const 921{ 922 return _ChildRect(fHScrollBar && fHVisible, fVScrollBar && fVVisible); 923} 924 925// _ChildRect 926// 927// The same as _ChildRect() with the exception that not the current 928// scroll bar visibility, but a fictitious one given by /hbar/ and /vbar/ 929// is considered. 930BRect 931ScrollView::_ChildRect(bool hbar, bool vbar) const 932{ 933 BRect rect(_InnerRect()); 934 if (vbar) 935 rect.right -= B_V_SCROLL_BAR_WIDTH; 936 if (hbar) 937 rect.bottom -= B_H_SCROLL_BAR_HEIGHT; 938 939 return rect; 940} 941 942// _GuessVisibleRect 943// 944// Returns an approximation of the visible rect for the 945// fictitious scroll bar visibility given by /hbar/ and /vbar/. 946// In the case !VisibleRectIsChildBounds() it is simply the current 947// visible rect. 948BRect 949ScrollView::_GuessVisibleRect(bool hbar, bool vbar) const 950{ 951 if (VisibleRectIsChildBounds()) 952 return _ChildRect(hbar, vbar).OffsetToCopy(ScrollOffset()); 953 return VisibleRect(); 954} 955 956// _MaxVisibleRect 957// 958// Returns the maximal possible visible rect in the current situation, that 959// is depending on if the visible rect is the child's bounds either the 960// rectangle the child covers when both scroll bars are hidden (offset to 961// the scroll offset) or the current visible rect. 962BRect 963ScrollView::_MaxVisibleRect() const 964{ 965 return _GuessVisibleRect(true, true); 966} 967 968#ifdef __HAIKU__ 969 970BSize 971ScrollView::_Size(BSize size) 972{ 973 if (fVVisible) 974 size.width += B_V_SCROLL_BAR_WIDTH; 975 if (fHVisible) 976 size.height += B_H_SCROLL_BAR_HEIGHT; 977 978 switch (fBorderStyle) { 979 case B_NO_BORDER: 980 // one line of pixels from scrollbar possibly hidden 981 if (fBorderFlags & BORDER_RIGHT) 982 size.width += fVVisible ? -1 : 0; 983 if (fBorderFlags & BORDER_BOTTOM) 984 size.height += fHVisible ? -1 : 0; 985 break; 986 987 case B_PLAIN_BORDER: 988 if (fBorderFlags & BORDER_LEFT) 989 size.width += 1; 990 if (fBorderFlags & BORDER_TOP) 991 size.height += 1; 992 // one line of pixels in frame possibly from scrollbar 993 if (fBorderFlags & BORDER_RIGHT) 994 size.width += fVVisible ? 0 : 1; 995 if (fBorderFlags & BORDER_BOTTOM) 996 size.height += fHVisible ? 0 : 1; 997 break; 998 999 case B_FANCY_BORDER: 1000 default: 1001 if (fBorderFlags & BORDER_LEFT) 1002 size.width += 2; 1003 if (fBorderFlags & BORDER_TOP) 1004 size.height += 2; 1005 // one line of pixels in frame possibly from scrollbar 1006 if (fBorderFlags & BORDER_RIGHT) 1007 size.width += fVVisible ? 1 : 2; 1008 if (fBorderFlags & BORDER_BOTTOM) 1009 size.height += fHVisible ? 1 : 2; 1010 break; 1011 } 1012 1013 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 1014} 1015 1016#endif // __HAIKU__ 1017 1018// _SetScrolling 1019void 1020ScrollView::_SetScrolling(bool scrolling) 1021{ 1022 fScrolling = scrolling; 1023} 1024