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