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