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