xref: /haiku/src/apps/icon-o-matic/generic/gui/scrollview/ScrollView.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
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 
27 using namespace std;
28 
29 // #pragma mark - InternalScrollBar
30 
31 class 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
48 InternalScrollBar::InternalScrollBar(ScrollView* scrollView, BRect frame,
49 									 orientation posture)
50 	: BScrollBar(frame, NULL, NULL, 0, 0, posture),
51 	  fScrollView(scrollView)
52 {
53 }
54 
55 // destructor
56 InternalScrollBar::~InternalScrollBar()
57 {
58 }
59 
60 // ValueChanged
61 void
62 InternalScrollBar::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
71 void
72 InternalScrollBar::MouseDown(BPoint where)
73 {
74 	if (fScrollView)
75 		fScrollView->_SetScrolling(true);
76 	BScrollBar::MouseDown(where);
77 }
78 
79 // MouseUp
80 void
81 InternalScrollBar::MouseUp(BPoint where)
82 {
83 	BScrollBar::MouseUp(where);
84 	if (fScrollView)
85 		fScrollView->_SetScrolling(false);
86 }
87 
88 
89 
90 // #pragma mark -ScrollCorner
91 
92 class 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
133 ScrollCorner::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
172 ScrollCorner::~ScrollCorner()
173 {
174 	for (int i = 0; i < 3; i++)
175 		delete fBitmaps[i];
176 }
177 
178 // MouseDown
179 void
180 ScrollCorner::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
196 void
197 ScrollCorner::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
207 void
208 ScrollCorner::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
230 void
231 ScrollCorner::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
244 void
245 ScrollCorner::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
259 void
260 ScrollCorner::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
274 void
275 ScrollCorner::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
291 ScrollView::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
304 ScrollView::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
316 ScrollView::~ScrollView()
317 {
318 }
319 
320 // AllAttached
321 void
322 ScrollView::AllAttached()
323 {
324 	// do a first layout
325 	_Layout(_UpdateScrollBarVisibility());
326 }
327 
328 // Draw
329 void 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
375 void
376 ScrollView::FrameResized(float width, float height)
377 {
378 	_Layout(0);
379 }
380 
381 // WindowActivated
382 void ScrollView::WindowActivated(bool activated)
383 {
384 	fWindowActive = activated;
385 	if (fChildFocused)
386 		Invalidate();
387 }
388 
389 #ifdef __HAIKU__
390 
391 // MinSize
392 BSize
393 ScrollView::MinSize()
394 {
395 	BSize size = (fChild ? fChild->MinSize() : BSize(-1, -1));
396 	return _Size(size);
397 }
398 
399 // PreferredSize
400 BSize
401 ScrollView::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
412 uint32
413 ScrollView::ScrollingFlags() const
414 {
415 	return fScrollingFlags;
416 }
417 
418 // SetVisibleRectIsChildBounds
419 void
420 ScrollView::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
433 bool
434 ScrollView::VisibleRectIsChildBounds() const
435 {
436 	return (fScrollingFlags & SCROLL_VISIBLE_RECT_IS_CHILD_BOUNDS);
437 }
438 
439 // Child
440 BView*
441 ScrollView::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.
450 void
451 ScrollView::ChildFocusChanged(bool focused)
452 {
453 	if (fChildFocused != focused) {
454 		fChildFocused = focused;
455 		Invalidate();
456 	}
457 }
458 
459 // HScrollBar
460 BScrollBar*
461 ScrollView::HScrollBar() const
462 {
463 	return fHScrollBar;
464 }
465 
466 // VScrollBar
467 BScrollBar*
468 ScrollView::VScrollBar() const
469 {
470 	return fVScrollBar;
471 }
472 
473 // HVScrollCorner
474 BView*
475 ScrollView::HVScrollCorner() const
476 {
477 	return fScrollCorner;
478 }
479 
480 // #pragma mark -
481 
482 // SetHSmallStep
483 void
484 ScrollView::SetHSmallStep(float hStep)
485 {
486 	SetSmallSteps(hStep, fVSmallStep);
487 }
488 
489 // SetVSmallStep
490 void
491 ScrollView::SetVSmallStep(float vStep)
492 {
493 	SetSmallSteps(fHSmallStep, vStep);
494 }
495 
496 // SetSmallSteps
497 void
498 ScrollView::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
508 void
509 ScrollView::GetSmallSteps(float* hStep, float* vStep) const
510 {
511 	*hStep = fHSmallStep;
512 	*vStep = fVSmallStep;
513 }
514 
515 // HSmallStep
516 float
517 ScrollView::HSmallStep() const
518 {
519 	return fHSmallStep;
520 }
521 
522 // VSmallStep
523 float
524 ScrollView::VSmallStep() const
525 {
526 	return fVSmallStep;
527 }
528 
529 // IsScrolling
530 bool
531 ScrollView::IsScrolling() const
532 {
533 	return fScrolling;
534 }
535 
536 void
537 ScrollView::SetScrollingEnabled(bool enabled)
538 {
539 	Scroller::SetScrollingEnabled(enabled);
540 	if (IsScrollingEnabled())
541 		SetScrollOffset(ScrollOffset());
542 }
543 
544 // #pragma mark -
545 
546 // DataRectChanged
547 void
548 ScrollView::DataRectChanged(BRect /*oldDataRect*/, BRect /*newDataRect*/)
549 {
550 	if (ScrollTarget()) {
551 		if (_UpdateScrollBarVisibility())
552 			_Layout(0);
553 		else
554 			_UpdateScrollBars();
555 	}
556 }
557 
558 // ScrollOffsetChanged
559 void
560 ScrollView::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
569 void
570 ScrollView::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
582 void
583 ScrollView::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
600 void
601 ScrollView::_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
656 void
657 ScrollView::_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
677 void
678 ScrollView::_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.
693 void
694 ScrollView::_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.
748 void
749 ScrollView::_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.
798 static inline
799 bool
800 set_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.
821 uint32
822 ScrollView::_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.
887 BRect
888 ScrollView::_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.
919 BRect
920 ScrollView::_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.
930 BRect
931 ScrollView::_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.
948 BRect
949 ScrollView::_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.
962 BRect
963 ScrollView::_MaxVisibleRect() const
964 {
965 	return _GuessVisibleRect(true, true);
966 }
967 
968 #ifdef __HAIKU__
969 
970 BSize
971 ScrollView::_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
1019 void
1020 ScrollView::_SetScrolling(bool scrolling)
1021 {
1022 	fScrolling = scrolling;
1023 }
1024