xref: /haiku/src/kits/interface/ScrollBar.cpp (revision 4b918abdb02a26a770d898594eaaccc6f1726e9b)
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, BControlLook::B_LEFT_ARROW, fOrientation,
415 			fPrivateData->fButtonDown == ARROW1);
416 
417 		if (doubleArrows) {
418 			buttonFrame.OffsetBy(rect.Height() + 1, 0.0f);
419 			be_control_look->DrawScrollBarButton(this, buttonFrame,
420 				updateRect, base, flags, BControlLook::B_RIGHT_ARROW,
421 				fOrientation, fPrivateData->fButtonDown == ARROW2);
422 
423 			buttonFrame.OffsetTo(rect.right - ((rect.Height() * 2) + 1),
424 				rect.top);
425 			be_control_look->DrawScrollBarButton(this, buttonFrame,
426 				updateRect, base, flags, BControlLook::B_LEFT_ARROW,
427 				fOrientation, fPrivateData->fButtonDown == ARROW3);
428 
429 			thumbBG.left += rect.Height() * 2 + 2;
430 			thumbBG.right -= rect.Height() * 2 + 2;
431 		} else {
432 			thumbBG.left += rect.Height() + 1;
433 			thumbBG.right -= rect.Height() + 1;
434 		}
435 
436 		buttonFrame.OffsetTo(rect.right - rect.Height(), rect.top);
437 		be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
438 			base, flags, BControlLook::B_RIGHT_ARROW, fOrientation,
439 			fPrivateData->fButtonDown == ARROW4);
440 	} else {
441 		BRect buttonFrame(rect.left, rect.top, rect.right,
442 			rect.top + rect.Width());
443 
444 		be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
445 			base, flags, BControlLook::B_UP_ARROW, fOrientation,
446 			fPrivateData->fButtonDown == ARROW1);
447 
448 		if (doubleArrows) {
449 			buttonFrame.OffsetBy(0, rect.Width() + 1);
450 			be_control_look->DrawScrollBarButton(this, buttonFrame,
451 				updateRect, base, flags, BControlLook::B_DOWN_ARROW,
452 				fOrientation, fPrivateData->fButtonDown == ARROW2);
453 
454 			buttonFrame.OffsetTo(rect.left, rect.bottom
455 				- ((rect.Width() * 2) + 1));
456 			be_control_look->DrawScrollBarButton(this, buttonFrame,
457 				updateRect, base, flags, BControlLook::B_UP_ARROW,
458 				fOrientation, fPrivateData->fButtonDown == ARROW3);
459 
460 			thumbBG.top += rect.Width() * 2 + 2;
461 			thumbBG.bottom -= rect.Width() * 2 + 2;
462 		} else {
463 			thumbBG.top += rect.Width() + 1;
464 			thumbBG.bottom -= rect.Width() + 1;
465 		}
466 
467 		buttonFrame.OffsetTo(rect.left, rect.bottom - rect.Width());
468 		be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect,
469 			base, flags, BControlLook::B_DOWN_ARROW, fOrientation,
470 			fPrivateData->fButtonDown == ARROW4);
471 	}
472 
473 	// fill background besides the thumb
474 	rect = fPrivateData->fThumbFrame;
475 	if (fOrientation == B_HORIZONTAL) {
476 		BRect leftOfThumb(thumbBG.left, thumbBG.top,
477 			rect.left - 1, thumbBG.bottom);
478 		BRect rightOfThumb(rect.right + 1, thumbBG.top,
479 			thumbBG.right, thumbBG.bottom);
480 		be_control_look->DrawScrollBarBackground(this, leftOfThumb,
481 			rightOfThumb, updateRect, base, flags, fOrientation);
482 	} else {
483 		BRect topOfThumb(thumbBG.left, thumbBG.top,
484 			thumbBG.right, rect.top - 1);
485 		BRect bottomOfThumb(thumbBG.left, rect.bottom + 1,
486 			thumbBG.right, thumbBG.bottom);
487 		be_control_look->DrawScrollBarBackground(this, topOfThumb,
488 			bottomOfThumb, updateRect, base, flags, fOrientation);
489 	}
490 
491 	// draw thumb
492 	rect = fPrivateData->fThumbFrame;
493 	be_control_look->DrawScrollBarThumb(this, rect, updateRect,
494 		ui_color(B_SCROLL_BAR_THUMB_COLOR), flags, fOrientation,
495 		fPrivateData->fScrollBarInfo.knob);
496 }
497 
498 
499 void
500 BScrollBar::FrameMoved(BPoint newPosition)
501 {
502 	BView::FrameMoved(newPosition);
503 }
504 
505 
506 void
507 BScrollBar::FrameResized(float newWidth, float newHeight)
508 {
509 	_UpdateThumbFrame();
510 }
511 
512 
513 void
514 BScrollBar::MessageReceived(BMessage* message)
515 {
516 	switch(message->what) {
517 		case B_VALUE_CHANGED:
518 		{
519 			int32 value;
520 			if (message->FindInt32("value", &value) == B_OK)
521 				ValueChanged(value);
522 
523 			break;
524 		}
525 
526 		case B_MOUSE_WHEEL_CHANGED:
527 		{
528 			// Must handle this here since BView checks for the existence of
529 			// scrollbars, which a scrollbar itself does not have
530 			float deltaX = 0.0f;
531 			float deltaY = 0.0f;
532 			message->FindFloat("be:wheel_delta_x", &deltaX);
533 			message->FindFloat("be:wheel_delta_y", &deltaY);
534 
535 			if (deltaX == 0.0f && deltaY == 0.0f)
536 				break;
537 
538 			if (deltaX != 0.0f && deltaY == 0.0f)
539 				deltaY = deltaX;
540 
541 			ScrollWithMouseWheelDelta(this, deltaY);
542 		}
543 
544 		default:
545 			BView::MessageReceived(message);
546 	}
547 }
548 
549 
550 void
551 BScrollBar::MouseDown(BPoint where)
552 {
553 	if (!fPrivateData->fEnabled || fMin == fMax)
554 		return;
555 
556 	SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
557 
558 	int32 buttons;
559 	if (Looper() == NULL || Looper()->CurrentMessage() == NULL
560 		|| Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) {
561 		buttons = B_PRIMARY_MOUSE_BUTTON;
562 	}
563 
564 	if (buttons & B_SECONDARY_MOUSE_BUTTON) {
565 		// special absolute scrolling: move thumb to where we clicked
566 		fPrivateData->fButtonDown = THUMB;
567 		fPrivateData->fClickOffset
568 			= fPrivateData->fThumbFrame.LeftTop() - where;
569 		if (Orientation() == B_HORIZONTAL) {
570 			fPrivateData->fClickOffset.x
571 				= -fPrivateData->fThumbFrame.Width() / 2;
572 		} else {
573 			fPrivateData->fClickOffset.y
574 				= -fPrivateData->fThumbFrame.Height() / 2;
575 		}
576 
577 		SetValue(_ValueFor(where + fPrivateData->fClickOffset));
578 		return;
579 	}
580 
581 	// hit test for the thumb
582 	if (fPrivateData->fThumbFrame.Contains(where)) {
583 		fPrivateData->fButtonDown = THUMB;
584 		fPrivateData->fClickOffset
585 			= fPrivateData->fThumbFrame.LeftTop() - where;
586 		Invalidate(fPrivateData->fThumbFrame);
587 		return;
588 	}
589 
590 	// hit test for arrows or empty area
591 	float scrollValue = 0.0;
592 
593 	// pressing the shift key scrolls faster
594 	float buttonStepSize
595 		= (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep;
596 
597 	fPrivateData->fButtonDown = _ButtonFor(where);
598 	switch (fPrivateData->fButtonDown) {
599 		case ARROW1:
600 			scrollValue = -buttonStepSize;
601 			break;
602 
603 		case ARROW2:
604 			scrollValue = buttonStepSize;
605 			break;
606 
607 		case ARROW3:
608 			scrollValue = -buttonStepSize;
609 			break;
610 
611 		case ARROW4:
612 			scrollValue = buttonStepSize;
613 			break;
614 
615 		case NOARROW:
616 			// we hit the empty area, figure out which side of the thumb
617 			if (fOrientation == B_VERTICAL) {
618 				if (where.y < fPrivateData->fThumbFrame.top)
619 					scrollValue = -fLargeStep;
620 				else
621 					scrollValue = fLargeStep;
622 			} else {
623 				if (where.x < fPrivateData->fThumbFrame.left)
624 					scrollValue = -fLargeStep;
625 				else
626 					scrollValue = fLargeStep;
627 			}
628 			_UpdateTargetValue(where);
629 			break;
630 	}
631 	if (scrollValue != 0.0) {
632 		SetValue(fValue + scrollValue);
633 		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
634 
635 		// launch the repeat thread
636 		if (fPrivateData->fRepeaterThread == -1) {
637 			fPrivateData->fExitRepeater = false;
638 			fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
639 			fPrivateData->fThumbInc = scrollValue;
640 			fPrivateData->fDoRepeat = true;
641 			fPrivateData->fRepeaterThread = spawn_thread(
642 				fPrivateData->button_repeater_thread, "scroll repeater",
643 				B_NORMAL_PRIORITY, fPrivateData);
644 			resume_thread(fPrivateData->fRepeaterThread);
645 		} else {
646 			fPrivateData->fExitRepeater = false;
647 			fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay;
648 			fPrivateData->fDoRepeat = true;
649 		}
650 	}
651 }
652 
653 
654 void
655 BScrollBar::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage)
656 {
657 	if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f
658 		|| fProportion < 0.0f) {
659 		return;
660 	}
661 
662 	if (fPrivateData->fButtonDown != NOARROW) {
663 		if (fPrivateData->fButtonDown == THUMB) {
664 			SetValue(_ValueFor(where + fPrivateData->fClickOffset));
665 		} else {
666 			// suspend the repeating if the mouse is not over the button
667 			bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains(
668 				where);
669 			if (fPrivateData->fDoRepeat != repeat) {
670 				fPrivateData->fDoRepeat = repeat;
671 				Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
672 			}
673 		}
674 	} else {
675 		// update the value at which we want to stop repeating
676 		if (fPrivateData->fDoRepeat) {
677 			_UpdateTargetValue(where);
678 			// we might have to turn arround
679 			if ((fValue < fPrivateData->fStopValue
680 					&& fPrivateData->fThumbInc < 0)
681 				|| (fValue > fPrivateData->fStopValue
682 					&& fPrivateData->fThumbInc > 0)) {
683 				fPrivateData->fThumbInc = -fPrivateData->fThumbInc;
684 			}
685 		}
686 	}
687 }
688 
689 
690 void
691 BScrollBar::MouseUp(BPoint where)
692 {
693 	if (fPrivateData->fButtonDown == THUMB)
694 		Invalidate(fPrivateData->fThumbFrame);
695 	else
696 		Invalidate(_ButtonRectFor(fPrivateData->fButtonDown));
697 
698 	fPrivateData->fButtonDown = NOARROW;
699 	fPrivateData->fExitRepeater = true;
700 	fPrivateData->fDoRepeat = false;
701 }
702 
703 
704 #if DISABLES_ON_WINDOW_DEACTIVATION
705 void
706 BScrollBar::WindowActivated(bool active)
707 {
708 	fPrivateData->fEnabled = active;
709 	Invalidate();
710 }
711 #endif // DISABLES_ON_WINDOW_DEACTIVATION
712 
713 
714 void
715 BScrollBar::SetValue(float value)
716 {
717 	if (value > fMax)
718 		value = fMax;
719 	else if (value < fMin)
720 		value = fMin;
721 	else if (isnan(value) || isinf(value))
722 		return;
723 
724 	value = roundf(value);
725 	if (value == fValue)
726 		return;
727 
728 	TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value);
729 
730 	fValue = value;
731 
732 	_UpdateThumbFrame();
733 	_UpdateArrowButtons();
734 
735 	ValueChanged(fValue);
736 }
737 
738 
739 float
740 BScrollBar::Value() const
741 {
742 	return fValue;
743 }
744 
745 
746 void
747 BScrollBar::ValueChanged(float newValue)
748 {
749 	TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue);
750 
751 	if (fTarget != NULL) {
752 		// cache target bounds
753 		BRect targetBounds = fTarget->Bounds();
754 		// if vertical, check bounds top and scroll if different from newValue
755 		if (fOrientation == B_VERTICAL && targetBounds.top != newValue)
756 			fTarget->ScrollBy(0.0, newValue - targetBounds.top);
757 
758 		// if horizontal, check bounds left and scroll if different from newValue
759 		if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue)
760 			fTarget->ScrollBy(newValue - targetBounds.left, 0.0);
761 	}
762 
763 	TRACE(" -> %.1f\n", newValue);
764 
765 	SetValue(newValue);
766 }
767 
768 
769 void
770 BScrollBar::SetProportion(float value)
771 {
772 	if (value < 0.0f)
773 		value = 0.0f;
774 	else if (value > 1.0f)
775 		value = 1.0f;
776 
777 	if (value == fProportion)
778 		return;
779 
780 	TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value);
781 
782 	bool oldEnabled = fPrivateData->fEnabled && fMin < fMax
783 		&& fProportion < 1.0f && fProportion >= 0.0f;
784 
785 	fProportion = value;
786 
787 	bool newEnabled = fPrivateData->fEnabled && fMin < fMax
788 		&& fProportion < 1.0f && fProportion >= 0.0f;
789 
790 	_UpdateThumbFrame();
791 
792 	if (oldEnabled != newEnabled)
793 		Invalidate();
794 }
795 
796 
797 float
798 BScrollBar::Proportion() const
799 {
800 	return fProportion;
801 }
802 
803 
804 void
805 BScrollBar::SetRange(float min, float max)
806 {
807 	if (min > max || isnanf(min) || isnanf(max)
808 		|| isinff(min) || isinff(max)) {
809 		min = 0.0f;
810 		max = 0.0f;
811 	}
812 
813 	min = roundf(min);
814 	max = roundf(max);
815 
816 	if (fMin == min && fMax == max)
817 		return;
818 
819 	TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max);
820 
821 	fMin = min;
822 	fMax = max;
823 
824 	if (fValue < fMin || fValue > fMax)
825 		SetValue(fValue);
826 	else {
827 		_UpdateThumbFrame();
828 		Invalidate();
829 	}
830 }
831 
832 
833 void
834 BScrollBar::GetRange(float* min, float* max) const
835 {
836 	if (min != NULL)
837 		*min = fMin;
838 
839 	if (max != NULL)
840 		*max = fMax;
841 }
842 
843 
844 void
845 BScrollBar::SetSteps(float smallStep, float largeStep)
846 {
847 	// Under R5, steps can be set only after being attached to a window,
848 	// probably because the data is kept server-side. We'll just remove
849 	// that limitation... :P
850 
851 	// The BeBook also says that we need to specify an integer value even
852 	// though the step values are floats. For the moment, we'll just make
853 	// sure that they are integers
854 	smallStep = roundf(smallStep);
855 	largeStep = roundf(largeStep);
856 	if (fSmallStep == smallStep && fLargeStep == largeStep)
857 		return;
858 
859 	TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(),
860 		smallStep, largeStep);
861 
862 	fSmallStep = smallStep;
863 	fLargeStep = largeStep;
864 
865 	if (fProportion == 0.0) {
866 		// special case, proportion is based on fLargeStep if it was never
867 		// set, so it means we need to invalidate here
868 		_UpdateThumbFrame();
869 		Invalidate();
870 	}
871 
872 	// TODO: test use of fractional values and make them work properly if
873 	// they don't
874 }
875 
876 
877 void
878 BScrollBar::GetSteps(float* smallStep, float* largeStep) const
879 {
880 	if (smallStep != NULL)
881 		*smallStep = fSmallStep;
882 
883 	if (largeStep != NULL)
884 		*largeStep = fLargeStep;
885 }
886 
887 
888 void
889 BScrollBar::SetTarget(BView* target)
890 {
891 	if (fTarget) {
892 		// unset the previous target's scrollbar pointer
893 		if (fOrientation == B_VERTICAL)
894 			fTarget->fVerScroller = NULL;
895 		else
896 			fTarget->fHorScroller = NULL;
897 	}
898 
899 	fTarget = target;
900 	if (fTarget) {
901 		if (fOrientation == B_VERTICAL)
902 			fTarget->fVerScroller = this;
903 		else
904 			fTarget->fHorScroller = this;
905 	}
906 }
907 
908 
909 void
910 BScrollBar::SetTarget(const char* targetName)
911 {
912 	// NOTE 1: BeOS implementation crashes for targetName == NULL
913 	// NOTE 2: BeOS implementation also does not modify the target
914 	// if it can't be found
915 	if (targetName == NULL)
916 		return;
917 
918 	if (Window() == NULL)
919 		debugger("Method requires window and doesn't have one");
920 
921 	BView* target = Window()->FindView(targetName);
922 	if (target != NULL)
923 		SetTarget(target);
924 }
925 
926 
927 BView*
928 BScrollBar::Target() const
929 {
930 	return fTarget;
931 }
932 
933 
934 void
935 BScrollBar::SetOrientation(orientation direction)
936 {
937 	if (fOrientation == direction)
938 		return;
939 
940 	fOrientation = direction;
941 	InvalidateLayout();
942 	Invalidate();
943 }
944 
945 
946 orientation
947 BScrollBar::Orientation() const
948 {
949 	return fOrientation;
950 }
951 
952 
953 status_t
954 BScrollBar::SetBorderHighlighted(bool highlight)
955 {
956 	if (fPrivateData->fBorderHighlighted == highlight)
957 		return B_OK;
958 
959 	fPrivateData->fBorderHighlighted = highlight;
960 
961 	BRect dirty(Bounds());
962 	if (fOrientation == B_HORIZONTAL)
963 		dirty.bottom = dirty.top;
964 	else
965 		dirty.right = dirty.left;
966 
967 	Invalidate(dirty);
968 
969 	return B_OK;
970 }
971 
972 
973 void
974 BScrollBar::GetPreferredSize(float* _width, float* _height)
975 {
976 	if (fOrientation == B_VERTICAL) {
977 		if (_width)
978 			*_width = B_V_SCROLL_BAR_WIDTH;
979 
980 		if (_height)
981 			*_height = Bounds().Height();
982 	} else if (fOrientation == B_HORIZONTAL) {
983 		if (_width)
984 			*_width = Bounds().Width();
985 
986 		if (_height)
987 			*_height = B_H_SCROLL_BAR_HEIGHT;
988 	}
989 }
990 
991 
992 void
993 BScrollBar::ResizeToPreferred()
994 {
995 	BView::ResizeToPreferred();
996 }
997 
998 
999 
1000 void
1001 BScrollBar::MakeFocus(bool focus)
1002 {
1003 	BView::MakeFocus(focus);
1004 }
1005 
1006 
1007 BSize
1008 BScrollBar::MinSize()
1009 {
1010 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize());
1011 }
1012 
1013 
1014 BSize
1015 BScrollBar::MaxSize()
1016 {
1017 	BSize maxSize = _MinSize();
1018 	if (fOrientation == B_HORIZONTAL)
1019 		maxSize.width = B_SIZE_UNLIMITED;
1020 	else
1021 		maxSize.height = B_SIZE_UNLIMITED;
1022 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize);
1023 }
1024 
1025 
1026 BSize
1027 BScrollBar::PreferredSize()
1028 {
1029 	BSize preferredSize = _MinSize();
1030 	if (fOrientation == B_HORIZONTAL)
1031 		preferredSize.width *= 2;
1032 	else
1033 		preferredSize.height *= 2;
1034 
1035 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize);
1036 }
1037 
1038 
1039 status_t
1040 BScrollBar::GetSupportedSuites(BMessage* message)
1041 {
1042 	return BView::GetSupportedSuites(message);
1043 }
1044 
1045 
1046 BHandler*
1047 BScrollBar::ResolveSpecifier(BMessage* message, int32 index,
1048 	BMessage* specifier, int32 what, const char* property)
1049 {
1050 	return BView::ResolveSpecifier(message, index, specifier, what, property);
1051 }
1052 
1053 
1054 status_t
1055 BScrollBar::Perform(perform_code code, void* _data)
1056 {
1057 	switch (code) {
1058 		case PERFORM_CODE_MIN_SIZE:
1059 			((perform_data_min_size*)_data)->return_value
1060 				= BScrollBar::MinSize();
1061 
1062 			return B_OK;
1063 
1064 		case PERFORM_CODE_MAX_SIZE:
1065 			((perform_data_max_size*)_data)->return_value
1066 				= BScrollBar::MaxSize();
1067 
1068 			return B_OK;
1069 
1070 		case PERFORM_CODE_PREFERRED_SIZE:
1071 			((perform_data_preferred_size*)_data)->return_value
1072 				= BScrollBar::PreferredSize();
1073 
1074 			return B_OK;
1075 
1076 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1077 			((perform_data_layout_alignment*)_data)->return_value
1078 				= BScrollBar::LayoutAlignment();
1079 
1080 			return B_OK;
1081 
1082 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1083 			((perform_data_has_height_for_width*)_data)->return_value
1084 				= BScrollBar::HasHeightForWidth();
1085 
1086 			return B_OK;
1087 
1088 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1089 		{
1090 			perform_data_get_height_for_width* data
1091 				= (perform_data_get_height_for_width*)_data;
1092 			BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max,
1093 				&data->preferred);
1094 
1095 			return B_OK;
1096 		}
1097 
1098 		case PERFORM_CODE_SET_LAYOUT:
1099 		{
1100 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1101 			BScrollBar::SetLayout(data->layout);
1102 
1103 			return B_OK;
1104 		}
1105 
1106 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1107 		{
1108 			perform_data_layout_invalidated* data
1109 				= (perform_data_layout_invalidated*)_data;
1110 			BScrollBar::LayoutInvalidated(data->descendants);
1111 
1112 			return B_OK;
1113 		}
1114 
1115 		case PERFORM_CODE_DO_LAYOUT:
1116 		{
1117 			BScrollBar::DoLayout();
1118 
1119 			return B_OK;
1120 		}
1121 	}
1122 
1123 	return BView::Perform(code, _data);
1124 }
1125 
1126 
1127 void BScrollBar::_ReservedScrollBar1() {}
1128 void BScrollBar::_ReservedScrollBar2() {}
1129 void BScrollBar::_ReservedScrollBar3() {}
1130 void BScrollBar::_ReservedScrollBar4() {}
1131 
1132 
1133 BScrollBar&
1134 BScrollBar::operator=(const BScrollBar&)
1135 {
1136 	return *this;
1137 }
1138 
1139 
1140 bool
1141 BScrollBar::_DoubleArrows() const
1142 {
1143 	if (!fPrivateData->fScrollBarInfo.double_arrows)
1144 		return false;
1145 
1146 	// if there is not enough room, switch to single arrows even though
1147 	// double arrows is specified
1148 	if (fOrientation == B_HORIZONTAL) {
1149 		return Bounds().Width() > (Bounds().Height() + 1) * 4
1150 			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1151 	} else {
1152 		return Bounds().Height() > (Bounds().Width() + 1) * 4
1153 			+ fPrivateData->fScrollBarInfo.min_knob_size * 2;
1154 	}
1155 }
1156 
1157 
1158 void
1159 BScrollBar::_UpdateThumbFrame()
1160 {
1161 	BRect bounds = Bounds();
1162 	bounds.InsetBy(1.0, 1.0);
1163 
1164 	BRect oldFrame = fPrivateData->fThumbFrame;
1165 	fPrivateData->fThumbFrame = bounds;
1166 	float minSize = fPrivateData->fScrollBarInfo.min_knob_size;
1167 	float maxSize;
1168 	float buttonSize;
1169 
1170 	// assume square buttons
1171 	if (fOrientation == B_VERTICAL) {
1172 		maxSize = bounds.Height();
1173 		buttonSize = bounds.Width() + 1.0;
1174 	} else {
1175 		maxSize = bounds.Width();
1176 		buttonSize = bounds.Height() + 1.0;
1177 	}
1178 
1179 	if (_DoubleArrows()) {
1180 		// subtract the size of four buttons
1181 		maxSize -= buttonSize * 4;
1182 	} else {
1183 		// subtract the size of two buttons
1184 		maxSize -= buttonSize * 2;
1185 	}
1186 	// visual adjustments (room for darker line between thumb and buttons)
1187 	maxSize--;
1188 
1189 	float thumbSize;
1190 	if (fPrivateData->fScrollBarInfo.proportional) {
1191 		float proportion = fProportion;
1192 		if (fMin >= fMax || proportion > 1.0 || proportion < 0.0)
1193 			proportion = 1.0;
1194 
1195 		if (proportion == 0.0) {
1196 			// Special case a proportion of 0.0, use the large step value
1197 			// in that case (NOTE: fMin == fMax already handled above)
1198 			// This calculation is based on the assumption that "large step"
1199 			// scrolls by one "page size".
1200 			proportion = fLargeStep / (2 * (fMax - fMin));
1201 			if (proportion > 1.0)
1202 				proportion = 1.0;
1203 		}
1204 		thumbSize = maxSize * proportion;
1205 		if (thumbSize < minSize)
1206 			thumbSize = minSize;
1207 	} else
1208 		thumbSize = minSize;
1209 
1210 	thumbSize = floorf(thumbSize + 0.5);
1211 	thumbSize--;
1212 
1213 	// the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0"
1214 	float offset = 0.0;
1215 	if (fMax > fMin) {
1216 		offset = floorf(((fValue - fMin) / (fMax - fMin))
1217 			* (maxSize - thumbSize - 1.0));
1218 	}
1219 
1220 	if (_DoubleArrows()) {
1221 		offset += buttonSize * 2;
1222 	} else
1223 		offset += buttonSize;
1224 
1225 	// visual adjustments (room for darker line between thumb and buttons)
1226 	offset++;
1227 
1228 	if (fOrientation == B_VERTICAL) {
1229 		fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top
1230 			+ thumbSize;
1231 		fPrivateData->fThumbFrame.OffsetBy(0.0, offset);
1232 	} else {
1233 		fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left
1234 			+ thumbSize;
1235 		fPrivateData->fThumbFrame.OffsetBy(offset, 0.0);
1236 	}
1237 
1238 	if (Window() != NULL) {
1239 		BRect invalid = oldFrame.IsValid()
1240 			? oldFrame | fPrivateData->fThumbFrame
1241 			: fPrivateData->fThumbFrame;
1242 		// account for those two dark lines
1243 		if (fOrientation == B_HORIZONTAL)
1244 			invalid.InsetBy(-2.0, 0.0);
1245 		else
1246 			invalid.InsetBy(0.0, -2.0);
1247 
1248 		Invalidate(invalid);
1249 	}
1250 }
1251 
1252 
1253 float
1254 BScrollBar::_ValueFor(BPoint where) const
1255 {
1256 	BRect bounds = Bounds();
1257 	bounds.InsetBy(1.0f, 1.0f);
1258 
1259 	float offset;
1260 	float thumbSize;
1261 	float maxSize;
1262 	float buttonSize;
1263 
1264 	if (fOrientation == B_VERTICAL) {
1265 		offset = where.y;
1266 		thumbSize = fPrivateData->fThumbFrame.Height();
1267 		maxSize = bounds.Height();
1268 		buttonSize = bounds.Width() + 1.0f;
1269 	} else {
1270 		offset = where.x;
1271 		thumbSize = fPrivateData->fThumbFrame.Width();
1272 		maxSize = bounds.Width();
1273 		buttonSize = bounds.Height() + 1.0f;
1274 	}
1275 
1276 	if (_DoubleArrows()) {
1277 		// subtract the size of four buttons
1278 		maxSize -= buttonSize * 4;
1279 		// convert point to inside of area between buttons
1280 		offset -= buttonSize * 2;
1281 	} else {
1282 		// subtract the size of two buttons
1283 		maxSize -= buttonSize * 2;
1284 		// convert point to inside of area between buttons
1285 		offset -= buttonSize;
1286 	}
1287 	// visual adjustments (room for darker line between thumb and buttons)
1288 	maxSize--;
1289 	offset++;
1290 
1291 	return roundf(fMin + (offset / (maxSize - thumbSize)
1292 		* (fMax - fMin + 1.0f)));
1293 }
1294 
1295 
1296 int32
1297 BScrollBar::_ButtonFor(BPoint where) const
1298 {
1299 	BRect bounds = Bounds();
1300 	bounds.InsetBy(1.0f, 1.0f);
1301 
1302 	float buttonSize = fOrientation == B_VERTICAL
1303 		? bounds.Width() + 1.0f
1304 		: bounds.Height() + 1.0f;
1305 
1306 	BRect rect(bounds.left, bounds.top,
1307 		bounds.left + buttonSize, bounds.top + buttonSize);
1308 
1309 	if (fOrientation == B_VERTICAL) {
1310 		if (rect.Contains(where))
1311 			return ARROW1;
1312 
1313 		if (_DoubleArrows()) {
1314 			rect.OffsetBy(0.0, buttonSize);
1315 			if (rect.Contains(where))
1316 				return ARROW2;
1317 
1318 			rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize);
1319 			if (rect.Contains(where))
1320 				return ARROW3;
1321 		}
1322 		rect.OffsetTo(bounds.left, bounds.bottom - buttonSize);
1323 		if (rect.Contains(where))
1324 			return ARROW4;
1325 	} else {
1326 		if (rect.Contains(where))
1327 			return ARROW1;
1328 
1329 		if (_DoubleArrows()) {
1330 			rect.OffsetBy(buttonSize, 0.0);
1331 			if (rect.Contains(where))
1332 				return ARROW2;
1333 
1334 			rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top);
1335 			if (rect.Contains(where))
1336 				return ARROW3;
1337 		}
1338 		rect.OffsetTo(bounds.right - buttonSize, bounds.top);
1339 		if (rect.Contains(where))
1340 			return ARROW4;
1341 	}
1342 
1343 	return NOARROW;
1344 }
1345 
1346 
1347 BRect
1348 BScrollBar::_ButtonRectFor(int32 button) const
1349 {
1350 	BRect bounds = Bounds();
1351 	bounds.InsetBy(1.0f, 1.0f);
1352 
1353 	float buttonSize = fOrientation == B_VERTICAL
1354 		? bounds.Width() + 1.0f
1355 		: bounds.Height() + 1.0f;
1356 
1357 	BRect rect(bounds.left, bounds.top,
1358 		bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f);
1359 
1360 	if (fOrientation == B_VERTICAL) {
1361 		switch (button) {
1362 			case ARROW1:
1363 				break;
1364 
1365 			case ARROW2:
1366 				rect.OffsetBy(0.0, buttonSize);
1367 				break;
1368 
1369 			case ARROW3:
1370 				rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1);
1371 				break;
1372 
1373 			case ARROW4:
1374 				rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1);
1375 				break;
1376 		}
1377 	} else {
1378 		switch (button) {
1379 			case ARROW1:
1380 				break;
1381 
1382 			case ARROW2:
1383 				rect.OffsetBy(buttonSize, 0.0);
1384 				break;
1385 
1386 			case ARROW3:
1387 				rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top);
1388 				break;
1389 
1390 			case ARROW4:
1391 				rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top);
1392 				break;
1393 		}
1394 	}
1395 
1396 	return rect;
1397 }
1398 
1399 
1400 void
1401 BScrollBar::_UpdateTargetValue(BPoint where)
1402 {
1403 	if (fOrientation == B_VERTICAL) {
1404 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y
1405 			- fPrivateData->fThumbFrame.Height() / 2.0));
1406 	} else {
1407 		fPrivateData->fStopValue = _ValueFor(BPoint(where.x
1408 			- fPrivateData->fThumbFrame.Width() / 2.0, where.y));
1409 	}
1410 }
1411 
1412 
1413 void
1414 BScrollBar::_UpdateArrowButtons()
1415 {
1416 	bool upEnabled = fValue > fMin;
1417 	if (fPrivateData->fUpArrowsEnabled != upEnabled) {
1418 		fPrivateData->fUpArrowsEnabled = upEnabled;
1419 		Invalidate(_ButtonRectFor(ARROW1));
1420 		if (_DoubleArrows())
1421 			Invalidate(_ButtonRectFor(ARROW3));
1422 	}
1423 
1424 	bool downEnabled = fValue < fMax;
1425 	if (fPrivateData->fDownArrowsEnabled != downEnabled) {
1426 		fPrivateData->fDownArrowsEnabled = downEnabled;
1427 		Invalidate(_ButtonRectFor(ARROW4));
1428 		if (_DoubleArrows())
1429 			Invalidate(_ButtonRectFor(ARROW2));
1430 	}
1431 }
1432 
1433 
1434 status_t
1435 control_scrollbar(scroll_bar_info* info, BScrollBar* bar)
1436 {
1437 	if (bar == NULL || info == NULL)
1438 		return B_BAD_VALUE;
1439 
1440 	if (bar->fPrivateData->fScrollBarInfo.double_arrows
1441 			!= info->double_arrows) {
1442 		bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows;
1443 
1444 		int8 multiplier = (info->double_arrows) ? 1 : -1;
1445 
1446 		if (bar->fOrientation == B_VERTICAL) {
1447 			bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier
1448 				* B_H_SCROLL_BAR_HEIGHT);
1449 		} else {
1450 			bar->fPrivateData->fThumbFrame.OffsetBy(multiplier
1451 				* B_V_SCROLL_BAR_WIDTH, 0);
1452 		}
1453 	}
1454 
1455 	bar->fPrivateData->fScrollBarInfo.proportional = info->proportional;
1456 
1457 	// TODO: Figure out how proportional relates to the size of the thumb
1458 
1459 	// TODO: Add redraw code to reflect the changes
1460 
1461 	if (info->knob >= 0 && info->knob <= 2)
1462 		bar->fPrivateData->fScrollBarInfo.knob = info->knob;
1463 	else
1464 		return B_BAD_VALUE;
1465 
1466 	if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE
1467 			&& info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) {
1468 		bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size;
1469 	} else
1470 		return B_BAD_VALUE;
1471 
1472 	return B_OK;
1473 }
1474 
1475 
1476 BSize
1477 BScrollBar::_MinSize() const
1478 {
1479 	BSize minSize;
1480 	if (fOrientation == B_HORIZONTAL) {
1481 		minSize.width = 2 * B_V_SCROLL_BAR_WIDTH
1482 			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1483 		minSize.height = B_H_SCROLL_BAR_HEIGHT;
1484 	} else {
1485 		minSize.width = B_V_SCROLL_BAR_WIDTH;
1486 		minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT
1487 			+ 2 * fPrivateData->fScrollBarInfo.min_knob_size;
1488 	}
1489 
1490 	return minSize;
1491 }
1492