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