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